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