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