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