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