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