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