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