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