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