wscript: Implemented Host_put_Interactive.
[wine] / programs / cmd / builtins.c
1 /*
2  * CMD - Wine-compatible command line interface - built-in functions.
3  *
4  * Copyright (C) 1999 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /*
23  * NOTES:
24  * On entry to each function, global variables quals, param1, param2 contain
25  * the qualifiers (uppercased and concatenated) and parameters entered, with
26  * environment-variable and batch parameter substitution already done.
27  */
28
29 /*
30  * FIXME:
31  * - No support for pipes, shell parameters
32  * - Lots of functionality missing from builtins
33  * - Messages etc need international support
34  */
35
36 #define WIN32_LEAN_AND_MEAN
37
38 #include "wcmd.h"
39 #include <shellapi.h>
40 #include "wine/debug.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43
44 static void WCMD_part_execute(CMD_LIST **commands, const WCHAR *firstcmd,
45                               const WCHAR *variable, const WCHAR *value,
46                               BOOL isIF, BOOL conditionTRUE);
47
48 struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
50
51 extern HINSTANCE hinst;
52 extern WCHAR inbuilt[][10];
53 extern int echo_mode, verify_mode, defaultColor;
54 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
55 extern BATCH_CONTEXT *context;
56 extern DWORD errorlevel;
57
58 static const WCHAR dotW[]    = {'.','\0'};
59 static const WCHAR dotdotW[] = {'.','.','\0'};
60 static const WCHAR slashW[]  = {'\\','\0'};
61 static const WCHAR starW[]   = {'*','\0'};
62 static const WCHAR equalW[]  = {'=','\0'};
63 static const WCHAR fslashW[] = {'/','\0'};
64 static const WCHAR onW[]  = {'O','N','\0'};
65 static const WCHAR offW[] = {'O','F','F','\0'};
66 static const WCHAR parmY[] = {'/','Y','\0'};
67 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
68 static const WCHAR nullW[] = {'\0'};
69
70 /**************************************************************************
71  * WCMD_ask_confirm
72  *
73  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
74  * answer.
75  *
76  * Returns True if Y (or A) answer is selected
77  *         If optionAll contains a pointer, ALL is allowed, and if answered
78  *                   set to TRUE
79  *
80  */
81 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
82                               const BOOL *optionAll) {
83
84     WCHAR  msgbuffer[MAXSTRING];
85     WCHAR  Ybuffer[MAXSTRING];
86     WCHAR  Nbuffer[MAXSTRING];
87     WCHAR  Abuffer[MAXSTRING];
88     WCHAR  answer[MAX_PATH] = {'\0'};
89     DWORD count = 0;
90
91     /* Load the translated 'Are you sure', plus valid answers */
92     LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
93     LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
94     LoadStringW(hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
95     LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
96
97     /* Loop waiting on a Y or N */
98     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
99       static const WCHAR startBkt[] = {' ','(','\0'};
100       static const WCHAR endBkt[]   = {')','?','\0'};
101
102       WCMD_output_asis (message);
103       if (showSureText) {
104         WCMD_output_asis (msgbuffer);
105       }
106       WCMD_output_asis (startBkt);
107       WCMD_output_asis (Ybuffer);
108       WCMD_output_asis (fslashW);
109       WCMD_output_asis (Nbuffer);
110       if (optionAll) {
111           WCMD_output_asis (fslashW);
112           WCMD_output_asis (Abuffer);
113       }
114       WCMD_output_asis (endBkt);
115       WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
116                      sizeof(answer)/sizeof(WCHAR), &count, NULL);
117       answer[0] = toupperW(answer[0]);
118     }
119
120     /* Return the answer */
121     return ((answer[0] == Ybuffer[0]) ||
122             (optionAll && (answer[0] == Abuffer[0])));
123 }
124
125 /****************************************************************************
126  * WCMD_clear_screen
127  *
128  * Clear the terminal screen.
129  */
130
131 void WCMD_clear_screen (void) {
132
133   /* Emulate by filling the screen from the top left to bottom right with
134         spaces, then moving the cursor to the top left afterwards */
135   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
136   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
137
138   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
139   {
140       COORD topLeft;
141       DWORD screenSize;
142
143       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
144
145       topLeft.X = 0;
146       topLeft.Y = 0;
147       FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
148       SetConsoleCursorPosition(hStdOut, topLeft);
149   }
150 }
151
152 /****************************************************************************
153  * WCMD_change_tty
154  *
155  * Change the default i/o device (ie redirect STDin/STDout).
156  */
157
158 void WCMD_change_tty (void) {
159
160   WCMD_output (WCMD_LoadMessage(WCMD_NYI));
161
162 }
163
164 /****************************************************************************
165  * WCMD_choice
166  *
167  */
168
169 void WCMD_choice (const WCHAR * command) {
170
171     static const WCHAR bellW[] = {7,0};
172     static const WCHAR commaW[] = {',',0};
173     static const WCHAR bracket_open[] = {'[',0};
174     static const WCHAR bracket_close[] = {']','?',0};
175     WCHAR answer[16];
176     WCHAR buffer[16];
177     WCHAR *ptr = NULL;
178     WCHAR *opt_c = NULL;
179     WCHAR *my_command = NULL;
180     WCHAR opt_default = 0;
181     DWORD opt_timeout = 0;
182     DWORD count;
183     DWORD oldmode;
184     DWORD have_console;
185     BOOL opt_n = FALSE;
186     BOOL opt_s = FALSE;
187
188     have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
189     errorlevel = 0;
190
191     my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
192     if (!my_command)
193         return;
194
195     ptr = WCMD_skip_leading_spaces(my_command);
196     while (*ptr == '/') {
197         switch (toupperW(ptr[1])) {
198             case 'C':
199                 ptr += 2;
200                 /* the colon is optional */
201                 if (*ptr == ':')
202                     ptr++;
203
204                 if (!*ptr || isspaceW(*ptr)) {
205                     WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
206                     HeapFree(GetProcessHeap(), 0, my_command);
207                     return;
208                 }
209
210                 /* remember the allowed keys (overwrite previous /C option) */
211                 opt_c = ptr;
212                 while (*ptr && (!isspaceW(*ptr)))
213                     ptr++;
214
215                 if (*ptr) {
216                     /* terminate allowed chars */
217                     *ptr = 0;
218                     ptr = WCMD_skip_leading_spaces(&ptr[1]);
219                 }
220                 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
221                 break;
222
223             case 'N':
224                 opt_n = TRUE;
225                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
226                 break;
227
228             case 'S':
229                 opt_s = TRUE;
230                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
231                 break;
232
233             case 'T':
234                 ptr = &ptr[2];
235                 /* the colon is optional */
236                 if (*ptr == ':')
237                     ptr++;
238
239                 opt_default = *ptr++;
240
241                 if (!opt_default || (*ptr != ',')) {
242                     WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
243                     HeapFree(GetProcessHeap(), 0, my_command);
244                     return;
245                 }
246                 ptr++;
247
248                 count = 0;
249                 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
250                     count++;
251                     ptr++;
252                 }
253
254                 answer[count] = 0;
255                 opt_timeout = atoiW(answer);
256
257                 ptr = WCMD_skip_leading_spaces(ptr);
258                 break;
259
260             default:
261                 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
262                 HeapFree(GetProcessHeap(), 0, my_command);
263                 return;
264         }
265     }
266
267     if (opt_timeout)
268         WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
269
270     if (have_console)
271         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
272
273     /* use default keys, when needed: localized versions of "Y"es and "No" */
274     if (!opt_c) {
275         LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
276         LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
277         opt_c = buffer;
278         buffer[2] = 0;
279     }
280
281     /* print the question, when needed */
282     if (*ptr)
283         WCMD_output_asis(ptr);
284
285     if (!opt_s) {
286         struprW(opt_c);
287         WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
288     }
289
290     if (!opt_n) {
291         /* print a list of all allowed answers inside brackets */
292         WCMD_output_asis(bracket_open);
293         ptr = opt_c;
294         answer[1] = 0;
295         while ((answer[0] = *ptr++)) {
296             WCMD_output_asis(answer);
297             if (*ptr)
298                 WCMD_output_asis(commaW);
299         }
300         WCMD_output_asis(bracket_close);
301     }
302
303     while (TRUE) {
304
305         /* FIXME: Add support for option /T */
306         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
307
308         if (!opt_s)
309             answer[0] = toupperW(answer[0]);
310
311         ptr = strchrW(opt_c, answer[0]);
312         if (ptr) {
313             WCMD_output_asis(answer);
314             WCMD_output(newline);
315             if (have_console)
316                 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
317
318             errorlevel = (ptr - opt_c) + 1;
319             WINE_TRACE("answer: %d\n", errorlevel);
320             HeapFree(GetProcessHeap(), 0, my_command);
321             return;
322         }
323         else
324         {
325             /* key not allowed: play the bell */
326             WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
327             WCMD_output_asis(bellW);
328         }
329     }
330 }
331
332 /****************************************************************************
333  * WCMD_copy
334  *
335  * Copy a file or wildcarded set.
336  * FIXME: Add support for a+b+c type syntax
337  */
338
339 void WCMD_copy (void) {
340
341   WIN32_FIND_DATAW fd;
342   HANDLE hff;
343   BOOL force, status;
344   WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
345   DWORD len;
346   static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
347   BOOL copyToDir = FALSE;
348   WCHAR srcspec[MAX_PATH];
349   DWORD attribs;
350   WCHAR drive[10];
351   WCHAR dir[MAX_PATH];
352   WCHAR fname[MAX_PATH];
353   WCHAR ext[MAX_PATH];
354
355   if (param1[0] == 0x00) {
356     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
357     return;
358   }
359
360   /* Convert source into full spec */
361   WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
362   GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
363   if (srcpath[strlenW(srcpath) - 1] == '\\')
364       srcpath[strlenW(srcpath) - 1] = '\0';
365
366   if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
367     attribs = GetFileAttributesW(srcpath);
368   } else {
369     attribs = 0;
370   }
371   strcpyW(srcspec, srcpath);
372
373   /* If a directory, then add \* on the end when searching */
374   if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
375     strcatW(srcpath, slashW);
376     strcatW(srcspec, slashW);
377     strcatW(srcspec, starW);
378   } else {
379     WCMD_splitpath(srcpath, drive, dir, fname, ext);
380     strcpyW(srcpath, drive);
381     strcatW(srcpath, dir);
382   }
383
384   WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
385
386   /* If no destination supplied, assume current directory */
387   WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
388   if (param2[0] == 0x00) {
389       strcpyW(param2, dotW);
390   }
391
392   GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
393   if (outpath[strlenW(outpath) - 1] == '\\')
394       outpath[strlenW(outpath) - 1] = '\0';
395   attribs = GetFileAttributesW(outpath);
396   if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
397     strcatW (outpath, slashW);
398     copyToDir = TRUE;
399   }
400   WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
401              wine_dbgstr_w(outpath), copyToDir);
402
403   /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
404   if (strstrW (quals, parmNoY))
405     force = FALSE;
406   else if (strstrW (quals, parmY))
407     force = TRUE;
408   else {
409     /* By default, we will force the overwrite in batch mode and ask for
410      * confirmation in interactive mode. */
411     force = !!context;
412
413     /* If COPYCMD is set, then we force the overwrite with /Y and ask for
414      * confirmation with /-Y. If COPYCMD is neither of those, then we use the
415      * default behavior. */
416     len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
417     if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
418       if (!lstrcmpiW (copycmd, parmY))
419         force = TRUE;
420       else if (!lstrcmpiW (copycmd, parmNoY))
421         force = FALSE;
422     }
423   }
424
425   /* Loop through all source files */
426   WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
427   hff = FindFirstFileW(srcspec, &fd);
428   if (hff != INVALID_HANDLE_VALUE) {
429       do {
430         WCHAR outname[MAX_PATH];
431         WCHAR srcname[MAX_PATH];
432         BOOL  overwrite = force;
433
434         /* Destination is either supplied filename, or source name in
435            supplied destination directory                             */
436         strcpyW(outname, outpath);
437         if (copyToDir) strcatW(outname, fd.cFileName);
438         strcpyW(srcname, srcpath);
439         strcatW(srcname, fd.cFileName);
440
441         WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
442         WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
443
444         /* Skip . and .., and directories */
445         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
446           overwrite = FALSE;
447           WINE_TRACE("Skipping directories\n");
448         }
449
450         /* Prompt before overwriting */
451         else if (!overwrite) {
452           attribs = GetFileAttributesW(outname);
453           if (attribs != INVALID_FILE_ATTRIBUTES) {
454             WCHAR buffer[MAXSTRING];
455             wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
456             overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
457           }
458           else overwrite = TRUE;
459         }
460
461         /* Do the copy as appropriate */
462         if (overwrite) {
463           status = CopyFileW(srcname, outname, FALSE);
464           if (!status) WCMD_print_error ();
465         }
466
467       } while (FindNextFileW(hff, &fd) != 0);
468       FindClose (hff);
469   } else {
470       status = ERROR_FILE_NOT_FOUND;
471       WCMD_print_error ();
472   }
473 }
474
475 /****************************************************************************
476  * WCMD_create_dir
477  *
478  * Create a directory (and, if needed, any intermediate directories).
479  *
480  * Modifies its argument by replacing slashes temporarily with nulls.
481  */
482
483 static BOOL create_full_path(WCHAR* path)
484 {
485     WCHAR *p, *start;
486
487     /* don't mess with drive letter portion of path, if any */
488     start = path;
489     if (path[1] == ':')
490         start = path+2;
491
492     /* Strip trailing slashes. */
493     for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
494         *p = 0;
495
496     /* Step through path, creating intermediate directories as needed. */
497     /* First component includes drive letter, if any. */
498     p = start;
499     for (;;) {
500         DWORD rv;
501         /* Skip to end of component */
502         while (*p == '\\') p++;
503         while (*p && *p != '\\') p++;
504         if (!*p) {
505             /* path is now the original full path */
506             return CreateDirectoryW(path, NULL);
507         }
508         /* Truncate path, create intermediate directory, and restore path */
509         *p = 0;
510         rv = CreateDirectoryW(path, NULL);
511         *p = '\\';
512         if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
513             return FALSE;
514     }
515     /* notreached */
516     return FALSE;
517 }
518
519 void WCMD_create_dir (WCHAR *command) {
520     int   argno = 0;
521     WCHAR *argN = command;
522
523     if (param1[0] == 0x00) {
524         WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
525         return;
526     }
527     /* Loop through all args */
528     while (TRUE) {
529         WCHAR *thisArg = WCMD_parameter(command, argno++, &argN);
530         if (!argN) break;
531         if (!create_full_path(thisArg)) {
532             WCMD_print_error ();
533             errorlevel = 1;
534         }
535     }
536 }
537
538 /* Parse the /A options given by the user on the commandline
539  * into a bitmask of wanted attributes (*wantSet),
540  * and a bitmask of unwanted attributes (*wantClear).
541  */
542 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
543     static const WCHAR parmA[] = {'/','A','\0'};
544     WCHAR *p;
545
546     /* both are strictly 'out' parameters */
547     *wantSet=0;
548     *wantClear=0;
549
550     /* For each /A argument */
551     for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
552         /* Skip /A itself */
553         p += 2;
554
555         /* Skip optional : */
556         if (*p == ':') p++;
557
558         /* For each of the attribute specifier chars to this /A option */
559         for (; *p != 0 && *p != '/'; p++) {
560             BOOL negate = FALSE;
561             DWORD mask  = 0;
562
563             if (*p == '-') {
564                 negate=TRUE;
565                 p++;
566             }
567
568             /* Convert the attribute specifier to a bit in one of the masks */
569             switch (*p) {
570             case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
571             case 'H': mask = FILE_ATTRIBUTE_HIDDEN;   break;
572             case 'S': mask = FILE_ATTRIBUTE_SYSTEM;   break;
573             case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;  break;
574             default:
575                 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
576             }
577             if (negate)
578                 *wantClear |= mask;
579             else
580                 *wantSet |= mask;
581         }
582     }
583 }
584
585 /* If filename part of parameter is * or *.*,
586  * and neither /Q nor /P options were given,
587  * prompt the user whether to proceed.
588  * Returns FALSE if user says no, TRUE otherwise.
589  * *pPrompted is set to TRUE if the user is prompted.
590  * (If /P supplied, del will prompt for individual files later.)
591  */
592 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
593     static const WCHAR parmP[] = {'/','P','\0'};
594     static const WCHAR parmQ[] = {'/','Q','\0'};
595
596     if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
597         static const WCHAR anyExt[]= {'.','*','\0'};
598         WCHAR drive[10];
599         WCHAR dir[MAX_PATH];
600         WCHAR fname[MAX_PATH];
601         WCHAR ext[MAX_PATH];
602         WCHAR fpath[MAX_PATH];
603
604         /* Convert path into actual directory spec */
605         GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
606         WCMD_splitpath(fpath, drive, dir, fname, ext);
607
608         /* Only prompt for * and *.*, not *a, a*, *.a* etc */
609         if ((strcmpW(fname, starW) == 0) &&
610             (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
611
612             WCHAR question[MAXSTRING];
613             static const WCHAR fmt[] = {'%','s',' ','\0'};
614
615             /* Caller uses this to suppress "file not found" warning later */
616             *pPrompted = TRUE;
617
618             /* Ask for confirmation */
619             wsprintfW(question, fmt, fpath);
620             return WCMD_ask_confirm(question, TRUE, NULL);
621         }
622     }
623     /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
624     return TRUE;
625 }
626
627 /* Helper function for WCMD_delete().
628  * Deletes a single file, directory, or wildcard.
629  * If /S was given, does it recursively.
630  * Returns TRUE if a file was deleted.
631  */
632 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
633
634     static const WCHAR parmP[] = {'/','P','\0'};
635     static const WCHAR parmS[] = {'/','S','\0'};
636     static const WCHAR parmF[] = {'/','F','\0'};
637     DWORD wanted_attrs;
638     DWORD unwanted_attrs;
639     BOOL found = FALSE;
640     WCHAR argCopy[MAX_PATH];
641     WIN32_FIND_DATAW fd;
642     HANDLE hff;
643     WCHAR fpath[MAX_PATH];
644     WCHAR *p;
645     BOOL handleParm = TRUE;
646
647     WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
648
649     strcpyW(argCopy, thisArg);
650     WINE_TRACE("del: Processing arg %s (quals:%s)\n",
651                wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
652
653     if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
654         /* Skip this arg if user declines to delete *.* */
655         return FALSE;
656     }
657
658     /* First, try to delete in the current directory */
659     hff = FindFirstFileW(argCopy, &fd);
660     if (hff == INVALID_HANDLE_VALUE) {
661       handleParm = FALSE;
662     } else {
663       found = TRUE;
664     }
665
666     /* Support del <dirname> by just deleting all files dirname\* */
667     if (handleParm
668         && (strchrW(argCopy,'*') == NULL)
669         && (strchrW(argCopy,'?') == NULL)
670         && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
671     {
672       WCHAR modifiedParm[MAX_PATH];
673       static const WCHAR slashStar[] = {'\\','*','\0'};
674
675       strcpyW(modifiedParm, argCopy);
676       strcatW(modifiedParm, slashStar);
677       FindClose(hff);
678       found = TRUE;
679       WCMD_delete_one(modifiedParm);
680
681     } else if (handleParm) {
682
683       /* Build the filename to delete as <supplied directory>\<findfirst filename> */
684       strcpyW (fpath, argCopy);
685       do {
686         p = strrchrW (fpath, '\\');
687         if (p != NULL) {
688           *++p = '\0';
689           strcatW (fpath, fd.cFileName);
690         }
691         else strcpyW (fpath, fd.cFileName);
692         if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
693           BOOL ok;
694
695           /* Handle attribute matching (/A) */
696           ok =  ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
697              && ((fd.dwFileAttributes & unwanted_attrs) == 0);
698
699           /* /P means prompt for each file */
700           if (ok && strstrW (quals, parmP) != NULL) {
701             WCHAR  question[MAXSTRING];
702
703             /* Ask for confirmation */
704             wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
705             ok = WCMD_ask_confirm(question, FALSE, NULL);
706           }
707
708           /* Only proceed if ok to */
709           if (ok) {
710
711             /* If file is read only, and /A:r or /F supplied, delete it */
712             if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
713                 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
714                 strstrW (quals, parmF) != NULL)) {
715                 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
716             }
717
718             /* Now do the delete */
719             if (!DeleteFileW(fpath)) WCMD_print_error ();
720           }
721
722         }
723       } while (FindNextFileW(hff, &fd) != 0);
724       FindClose (hff);
725     }
726
727     /* Now recurse into all subdirectories handling the parameter in the same way */
728     if (strstrW (quals, parmS) != NULL) {
729
730       WCHAR thisDir[MAX_PATH];
731       int cPos;
732
733       WCHAR drive[10];
734       WCHAR dir[MAX_PATH];
735       WCHAR fname[MAX_PATH];
736       WCHAR ext[MAX_PATH];
737
738       /* Convert path into actual directory spec */
739       GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
740       WCMD_splitpath(thisDir, drive, dir, fname, ext);
741
742       strcpyW(thisDir, drive);
743       strcatW(thisDir, dir);
744       cPos = strlenW(thisDir);
745
746       WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
747
748       /* Append '*' to the directory */
749       thisDir[cPos] = '*';
750       thisDir[cPos+1] = 0x00;
751
752       hff = FindFirstFileW(thisDir, &fd);
753
754       /* Remove residual '*' */
755       thisDir[cPos] = 0x00;
756
757       if (hff != INVALID_HANDLE_VALUE) {
758         DIRECTORY_STACK *allDirs = NULL;
759         DIRECTORY_STACK *lastEntry = NULL;
760
761         do {
762           if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
763               (strcmpW(fd.cFileName, dotdotW) != 0) &&
764               (strcmpW(fd.cFileName, dotW) != 0)) {
765
766             DIRECTORY_STACK *nextDir;
767             WCHAR subParm[MAX_PATH];
768
769             /* Work out search parameter in sub dir */
770             strcpyW (subParm, thisDir);
771             strcatW (subParm, fd.cFileName);
772             strcatW (subParm, slashW);
773             strcatW (subParm, fname);
774             strcatW (subParm, ext);
775             WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
776
777             /* Allocate memory, add to list */
778             nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
779             if (allDirs == NULL) allDirs = nextDir;
780             if (lastEntry != NULL) lastEntry->next = nextDir;
781             lastEntry = nextDir;
782             nextDir->next = NULL;
783             nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
784          (strlenW(subParm)+1) * sizeof(WCHAR));
785             strcpyW(nextDir->dirName, subParm);
786           }
787         } while (FindNextFileW(hff, &fd) != 0);
788         FindClose (hff);
789
790         /* Go through each subdir doing the delete */
791         while (allDirs != NULL) {
792           DIRECTORY_STACK *tempDir;
793
794           tempDir = allDirs->next;
795           found |= WCMD_delete_one (allDirs->dirName);
796
797           HeapFree(GetProcessHeap(),0,allDirs->dirName);
798           HeapFree(GetProcessHeap(),0,allDirs);
799           allDirs = tempDir;
800         }
801       }
802     }
803
804     return found;
805 }
806
807 /****************************************************************************
808  * WCMD_delete
809  *
810  * Delete a file or wildcarded set.
811  *
812  * Note on /A:
813  *  - Testing shows /A is repeatable, eg. /a-r /ar matches all files
814  *  - Each set is a pattern, eg /ahr /as-r means
815  *         readonly+hidden OR nonreadonly system files
816  *  - The '-' applies to a single field, ie /a:-hr means read only
817  *         non-hidden files
818  */
819
820 BOOL WCMD_delete (WCHAR *command) {
821     int   argno;
822     WCHAR *argN;
823     BOOL  argsProcessed = FALSE;
824     BOOL  foundAny      = FALSE;
825
826     errorlevel = 0;
827
828     for (argno=0; ; argno++) {
829         BOOL found;
830         WCHAR *thisArg;
831
832         argN = NULL;
833         thisArg = WCMD_parameter (command, argno, &argN);
834         if (!argN)
835             break;       /* no more parameters */
836         if (argN[0] == '/')
837             continue;    /* skip options */
838
839         argsProcessed = TRUE;
840         found = WCMD_delete_one(thisArg);
841         if (!found) {
842             errorlevel = 1;
843             WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
844         }
845         foundAny |= found;
846     }
847
848     /* Handle no valid args */
849     if (!argsProcessed)
850         WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
851
852     return foundAny;
853 }
854
855 /****************************************************************************
856  * WCMD_echo
857  *
858  * Echo input to the screen (or not). We don't try to emulate the bugs
859  * in DOS (try typing "ECHO ON AGAIN" for an example).
860  */
861
862 void WCMD_echo (const WCHAR *command) {
863
864   int count;
865   const WCHAR *origcommand = command;
866
867   if (command[0]==' ' || command[0]=='\t' || command[0]=='.' || command[0]==':')
868     command++;
869   count = strlenW(command);
870   if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':') {
871     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
872     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
873     return;
874   }
875   if (lstrcmpiW(command, onW) == 0) {
876     echo_mode = 1;
877     return;
878   }
879   if (lstrcmpiW(command, offW) == 0) {
880     echo_mode = 0;
881     return;
882   }
883   WCMD_output_asis (command);
884   WCMD_output (newline);
885
886 }
887
888 /**************************************************************************
889  * WCMD_for
890  *
891  * Batch file loop processing.
892  *
893  * On entry: cmdList       contains the syntax up to the set
894  *           next cmdList and all in that bracket contain the set data
895  *           next cmdlist  contains the DO cmd
896  *           following that is either brackets or && entries (as per if)
897  *
898  */
899
900 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
901
902   WIN32_FIND_DATAW fd;
903   HANDLE hff;
904   int i;
905   const WCHAR inW[]    = {'i', 'n', ' ',  '\0'};
906   const WCHAR inTabW[] = {'i', 'n', '\t', '\0'};
907   const WCHAR doW[]    = {'d', 'o', ' ',  '\0'};
908   const WCHAR doTabW[] = {'d', 'o', '\t', '\0'};
909   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
910   WCHAR variable[4];
911   WCHAR *firstCmd;
912   int thisDepth;
913
914   WCHAR *curPos = p;
915   BOOL   expandDirs  = FALSE;
916   BOOL   useNumbers  = FALSE;
917   BOOL   doFileset   = FALSE;
918   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
919   int    itemNum;
920   CMD_LIST *thisCmdStart;
921
922
923   /* Handle optional qualifiers (multiple are allowed) */
924   while (*curPos && *curPos == '/') {
925       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
926       curPos++;
927       switch (toupperW(*curPos)) {
928       case 'D': curPos++; expandDirs = TRUE; break;
929       case 'L': curPos++; useNumbers = TRUE; break;
930
931       /* Recursive is special case - /R can have an optional path following it                */
932       /* filenamesets are another special case - /F can have an optional options following it */
933       case 'R':
934       case 'F':
935           {
936               BOOL isRecursive = (*curPos == 'R');
937
938               if (!isRecursive)
939                   doFileset = TRUE;
940
941               /* Skip whitespace */
942               curPos++;
943               while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
944
945               /* Next parm is either qualifier, path/options or variable -
946                  only care about it if it is the path/options              */
947               if (*curPos && *curPos != '/' && *curPos != '%') {
948                   if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
949                   else WINE_FIXME("/F needs to handle options\n");
950               }
951               break;
952           }
953       default:
954           WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
955           curPos++;
956       }
957
958       /* Skip whitespace between qualifiers */
959       while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
960   }
961
962   /* Skip whitespace before variable */
963   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
964
965   /* Ensure line continues with variable */
966   if (!*curPos || *curPos != '%') {
967       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
968       return;
969   }
970
971   /* Variable should follow */
972   i = 0;
973   while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
974   memcpy(&variable[0], curPos, i*sizeof(WCHAR));
975   variable[i] = 0x00;
976   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
977   curPos = &curPos[i];
978
979   /* Skip whitespace before IN */
980   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
981
982   /* Ensure line continues with IN */
983   if (!*curPos
984        || (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
985                          curPos, 3, inW, -1) != CSTR_EQUAL
986            && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
987                          curPos, 3, inTabW, -1) != CSTR_EQUAL)) {
988       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
989       return;
990   }
991
992   /* Save away where the set of data starts and the variable */
993   thisDepth = (*cmdList)->bracketDepth;
994   *cmdList = (*cmdList)->nextcommand;
995   setStart = (*cmdList);
996
997   /* Skip until the close bracket */
998   WINE_TRACE("Searching %p as the set\n", *cmdList);
999   while (*cmdList &&
1000          (*cmdList)->command != NULL &&
1001          (*cmdList)->bracketDepth > thisDepth) {
1002     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1003     *cmdList = (*cmdList)->nextcommand;
1004   }
1005
1006   /* Skip the close bracket, if there is one */
1007   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1008
1009   /* Syntax error if missing close bracket, or nothing following it
1010      and once we have the complete set, we expect a DO              */
1011   WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1012   if ((*cmdList == NULL)
1013        || (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1014                          (*cmdList)->command, 3, doW, -1) != CSTR_EQUAL
1015            && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1016                          (*cmdList)->command, 3, doTabW, -1) != CSTR_EQUAL)) {
1017       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1018       return;
1019   }
1020
1021   /* Save away the starting position for the commands (and offset for the
1022      first one                                                           */
1023   cmdStart = *cmdList;
1024   cmdEnd   = *cmdList;
1025   firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1026   itemNum  = 0;
1027
1028   thisSet = setStart;
1029   /* Loop through all set entries */
1030   while (thisSet &&
1031          thisSet->command != NULL &&
1032          thisSet->bracketDepth >= thisDepth) {
1033
1034     /* Loop through all entries on the same line */
1035     WCHAR *item;
1036     WCHAR *itemStart;
1037
1038     WINE_TRACE("Processing for set %p\n", thisSet);
1039     i = 0;
1040     while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1041
1042       /*
1043        * If the parameter within the set has a wildcard then search for matching files
1044        * otherwise do a literal substitution.
1045        */
1046       static const WCHAR wildcards[] = {'*','?','\0'};
1047       thisCmdStart = cmdStart;
1048
1049       itemNum++;
1050       WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1051
1052       if (!useNumbers && !doFileset) {
1053           if (strpbrkW (item, wildcards)) {
1054             hff = FindFirstFileW(item, &fd);
1055             if (hff != INVALID_HANDLE_VALUE) {
1056               do {
1057                 BOOL isDirectory = FALSE;
1058
1059                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1060
1061                 /* Handle as files or dirs appropriately, but ignore . and .. */
1062                 if (isDirectory == expandDirs &&
1063                     (strcmpW(fd.cFileName, dotdotW) != 0) &&
1064                     (strcmpW(fd.cFileName, dotW) != 0))
1065                 {
1066                   thisCmdStart = cmdStart;
1067                   WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1068                   WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1069                                                fd.cFileName, FALSE, TRUE);
1070                 }
1071
1072               } while (FindNextFileW(hff, &fd) != 0);
1073               FindClose (hff);
1074             }
1075           } else {
1076             WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1077           }
1078
1079       } else if (useNumbers) {
1080           /* Convert the first 3 numbers to signed longs and save */
1081           if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1082           /* else ignore them! */
1083
1084       /* Filesets - either a list of files, or a command to run and parse the output */
1085       } else if (doFileset && *itemStart != '"') {
1086
1087           HANDLE input;
1088           WCHAR temp_file[MAX_PATH];
1089
1090           WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1091                      wine_dbgstr_w(item));
1092
1093           /* If backquote or single quote, we need to launch that command
1094              and parse the results - use a temporary file                 */
1095           if (*itemStart == '`' || *itemStart == '\'') {
1096
1097               WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1098               static const WCHAR redirOut[] = {'>','%','s','\0'};
1099               static const WCHAR cmdW[]     = {'C','M','D','\0'};
1100
1101               /* Remove trailing character */
1102               itemStart[strlenW(itemStart)-1] = 0x00;
1103
1104               /* Get temp filename */
1105               GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1106               GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1107
1108               /* Execute program and redirect output */
1109               wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1110               WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1111
1112               /* Open the file, read line by line and process */
1113               input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1114                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1115           } else {
1116
1117               /* Open the file, read line by line and process */
1118               input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1119                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1120           }
1121
1122           /* Process the input file */
1123           if (input == INVALID_HANDLE_VALUE) {
1124             WCMD_print_error ();
1125             WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1126             errorlevel = 1;
1127             return; /* FOR loop aborts at first failure here */
1128
1129           } else {
1130
1131             WCHAR buffer[MAXSTRING] = {'\0'};
1132             WCHAR *where, *parm;
1133
1134             while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1135
1136               /* Skip blank lines*/
1137               parm = WCMD_parameter (buffer, 0, &where);
1138               WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1139                          wine_dbgstr_w(buffer));
1140
1141               if (where) {
1142                   /* FIXME: The following should be moved into its own routine and
1143                      reused for the string literal parsing below                  */
1144                   thisCmdStart = cmdStart;
1145                   WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1146                   cmdEnd = thisCmdStart;
1147               }
1148
1149               buffer[0] = 0x00;
1150
1151             }
1152             CloseHandle (input);
1153           }
1154
1155           /* Delete the temporary file */
1156           if (*itemStart == '`' || *itemStart == '\'') {
1157               DeleteFileW(temp_file);
1158           }
1159
1160       /* Filesets - A string literal */
1161       } else if (doFileset && *itemStart == '"') {
1162           WCHAR buffer[MAXSTRING] = {'\0'};
1163           WCHAR *where, *parm;
1164
1165           /* Skip blank lines, and re-extract parameter now string has quotes removed */
1166           strcpyW(buffer, item);
1167           parm = WCMD_parameter (buffer, 0, &where);
1168           WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1169                        wine_dbgstr_w(buffer));
1170
1171           if (where) {
1172               /* FIXME: The following should be moved into its own routine and
1173                  reused for the string literal parsing below                  */
1174               thisCmdStart = cmdStart;
1175               WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1176               cmdEnd = thisCmdStart;
1177           }
1178       }
1179
1180       WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1181       cmdEnd = thisCmdStart;
1182       i++;
1183     }
1184
1185     /* Move onto the next set line */
1186     thisSet = thisSet->nextcommand;
1187   }
1188
1189   /* If /L is provided, now run the for loop */
1190   if (useNumbers) {
1191       WCHAR thisNum[20];
1192       static const WCHAR fmt[] = {'%','d','\0'};
1193
1194       WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1195                  numbers[0], numbers[2], numbers[1]);
1196       for (i=numbers[0];
1197            (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1198            i=i + numbers[1]) {
1199
1200           sprintfW(thisNum, fmt, i);
1201           WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1202
1203           thisCmdStart = cmdStart;
1204           WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1205           cmdEnd = thisCmdStart;
1206       }
1207   }
1208
1209   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1210      all processing, OR it should be pointing to the end of && processing OR
1211      it should be pointing at the NULL end of bracket for the DO. The return
1212      value needs to be the NEXT command to execute, which it either is, or
1213      we need to step over the closing bracket                                  */
1214   *cmdList = cmdEnd;
1215   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1216 }
1217
1218
1219 /*****************************************************************************
1220  * WCMD_part_execute
1221  *
1222  * Execute a command, and any && or bracketed follow on to the command. The
1223  * first command to be executed may not be at the front of the
1224  * commands->thiscommand string (eg. it may point after a DO or ELSE)
1225  */
1226 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1227                               const WCHAR *variable, const WCHAR *value,
1228                               BOOL isIF, BOOL conditionTRUE) {
1229
1230   CMD_LIST *curPosition = *cmdList;
1231   int myDepth = (*cmdList)->bracketDepth;
1232
1233   WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1234              cmdList, wine_dbgstr_w(firstcmd),
1235              wine_dbgstr_w(variable), wine_dbgstr_w(value),
1236              conditionTRUE);
1237
1238   /* Skip leading whitespace between condition and the command */
1239   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1240
1241   /* Process the first command, if there is one */
1242   if (conditionTRUE && firstcmd && *firstcmd) {
1243     WCHAR *command = WCMD_strdupW(firstcmd);
1244     WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1245     HeapFree(GetProcessHeap(), 0, command);
1246   }
1247
1248
1249   /* If it didn't move the position, step to next command */
1250   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1251
1252   /* Process any other parts of the command */
1253   if (*cmdList) {
1254     BOOL processThese = TRUE;
1255
1256     if (isIF) processThese = conditionTRUE;
1257
1258     while (*cmdList) {
1259       const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1260
1261       /* execute all appropriate commands */
1262       curPosition = *cmdList;
1263
1264       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1265                  *cmdList,
1266                  (*cmdList)->prevDelim,
1267                  (*cmdList)->bracketDepth, myDepth);
1268
1269       /* Execute any statements appended to the line */
1270       /* FIXME: Only if previous call worked for && or failed for || */
1271       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1272           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1273         if (processThese && (*cmdList)->command) {
1274           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1275                         value, cmdList);
1276         }
1277         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1278
1279       /* Execute any appended to the statement with (...) */
1280       } else if ((*cmdList)->bracketDepth > myDepth) {
1281         if (processThese) {
1282           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1283           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1284         }
1285         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1286
1287       /* End of the command - does 'ELSE ' follow as the next command? */
1288       } else {
1289         if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1290                                    NORM_IGNORECASE | SORT_STRINGSORT,
1291                            (*cmdList)->command, 5, ifElse, -1) == CSTR_EQUAL) {
1292
1293           /* Swap between if and else processing */
1294           processThese = !processThese;
1295
1296           /* Process the ELSE part */
1297           if (processThese) {
1298             WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1299
1300             /* Skip leading whitespace between condition and the command */
1301             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1302             if (*cmd) {
1303               WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1304             }
1305           }
1306           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1307         } else {
1308           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1309           break;
1310         }
1311       }
1312     }
1313   }
1314   return;
1315 }
1316
1317 /**************************************************************************
1318  * WCMD_give_help
1319  *
1320  *      Simple on-line help. Help text is stored in the resource file.
1321  */
1322
1323 void WCMD_give_help (const WCHAR *command) {
1324
1325   int i;
1326
1327   command = WCMD_skip_leading_spaces((WCHAR*) command);
1328   if (strlenW(command) == 0) {
1329     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1330   }
1331   else {
1332     for (i=0; i<=WCMD_EXIT; i++) {
1333       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1334           command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1335         WCMD_output_asis (WCMD_LoadMessage(i));
1336         return;
1337       }
1338     }
1339     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1340   }
1341   return;
1342 }
1343
1344 /****************************************************************************
1345  * WCMD_go_to
1346  *
1347  * Batch file jump instruction. Not the most efficient algorithm ;-)
1348  * Prints error message if the specified label cannot be found - the file pointer is
1349  * then at EOF, effectively stopping the batch file.
1350  * FIXME: DOS is supposed to allow labels with spaces - we don't.
1351  */
1352
1353 void WCMD_goto (CMD_LIST **cmdList) {
1354
1355   WCHAR string[MAX_PATH];
1356   WCHAR current[MAX_PATH];
1357
1358   /* Do not process any more parts of a processed multipart or multilines command */
1359   if (cmdList) *cmdList = NULL;
1360
1361   if (param1[0] == 0x00) {
1362     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1363     return;
1364   }
1365   if (context != NULL) {
1366     WCHAR *paramStart = param1, *str;
1367     static const WCHAR eofW[] = {':','e','o','f','\0'};
1368
1369     /* Handle special :EOF label */
1370     if (lstrcmpiW (eofW, param1) == 0) {
1371       context -> skip_rest = TRUE;
1372       return;
1373     }
1374
1375     /* Support goto :label as well as goto label */
1376     if (*paramStart == ':') paramStart++;
1377
1378     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1379     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1380       str = string;
1381       while (isspaceW (*str)) str++;
1382       if (*str == ':') {
1383         DWORD index = 0;
1384         str++;
1385         while (((current[index] = str[index])) && (!isspaceW (current[index])))
1386             index++;
1387
1388         /* ignore space at the end */
1389         current[index] = 0;
1390         if (lstrcmpiW (current, paramStart) == 0) return;
1391       }
1392     }
1393     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1394   }
1395   return;
1396 }
1397
1398 /*****************************************************************************
1399  * WCMD_pushd
1400  *
1401  *      Push a directory onto the stack
1402  */
1403
1404 void WCMD_pushd (WCHAR *command) {
1405     struct env_stack *curdir;
1406     WCHAR *thisdir;
1407     static const WCHAR parmD[] = {'/','D','\0'};
1408
1409     if (strchrW(command, '/') != NULL) {
1410       SetLastError(ERROR_INVALID_PARAMETER);
1411       WCMD_print_error();
1412       return;
1413     }
1414
1415     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1416     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1417     if( !curdir || !thisdir ) {
1418       LocalFree(curdir);
1419       LocalFree(thisdir);
1420       WINE_ERR ("out of memory\n");
1421       return;
1422     }
1423
1424     /* Change directory using CD code with /D parameter */
1425     strcpyW(quals, parmD);
1426     GetCurrentDirectoryW (1024, thisdir);
1427     errorlevel = 0;
1428     WCMD_setshow_default(command);
1429     if (errorlevel) {
1430       LocalFree(curdir);
1431       LocalFree(thisdir);
1432       return;
1433     } else {
1434       curdir -> next    = pushd_directories;
1435       curdir -> strings = thisdir;
1436       if (pushd_directories == NULL) {
1437         curdir -> u.stackdepth = 1;
1438       } else {
1439         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1440       }
1441       pushd_directories = curdir;
1442     }
1443 }
1444
1445
1446 /*****************************************************************************
1447  * WCMD_popd
1448  *
1449  *      Pop a directory from the stack
1450  */
1451
1452 void WCMD_popd (void) {
1453     struct env_stack *temp = pushd_directories;
1454
1455     if (!pushd_directories)
1456       return;
1457
1458     /* pop the old environment from the stack, and make it the current dir */
1459     pushd_directories = temp->next;
1460     SetCurrentDirectoryW(temp->strings);
1461     LocalFree (temp->strings);
1462     LocalFree (temp);
1463 }
1464
1465 /****************************************************************************
1466  * WCMD_if
1467  *
1468  * Batch file conditional.
1469  *
1470  * On entry, cmdlist will point to command containing the IF, and optionally
1471  *   the first command to execute (if brackets not found)
1472  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1473  *   If ('s were found, execute all within that bracket
1474  *   Command may optionally be followed by an ELSE - need to skip instructions
1475  *   in the else using the same logic
1476  *
1477  * FIXME: Much more syntax checking needed!
1478  */
1479
1480 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1481
1482   int negate = 0, test = 0;
1483   WCHAR condition[MAX_PATH], *command, *s;
1484   static const WCHAR notW[]    = {'n','o','t','\0'};
1485   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1486   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1487   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1488   static const WCHAR eqeqW[]   = {'=','=','\0'};
1489   static const WCHAR parmI[]   = {'/','I','\0'};
1490
1491   if (!lstrcmpiW (param1, notW)) {
1492     negate = 1;
1493     strcpyW (condition, param2);
1494   }
1495   else {
1496     strcpyW (condition, param1);
1497   }
1498   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1499
1500   if (!lstrcmpiW (condition, errlvlW)) {
1501     if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1502     WCMD_parameter (p, 2+negate, &command);
1503   }
1504   else if (!lstrcmpiW (condition, existW)) {
1505     if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1506         test = 1;
1507     }
1508     WCMD_parameter (p, 2+negate, &command);
1509   }
1510   else if (!lstrcmpiW (condition, defdW)) {
1511     if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1512         test = 1;
1513     }
1514     WCMD_parameter (p, 2+negate, &command);
1515   }
1516   else if ((s = strstrW (p, eqeqW))) {
1517     s += 2;
1518     if (strstrW (quals, parmI) == NULL) {
1519         if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1520     }
1521     else {
1522         if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1523     }
1524     WCMD_parameter (s, 1, &command);
1525   }
1526   else {
1527     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1528     return;
1529   }
1530
1531   /* Process rest of IF statement which is on the same line
1532      Note: This may process all or some of the cmdList (eg a GOTO) */
1533   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1534 }
1535
1536 /****************************************************************************
1537  * WCMD_move
1538  *
1539  * Move a file, directory tree or wildcarded set of files.
1540  */
1541
1542 void WCMD_move (void) {
1543
1544   int             status;
1545   WIN32_FIND_DATAW fd;
1546   HANDLE          hff;
1547   WCHAR            input[MAX_PATH];
1548   WCHAR            output[MAX_PATH];
1549   WCHAR            drive[10];
1550   WCHAR            dir[MAX_PATH];
1551   WCHAR            fname[MAX_PATH];
1552   WCHAR            ext[MAX_PATH];
1553
1554   if (param1[0] == 0x00) {
1555     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1556     return;
1557   }
1558
1559   /* If no destination supplied, assume current directory */
1560   if (param2[0] == 0x00) {
1561       strcpyW(param2, dotW);
1562   }
1563
1564   /* If 2nd parm is directory, then use original filename */
1565   /* Convert partial path to full path */
1566   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1567   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1568   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1569              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1570
1571   /* Split into components */
1572   WCMD_splitpath(input, drive, dir, fname, ext);
1573
1574   hff = FindFirstFileW(input, &fd);
1575   while (hff != INVALID_HANDLE_VALUE) {
1576     WCHAR  dest[MAX_PATH];
1577     WCHAR  src[MAX_PATH];
1578     DWORD attribs;
1579
1580     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1581
1582     /* Build src & dest name */
1583     strcpyW(src, drive);
1584     strcatW(src, dir);
1585
1586     /* See if dest is an existing directory */
1587     attribs = GetFileAttributesW(output);
1588     if (attribs != INVALID_FILE_ATTRIBUTES &&
1589        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1590       strcpyW(dest, output);
1591       strcatW(dest, slashW);
1592       strcatW(dest, fd.cFileName);
1593     } else {
1594       strcpyW(dest, output);
1595     }
1596
1597     strcatW(src, fd.cFileName);
1598
1599     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1600     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1601
1602     /* Check if file is read only, otherwise move it */
1603     attribs = GetFileAttributesW(src);
1604     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1605         (attribs & FILE_ATTRIBUTE_READONLY)) {
1606       SetLastError(ERROR_ACCESS_DENIED);
1607       status = 0;
1608     } else {
1609       BOOL ok = TRUE;
1610
1611       /* If destination exists, prompt unless /Y supplied */
1612       if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1613         BOOL force = FALSE;
1614         WCHAR copycmd[MAXSTRING];
1615         int len;
1616
1617         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1618         if (strstrW (quals, parmNoY))
1619           force = FALSE;
1620         else if (strstrW (quals, parmY))
1621           force = TRUE;
1622         else {
1623           const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1624           len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1625           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1626                        && ! lstrcmpiW (copycmd, parmY));
1627         }
1628
1629         /* Prompt if overwriting */
1630         if (!force) {
1631           WCHAR  question[MAXSTRING];
1632           WCHAR  yesChar[10];
1633
1634           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1635
1636           /* Ask for confirmation */
1637           wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1638           ok = WCMD_ask_confirm(question, FALSE, NULL);
1639
1640           /* So delete the destination prior to the move */
1641           if (ok) {
1642             if (!DeleteFileW(dest)) {
1643               WCMD_print_error ();
1644               errorlevel = 1;
1645               ok = FALSE;
1646             }
1647           }
1648         }
1649       }
1650
1651       if (ok) {
1652         status = MoveFileW(src, dest);
1653       } else {
1654         status = 1; /* Anything other than 0 to prevent error msg below */
1655       }
1656     }
1657
1658     if (!status) {
1659       WCMD_print_error ();
1660       errorlevel = 1;
1661     }
1662
1663     /* Step on to next match */
1664     if (FindNextFileW(hff, &fd) == 0) {
1665       FindClose(hff);
1666       hff = INVALID_HANDLE_VALUE;
1667       break;
1668     }
1669   }
1670 }
1671
1672 /****************************************************************************
1673  * WCMD_pause
1674  *
1675  * Wait for keyboard input.
1676  */
1677
1678 void WCMD_pause (void) {
1679
1680   DWORD count;
1681   WCHAR string[32];
1682
1683   WCMD_output (anykey);
1684   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1685                  sizeof(string)/sizeof(WCHAR), &count, NULL);
1686 }
1687
1688 /****************************************************************************
1689  * WCMD_remove_dir
1690  *
1691  * Delete a directory.
1692  */
1693
1694 void WCMD_remove_dir (WCHAR *command) {
1695
1696   int   argno         = 0;
1697   int   argsProcessed = 0;
1698   WCHAR *argN          = command;
1699   static const WCHAR parmS[] = {'/','S','\0'};
1700   static const WCHAR parmQ[] = {'/','Q','\0'};
1701
1702   /* Loop through all args */
1703   while (argN) {
1704     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1705     if (argN && argN[0] != '/') {
1706       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1707                  wine_dbgstr_w(quals));
1708       argsProcessed++;
1709
1710       /* If subdirectory search not supplied, just try to remove
1711          and report error if it fails (eg if it contains a file) */
1712       if (strstrW (quals, parmS) == NULL) {
1713         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1714
1715       /* Otherwise use ShFileOp to recursively remove a directory */
1716       } else {
1717
1718         SHFILEOPSTRUCTW lpDir;
1719
1720         /* Ask first */
1721         if (strstrW (quals, parmQ) == NULL) {
1722           BOOL  ok;
1723           WCHAR  question[MAXSTRING];
1724           static const WCHAR fmt[] = {'%','s',' ','\0'};
1725
1726           /* Ask for confirmation */
1727           wsprintfW(question, fmt, thisArg);
1728           ok = WCMD_ask_confirm(question, TRUE, NULL);
1729
1730           /* Abort if answer is 'N' */
1731           if (!ok) return;
1732         }
1733
1734         /* Do the delete */
1735         lpDir.hwnd   = NULL;
1736         lpDir.pTo    = NULL;
1737         lpDir.pFrom  = thisArg;
1738         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1739         lpDir.wFunc  = FO_DELETE;
1740         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1741       }
1742     }
1743   }
1744
1745   /* Handle no valid args */
1746   if (argsProcessed == 0) {
1747     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1748     return;
1749   }
1750
1751 }
1752
1753 /****************************************************************************
1754  * WCMD_rename
1755  *
1756  * Rename a file.
1757  */
1758
1759 void WCMD_rename (void) {
1760
1761   int             status;
1762   HANDLE          hff;
1763   WIN32_FIND_DATAW fd;
1764   WCHAR            input[MAX_PATH];
1765   WCHAR           *dotDst = NULL;
1766   WCHAR            drive[10];
1767   WCHAR            dir[MAX_PATH];
1768   WCHAR            fname[MAX_PATH];
1769   WCHAR            ext[MAX_PATH];
1770   DWORD           attribs;
1771
1772   errorlevel = 0;
1773
1774   /* Must be at least two args */
1775   if (param1[0] == 0x00 || param2[0] == 0x00) {
1776     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1777     errorlevel = 1;
1778     return;
1779   }
1780
1781   /* Destination cannot contain a drive letter or directory separator */
1782   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1783       SetLastError(ERROR_INVALID_PARAMETER);
1784       WCMD_print_error();
1785       errorlevel = 1;
1786       return;
1787   }
1788
1789   /* Convert partial path to full path */
1790   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1791   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1792              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1793   dotDst = strchrW(param2, '.');
1794
1795   /* Split into components */
1796   WCMD_splitpath(input, drive, dir, fname, ext);
1797
1798   hff = FindFirstFileW(input, &fd);
1799   while (hff != INVALID_HANDLE_VALUE) {
1800     WCHAR  dest[MAX_PATH];
1801     WCHAR  src[MAX_PATH];
1802     WCHAR *dotSrc = NULL;
1803     int   dirLen;
1804
1805     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1806
1807     /* FIXME: If dest name or extension is *, replace with filename/ext
1808        part otherwise use supplied name. This supports:
1809           ren *.fred *.jim
1810           ren jim.* fred.* etc
1811        However, windows has a more complex algorithm supporting eg
1812           ?'s and *'s mid name                                         */
1813     dotSrc = strchrW(fd.cFileName, '.');
1814
1815     /* Build src & dest name */
1816     strcpyW(src, drive);
1817     strcatW(src, dir);
1818     strcpyW(dest, src);
1819     dirLen = strlenW(src);
1820     strcatW(src, fd.cFileName);
1821
1822     /* Build name */
1823     if (param2[0] == '*') {
1824       strcatW(dest, fd.cFileName);
1825       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1826     } else {
1827       strcatW(dest, param2);
1828       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1829     }
1830
1831     /* Build Extension */
1832     if (dotDst && (*(dotDst+1)=='*')) {
1833       if (dotSrc) strcatW(dest, dotSrc);
1834     } else if (dotDst) {
1835       if (dotDst) strcatW(dest, dotDst);
1836     }
1837
1838     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1839     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1840
1841     /* Check if file is read only, otherwise move it */
1842     attribs = GetFileAttributesW(src);
1843     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1844         (attribs & FILE_ATTRIBUTE_READONLY)) {
1845       SetLastError(ERROR_ACCESS_DENIED);
1846       status = 0;
1847     } else {
1848       status = MoveFileW(src, dest);
1849     }
1850
1851     if (!status) {
1852       WCMD_print_error ();
1853       errorlevel = 1;
1854     }
1855
1856     /* Step on to next match */
1857     if (FindNextFileW(hff, &fd) == 0) {
1858       FindClose(hff);
1859       hff = INVALID_HANDLE_VALUE;
1860       break;
1861     }
1862   }
1863 }
1864
1865 /*****************************************************************************
1866  * WCMD_dupenv
1867  *
1868  * Make a copy of the environment.
1869  */
1870 static WCHAR *WCMD_dupenv( const WCHAR *env )
1871 {
1872   WCHAR *env_copy;
1873   int len;
1874
1875   if( !env )
1876     return NULL;
1877
1878   len = 0;
1879   while ( env[len] )
1880     len += (strlenW(&env[len]) + 1);
1881
1882   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1883   if (!env_copy)
1884   {
1885     WINE_ERR("out of memory\n");
1886     return env_copy;
1887   }
1888   memcpy (env_copy, env, len*sizeof (WCHAR));
1889   env_copy[len] = 0;
1890
1891   return env_copy;
1892 }
1893
1894 /*****************************************************************************
1895  * WCMD_setlocal
1896  *
1897  *  setlocal pushes the environment onto a stack
1898  *  Save the environment as unicode so we don't screw anything up.
1899  */
1900 void WCMD_setlocal (const WCHAR *s) {
1901   WCHAR *env;
1902   struct env_stack *env_copy;
1903   WCHAR cwd[MAX_PATH];
1904
1905   /* DISABLEEXTENSIONS ignored */
1906
1907   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1908   if( !env_copy )
1909   {
1910     WINE_ERR ("out of memory\n");
1911     return;
1912   }
1913
1914   env = GetEnvironmentStringsW ();
1915
1916   env_copy->strings = WCMD_dupenv (env);
1917   if (env_copy->strings)
1918   {
1919     env_copy->next = saved_environment;
1920     saved_environment = env_copy;
1921
1922     /* Save the current drive letter */
1923     GetCurrentDirectoryW(MAX_PATH, cwd);
1924     env_copy->u.cwd = cwd[0];
1925   }
1926   else
1927     LocalFree (env_copy);
1928
1929   FreeEnvironmentStringsW (env);
1930
1931 }
1932
1933 /*****************************************************************************
1934  * WCMD_endlocal
1935  *
1936  *  endlocal pops the environment off a stack
1937  *  Note: When searching for '=', search from WCHAR position 1, to handle
1938  *        special internal environment variables =C:, =D: etc
1939  */
1940 void WCMD_endlocal (void) {
1941   WCHAR *env, *old, *p;
1942   struct env_stack *temp;
1943   int len, n;
1944
1945   if (!saved_environment)
1946     return;
1947
1948   /* pop the old environment from the stack */
1949   temp = saved_environment;
1950   saved_environment = temp->next;
1951
1952   /* delete the current environment, totally */
1953   env = GetEnvironmentStringsW ();
1954   old = WCMD_dupenv (GetEnvironmentStringsW ());
1955   len = 0;
1956   while (old[len]) {
1957     n = strlenW(&old[len]) + 1;
1958     p = strchrW(&old[len] + 1, '=');
1959     if (p)
1960     {
1961       *p++ = 0;
1962       SetEnvironmentVariableW (&old[len], NULL);
1963     }
1964     len += n;
1965   }
1966   LocalFree (old);
1967   FreeEnvironmentStringsW (env);
1968
1969   /* restore old environment */
1970   env = temp->strings;
1971   len = 0;
1972   while (env[len]) {
1973     n = strlenW(&env[len]) + 1;
1974     p = strchrW(&env[len] + 1, '=');
1975     if (p)
1976     {
1977       *p++ = 0;
1978       SetEnvironmentVariableW (&env[len], p);
1979     }
1980     len += n;
1981   }
1982
1983   /* Restore current drive letter */
1984   if (IsCharAlphaW(temp->u.cwd)) {
1985     WCHAR envvar[4];
1986     WCHAR cwd[MAX_PATH];
1987     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1988
1989     wsprintfW(envvar, fmt, temp->u.cwd);
1990     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1991       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1992       SetCurrentDirectoryW(cwd);
1993     }
1994   }
1995
1996   LocalFree (env);
1997   LocalFree (temp);
1998 }
1999
2000 /*****************************************************************************
2001  * WCMD_setshow_attrib
2002  *
2003  * Display and optionally sets DOS attributes on a file or directory
2004  *
2005  */
2006
2007 void WCMD_setshow_attrib (void) {
2008
2009   DWORD count;
2010   HANDLE hff;
2011   WIN32_FIND_DATAW fd;
2012   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2013   WCHAR *name = param1;
2014   DWORD attrib_set=0;
2015   DWORD attrib_clear=0;
2016
2017   if (param1[0] == '+' || param1[0] == '-') {
2018     DWORD attrib = 0;
2019     /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2020     switch (param1[1]) {
2021     case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2022     case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2023     case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2024     case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2025     default:
2026       WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2027       return;
2028     }
2029     switch (param1[0]) {
2030     case '+': attrib_set = attrib; break;
2031     case '-': attrib_clear = attrib; break;
2032     }
2033     name = param2;
2034   }
2035
2036   if (strlenW(name) == 0) {
2037     static const WCHAR slashStarW[]  = {'\\','*','\0'};
2038
2039     GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2040     strcatW (name, slashStarW);
2041   }
2042
2043   hff = FindFirstFileW(name, &fd);
2044   if (hff == INVALID_HANDLE_VALUE) {
2045     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2046   }
2047   else {
2048     do {
2049       if (attrib_set || attrib_clear) {
2050         fd.dwFileAttributes &= ~attrib_clear;
2051         fd.dwFileAttributes |= attrib_set;
2052         if (!fd.dwFileAttributes)
2053            fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2054         SetFileAttributesW(name, fd.dwFileAttributes);
2055       } else {
2056         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2057         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2058           flags[0] = 'H';
2059         }
2060         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2061           flags[1] = 'S';
2062         }
2063         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2064           flags[2] = 'A';
2065         }
2066         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2067           flags[3] = 'R';
2068         }
2069         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2070           flags[4] = 'T';
2071         }
2072         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2073           flags[5] = 'C';
2074         }
2075         WCMD_output (fmt, flags, fd.cFileName);
2076         for (count=0; count < 8; count++) flags[count] = ' ';
2077       }
2078     } while (FindNextFileW(hff, &fd) != 0);
2079   }
2080   FindClose (hff);
2081 }
2082
2083 /*****************************************************************************
2084  * WCMD_setshow_default
2085  *
2086  *      Set/Show the current default directory
2087  */
2088
2089 void WCMD_setshow_default (const WCHAR *command) {
2090
2091   BOOL status;
2092   WCHAR string[1024];
2093   WCHAR cwd[1024];
2094   WCHAR *pos;
2095   WIN32_FIND_DATAW fd;
2096   HANDLE hff;
2097   static const WCHAR parmD[] = {'/','D','\0'};
2098
2099   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2100
2101   /* Skip /D and trailing whitespace if on the front of the command line */
2102   if (CompareStringW(LOCALE_USER_DEFAULT,
2103                      NORM_IGNORECASE | SORT_STRINGSORT,
2104                      command, 2, parmD, -1) == CSTR_EQUAL) {
2105     command += 2;
2106     while (*command && *command==' ') command++;
2107   }
2108
2109   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2110   if (strlenW(command) == 0) {
2111     strcatW (cwd, newline);
2112     WCMD_output (cwd);
2113   }
2114   else {
2115     /* Remove any double quotes, which may be in the
2116        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2117     pos = string;
2118     while (*command) {
2119       if (*command != '"') *pos++ = *command;
2120       command++;
2121     }
2122     *pos = 0x00;
2123
2124     /* Search for appropriate directory */
2125     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2126     hff = FindFirstFileW(string, &fd);
2127     while (hff != INVALID_HANDLE_VALUE) {
2128       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2129         WCHAR fpath[MAX_PATH];
2130         WCHAR drive[10];
2131         WCHAR dir[MAX_PATH];
2132         WCHAR fname[MAX_PATH];
2133         WCHAR ext[MAX_PATH];
2134         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2135
2136         /* Convert path into actual directory spec */
2137         GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2138         WCMD_splitpath(fpath, drive, dir, fname, ext);
2139
2140         /* Rebuild path */
2141         wsprintfW(string, fmt, drive, dir, fd.cFileName);
2142
2143         FindClose(hff);
2144         hff = INVALID_HANDLE_VALUE;
2145         break;
2146       }
2147
2148       /* Step on to next match */
2149       if (FindNextFileW(hff, &fd) == 0) {
2150         FindClose(hff);
2151         hff = INVALID_HANDLE_VALUE;
2152         break;
2153       }
2154     }
2155
2156     /* Change to that directory */
2157     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2158
2159     status = SetCurrentDirectoryW(string);
2160     if (!status) {
2161       errorlevel = 1;
2162       WCMD_print_error ();
2163       return;
2164     } else {
2165
2166       /* Save away the actual new directory, to store as current location */
2167       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2168
2169       /* Restore old directory if drive letter would change, and
2170            CD x:\directory /D (or pushd c:\directory) not supplied */
2171       if ((strstrW(quals, parmD) == NULL) &&
2172           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2173         SetCurrentDirectoryW(cwd);
2174       }
2175     }
2176
2177     /* Set special =C: type environment variable, for drive letter of
2178        change of directory, even if path was restored due to missing
2179        /D (allows changing drive letter when not resident on that
2180        drive                                                          */
2181     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2182       WCHAR env[4];
2183       strcpyW(env, equalW);
2184       memcpy(env+1, string, 2 * sizeof(WCHAR));
2185       env[3] = 0x00;
2186       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2187       SetEnvironmentVariableW(env, string);
2188     }
2189
2190    }
2191   return;
2192 }
2193
2194 /****************************************************************************
2195  * WCMD_setshow_date
2196  *
2197  * Set/Show the system date
2198  * FIXME: Can't change date yet
2199  */
2200
2201 void WCMD_setshow_date (void) {
2202
2203   WCHAR curdate[64], buffer[64];
2204   DWORD count;
2205   static const WCHAR parmT[] = {'/','T','\0'};
2206
2207   if (strlenW(param1) == 0) {
2208     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2209                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2210       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2211       if (strstrW (quals, parmT) == NULL) {
2212         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2213         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2214                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2215         if (count > 2) {
2216           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2217         }
2218       }
2219     }
2220     else WCMD_print_error ();
2221   }
2222   else {
2223     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2224   }
2225 }
2226
2227 /****************************************************************************
2228  * WCMD_compare
2229  */
2230 static int WCMD_compare( const void *a, const void *b )
2231 {
2232     int r;
2233     const WCHAR * const *str_a = a, * const *str_b = b;
2234     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2235           *str_a, -1, *str_b, -1 );
2236     if( r == CSTR_LESS_THAN ) return -1;
2237     if( r == CSTR_GREATER_THAN ) return 1;
2238     return 0;
2239 }
2240
2241 /****************************************************************************
2242  * WCMD_setshow_sortenv
2243  *
2244  * sort variables into order for display
2245  * Optionally only display those who start with a stub
2246  * returns the count displayed
2247  */
2248 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2249 {
2250   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2251   const WCHAR **str;
2252
2253   if (stub) stublen = strlenW(stub);
2254
2255   /* count the number of strings, and the total length */
2256   while ( s[len] ) {
2257     len += (strlenW(&s[len]) + 1);
2258     count++;
2259   }
2260
2261   /* add the strings to an array */
2262   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2263   if( !str )
2264     return 0;
2265   str[0] = s;
2266   for( i=1; i<count; i++ )
2267     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2268
2269   /* sort the array */
2270   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2271
2272   /* print it */
2273   for( i=0; i<count; i++ ) {
2274     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2275                                 NORM_IGNORECASE | SORT_STRINGSORT,
2276                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2277       /* Don't display special internal variables */
2278       if (str[i][0] != '=') {
2279         WCMD_output_asis(str[i]);
2280         WCMD_output_asis(newline);
2281         displayedcount++;
2282       }
2283     }
2284   }
2285
2286   LocalFree( str );
2287   return displayedcount;
2288 }
2289
2290 /****************************************************************************
2291  * WCMD_setshow_env
2292  *
2293  * Set/Show the environment variables
2294  */
2295
2296 void WCMD_setshow_env (WCHAR *s) {
2297
2298   LPVOID env;
2299   WCHAR *p;
2300   int status;
2301   static const WCHAR parmP[] = {'/','P','\0'};
2302
2303   if (param1[0] == 0x00 && quals[0] == 0x00) {
2304     env = GetEnvironmentStringsW();
2305     WCMD_setshow_sortenv( env, NULL );
2306     return;
2307   }
2308
2309   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2310   if (CompareStringW(LOCALE_USER_DEFAULT,
2311                      NORM_IGNORECASE | SORT_STRINGSORT,
2312                      s, 2, parmP, -1) == CSTR_EQUAL) {
2313     WCHAR string[MAXSTRING];
2314     DWORD count;
2315
2316     s += 2;
2317     while (*s && *s==' ') s++;
2318     if (*s=='\"')
2319         WCMD_opt_s_strip_quotes(s);
2320
2321     /* If no parameter, or no '=' sign, return an error */
2322     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2323       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2324       return;
2325     }
2326
2327     /* Output the prompt */
2328     *p++ = '\0';
2329     if (strlenW(p) != 0) WCMD_output(p);
2330
2331     /* Read the reply */
2332     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2333                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2334     if (count > 1) {
2335       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2336       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2337       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2338                  wine_dbgstr_w(string));
2339       status = SetEnvironmentVariableW(s, string);
2340     }
2341
2342   } else {
2343     DWORD gle;
2344
2345     if (*s=='\"')
2346         WCMD_opt_s_strip_quotes(s);
2347     p = strchrW (s, '=');
2348     if (p == NULL) {
2349       env = GetEnvironmentStringsW();
2350       if (WCMD_setshow_sortenv( env, s ) == 0) {
2351         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2352         errorlevel = 1;
2353       }
2354       return;
2355     }
2356     *p++ = '\0';
2357
2358     if (strlenW(p) == 0) p = NULL;
2359     status = SetEnvironmentVariableW(s, p);
2360     gle = GetLastError();
2361     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2362       errorlevel = 1;
2363     } else if ((!status)) WCMD_print_error();
2364   }
2365 }
2366
2367 /****************************************************************************
2368  * WCMD_setshow_path
2369  *
2370  * Set/Show the path environment variable
2371  */
2372
2373 void WCMD_setshow_path (const WCHAR *command) {
2374
2375   WCHAR string[1024];
2376   DWORD status;
2377   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2378   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2379
2380   if (strlenW(param1) == 0) {
2381     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2382     if (status != 0) {
2383       WCMD_output_asis ( pathEqW);
2384       WCMD_output_asis ( string);
2385       WCMD_output_asis ( newline);
2386     }
2387     else {
2388       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2389     }
2390   }
2391   else {
2392     if (*command == '=') command++; /* Skip leading '=' */
2393     status = SetEnvironmentVariableW(pathW, command);
2394     if (!status) WCMD_print_error();
2395   }
2396 }
2397
2398 /****************************************************************************
2399  * WCMD_setshow_prompt
2400  *
2401  * Set or show the command prompt.
2402  */
2403
2404 void WCMD_setshow_prompt (void) {
2405
2406   WCHAR *s;
2407   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2408
2409   if (strlenW(param1) == 0) {
2410     SetEnvironmentVariableW(promptW, NULL);
2411   }
2412   else {
2413     s = param1;
2414     while ((*s == '=') || (*s == ' ')) s++;
2415     if (strlenW(s) == 0) {
2416       SetEnvironmentVariableW(promptW, NULL);
2417     }
2418     else SetEnvironmentVariableW(promptW, s);
2419   }
2420 }
2421
2422 /****************************************************************************
2423  * WCMD_setshow_time
2424  *
2425  * Set/Show the system time
2426  * FIXME: Can't change time yet
2427  */
2428
2429 void WCMD_setshow_time (void) {
2430
2431   WCHAR curtime[64], buffer[64];
2432   DWORD count;
2433   SYSTEMTIME st;
2434   static const WCHAR parmT[] = {'/','T','\0'};
2435
2436   if (strlenW(param1) == 0) {
2437     GetLocalTime(&st);
2438     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2439                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2440       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2441       if (strstrW (quals, parmT) == NULL) {
2442         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2443         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2444                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2445         if (count > 2) {
2446           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2447         }
2448       }
2449     }
2450     else WCMD_print_error ();
2451   }
2452   else {
2453     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2454   }
2455 }
2456
2457 /****************************************************************************
2458  * WCMD_shift
2459  *
2460  * Shift batch parameters.
2461  * Optional /n says where to start shifting (n=0-8)
2462  */
2463
2464 void WCMD_shift (const WCHAR *command) {
2465   int start;
2466
2467   if (context != NULL) {
2468     WCHAR *pos = strchrW(command, '/');
2469     int   i;
2470
2471     if (pos == NULL) {
2472       start = 0;
2473     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2474       start = (*(pos+1) - '0');
2475     } else {
2476       SetLastError(ERROR_INVALID_PARAMETER);
2477       WCMD_print_error();
2478       return;
2479     }
2480
2481     WINE_TRACE("Shifting variables, starting at %d\n", start);
2482     for (i=start;i<=8;i++) {
2483       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2484     }
2485     context -> shift_count[9] = context -> shift_count[9] + 1;
2486   }
2487
2488 }
2489
2490 /****************************************************************************
2491  * WCMD_title
2492  *
2493  * Set the console title
2494  */
2495 void WCMD_title (const WCHAR *command) {
2496   SetConsoleTitleW(command);
2497 }
2498
2499 /****************************************************************************
2500  * WCMD_type
2501  *
2502  * Copy a file to standard output.
2503  */
2504
2505 void WCMD_type (WCHAR *command) {
2506
2507   int   argno         = 0;
2508   WCHAR *argN          = command;
2509   BOOL  writeHeaders  = FALSE;
2510
2511   if (param1[0] == 0x00) {
2512     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2513     return;
2514   }
2515
2516   if (param2[0] != 0x00) writeHeaders = TRUE;
2517
2518   /* Loop through all args */
2519   errorlevel = 0;
2520   while (argN) {
2521     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2522
2523     HANDLE h;
2524     WCHAR buffer[512];
2525     DWORD count;
2526
2527     if (!argN) break;
2528
2529     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2530     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2531                 FILE_ATTRIBUTE_NORMAL, NULL);
2532     if (h == INVALID_HANDLE_VALUE) {
2533       WCMD_print_error ();
2534       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2535       errorlevel = 1;
2536     } else {
2537       if (writeHeaders) {
2538         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2539         WCMD_output(fmt, thisArg);
2540       }
2541       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2542         if (count == 0) break;  /* ReadFile reports success on EOF! */
2543         buffer[count] = 0;
2544         WCMD_output_asis (buffer);
2545       }
2546       CloseHandle (h);
2547     }
2548   }
2549 }
2550
2551 /****************************************************************************
2552  * WCMD_more
2553  *
2554  * Output either a file or stdin to screen in pages
2555  */
2556
2557 void WCMD_more (WCHAR *command) {
2558
2559   int   argno         = 0;
2560   WCHAR *argN          = command;
2561   WCHAR  moreStr[100];
2562   WCHAR  moreStrPage[100];
2563   WCHAR  buffer[512];
2564   DWORD count;
2565   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2566   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2567   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2568                                     ')',' ','-','-','\n','\0'};
2569   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2570
2571   /* Prefix the NLS more with '-- ', then load the text */
2572   errorlevel = 0;
2573   strcpyW(moreStr, moreStart);
2574   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2575               (sizeof(moreStr)/sizeof(WCHAR))-3);
2576
2577   if (param1[0] == 0x00) {
2578
2579     /* Wine implements pipes via temporary files, and hence stdin is
2580        effectively reading from the file. This means the prompts for
2581        more are satisfied by the next line from the input (file). To
2582        avoid this, ensure stdin is to the console                    */
2583     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2584     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2585                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2586                          FILE_ATTRIBUTE_NORMAL, 0);
2587     WINE_TRACE("No parms - working probably in pipe mode\n");
2588     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2589
2590     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2591        once you get in this bit unless due to a pipe, its going to end badly...  */
2592     wsprintfW(moreStrPage, moreFmt, moreStr);
2593
2594     WCMD_enter_paged_mode(moreStrPage);
2595     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2596       if (count == 0) break;    /* ReadFile reports success on EOF! */
2597       buffer[count] = 0;
2598       WCMD_output_asis (buffer);
2599     }
2600     WCMD_leave_paged_mode();
2601
2602     /* Restore stdin to what it was */
2603     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2604     CloseHandle(hConIn);
2605
2606     return;
2607   } else {
2608     BOOL needsPause = FALSE;
2609
2610     /* Loop through all args */
2611     WINE_TRACE("Parms supplied - working through each file\n");
2612     WCMD_enter_paged_mode(moreStrPage);
2613
2614     while (argN) {
2615       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2616       HANDLE h;
2617
2618       if (!argN) break;
2619
2620       if (needsPause) {
2621
2622         /* Wait */
2623         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2624         WCMD_leave_paged_mode();
2625         WCMD_output_asis(moreStrPage);
2626         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2627                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2628         WCMD_enter_paged_mode(moreStrPage);
2629       }
2630
2631
2632       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2633       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2634                 FILE_ATTRIBUTE_NORMAL, NULL);
2635       if (h == INVALID_HANDLE_VALUE) {
2636         WCMD_print_error ();
2637         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2638         errorlevel = 1;
2639       } else {
2640         ULONG64 curPos  = 0;
2641         ULONG64 fileLen = 0;
2642         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2643
2644         /* Get the file size */
2645         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2646         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2647
2648         needsPause = TRUE;
2649         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2650           if (count == 0) break;        /* ReadFile reports success on EOF! */
2651           buffer[count] = 0;
2652           curPos += count;
2653
2654           /* Update % count (would be used in WCMD_output_asis as prompt) */
2655           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2656
2657           WCMD_output_asis (buffer);
2658         }
2659         CloseHandle (h);
2660       }
2661     }
2662
2663     WCMD_leave_paged_mode();
2664   }
2665 }
2666
2667 /****************************************************************************
2668  * WCMD_verify
2669  *
2670  * Display verify flag.
2671  * FIXME: We don't actually do anything with the verify flag other than toggle
2672  * it...
2673  */
2674
2675 void WCMD_verify (const WCHAR *command) {
2676
2677   int count;
2678
2679   count = strlenW(command);
2680   if (count == 0) {
2681     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2682     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2683     return;
2684   }
2685   if (lstrcmpiW(command, onW) == 0) {
2686     verify_mode = 1;
2687     return;
2688   }
2689   else if (lstrcmpiW(command, offW) == 0) {
2690     verify_mode = 0;
2691     return;
2692   }
2693   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2694 }
2695
2696 /****************************************************************************
2697  * WCMD_version
2698  *
2699  * Display version info.
2700  */
2701
2702 void WCMD_version (void) {
2703
2704   WCMD_output (version_string);
2705
2706 }
2707
2708 /****************************************************************************
2709  * WCMD_volume
2710  *
2711  * Display volume info and/or set volume label. Returns 0 if error.
2712  */
2713
2714 int WCMD_volume (int mode, const WCHAR *path) {
2715
2716   DWORD count, serial;
2717   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2718   BOOL status;
2719
2720   if (strlenW(path) == 0) {
2721     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2722     if (!status) {
2723       WCMD_print_error ();
2724       return 0;
2725     }
2726     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2727                                    &serial, NULL, NULL, NULL, 0);
2728   }
2729   else {
2730     static const WCHAR fmt[] = {'%','s','\\','\0'};
2731     if ((path[1] != ':') || (strlenW(path) != 2)) {
2732       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2733       return 0;
2734     }
2735     wsprintfW (curdir, fmt, path);
2736     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2737                                    &serial, NULL,
2738         NULL, NULL, 0);
2739   }
2740   if (!status) {
2741     WCMD_print_error ();
2742     return 0;
2743   }
2744   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2745         curdir[0], label, HIWORD(serial), LOWORD(serial));
2746   if (mode) {
2747     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2748     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2749                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2750     if (count > 1) {
2751       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2752       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2753     }
2754     if (strlenW(path) != 0) {
2755       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2756     }
2757     else {
2758       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2759     }
2760   }
2761   return 1;
2762 }
2763
2764 /**************************************************************************
2765  * WCMD_exit
2766  *
2767  * Exit either the process, or just this batch program
2768  *
2769  */
2770
2771 void WCMD_exit (CMD_LIST **cmdList) {
2772
2773     static const WCHAR parmB[] = {'/','B','\0'};
2774     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2775
2776     if (context && lstrcmpiW(quals, parmB) == 0) {
2777         errorlevel = rc;
2778         context -> skip_rest = TRUE;
2779         *cmdList = NULL;
2780     } else {
2781         ExitProcess(rc);
2782     }
2783 }
2784
2785
2786 /*****************************************************************************
2787  * WCMD_assoc
2788  *
2789  *      Lists or sets file associations  (assoc = TRUE)
2790  *      Lists or sets file types         (assoc = FALSE)
2791  */
2792 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2793
2794     HKEY    key;
2795     DWORD   accessOptions = KEY_READ;
2796     WCHAR   *newValue;
2797     LONG    rc = ERROR_SUCCESS;
2798     WCHAR    keyValue[MAXSTRING];
2799     DWORD   valueLen = MAXSTRING;
2800     HKEY    readKey;
2801     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2802                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2803
2804     /* See if parameter includes '=' */
2805     errorlevel = 0;
2806     newValue = strchrW(command, '=');
2807     if (newValue) accessOptions |= KEY_WRITE;
2808
2809     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2810     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2811                       accessOptions, &key) != ERROR_SUCCESS) {
2812       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2813       return;
2814     }
2815
2816     /* If no parameters then list all associations */
2817     if (*command == 0x00) {
2818       int index = 0;
2819
2820       /* Enumerate all the keys */
2821       while (rc != ERROR_NO_MORE_ITEMS) {
2822         WCHAR  keyName[MAXSTRING];
2823         DWORD nameLen;
2824
2825         /* Find the next value */
2826         nameLen = MAXSTRING;
2827         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2828
2829         if (rc == ERROR_SUCCESS) {
2830
2831           /* Only interested in extension ones if assoc, or others
2832              if not assoc                                          */
2833           if ((keyName[0] == '.' && assoc) ||
2834               (!(keyName[0] == '.') && (!assoc)))
2835           {
2836             WCHAR subkey[MAXSTRING];
2837             strcpyW(subkey, keyName);
2838             if (!assoc) strcatW(subkey, shOpCmdW);
2839
2840             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2841
2842               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2843               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2844               WCMD_output_asis(keyName);
2845               WCMD_output_asis(equalW);
2846               /* If no default value found, leave line empty after '=' */
2847               if (rc == ERROR_SUCCESS) {
2848                 WCMD_output_asis(keyValue);
2849               }
2850               WCMD_output_asis(newline);
2851               RegCloseKey(readKey);
2852             }
2853           }
2854         }
2855       }
2856
2857     } else {
2858
2859       /* Parameter supplied - if no '=' on command line, its a query */
2860       if (newValue == NULL) {
2861         WCHAR *space;
2862         WCHAR subkey[MAXSTRING];
2863
2864         /* Query terminates the parameter at the first space */
2865         strcpyW(keyValue, command);
2866         space = strchrW(keyValue, ' ');
2867         if (space) *space=0x00;
2868
2869         /* Set up key name */
2870         strcpyW(subkey, keyValue);
2871         if (!assoc) strcatW(subkey, shOpCmdW);
2872
2873         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2874
2875           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2876           WCMD_output_asis(command);
2877           WCMD_output_asis(equalW);
2878           /* If no default value found, leave line empty after '=' */
2879           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2880           WCMD_output_asis(newline);
2881           RegCloseKey(readKey);
2882
2883         } else {
2884           WCHAR  msgbuffer[MAXSTRING];
2885           WCHAR  outbuffer[MAXSTRING];
2886
2887           /* Load the translated 'File association not found' */
2888           if (assoc) {
2889             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2890           } else {
2891             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2892           }
2893           wsprintfW(outbuffer, msgbuffer, keyValue);
2894           WCMD_output_asis(outbuffer);
2895           errorlevel = 2;
2896         }
2897
2898       /* Not a query - its a set or clear of a value */
2899       } else {
2900
2901         WCHAR subkey[MAXSTRING];
2902
2903         /* Get pointer to new value */
2904         *newValue = 0x00;
2905         newValue++;
2906
2907         /* Set up key name */
2908         strcpyW(subkey, command);
2909         if (!assoc) strcatW(subkey, shOpCmdW);
2910
2911         /* If nothing after '=' then clear value - only valid for ASSOC */
2912         if (*newValue == 0x00) {
2913
2914           if (assoc) rc = RegDeleteKeyW(key, command);
2915           if (assoc && rc == ERROR_SUCCESS) {
2916             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2917
2918           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2919             WCMD_print_error();
2920             errorlevel = 2;
2921
2922           } else {
2923             WCHAR  msgbuffer[MAXSTRING];
2924             WCHAR  outbuffer[MAXSTRING];
2925
2926             /* Load the translated 'File association not found' */
2927             if (assoc) {
2928               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2929                           sizeof(msgbuffer)/sizeof(WCHAR));
2930             } else {
2931               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2932                           sizeof(msgbuffer)/sizeof(WCHAR));
2933             }
2934             wsprintfW(outbuffer, msgbuffer, keyValue);
2935             WCMD_output_asis(outbuffer);
2936             errorlevel = 2;
2937           }
2938
2939         /* It really is a set value = contents */
2940         } else {
2941           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2942                               accessOptions, NULL, &readKey, NULL);
2943           if (rc == ERROR_SUCCESS) {
2944             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2945                                  (LPBYTE)newValue, strlenW(newValue));
2946             RegCloseKey(readKey);
2947           }
2948
2949           if (rc != ERROR_SUCCESS) {
2950             WCMD_print_error();
2951             errorlevel = 2;
2952           } else {
2953             WCMD_output_asis(command);
2954             WCMD_output_asis(equalW);
2955             WCMD_output_asis(newValue);
2956             WCMD_output_asis(newline);
2957           }
2958         }
2959       }
2960     }
2961
2962     /* Clean up */
2963     RegCloseKey(key);
2964 }
2965
2966 /****************************************************************************
2967  * WCMD_color
2968  *
2969  * Clear the terminal screen.
2970  */
2971
2972 void WCMD_color (void) {
2973
2974   /* Emulate by filling the screen from the top left to bottom right with
2975         spaces, then moving the cursor to the top left afterwards */
2976   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2977   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2978
2979   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2980     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2981     return;
2982   }
2983
2984   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2985   {
2986       COORD topLeft;
2987       DWORD screenSize;
2988       DWORD color = 0;
2989
2990       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2991
2992       topLeft.X = 0;
2993       topLeft.Y = 0;
2994
2995       /* Convert the color hex digits */
2996       if (param1[0] == 0x00) {
2997         color = defaultColor;
2998       } else {
2999         color = strtoulW(param1, NULL, 16);
3000       }
3001
3002       /* Fail if fg == bg color */
3003       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3004         errorlevel = 1;
3005         return;
3006       }
3007
3008       /* Set the current screen contents and ensure all future writes
3009          remain this color                                             */
3010       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3011       SetConsoleTextAttribute(hStdOut, color);
3012   }
3013 }