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