cmd: for loops did not respect boundaries.
[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 conditionTRUE)
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              conditionTRUE);
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 (conditionTRUE && 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 = TRUE;
998
999     if (isIF) processThese = conditionTRUE;
1000
1001     while (*cmdList) {
1002       static const WCHAR ifElse[] = {'e','l','s','e'};
1003
1004       /* execute all appropriate commands */
1005       curPosition = *cmdList;
1006
1007       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1008                  *cmdList,
1009                  (*cmdList)->prevDelim,
1010                  (*cmdList)->bracketDepth, myDepth);
1011
1012       /* Execute any statements appended to the line */
1013       /* FIXME: Only if previous call worked for && or failed for || */
1014       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1015           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1016         if (processThese && (*cmdList)->command) {
1017           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1018                         value, cmdList);
1019         }
1020         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1021
1022       /* Execute any appended to the statement with (...) */
1023       } else if ((*cmdList)->bracketDepth > myDepth) {
1024         if (processThese) {
1025           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1026           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1027         }
1028         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1029
1030       /* End of the command - does 'ELSE ' follow as the next command? */
1031       } else {
1032         if (isIF
1033             && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1034                                      (*cmdList)->command)) {
1035
1036           /* Swap between if and else processing */
1037           processThese = !processThese;
1038
1039           /* Process the ELSE part */
1040           if (processThese) {
1041             const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1042             WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1043
1044             /* Skip leading whitespace between condition and the command */
1045             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1046             if (*cmd) {
1047               WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1048             }
1049           }
1050           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1051         } else {
1052           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1053           break;
1054         }
1055       }
1056     }
1057   }
1058   return;
1059 }
1060
1061 /**************************************************************************
1062  * WCMD_for
1063  *
1064  * Batch file loop processing.
1065  *
1066  * On entry: cmdList       contains the syntax up to the set
1067  *           next cmdList and all in that bracket contain the set data
1068  *           next cmdlist  contains the DO cmd
1069  *           following that is either brackets or && entries (as per if)
1070  *
1071  */
1072
1073 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1074
1075   WIN32_FIND_DATAW fd;
1076   HANDLE hff;
1077   int i;
1078   static const WCHAR inW[] = {'i','n'};
1079   static const WCHAR doW[] = {'d','o'};
1080   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1081   WCHAR variable[4];
1082   WCHAR *firstCmd;
1083   int thisDepth;
1084
1085   WCHAR *curPos = p;
1086   BOOL   expandDirs  = FALSE;
1087   BOOL   useNumbers  = FALSE;
1088   BOOL   doFileset   = FALSE;
1089   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1090   int    itemNum;
1091   CMD_LIST *thisCmdStart;
1092
1093
1094   /* Handle optional qualifiers (multiple are allowed) */
1095   while (*curPos && *curPos == '/') {
1096       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1097       curPos++;
1098       switch (toupperW(*curPos)) {
1099       case 'D': curPos++; expandDirs = TRUE; break;
1100       case 'L': curPos++; useNumbers = TRUE; break;
1101
1102       /* Recursive is special case - /R can have an optional path following it                */
1103       /* filenamesets are another special case - /F can have an optional options following it */
1104       case 'R':
1105       case 'F':
1106           {
1107               BOOL isRecursive = (*curPos == 'R');
1108
1109               if (!isRecursive)
1110                   doFileset = TRUE;
1111
1112               /* Skip whitespace */
1113               curPos++;
1114               while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1115
1116               /* Next parm is either qualifier, path/options or variable -
1117                  only care about it if it is the path/options              */
1118               if (*curPos && *curPos != '/' && *curPos != '%') {
1119                   if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1120                   else {
1121                       static unsigned int once;
1122                       if (!once++) WINE_FIXME("/F needs to handle options\n");
1123                   }
1124               }
1125               break;
1126           }
1127       default:
1128           WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1129           curPos++;
1130       }
1131
1132       /* Skip whitespace between qualifiers */
1133       while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1134   }
1135
1136   /* Skip whitespace before variable */
1137   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1138
1139   /* Ensure line continues with variable */
1140   if (!*curPos || *curPos != '%') {
1141       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1142       return;
1143   }
1144
1145   /* Variable should follow */
1146   i = 0;
1147   while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1148   memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1149   variable[i] = 0x00;
1150   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1151   curPos = &curPos[i];
1152
1153   /* Skip whitespace before IN */
1154   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1155
1156   /* Ensure line continues with IN */
1157   if (!*curPos
1158        || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1159
1160       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1161       return;
1162   }
1163
1164   /* Save away where the set of data starts and the variable */
1165   thisDepth = (*cmdList)->bracketDepth;
1166   *cmdList = (*cmdList)->nextcommand;
1167   setStart = (*cmdList);
1168
1169   /* Skip until the close bracket */
1170   WINE_TRACE("Searching %p as the set\n", *cmdList);
1171   while (*cmdList &&
1172          (*cmdList)->command != NULL &&
1173          (*cmdList)->bracketDepth > thisDepth) {
1174     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1175     *cmdList = (*cmdList)->nextcommand;
1176   }
1177
1178   /* Skip the close bracket, if there is one */
1179   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1180
1181   /* Syntax error if missing close bracket, or nothing following it
1182      and once we have the complete set, we expect a DO              */
1183   WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1184   if ((*cmdList == NULL)
1185       || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1186
1187       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1188       return;
1189   }
1190
1191   /* Save away the starting position for the commands (and offset for the
1192      first one                                                           */
1193   cmdStart = *cmdList;
1194   cmdEnd   = *cmdList;
1195   firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1196   itemNum  = 0;
1197
1198   thisSet = setStart;
1199   /* Loop through all set entries */
1200   while (thisSet &&
1201          thisSet->command != NULL &&
1202          thisSet->bracketDepth >= thisDepth) {
1203
1204     /* Loop through all entries on the same line */
1205     WCHAR *item;
1206     WCHAR *itemStart;
1207
1208     WINE_TRACE("Processing for set %p\n", thisSet);
1209     i = 0;
1210     while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1211
1212       /*
1213        * If the parameter within the set has a wildcard then search for matching files
1214        * otherwise do a literal substitution.
1215        */
1216       static const WCHAR wildcards[] = {'*','?','\0'};
1217       thisCmdStart = cmdStart;
1218
1219       itemNum++;
1220       WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1221
1222       if (!useNumbers && !doFileset) {
1223           if (strpbrkW (item, wildcards)) {
1224             hff = FindFirstFileW(item, &fd);
1225             if (hff != INVALID_HANDLE_VALUE) {
1226               do {
1227                 BOOL isDirectory = FALSE;
1228
1229                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1230
1231                 /* Handle as files or dirs appropriately, but ignore . and .. */
1232                 if (isDirectory == expandDirs &&
1233                     (strcmpW(fd.cFileName, dotdotW) != 0) &&
1234                     (strcmpW(fd.cFileName, dotW) != 0))
1235                 {
1236                   thisCmdStart = cmdStart;
1237                   WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
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             WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1247           }
1248
1249       } else if (useNumbers) {
1250           /* Convert the first 3 numbers to signed longs and save */
1251           if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1252           /* else ignore them! */
1253
1254       /* Filesets - either a list of files, or a command to run and parse the output */
1255       } else if (doFileset && *itemStart != '"') {
1256
1257           HANDLE input;
1258           WCHAR temp_file[MAX_PATH];
1259
1260           WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1261                      wine_dbgstr_w(item));
1262
1263           /* If backquote or single quote, we need to launch that command
1264              and parse the results - use a temporary file                 */
1265           if (*itemStart == '`' || *itemStart == '\'') {
1266
1267               WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1268               static const WCHAR redirOut[] = {'>','%','s','\0'};
1269               static const WCHAR cmdW[]     = {'C','M','D','\0'};
1270
1271               /* Remove trailing character */
1272               itemStart[strlenW(itemStart)-1] = 0x00;
1273
1274               /* Get temp filename */
1275               GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1276               GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1277
1278               /* Execute program and redirect output */
1279               wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1280               WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1281
1282               /* Open the file, read line by line and process */
1283               input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1284                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1285           } else {
1286
1287               /* Open the file, read line by line and process */
1288               input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1289                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1290           }
1291
1292           /* Process the input file */
1293           if (input == INVALID_HANDLE_VALUE) {
1294             WCMD_print_error ();
1295             WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1296             errorlevel = 1;
1297             return; /* FOR loop aborts at first failure here */
1298
1299           } else {
1300
1301             WCHAR buffer[MAXSTRING] = {'\0'};
1302             WCHAR *where, *parm;
1303
1304             while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1305
1306               /* Skip blank lines*/
1307               parm = WCMD_parameter (buffer, 0, &where, NULL);
1308               WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1309                          wine_dbgstr_w(buffer));
1310
1311               if (where) {
1312                   /* FIXME: The following should be moved into its own routine and
1313                      reused for the string literal parsing below                  */
1314                   thisCmdStart = cmdStart;
1315                   WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1316                   cmdEnd = thisCmdStart;
1317               }
1318
1319               buffer[0] = 0x00;
1320
1321             }
1322             CloseHandle (input);
1323           }
1324
1325           /* Delete the temporary file */
1326           if (*itemStart == '`' || *itemStart == '\'') {
1327               DeleteFileW(temp_file);
1328           }
1329
1330       /* Filesets - A string literal */
1331       } else if (doFileset && *itemStart == '"') {
1332           WCHAR buffer[MAXSTRING] = {'\0'};
1333           WCHAR *where, *parm;
1334
1335           /* Skip blank lines, and re-extract parameter now string has quotes removed */
1336           strcpyW(buffer, item);
1337           parm = WCMD_parameter (buffer, 0, &where, NULL);
1338           WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1339                        wine_dbgstr_w(buffer));
1340
1341           if (where) {
1342               /* FIXME: The following should be moved into its own routine and
1343                  reused for the string literal parsing below                  */
1344               thisCmdStart = cmdStart;
1345               WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1346               cmdEnd = thisCmdStart;
1347           }
1348       }
1349
1350       WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1351       cmdEnd = thisCmdStart;
1352       i++;
1353     }
1354
1355     /* Move onto the next set line */
1356     thisSet = thisSet->nextcommand;
1357   }
1358
1359   /* If /L is provided, now run the for loop */
1360   if (useNumbers) {
1361       WCHAR thisNum[20];
1362       static const WCHAR fmt[] = {'%','d','\0'};
1363
1364       WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1365                  numbers[0], numbers[2], numbers[1]);
1366       for (i=numbers[0];
1367            (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1368            i=i + numbers[1]) {
1369
1370           sprintfW(thisNum, fmt, i);
1371           WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1372
1373           thisCmdStart = cmdStart;
1374           WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1375           cmdEnd = thisCmdStart;
1376       }
1377   }
1378
1379   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1380      all processing, OR it should be pointing to the end of && processing OR
1381      it should be pointing at the NULL end of bracket for the DO. The return
1382      value needs to be the NEXT command to execute, which it either is, or
1383      we need to step over the closing bracket                                  */
1384   *cmdList = cmdEnd;
1385   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1386 }
1387
1388 /**************************************************************************
1389  * WCMD_give_help
1390  *
1391  *      Simple on-line help. Help text is stored in the resource file.
1392  */
1393
1394 void WCMD_give_help (const WCHAR *command)
1395 {
1396   size_t i;
1397
1398   command = WCMD_skip_leading_spaces((WCHAR*) command);
1399   if (strlenW(command) == 0) {
1400     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1401   }
1402   else {
1403     /* Display help message for builtin commands */
1404     for (i=0; i<=WCMD_EXIT; i++) {
1405       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1406           command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1407         WCMD_output_asis (WCMD_LoadMessage(i));
1408         return;
1409       }
1410     }
1411     /* Launch the command with the /? option for external commands shipped with cmd.exe */
1412     for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1413       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1414           command, -1, externals[i], -1) == CSTR_EQUAL) {
1415         WCHAR cmd[128];
1416         static const WCHAR helpW[] = {' ', '/','?','\0'};
1417         strcpyW(cmd, command);
1418         strcatW(cmd, helpW);
1419         WCMD_run_program(cmd, FALSE);
1420         return;
1421       }
1422     }
1423     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1424   }
1425   return;
1426 }
1427
1428 /****************************************************************************
1429  * WCMD_go_to
1430  *
1431  * Batch file jump instruction. Not the most efficient algorithm ;-)
1432  * Prints error message if the specified label cannot be found - the file pointer is
1433  * then at EOF, effectively stopping the batch file.
1434  * FIXME: DOS is supposed to allow labels with spaces - we don't.
1435  */
1436
1437 void WCMD_goto (CMD_LIST **cmdList) {
1438
1439   WCHAR string[MAX_PATH];
1440   WCHAR current[MAX_PATH];
1441
1442   /* Do not process any more parts of a processed multipart or multilines command */
1443   if (cmdList) *cmdList = NULL;
1444
1445   if (context != NULL) {
1446     WCHAR *paramStart = param1, *str;
1447     static const WCHAR eofW[] = {':','e','o','f','\0'};
1448
1449     if (param1[0] == 0x00) {
1450       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1451       return;
1452     }
1453
1454     /* Handle special :EOF label */
1455     if (lstrcmpiW (eofW, param1) == 0) {
1456       context -> skip_rest = TRUE;
1457       return;
1458     }
1459
1460     /* Support goto :label as well as goto label */
1461     if (*paramStart == ':') paramStart++;
1462
1463     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1464     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1465       str = string;
1466       while (isspaceW (*str)) str++;
1467       if (*str == ':') {
1468         DWORD index = 0;
1469         str++;
1470         while (((current[index] = str[index])) && (!isspaceW (current[index])))
1471             index++;
1472
1473         /* ignore space at the end */
1474         current[index] = 0;
1475         if (lstrcmpiW (current, paramStart) == 0) return;
1476       }
1477     }
1478     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1479   }
1480   return;
1481 }
1482
1483 /*****************************************************************************
1484  * WCMD_pushd
1485  *
1486  *      Push a directory onto the stack
1487  */
1488
1489 void WCMD_pushd (const WCHAR *command)
1490 {
1491     struct env_stack *curdir;
1492     WCHAR *thisdir;
1493     static const WCHAR parmD[] = {'/','D','\0'};
1494
1495     if (strchrW(command, '/') != NULL) {
1496       SetLastError(ERROR_INVALID_PARAMETER);
1497       WCMD_print_error();
1498       return;
1499     }
1500
1501     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1502     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1503     if( !curdir || !thisdir ) {
1504       LocalFree(curdir);
1505       LocalFree(thisdir);
1506       WINE_ERR ("out of memory\n");
1507       return;
1508     }
1509
1510     /* Change directory using CD code with /D parameter */
1511     strcpyW(quals, parmD);
1512     GetCurrentDirectoryW (1024, thisdir);
1513     errorlevel = 0;
1514     WCMD_setshow_default(command);
1515     if (errorlevel) {
1516       LocalFree(curdir);
1517       LocalFree(thisdir);
1518       return;
1519     } else {
1520       curdir -> next    = pushd_directories;
1521       curdir -> strings = thisdir;
1522       if (pushd_directories == NULL) {
1523         curdir -> u.stackdepth = 1;
1524       } else {
1525         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1526       }
1527       pushd_directories = curdir;
1528     }
1529 }
1530
1531
1532 /*****************************************************************************
1533  * WCMD_popd
1534  *
1535  *      Pop a directory from the stack
1536  */
1537
1538 void WCMD_popd (void) {
1539     struct env_stack *temp = pushd_directories;
1540
1541     if (!pushd_directories)
1542       return;
1543
1544     /* pop the old environment from the stack, and make it the current dir */
1545     pushd_directories = temp->next;
1546     SetCurrentDirectoryW(temp->strings);
1547     LocalFree (temp->strings);
1548     LocalFree (temp);
1549 }
1550
1551 /****************************************************************************
1552  * WCMD_if
1553  *
1554  * Batch file conditional.
1555  *
1556  * On entry, cmdlist will point to command containing the IF, and optionally
1557  *   the first command to execute (if brackets not found)
1558  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1559  *   If ('s were found, execute all within that bracket
1560  *   Command may optionally be followed by an ELSE - need to skip instructions
1561  *   in the else using the same logic
1562  *
1563  * FIXME: Much more syntax checking needed!
1564  */
1565
1566 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1567
1568   int negate; /* Negate condition */
1569   int test;   /* Condition evaluation result */
1570   WCHAR condition[MAX_PATH], *command, *s;
1571   static const WCHAR notW[]    = {'n','o','t','\0'};
1572   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1573   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1574   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1575   static const WCHAR eqeqW[]   = {'=','=','\0'};
1576   static const WCHAR parmI[]   = {'/','I','\0'};
1577   int caseInsensitive = (strstrW(quals, parmI) != NULL);
1578
1579   negate = !lstrcmpiW(param1,notW);
1580   strcpyW(condition, (negate ? param2 : param1));
1581   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1582
1583   if (!lstrcmpiW (condition, errlvlW)) {
1584     WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1585     WCHAR *endptr;
1586     long int param_int = strtolW(param, &endptr, 10);
1587     if (*endptr) {
1588       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1589       return;
1590     }
1591     test = ((long int)errorlevel >= param_int);
1592     WCMD_parameter(p, 2+negate, &command, NULL);
1593   }
1594   else if (!lstrcmpiW (condition, existW)) {
1595     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1596     WCMD_parameter(p, 2+negate, &command, NULL);
1597   }
1598   else if (!lstrcmpiW (condition, defdW)) {
1599     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1600     WCMD_parameter(p, 2+negate, &command, NULL);
1601   }
1602   else if ((s = strstrW (p, eqeqW))) {
1603     /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1604     WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1605     s += 2;
1606     WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1607     WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1608     test = caseInsensitive
1609             ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1610                               leftPart, leftPartEnd-leftPart+1,
1611                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1612             : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1613                               leftPart, leftPartEnd-leftPart+1,
1614                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1615     WCMD_parameter(s, 1, &command, NULL);
1616   }
1617   else {
1618     WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1619     return;
1620   }
1621
1622   /* Process rest of IF statement which is on the same line
1623      Note: This may process all or some of the cmdList (eg a GOTO) */
1624   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1625 }
1626
1627 /****************************************************************************
1628  * WCMD_move
1629  *
1630  * Move a file, directory tree or wildcarded set of files.
1631  */
1632
1633 void WCMD_move (void)
1634 {
1635   int             status;
1636   WIN32_FIND_DATAW fd;
1637   HANDLE          hff;
1638   WCHAR            input[MAX_PATH];
1639   WCHAR            output[MAX_PATH];
1640   WCHAR            drive[10];
1641   WCHAR            dir[MAX_PATH];
1642   WCHAR            fname[MAX_PATH];
1643   WCHAR            ext[MAX_PATH];
1644
1645   if (param1[0] == 0x00) {
1646     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1647     return;
1648   }
1649
1650   /* If no destination supplied, assume current directory */
1651   if (param2[0] == 0x00) {
1652       strcpyW(param2, dotW);
1653   }
1654
1655   /* If 2nd parm is directory, then use original filename */
1656   /* Convert partial path to full path */
1657   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1658   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1659   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1660              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1661
1662   /* Split into components */
1663   WCMD_splitpath(input, drive, dir, fname, ext);
1664
1665   hff = FindFirstFileW(input, &fd);
1666   if (hff == INVALID_HANDLE_VALUE)
1667     return;
1668
1669   do {
1670     WCHAR  dest[MAX_PATH];
1671     WCHAR  src[MAX_PATH];
1672     DWORD attribs;
1673     BOOL ok = TRUE;
1674
1675     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1676
1677     /* Build src & dest name */
1678     strcpyW(src, drive);
1679     strcatW(src, dir);
1680
1681     /* See if dest is an existing directory */
1682     attribs = GetFileAttributesW(output);
1683     if (attribs != INVALID_FILE_ATTRIBUTES &&
1684        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1685       strcpyW(dest, output);
1686       strcatW(dest, slashW);
1687       strcatW(dest, fd.cFileName);
1688     } else {
1689       strcpyW(dest, output);
1690     }
1691
1692     strcatW(src, fd.cFileName);
1693
1694     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1695     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1696
1697     /* If destination exists, prompt unless /Y supplied */
1698     if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1699       BOOL force = FALSE;
1700       WCHAR copycmd[MAXSTRING];
1701       DWORD len;
1702
1703       /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1704       if (strstrW (quals, parmNoY))
1705         force = FALSE;
1706       else if (strstrW (quals, parmY))
1707         force = TRUE;
1708       else {
1709         static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1710         len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1711         force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1712                      && ! lstrcmpiW (copycmd, parmY));
1713       }
1714
1715       /* Prompt if overwriting */
1716       if (!force) {
1717         WCHAR* question;
1718
1719         /* Ask for confirmation */
1720         question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1721         ok = WCMD_ask_confirm(question, FALSE, NULL);
1722         LocalFree(question);
1723
1724         /* So delete the destination prior to the move */
1725         if (ok) {
1726           if (!DeleteFileW(dest)) {
1727             WCMD_print_error ();
1728             errorlevel = 1;
1729             ok = FALSE;
1730           }
1731         }
1732       }
1733     }
1734
1735     if (ok) {
1736       status = MoveFileW(src, dest);
1737     } else {
1738       status = 1; /* Anything other than 0 to prevent error msg below */
1739     }
1740
1741     if (!status) {
1742       WCMD_print_error ();
1743       errorlevel = 1;
1744     }
1745   } while (FindNextFileW(hff, &fd) != 0);
1746
1747   FindClose(hff);
1748 }
1749
1750 /****************************************************************************
1751  * WCMD_pause
1752  *
1753  * Suspend execution of a batch script until a key is typed
1754  */
1755
1756 void WCMD_pause (void)
1757 {
1758   DWORD oldmode;
1759   BOOL have_console;
1760   DWORD count;
1761   WCHAR key;
1762   HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1763
1764   have_console = GetConsoleMode(hIn, &oldmode);
1765   if (have_console)
1766       SetConsoleMode(hIn, 0);
1767
1768   WCMD_output_asis(anykey);
1769   WCMD_ReadFile(hIn, &key, 1, &count);
1770   if (have_console)
1771     SetConsoleMode(hIn, oldmode);
1772 }
1773
1774 /****************************************************************************
1775  * WCMD_remove_dir
1776  *
1777  * Delete a directory.
1778  */
1779
1780 void WCMD_remove_dir (WCHAR *command) {
1781
1782   int   argno         = 0;
1783   int   argsProcessed = 0;
1784   WCHAR *argN          = command;
1785   static const WCHAR parmS[] = {'/','S','\0'};
1786   static const WCHAR parmQ[] = {'/','Q','\0'};
1787
1788   /* Loop through all args */
1789   while (argN) {
1790     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1791     if (argN && argN[0] != '/') {
1792       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1793                  wine_dbgstr_w(quals));
1794       argsProcessed++;
1795
1796       /* If subdirectory search not supplied, just try to remove
1797          and report error if it fails (eg if it contains a file) */
1798       if (strstrW (quals, parmS) == NULL) {
1799         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1800
1801       /* Otherwise use ShFileOp to recursively remove a directory */
1802       } else {
1803
1804         SHFILEOPSTRUCTW lpDir;
1805
1806         /* Ask first */
1807         if (strstrW (quals, parmQ) == NULL) {
1808           BOOL  ok;
1809           WCHAR  question[MAXSTRING];
1810           static const WCHAR fmt[] = {'%','s',' ','\0'};
1811
1812           /* Ask for confirmation */
1813           wsprintfW(question, fmt, thisArg);
1814           ok = WCMD_ask_confirm(question, TRUE, NULL);
1815
1816           /* Abort if answer is 'N' */
1817           if (!ok) return;
1818         }
1819
1820         /* Do the delete */
1821         lpDir.hwnd   = NULL;
1822         lpDir.pTo    = NULL;
1823         lpDir.pFrom  = thisArg;
1824         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1825         lpDir.wFunc  = FO_DELETE;
1826         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1827       }
1828     }
1829   }
1830
1831   /* Handle no valid args */
1832   if (argsProcessed == 0) {
1833     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1834     return;
1835   }
1836
1837 }
1838
1839 /****************************************************************************
1840  * WCMD_rename
1841  *
1842  * Rename a file.
1843  */
1844
1845 void WCMD_rename (void)
1846 {
1847   int             status;
1848   HANDLE          hff;
1849   WIN32_FIND_DATAW fd;
1850   WCHAR            input[MAX_PATH];
1851   WCHAR           *dotDst = NULL;
1852   WCHAR            drive[10];
1853   WCHAR            dir[MAX_PATH];
1854   WCHAR            fname[MAX_PATH];
1855   WCHAR            ext[MAX_PATH];
1856
1857   errorlevel = 0;
1858
1859   /* Must be at least two args */
1860   if (param1[0] == 0x00 || param2[0] == 0x00) {
1861     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1862     errorlevel = 1;
1863     return;
1864   }
1865
1866   /* Destination cannot contain a drive letter or directory separator */
1867   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1868       SetLastError(ERROR_INVALID_PARAMETER);
1869       WCMD_print_error();
1870       errorlevel = 1;
1871       return;
1872   }
1873
1874   /* Convert partial path to full path */
1875   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1876   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1877              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1878   dotDst = strchrW(param2, '.');
1879
1880   /* Split into components */
1881   WCMD_splitpath(input, drive, dir, fname, ext);
1882
1883   hff = FindFirstFileW(input, &fd);
1884   if (hff == INVALID_HANDLE_VALUE)
1885     return;
1886
1887  do {
1888     WCHAR  dest[MAX_PATH];
1889     WCHAR  src[MAX_PATH];
1890     WCHAR *dotSrc = NULL;
1891     int   dirLen;
1892
1893     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1894
1895     /* FIXME: If dest name or extension is *, replace with filename/ext
1896        part otherwise use supplied name. This supports:
1897           ren *.fred *.jim
1898           ren jim.* fred.* etc
1899        However, windows has a more complex algorithm supporting eg
1900           ?'s and *'s mid name                                         */
1901     dotSrc = strchrW(fd.cFileName, '.');
1902
1903     /* Build src & dest name */
1904     strcpyW(src, drive);
1905     strcatW(src, dir);
1906     strcpyW(dest, src);
1907     dirLen = strlenW(src);
1908     strcatW(src, fd.cFileName);
1909
1910     /* Build name */
1911     if (param2[0] == '*') {
1912       strcatW(dest, fd.cFileName);
1913       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1914     } else {
1915       strcatW(dest, param2);
1916       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1917     }
1918
1919     /* Build Extension */
1920     if (dotDst && (*(dotDst+1)=='*')) {
1921       if (dotSrc) strcatW(dest, dotSrc);
1922     } else if (dotDst) {
1923       if (dotDst) strcatW(dest, dotDst);
1924     }
1925
1926     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1927     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1928
1929     status = MoveFileW(src, dest);
1930
1931     if (!status) {
1932       WCMD_print_error ();
1933       errorlevel = 1;
1934     }
1935   } while (FindNextFileW(hff, &fd) != 0);
1936
1937   FindClose(hff);
1938 }
1939
1940 /*****************************************************************************
1941  * WCMD_dupenv
1942  *
1943  * Make a copy of the environment.
1944  */
1945 static WCHAR *WCMD_dupenv( const WCHAR *env )
1946 {
1947   WCHAR *env_copy;
1948   int len;
1949
1950   if( !env )
1951     return NULL;
1952
1953   len = 0;
1954   while ( env[len] )
1955     len += (strlenW(&env[len]) + 1);
1956
1957   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1958   if (!env_copy)
1959   {
1960     WINE_ERR("out of memory\n");
1961     return env_copy;
1962   }
1963   memcpy (env_copy, env, len*sizeof (WCHAR));
1964   env_copy[len] = 0;
1965
1966   return env_copy;
1967 }
1968
1969 /*****************************************************************************
1970  * WCMD_setlocal
1971  *
1972  *  setlocal pushes the environment onto a stack
1973  *  Save the environment as unicode so we don't screw anything up.
1974  */
1975 void WCMD_setlocal (const WCHAR *s) {
1976   WCHAR *env;
1977   struct env_stack *env_copy;
1978   WCHAR cwd[MAX_PATH];
1979
1980   /* DISABLEEXTENSIONS ignored */
1981
1982   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1983   if( !env_copy )
1984   {
1985     WINE_ERR ("out of memory\n");
1986     return;
1987   }
1988
1989   env = GetEnvironmentStringsW ();
1990
1991   env_copy->strings = WCMD_dupenv (env);
1992   if (env_copy->strings)
1993   {
1994     env_copy->next = saved_environment;
1995     saved_environment = env_copy;
1996
1997     /* Save the current drive letter */
1998     GetCurrentDirectoryW(MAX_PATH, cwd);
1999     env_copy->u.cwd = cwd[0];
2000   }
2001   else
2002     LocalFree (env_copy);
2003
2004   FreeEnvironmentStringsW (env);
2005
2006 }
2007
2008 /*****************************************************************************
2009  * WCMD_endlocal
2010  *
2011  *  endlocal pops the environment off a stack
2012  *  Note: When searching for '=', search from WCHAR position 1, to handle
2013  *        special internal environment variables =C:, =D: etc
2014  */
2015 void WCMD_endlocal (void) {
2016   WCHAR *env, *old, *p;
2017   struct env_stack *temp;
2018   int len, n;
2019
2020   if (!saved_environment)
2021     return;
2022
2023   /* pop the old environment from the stack */
2024   temp = saved_environment;
2025   saved_environment = temp->next;
2026
2027   /* delete the current environment, totally */
2028   env = GetEnvironmentStringsW ();
2029   old = WCMD_dupenv (GetEnvironmentStringsW ());
2030   len = 0;
2031   while (old[len]) {
2032     n = strlenW(&old[len]) + 1;
2033     p = strchrW(&old[len] + 1, '=');
2034     if (p)
2035     {
2036       *p++ = 0;
2037       SetEnvironmentVariableW (&old[len], NULL);
2038     }
2039     len += n;
2040   }
2041   LocalFree (old);
2042   FreeEnvironmentStringsW (env);
2043
2044   /* restore old environment */
2045   env = temp->strings;
2046   len = 0;
2047   while (env[len]) {
2048     n = strlenW(&env[len]) + 1;
2049     p = strchrW(&env[len] + 1, '=');
2050     if (p)
2051     {
2052       *p++ = 0;
2053       SetEnvironmentVariableW (&env[len], p);
2054     }
2055     len += n;
2056   }
2057
2058   /* Restore current drive letter */
2059   if (IsCharAlphaW(temp->u.cwd)) {
2060     WCHAR envvar[4];
2061     WCHAR cwd[MAX_PATH];
2062     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2063
2064     wsprintfW(envvar, fmt, temp->u.cwd);
2065     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2066       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2067       SetCurrentDirectoryW(cwd);
2068     }
2069   }
2070
2071   LocalFree (env);
2072   LocalFree (temp);
2073 }
2074
2075 /*****************************************************************************
2076  * WCMD_setshow_default
2077  *
2078  *      Set/Show the current default directory
2079  */
2080
2081 void WCMD_setshow_default (const WCHAR *command) {
2082
2083   BOOL status;
2084   WCHAR string[1024];
2085   WCHAR cwd[1024];
2086   WCHAR *pos;
2087   WIN32_FIND_DATAW fd;
2088   HANDLE hff;
2089   static const WCHAR parmD[] = {'/','D','\0'};
2090
2091   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2092
2093   /* Skip /D and trailing whitespace if on the front of the command line */
2094   if (CompareStringW(LOCALE_USER_DEFAULT,
2095                      NORM_IGNORECASE | SORT_STRINGSORT,
2096                      command, 2, parmD, -1) == CSTR_EQUAL) {
2097     command += 2;
2098     while (*command && (*command==' ' || *command=='\t'))
2099       command++;
2100   }
2101
2102   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2103   if (strlenW(command) == 0) {
2104     strcatW (cwd, newlineW);
2105     WCMD_output_asis (cwd);
2106   }
2107   else {
2108     /* Remove any double quotes, which may be in the
2109        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2110     pos = string;
2111     while (*command) {
2112       if (*command != '"') *pos++ = *command;
2113       command++;
2114     }
2115     while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2116       pos--;
2117     *pos = 0x00;
2118
2119     /* Search for appropriate directory */
2120     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2121     hff = FindFirstFileW(string, &fd);
2122     if (hff != INVALID_HANDLE_VALUE) {
2123       do {
2124         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2125           WCHAR fpath[MAX_PATH];
2126           WCHAR drive[10];
2127           WCHAR dir[MAX_PATH];
2128           WCHAR fname[MAX_PATH];
2129           WCHAR ext[MAX_PATH];
2130           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2131
2132           /* Convert path into actual directory spec */
2133           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2134           WCMD_splitpath(fpath, drive, dir, fname, ext);
2135
2136           /* Rebuild path */
2137           wsprintfW(string, fmt, drive, dir, fd.cFileName);
2138           break;
2139         }
2140       } while (FindNextFileW(hff, &fd) != 0);
2141       FindClose(hff);
2142     }
2143
2144     /* Change to that directory */
2145     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2146
2147     status = SetCurrentDirectoryW(string);
2148     if (!status) {
2149       errorlevel = 1;
2150       WCMD_print_error ();
2151       return;
2152     } else {
2153
2154       /* Save away the actual new directory, to store as current location */
2155       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2156
2157       /* Restore old directory if drive letter would change, and
2158            CD x:\directory /D (or pushd c:\directory) not supplied */
2159       if ((strstrW(quals, parmD) == NULL) &&
2160           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2161         SetCurrentDirectoryW(cwd);
2162       }
2163     }
2164
2165     /* Set special =C: type environment variable, for drive letter of
2166        change of directory, even if path was restored due to missing
2167        /D (allows changing drive letter when not resident on that
2168        drive                                                          */
2169     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2170       WCHAR env[4];
2171       strcpyW(env, equalW);
2172       memcpy(env+1, string, 2 * sizeof(WCHAR));
2173       env[3] = 0x00;
2174       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2175       SetEnvironmentVariableW(env, string);
2176     }
2177
2178    }
2179   return;
2180 }
2181
2182 /****************************************************************************
2183  * WCMD_setshow_date
2184  *
2185  * Set/Show the system date
2186  * FIXME: Can't change date yet
2187  */
2188
2189 void WCMD_setshow_date (void) {
2190
2191   WCHAR curdate[64], buffer[64];
2192   DWORD count;
2193   static const WCHAR parmT[] = {'/','T','\0'};
2194
2195   if (strlenW(param1) == 0) {
2196     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2197                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2198       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2199       if (strstrW (quals, parmT) == NULL) {
2200         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2201         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2202         if (count > 2) {
2203           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2204         }
2205       }
2206     }
2207     else WCMD_print_error ();
2208   }
2209   else {
2210     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2211   }
2212 }
2213
2214 /****************************************************************************
2215  * WCMD_compare
2216  */
2217 static int WCMD_compare( const void *a, const void *b )
2218 {
2219     int r;
2220     const WCHAR * const *str_a = a, * const *str_b = b;
2221     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2222           *str_a, -1, *str_b, -1 );
2223     if( r == CSTR_LESS_THAN ) return -1;
2224     if( r == CSTR_GREATER_THAN ) return 1;
2225     return 0;
2226 }
2227
2228 /****************************************************************************
2229  * WCMD_setshow_sortenv
2230  *
2231  * sort variables into order for display
2232  * Optionally only display those who start with a stub
2233  * returns the count displayed
2234  */
2235 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2236 {
2237   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2238   const WCHAR **str;
2239
2240   if (stub) stublen = strlenW(stub);
2241
2242   /* count the number of strings, and the total length */
2243   while ( s[len] ) {
2244     len += (strlenW(&s[len]) + 1);
2245     count++;
2246   }
2247
2248   /* add the strings to an array */
2249   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2250   if( !str )
2251     return 0;
2252   str[0] = s;
2253   for( i=1; i<count; i++ )
2254     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2255
2256   /* sort the array */
2257   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2258
2259   /* print it */
2260   for( i=0; i<count; i++ ) {
2261     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2262                                 NORM_IGNORECASE | SORT_STRINGSORT,
2263                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2264       /* Don't display special internal variables */
2265       if (str[i][0] != '=') {
2266         WCMD_output_asis(str[i]);
2267         WCMD_output_asis(newlineW);
2268         displayedcount++;
2269       }
2270     }
2271   }
2272
2273   LocalFree( str );
2274   return displayedcount;
2275 }
2276
2277 /****************************************************************************
2278  * WCMD_setshow_env
2279  *
2280  * Set/Show the environment variables
2281  */
2282
2283 void WCMD_setshow_env (WCHAR *s) {
2284
2285   LPVOID env;
2286   WCHAR *p;
2287   int status;
2288   static const WCHAR parmP[] = {'/','P','\0'};
2289
2290   if (param1[0] == 0x00 && quals[0] == 0x00) {
2291     env = GetEnvironmentStringsW();
2292     WCMD_setshow_sortenv( env, NULL );
2293     return;
2294   }
2295
2296   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2297   if (CompareStringW(LOCALE_USER_DEFAULT,
2298                      NORM_IGNORECASE | SORT_STRINGSORT,
2299                      s, 2, parmP, -1) == CSTR_EQUAL) {
2300     WCHAR string[MAXSTRING];
2301     DWORD count;
2302
2303     s += 2;
2304     while (*s && (*s==' ' || *s=='\t')) s++;
2305     if (*s=='\"')
2306         WCMD_strip_quotes(s);
2307
2308     /* If no parameter, or no '=' sign, return an error */
2309     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2310       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2311       return;
2312     }
2313
2314     /* Output the prompt */
2315     *p++ = '\0';
2316     if (strlenW(p) != 0) WCMD_output_asis(p);
2317
2318     /* Read the reply */
2319     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2320     if (count > 1) {
2321       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2322       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2323       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2324                  wine_dbgstr_w(string));
2325       status = SetEnvironmentVariableW(s, string);
2326     }
2327
2328   } else {
2329     DWORD gle;
2330
2331     if (*s=='\"')
2332         WCMD_strip_quotes(s);
2333     p = strchrW (s, '=');
2334     if (p == NULL) {
2335       env = GetEnvironmentStringsW();
2336       if (WCMD_setshow_sortenv( env, s ) == 0) {
2337         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2338         errorlevel = 1;
2339       }
2340       return;
2341     }
2342     *p++ = '\0';
2343
2344     if (strlenW(p) == 0) p = NULL;
2345     status = SetEnvironmentVariableW(s, p);
2346     gle = GetLastError();
2347     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2348       errorlevel = 1;
2349     } else if ((!status)) WCMD_print_error();
2350   }
2351 }
2352
2353 /****************************************************************************
2354  * WCMD_setshow_path
2355  *
2356  * Set/Show the path environment variable
2357  */
2358
2359 void WCMD_setshow_path (const WCHAR *command) {
2360
2361   WCHAR string[1024];
2362   DWORD status;
2363   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2364   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2365
2366   if (strlenW(param1) == 0) {
2367     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2368     if (status != 0) {
2369       WCMD_output_asis ( pathEqW);
2370       WCMD_output_asis ( string);
2371       WCMD_output_asis ( newlineW);
2372     }
2373     else {
2374       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2375     }
2376   }
2377   else {
2378     if (*command == '=') command++; /* Skip leading '=' */
2379     status = SetEnvironmentVariableW(pathW, command);
2380     if (!status) WCMD_print_error();
2381   }
2382 }
2383
2384 /****************************************************************************
2385  * WCMD_setshow_prompt
2386  *
2387  * Set or show the command prompt.
2388  */
2389
2390 void WCMD_setshow_prompt (void) {
2391
2392   WCHAR *s;
2393   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2394
2395   if (strlenW(param1) == 0) {
2396     SetEnvironmentVariableW(promptW, NULL);
2397   }
2398   else {
2399     s = param1;
2400     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2401     if (strlenW(s) == 0) {
2402       SetEnvironmentVariableW(promptW, NULL);
2403     }
2404     else SetEnvironmentVariableW(promptW, s);
2405   }
2406 }
2407
2408 /****************************************************************************
2409  * WCMD_setshow_time
2410  *
2411  * Set/Show the system time
2412  * FIXME: Can't change time yet
2413  */
2414
2415 void WCMD_setshow_time (void) {
2416
2417   WCHAR curtime[64], buffer[64];
2418   DWORD count;
2419   SYSTEMTIME st;
2420   static const WCHAR parmT[] = {'/','T','\0'};
2421
2422   if (strlenW(param1) == 0) {
2423     GetLocalTime(&st);
2424     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2425                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2426       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2427       if (strstrW (quals, parmT) == NULL) {
2428         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2429         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2430         if (count > 2) {
2431           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2432         }
2433       }
2434     }
2435     else WCMD_print_error ();
2436   }
2437   else {
2438     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2439   }
2440 }
2441
2442 /****************************************************************************
2443  * WCMD_shift
2444  *
2445  * Shift batch parameters.
2446  * Optional /n says where to start shifting (n=0-8)
2447  */
2448
2449 void WCMD_shift (const WCHAR *command) {
2450   int start;
2451
2452   if (context != NULL) {
2453     WCHAR *pos = strchrW(command, '/');
2454     int   i;
2455
2456     if (pos == NULL) {
2457       start = 0;
2458     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2459       start = (*(pos+1) - '0');
2460     } else {
2461       SetLastError(ERROR_INVALID_PARAMETER);
2462       WCMD_print_error();
2463       return;
2464     }
2465
2466     WINE_TRACE("Shifting variables, starting at %d\n", start);
2467     for (i=start;i<=8;i++) {
2468       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2469     }
2470     context -> shift_count[9] = context -> shift_count[9] + 1;
2471   }
2472
2473 }
2474
2475 /****************************************************************************
2476  * WCMD_start
2477  */
2478 void WCMD_start(const WCHAR *command)
2479 {
2480     static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2481                                  '\\','s','t','a','r','t','.','e','x','e',0};
2482     WCHAR file[MAX_PATH];
2483     WCHAR *cmdline;
2484     STARTUPINFOW st;
2485     PROCESS_INFORMATION pi;
2486
2487     GetWindowsDirectoryW( file, MAX_PATH );
2488     strcatW( file, exeW );
2489     cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2490     strcpyW( cmdline, file );
2491     strcatW( cmdline, spaceW );
2492     strcatW( cmdline, command );
2493
2494     memset( &st, 0, sizeof(STARTUPINFOW) );
2495     st.cb = sizeof(STARTUPINFOW);
2496
2497     if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2498     {
2499         WaitForSingleObject( pi.hProcess, INFINITE );
2500         GetExitCodeProcess( pi.hProcess, &errorlevel );
2501         if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2502         CloseHandle(pi.hProcess);
2503         CloseHandle(pi.hThread);
2504     }
2505     else
2506     {
2507         SetLastError(ERROR_FILE_NOT_FOUND);
2508         WCMD_print_error ();
2509         errorlevel = 9009;
2510     }
2511     HeapFree( GetProcessHeap(), 0, cmdline );
2512 }
2513
2514 /****************************************************************************
2515  * WCMD_title
2516  *
2517  * Set the console title
2518  */
2519 void WCMD_title (const WCHAR *command) {
2520   SetConsoleTitleW(command);
2521 }
2522
2523 /****************************************************************************
2524  * WCMD_type
2525  *
2526  * Copy a file to standard output.
2527  */
2528
2529 void WCMD_type (WCHAR *command) {
2530
2531   int   argno         = 0;
2532   WCHAR *argN          = command;
2533   BOOL  writeHeaders  = FALSE;
2534
2535   if (param1[0] == 0x00) {
2536     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2537     return;
2538   }
2539
2540   if (param2[0] != 0x00) writeHeaders = TRUE;
2541
2542   /* Loop through all args */
2543   errorlevel = 0;
2544   while (argN) {
2545     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2546
2547     HANDLE h;
2548     WCHAR buffer[512];
2549     DWORD count;
2550
2551     if (!argN) break;
2552
2553     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2554     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2555                 FILE_ATTRIBUTE_NORMAL, NULL);
2556     if (h == INVALID_HANDLE_VALUE) {
2557       WCMD_print_error ();
2558       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2559       errorlevel = 1;
2560     } else {
2561       if (writeHeaders) {
2562         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2563         WCMD_output(fmt, thisArg);
2564       }
2565       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2566         if (count == 0) break;  /* ReadFile reports success on EOF! */
2567         buffer[count] = 0;
2568         WCMD_output_asis (buffer);
2569       }
2570       CloseHandle (h);
2571     }
2572   }
2573 }
2574
2575 /****************************************************************************
2576  * WCMD_more
2577  *
2578  * Output either a file or stdin to screen in pages
2579  */
2580
2581 void WCMD_more (WCHAR *command) {
2582
2583   int   argno         = 0;
2584   WCHAR *argN          = command;
2585   WCHAR  moreStr[100];
2586   WCHAR  moreStrPage[100];
2587   WCHAR  buffer[512];
2588   DWORD count;
2589   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2590   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2591   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2592                                     ')',' ','-','-','\n','\0'};
2593   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2594
2595   /* Prefix the NLS more with '-- ', then load the text */
2596   errorlevel = 0;
2597   strcpyW(moreStr, moreStart);
2598   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2599               (sizeof(moreStr)/sizeof(WCHAR))-3);
2600
2601   if (param1[0] == 0x00) {
2602
2603     /* Wine implements pipes via temporary files, and hence stdin is
2604        effectively reading from the file. This means the prompts for
2605        more are satisfied by the next line from the input (file). To
2606        avoid this, ensure stdin is to the console                    */
2607     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2608     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2609                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2610                          FILE_ATTRIBUTE_NORMAL, 0);
2611     WINE_TRACE("No parms - working probably in pipe mode\n");
2612     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2613
2614     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2615        once you get in this bit unless due to a pipe, its going to end badly...  */
2616     wsprintfW(moreStrPage, moreFmt, moreStr);
2617
2618     WCMD_enter_paged_mode(moreStrPage);
2619     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2620       if (count == 0) break;    /* ReadFile reports success on EOF! */
2621       buffer[count] = 0;
2622       WCMD_output_asis (buffer);
2623     }
2624     WCMD_leave_paged_mode();
2625
2626     /* Restore stdin to what it was */
2627     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2628     CloseHandle(hConIn);
2629
2630     return;
2631   } else {
2632     BOOL needsPause = FALSE;
2633
2634     /* Loop through all args */
2635     WINE_TRACE("Parms supplied - working through each file\n");
2636     WCMD_enter_paged_mode(moreStrPage);
2637
2638     while (argN) {
2639       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2640       HANDLE h;
2641
2642       if (!argN) break;
2643
2644       if (needsPause) {
2645
2646         /* Wait */
2647         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2648         WCMD_leave_paged_mode();
2649         WCMD_output_asis(moreStrPage);
2650         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2651         WCMD_enter_paged_mode(moreStrPage);
2652       }
2653
2654
2655       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2656       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2657                 FILE_ATTRIBUTE_NORMAL, NULL);
2658       if (h == INVALID_HANDLE_VALUE) {
2659         WCMD_print_error ();
2660         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2661         errorlevel = 1;
2662       } else {
2663         ULONG64 curPos  = 0;
2664         ULONG64 fileLen = 0;
2665         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2666
2667         /* Get the file size */
2668         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2669         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2670
2671         needsPause = TRUE;
2672         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2673           if (count == 0) break;        /* ReadFile reports success on EOF! */
2674           buffer[count] = 0;
2675           curPos += count;
2676
2677           /* Update % count (would be used in WCMD_output_asis as prompt) */
2678           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2679
2680           WCMD_output_asis (buffer);
2681         }
2682         CloseHandle (h);
2683       }
2684     }
2685
2686     WCMD_leave_paged_mode();
2687   }
2688 }
2689
2690 /****************************************************************************
2691  * WCMD_verify
2692  *
2693  * Display verify flag.
2694  * FIXME: We don't actually do anything with the verify flag other than toggle
2695  * it...
2696  */
2697
2698 void WCMD_verify (const WCHAR *command) {
2699
2700   int count;
2701
2702   count = strlenW(command);
2703   if (count == 0) {
2704     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2705     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2706     return;
2707   }
2708   if (lstrcmpiW(command, onW) == 0) {
2709     verify_mode = TRUE;
2710     return;
2711   }
2712   else if (lstrcmpiW(command, offW) == 0) {
2713     verify_mode = FALSE;
2714     return;
2715   }
2716   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2717 }
2718
2719 /****************************************************************************
2720  * WCMD_version
2721  *
2722  * Display version info.
2723  */
2724
2725 void WCMD_version (void) {
2726
2727   WCMD_output_asis (version_string);
2728
2729 }
2730
2731 /****************************************************************************
2732  * WCMD_volume
2733  *
2734  * Display volume information (set_label = FALSE)
2735  * Additionally set volume label (set_label = TRUE)
2736  * Returns 1 on success, 0 otherwise
2737  */
2738
2739 int WCMD_volume(BOOL set_label, const WCHAR *path)
2740 {
2741   DWORD count, serial;
2742   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2743   BOOL status;
2744
2745   if (strlenW(path) == 0) {
2746     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2747     if (!status) {
2748       WCMD_print_error ();
2749       return 0;
2750     }
2751     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2752                                    &serial, NULL, NULL, NULL, 0);
2753   }
2754   else {
2755     static const WCHAR fmt[] = {'%','s','\\','\0'};
2756     if ((path[1] != ':') || (strlenW(path) != 2)) {
2757       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2758       return 0;
2759     }
2760     wsprintfW (curdir, fmt, path);
2761     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2762                                    &serial, NULL,
2763         NULL, NULL, 0);
2764   }
2765   if (!status) {
2766     WCMD_print_error ();
2767     return 0;
2768   }
2769   if (label[0] != '\0') {
2770     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2771         curdir[0], label);
2772   }
2773   else {
2774     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2775         curdir[0]);
2776   }
2777   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2778         HIWORD(serial), LOWORD(serial));
2779   if (set_label) {
2780     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2781     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2782     if (count > 1) {
2783       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2784       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2785     }
2786     if (strlenW(path) != 0) {
2787       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2788     }
2789     else {
2790       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2791     }
2792   }
2793   return 1;
2794 }
2795
2796 /**************************************************************************
2797  * WCMD_exit
2798  *
2799  * Exit either the process, or just this batch program
2800  *
2801  */
2802
2803 void WCMD_exit (CMD_LIST **cmdList) {
2804
2805     static const WCHAR parmB[] = {'/','B','\0'};
2806     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2807
2808     if (context && lstrcmpiW(quals, parmB) == 0) {
2809         errorlevel = rc;
2810         context -> skip_rest = TRUE;
2811         *cmdList = NULL;
2812     } else {
2813         ExitProcess(rc);
2814     }
2815 }
2816
2817
2818 /*****************************************************************************
2819  * WCMD_assoc
2820  *
2821  *      Lists or sets file associations  (assoc = TRUE)
2822  *      Lists or sets file types         (assoc = FALSE)
2823  */
2824 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2825
2826     HKEY    key;
2827     DWORD   accessOptions = KEY_READ;
2828     WCHAR   *newValue;
2829     LONG    rc = ERROR_SUCCESS;
2830     WCHAR    keyValue[MAXSTRING];
2831     DWORD   valueLen = MAXSTRING;
2832     HKEY    readKey;
2833     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2834                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2835
2836     /* See if parameter includes '=' */
2837     errorlevel = 0;
2838     newValue = strchrW(command, '=');
2839     if (newValue) accessOptions |= KEY_WRITE;
2840
2841     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2842     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2843                       accessOptions, &key) != ERROR_SUCCESS) {
2844       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2845       return;
2846     }
2847
2848     /* If no parameters then list all associations */
2849     if (*command == 0x00) {
2850       int index = 0;
2851
2852       /* Enumerate all the keys */
2853       while (rc != ERROR_NO_MORE_ITEMS) {
2854         WCHAR  keyName[MAXSTRING];
2855         DWORD nameLen;
2856
2857         /* Find the next value */
2858         nameLen = MAXSTRING;
2859         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2860
2861         if (rc == ERROR_SUCCESS) {
2862
2863           /* Only interested in extension ones if assoc, or others
2864              if not assoc                                          */
2865           if ((keyName[0] == '.' && assoc) ||
2866               (!(keyName[0] == '.') && (!assoc)))
2867           {
2868             WCHAR subkey[MAXSTRING];
2869             strcpyW(subkey, keyName);
2870             if (!assoc) strcatW(subkey, shOpCmdW);
2871
2872             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2873
2874               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2875               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2876               WCMD_output_asis(keyName);
2877               WCMD_output_asis(equalW);
2878               /* If no default value found, leave line empty after '=' */
2879               if (rc == ERROR_SUCCESS) {
2880                 WCMD_output_asis(keyValue);
2881               }
2882               WCMD_output_asis(newlineW);
2883               RegCloseKey(readKey);
2884             }
2885           }
2886         }
2887       }
2888
2889     } else {
2890
2891       /* Parameter supplied - if no '=' on command line, its a query */
2892       if (newValue == NULL) {
2893         WCHAR *space;
2894         WCHAR subkey[MAXSTRING];
2895
2896         /* Query terminates the parameter at the first space */
2897         strcpyW(keyValue, command);
2898         space = strchrW(keyValue, ' ');
2899         if (space) *space=0x00;
2900
2901         /* Set up key name */
2902         strcpyW(subkey, keyValue);
2903         if (!assoc) strcatW(subkey, shOpCmdW);
2904
2905         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2906
2907           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2908           WCMD_output_asis(command);
2909           WCMD_output_asis(equalW);
2910           /* If no default value found, leave line empty after '=' */
2911           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2912           WCMD_output_asis(newlineW);
2913           RegCloseKey(readKey);
2914
2915         } else {
2916           WCHAR  msgbuffer[MAXSTRING];
2917
2918           /* Load the translated 'File association not found' */
2919           if (assoc) {
2920             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2921           } else {
2922             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2923           }
2924           WCMD_output_stderr(msgbuffer, keyValue);
2925           errorlevel = 2;
2926         }
2927
2928       /* Not a query - its a set or clear of a value */
2929       } else {
2930
2931         WCHAR subkey[MAXSTRING];
2932
2933         /* Get pointer to new value */
2934         *newValue = 0x00;
2935         newValue++;
2936
2937         /* Set up key name */
2938         strcpyW(subkey, command);
2939         if (!assoc) strcatW(subkey, shOpCmdW);
2940
2941         /* If nothing after '=' then clear value - only valid for ASSOC */
2942         if (*newValue == 0x00) {
2943
2944           if (assoc) rc = RegDeleteKeyW(key, command);
2945           if (assoc && rc == ERROR_SUCCESS) {
2946             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2947
2948           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2949             WCMD_print_error();
2950             errorlevel = 2;
2951
2952           } else {
2953             WCHAR  msgbuffer[MAXSTRING];
2954
2955             /* Load the translated 'File association not found' */
2956             if (assoc) {
2957               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2958                           sizeof(msgbuffer)/sizeof(WCHAR));
2959             } else {
2960               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2961                           sizeof(msgbuffer)/sizeof(WCHAR));
2962             }
2963             WCMD_output_stderr(msgbuffer, keyValue);
2964             errorlevel = 2;
2965           }
2966
2967         /* It really is a set value = contents */
2968         } else {
2969           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2970                               accessOptions, NULL, &readKey, NULL);
2971           if (rc == ERROR_SUCCESS) {
2972             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2973                                 (LPBYTE)newValue,
2974                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
2975             RegCloseKey(readKey);
2976           }
2977
2978           if (rc != ERROR_SUCCESS) {
2979             WCMD_print_error();
2980             errorlevel = 2;
2981           } else {
2982             WCMD_output_asis(command);
2983             WCMD_output_asis(equalW);
2984             WCMD_output_asis(newValue);
2985             WCMD_output_asis(newlineW);
2986           }
2987         }
2988       }
2989     }
2990
2991     /* Clean up */
2992     RegCloseKey(key);
2993 }
2994
2995 /****************************************************************************
2996  * WCMD_color
2997  *
2998  * Colors the terminal screen.
2999  */
3000
3001 void WCMD_color (void) {
3002
3003   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3004   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3005
3006   if (param1[0] != 0x00 && strlenW(param1) > 2) {
3007     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3008     return;
3009   }
3010
3011   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3012   {
3013       COORD topLeft;
3014       DWORD screenSize;
3015       DWORD color = 0;
3016
3017       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3018
3019       topLeft.X = 0;
3020       topLeft.Y = 0;
3021
3022       /* Convert the color hex digits */
3023       if (param1[0] == 0x00) {
3024         color = defaultColor;
3025       } else {
3026         color = strtoulW(param1, NULL, 16);
3027       }
3028
3029       /* Fail if fg == bg color */
3030       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3031         errorlevel = 1;
3032         return;
3033       }
3034
3035       /* Set the current screen contents and ensure all future writes
3036          remain this color                                             */
3037       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3038       SetConsoleTextAttribute(hStdOut, color);
3039   }
3040 }