cmd: Fix "PATH=value" command.
[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
1831         /* SHFileOperationW needs file list with a double null termination */
1832         thisArg[lstrlenW(thisArg) + 1] = 0x00;
1833
1834         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1835       }
1836     }
1837   }
1838
1839   /* Handle no valid args */
1840   if (argsProcessed == 0) {
1841     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1842     return;
1843   }
1844
1845 }
1846
1847 /****************************************************************************
1848  * WCMD_rename
1849  *
1850  * Rename a file.
1851  */
1852
1853 void WCMD_rename (void)
1854 {
1855   int             status;
1856   HANDLE          hff;
1857   WIN32_FIND_DATAW fd;
1858   WCHAR            input[MAX_PATH];
1859   WCHAR           *dotDst = NULL;
1860   WCHAR            drive[10];
1861   WCHAR            dir[MAX_PATH];
1862   WCHAR            fname[MAX_PATH];
1863   WCHAR            ext[MAX_PATH];
1864
1865   errorlevel = 0;
1866
1867   /* Must be at least two args */
1868   if (param1[0] == 0x00 || param2[0] == 0x00) {
1869     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1870     errorlevel = 1;
1871     return;
1872   }
1873
1874   /* Destination cannot contain a drive letter or directory separator */
1875   if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1876       SetLastError(ERROR_INVALID_PARAMETER);
1877       WCMD_print_error();
1878       errorlevel = 1;
1879       return;
1880   }
1881
1882   /* Convert partial path to full path */
1883   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1884   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1885              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1886   dotDst = strchrW(param2, '.');
1887
1888   /* Split into components */
1889   WCMD_splitpath(input, drive, dir, fname, ext);
1890
1891   hff = FindFirstFileW(input, &fd);
1892   if (hff == INVALID_HANDLE_VALUE)
1893     return;
1894
1895  do {
1896     WCHAR  dest[MAX_PATH];
1897     WCHAR  src[MAX_PATH];
1898     WCHAR *dotSrc = NULL;
1899     int   dirLen;
1900
1901     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1902
1903     /* FIXME: If dest name or extension is *, replace with filename/ext
1904        part otherwise use supplied name. This supports:
1905           ren *.fred *.jim
1906           ren jim.* fred.* etc
1907        However, windows has a more complex algorithm supporting eg
1908           ?'s and *'s mid name                                         */
1909     dotSrc = strchrW(fd.cFileName, '.');
1910
1911     /* Build src & dest name */
1912     strcpyW(src, drive);
1913     strcatW(src, dir);
1914     strcpyW(dest, src);
1915     dirLen = strlenW(src);
1916     strcatW(src, fd.cFileName);
1917
1918     /* Build name */
1919     if (param2[0] == '*') {
1920       strcatW(dest, fd.cFileName);
1921       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1922     } else {
1923       strcatW(dest, param2);
1924       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1925     }
1926
1927     /* Build Extension */
1928     if (dotDst && (*(dotDst+1)=='*')) {
1929       if (dotSrc) strcatW(dest, dotSrc);
1930     } else if (dotDst) {
1931       if (dotDst) strcatW(dest, dotDst);
1932     }
1933
1934     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1935     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1936
1937     status = MoveFileW(src, dest);
1938
1939     if (!status) {
1940       WCMD_print_error ();
1941       errorlevel = 1;
1942     }
1943   } while (FindNextFileW(hff, &fd) != 0);
1944
1945   FindClose(hff);
1946 }
1947
1948 /*****************************************************************************
1949  * WCMD_dupenv
1950  *
1951  * Make a copy of the environment.
1952  */
1953 static WCHAR *WCMD_dupenv( const WCHAR *env )
1954 {
1955   WCHAR *env_copy;
1956   int len;
1957
1958   if( !env )
1959     return NULL;
1960
1961   len = 0;
1962   while ( env[len] )
1963     len += (strlenW(&env[len]) + 1);
1964
1965   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1966   if (!env_copy)
1967   {
1968     WINE_ERR("out of memory\n");
1969     return env_copy;
1970   }
1971   memcpy (env_copy, env, len*sizeof (WCHAR));
1972   env_copy[len] = 0;
1973
1974   return env_copy;
1975 }
1976
1977 /*****************************************************************************
1978  * WCMD_setlocal
1979  *
1980  *  setlocal pushes the environment onto a stack
1981  *  Save the environment as unicode so we don't screw anything up.
1982  */
1983 void WCMD_setlocal (const WCHAR *s) {
1984   WCHAR *env;
1985   struct env_stack *env_copy;
1986   WCHAR cwd[MAX_PATH];
1987
1988   /* DISABLEEXTENSIONS ignored */
1989
1990   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1991   if( !env_copy )
1992   {
1993     WINE_ERR ("out of memory\n");
1994     return;
1995   }
1996
1997   env = GetEnvironmentStringsW ();
1998
1999   env_copy->strings = WCMD_dupenv (env);
2000   if (env_copy->strings)
2001   {
2002     env_copy->next = saved_environment;
2003     saved_environment = env_copy;
2004
2005     /* Save the current drive letter */
2006     GetCurrentDirectoryW(MAX_PATH, cwd);
2007     env_copy->u.cwd = cwd[0];
2008   }
2009   else
2010     LocalFree (env_copy);
2011
2012   FreeEnvironmentStringsW (env);
2013
2014 }
2015
2016 /*****************************************************************************
2017  * WCMD_endlocal
2018  *
2019  *  endlocal pops the environment off a stack
2020  *  Note: When searching for '=', search from WCHAR position 1, to handle
2021  *        special internal environment variables =C:, =D: etc
2022  */
2023 void WCMD_endlocal (void) {
2024   WCHAR *env, *old, *p;
2025   struct env_stack *temp;
2026   int len, n;
2027
2028   if (!saved_environment)
2029     return;
2030
2031   /* pop the old environment from the stack */
2032   temp = saved_environment;
2033   saved_environment = temp->next;
2034
2035   /* delete the current environment, totally */
2036   env = GetEnvironmentStringsW ();
2037   old = WCMD_dupenv (GetEnvironmentStringsW ());
2038   len = 0;
2039   while (old[len]) {
2040     n = strlenW(&old[len]) + 1;
2041     p = strchrW(&old[len] + 1, '=');
2042     if (p)
2043     {
2044       *p++ = 0;
2045       SetEnvironmentVariableW (&old[len], NULL);
2046     }
2047     len += n;
2048   }
2049   LocalFree (old);
2050   FreeEnvironmentStringsW (env);
2051
2052   /* restore old environment */
2053   env = temp->strings;
2054   len = 0;
2055   while (env[len]) {
2056     n = strlenW(&env[len]) + 1;
2057     p = strchrW(&env[len] + 1, '=');
2058     if (p)
2059     {
2060       *p++ = 0;
2061       SetEnvironmentVariableW (&env[len], p);
2062     }
2063     len += n;
2064   }
2065
2066   /* Restore current drive letter */
2067   if (IsCharAlphaW(temp->u.cwd)) {
2068     WCHAR envvar[4];
2069     WCHAR cwd[MAX_PATH];
2070     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2071
2072     wsprintfW(envvar, fmt, temp->u.cwd);
2073     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2074       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2075       SetCurrentDirectoryW(cwd);
2076     }
2077   }
2078
2079   LocalFree (env);
2080   LocalFree (temp);
2081 }
2082
2083 /*****************************************************************************
2084  * WCMD_setshow_default
2085  *
2086  *      Set/Show the current default directory
2087  */
2088
2089 void WCMD_setshow_default (const WCHAR *command) {
2090
2091   BOOL status;
2092   WCHAR string[1024];
2093   WCHAR cwd[1024];
2094   WCHAR *pos;
2095   WIN32_FIND_DATAW fd;
2096   HANDLE hff;
2097   static const WCHAR parmD[] = {'/','D','\0'};
2098
2099   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2100
2101   /* Skip /D and trailing whitespace if on the front of the command line */
2102   if (CompareStringW(LOCALE_USER_DEFAULT,
2103                      NORM_IGNORECASE | SORT_STRINGSORT,
2104                      command, 2, parmD, -1) == CSTR_EQUAL) {
2105     command += 2;
2106     while (*command && (*command==' ' || *command=='\t'))
2107       command++;
2108   }
2109
2110   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2111   if (strlenW(command) == 0) {
2112     strcatW (cwd, newlineW);
2113     WCMD_output_asis (cwd);
2114   }
2115   else {
2116     /* Remove any double quotes, which may be in the
2117        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2118     pos = string;
2119     while (*command) {
2120       if (*command != '"') *pos++ = *command;
2121       command++;
2122     }
2123     while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2124       pos--;
2125     *pos = 0x00;
2126
2127     /* Search for appropriate directory */
2128     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2129     hff = FindFirstFileW(string, &fd);
2130     if (hff != INVALID_HANDLE_VALUE) {
2131       do {
2132         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2133           WCHAR fpath[MAX_PATH];
2134           WCHAR drive[10];
2135           WCHAR dir[MAX_PATH];
2136           WCHAR fname[MAX_PATH];
2137           WCHAR ext[MAX_PATH];
2138           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2139
2140           /* Convert path into actual directory spec */
2141           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2142           WCMD_splitpath(fpath, drive, dir, fname, ext);
2143
2144           /* Rebuild path */
2145           wsprintfW(string, fmt, drive, dir, fd.cFileName);
2146           break;
2147         }
2148       } while (FindNextFileW(hff, &fd) != 0);
2149       FindClose(hff);
2150     }
2151
2152     /* Change to that directory */
2153     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2154
2155     status = SetCurrentDirectoryW(string);
2156     if (!status) {
2157       errorlevel = 1;
2158       WCMD_print_error ();
2159       return;
2160     } else {
2161
2162       /* Save away the actual new directory, to store as current location */
2163       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2164
2165       /* Restore old directory if drive letter would change, and
2166            CD x:\directory /D (or pushd c:\directory) not supplied */
2167       if ((strstrW(quals, parmD) == NULL) &&
2168           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2169         SetCurrentDirectoryW(cwd);
2170       }
2171     }
2172
2173     /* Set special =C: type environment variable, for drive letter of
2174        change of directory, even if path was restored due to missing
2175        /D (allows changing drive letter when not resident on that
2176        drive                                                          */
2177     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2178       WCHAR env[4];
2179       strcpyW(env, equalW);
2180       memcpy(env+1, string, 2 * sizeof(WCHAR));
2181       env[3] = 0x00;
2182       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2183       SetEnvironmentVariableW(env, string);
2184     }
2185
2186    }
2187   return;
2188 }
2189
2190 /****************************************************************************
2191  * WCMD_setshow_date
2192  *
2193  * Set/Show the system date
2194  * FIXME: Can't change date yet
2195  */
2196
2197 void WCMD_setshow_date (void) {
2198
2199   WCHAR curdate[64], buffer[64];
2200   DWORD count;
2201   static const WCHAR parmT[] = {'/','T','\0'};
2202
2203   if (strlenW(param1) == 0) {
2204     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2205                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2206       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2207       if (strstrW (quals, parmT) == NULL) {
2208         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2209         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2210         if (count > 2) {
2211           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2212         }
2213       }
2214     }
2215     else WCMD_print_error ();
2216   }
2217   else {
2218     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2219   }
2220 }
2221
2222 /****************************************************************************
2223  * WCMD_compare
2224  */
2225 static int WCMD_compare( const void *a, const void *b )
2226 {
2227     int r;
2228     const WCHAR * const *str_a = a, * const *str_b = b;
2229     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2230           *str_a, -1, *str_b, -1 );
2231     if( r == CSTR_LESS_THAN ) return -1;
2232     if( r == CSTR_GREATER_THAN ) return 1;
2233     return 0;
2234 }
2235
2236 /****************************************************************************
2237  * WCMD_setshow_sortenv
2238  *
2239  * sort variables into order for display
2240  * Optionally only display those who start with a stub
2241  * returns the count displayed
2242  */
2243 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2244 {
2245   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2246   const WCHAR **str;
2247
2248   if (stub) stublen = strlenW(stub);
2249
2250   /* count the number of strings, and the total length */
2251   while ( s[len] ) {
2252     len += (strlenW(&s[len]) + 1);
2253     count++;
2254   }
2255
2256   /* add the strings to an array */
2257   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2258   if( !str )
2259     return 0;
2260   str[0] = s;
2261   for( i=1; i<count; i++ )
2262     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2263
2264   /* sort the array */
2265   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2266
2267   /* print it */
2268   for( i=0; i<count; i++ ) {
2269     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2270                                 NORM_IGNORECASE | SORT_STRINGSORT,
2271                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2272       /* Don't display special internal variables */
2273       if (str[i][0] != '=') {
2274         WCMD_output_asis(str[i]);
2275         WCMD_output_asis(newlineW);
2276         displayedcount++;
2277       }
2278     }
2279   }
2280
2281   LocalFree( str );
2282   return displayedcount;
2283 }
2284
2285 /****************************************************************************
2286  * WCMD_setshow_env
2287  *
2288  * Set/Show the environment variables
2289  */
2290
2291 void WCMD_setshow_env (WCHAR *s) {
2292
2293   LPVOID env;
2294   WCHAR *p;
2295   int status;
2296   static const WCHAR parmP[] = {'/','P','\0'};
2297
2298   if (param1[0] == 0x00 && quals[0] == 0x00) {
2299     env = GetEnvironmentStringsW();
2300     WCMD_setshow_sortenv( env, NULL );
2301     return;
2302   }
2303
2304   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2305   if (CompareStringW(LOCALE_USER_DEFAULT,
2306                      NORM_IGNORECASE | SORT_STRINGSORT,
2307                      s, 2, parmP, -1) == CSTR_EQUAL) {
2308     WCHAR string[MAXSTRING];
2309     DWORD count;
2310
2311     s += 2;
2312     while (*s && (*s==' ' || *s=='\t')) s++;
2313     if (*s=='\"')
2314         WCMD_strip_quotes(s);
2315
2316     /* If no parameter, or no '=' sign, return an error */
2317     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2318       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2319       return;
2320     }
2321
2322     /* Output the prompt */
2323     *p++ = '\0';
2324     if (strlenW(p) != 0) WCMD_output_asis(p);
2325
2326     /* Read the reply */
2327     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2328     if (count > 1) {
2329       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2330       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2331       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2332                  wine_dbgstr_w(string));
2333       status = SetEnvironmentVariableW(s, string);
2334     }
2335
2336   } else {
2337     DWORD gle;
2338
2339     if (*s=='\"')
2340         WCMD_strip_quotes(s);
2341     p = strchrW (s, '=');
2342     if (p == NULL) {
2343       env = GetEnvironmentStringsW();
2344       if (WCMD_setshow_sortenv( env, s ) == 0) {
2345         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2346         errorlevel = 1;
2347       }
2348       return;
2349     }
2350     *p++ = '\0';
2351
2352     if (strlenW(p) == 0) p = NULL;
2353     status = SetEnvironmentVariableW(s, p);
2354     gle = GetLastError();
2355     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2356       errorlevel = 1;
2357     } else if ((!status)) WCMD_print_error();
2358   }
2359 }
2360
2361 /****************************************************************************
2362  * WCMD_setshow_path
2363  *
2364  * Set/Show the path environment variable
2365  */
2366
2367 void WCMD_setshow_path (const WCHAR *command) {
2368
2369   WCHAR string[1024];
2370   DWORD status;
2371   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2372   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2373
2374   if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2375     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2376     if (status != 0) {
2377       WCMD_output_asis ( pathEqW);
2378       WCMD_output_asis ( string);
2379       WCMD_output_asis ( newlineW);
2380     }
2381     else {
2382       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2383     }
2384   }
2385   else {
2386     if (*command == '=') command++; /* Skip leading '=' */
2387     status = SetEnvironmentVariableW(pathW, command);
2388     if (!status) WCMD_print_error();
2389   }
2390 }
2391
2392 /****************************************************************************
2393  * WCMD_setshow_prompt
2394  *
2395  * Set or show the command prompt.
2396  */
2397
2398 void WCMD_setshow_prompt (void) {
2399
2400   WCHAR *s;
2401   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2402
2403   if (strlenW(param1) == 0) {
2404     SetEnvironmentVariableW(promptW, NULL);
2405   }
2406   else {
2407     s = param1;
2408     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2409     if (strlenW(s) == 0) {
2410       SetEnvironmentVariableW(promptW, NULL);
2411     }
2412     else SetEnvironmentVariableW(promptW, s);
2413   }
2414 }
2415
2416 /****************************************************************************
2417  * WCMD_setshow_time
2418  *
2419  * Set/Show the system time
2420  * FIXME: Can't change time yet
2421  */
2422
2423 void WCMD_setshow_time (void) {
2424
2425   WCHAR curtime[64], buffer[64];
2426   DWORD count;
2427   SYSTEMTIME st;
2428   static const WCHAR parmT[] = {'/','T','\0'};
2429
2430   if (strlenW(param1) == 0) {
2431     GetLocalTime(&st);
2432     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2433                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2434       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2435       if (strstrW (quals, parmT) == NULL) {
2436         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2437         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2438         if (count > 2) {
2439           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2440         }
2441       }
2442     }
2443     else WCMD_print_error ();
2444   }
2445   else {
2446     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2447   }
2448 }
2449
2450 /****************************************************************************
2451  * WCMD_shift
2452  *
2453  * Shift batch parameters.
2454  * Optional /n says where to start shifting (n=0-8)
2455  */
2456
2457 void WCMD_shift (const WCHAR *command) {
2458   int start;
2459
2460   if (context != NULL) {
2461     WCHAR *pos = strchrW(command, '/');
2462     int   i;
2463
2464     if (pos == NULL) {
2465       start = 0;
2466     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2467       start = (*(pos+1) - '0');
2468     } else {
2469       SetLastError(ERROR_INVALID_PARAMETER);
2470       WCMD_print_error();
2471       return;
2472     }
2473
2474     WINE_TRACE("Shifting variables, starting at %d\n", start);
2475     for (i=start;i<=8;i++) {
2476       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2477     }
2478     context -> shift_count[9] = context -> shift_count[9] + 1;
2479   }
2480
2481 }
2482
2483 /****************************************************************************
2484  * WCMD_start
2485  */
2486 void WCMD_start(const WCHAR *command)
2487 {
2488     static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2489                                  '\\','s','t','a','r','t','.','e','x','e',0};
2490     WCHAR file[MAX_PATH];
2491     WCHAR *cmdline;
2492     STARTUPINFOW st;
2493     PROCESS_INFORMATION pi;
2494
2495     GetWindowsDirectoryW( file, MAX_PATH );
2496     strcatW( file, exeW );
2497     cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2498     strcpyW( cmdline, file );
2499     strcatW( cmdline, spaceW );
2500     strcatW( cmdline, command );
2501
2502     memset( &st, 0, sizeof(STARTUPINFOW) );
2503     st.cb = sizeof(STARTUPINFOW);
2504
2505     if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2506     {
2507         WaitForSingleObject( pi.hProcess, INFINITE );
2508         GetExitCodeProcess( pi.hProcess, &errorlevel );
2509         if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2510         CloseHandle(pi.hProcess);
2511         CloseHandle(pi.hThread);
2512     }
2513     else
2514     {
2515         SetLastError(ERROR_FILE_NOT_FOUND);
2516         WCMD_print_error ();
2517         errorlevel = 9009;
2518     }
2519     HeapFree( GetProcessHeap(), 0, cmdline );
2520 }
2521
2522 /****************************************************************************
2523  * WCMD_title
2524  *
2525  * Set the console title
2526  */
2527 void WCMD_title (const WCHAR *command) {
2528   SetConsoleTitleW(command);
2529 }
2530
2531 /****************************************************************************
2532  * WCMD_type
2533  *
2534  * Copy a file to standard output.
2535  */
2536
2537 void WCMD_type (WCHAR *command) {
2538
2539   int   argno         = 0;
2540   WCHAR *argN          = command;
2541   BOOL  writeHeaders  = FALSE;
2542
2543   if (param1[0] == 0x00) {
2544     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2545     return;
2546   }
2547
2548   if (param2[0] != 0x00) writeHeaders = TRUE;
2549
2550   /* Loop through all args */
2551   errorlevel = 0;
2552   while (argN) {
2553     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2554
2555     HANDLE h;
2556     WCHAR buffer[512];
2557     DWORD count;
2558
2559     if (!argN) break;
2560
2561     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2562     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2563                 FILE_ATTRIBUTE_NORMAL, NULL);
2564     if (h == INVALID_HANDLE_VALUE) {
2565       WCMD_print_error ();
2566       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2567       errorlevel = 1;
2568     } else {
2569       if (writeHeaders) {
2570         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2571         WCMD_output(fmt, thisArg);
2572       }
2573       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2574         if (count == 0) break;  /* ReadFile reports success on EOF! */
2575         buffer[count] = 0;
2576         WCMD_output_asis (buffer);
2577       }
2578       CloseHandle (h);
2579     }
2580   }
2581 }
2582
2583 /****************************************************************************
2584  * WCMD_more
2585  *
2586  * Output either a file or stdin to screen in pages
2587  */
2588
2589 void WCMD_more (WCHAR *command) {
2590
2591   int   argno         = 0;
2592   WCHAR *argN          = command;
2593   WCHAR  moreStr[100];
2594   WCHAR  moreStrPage[100];
2595   WCHAR  buffer[512];
2596   DWORD count;
2597   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2598   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2599   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2600                                     ')',' ','-','-','\n','\0'};
2601   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2602
2603   /* Prefix the NLS more with '-- ', then load the text */
2604   errorlevel = 0;
2605   strcpyW(moreStr, moreStart);
2606   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2607               (sizeof(moreStr)/sizeof(WCHAR))-3);
2608
2609   if (param1[0] == 0x00) {
2610
2611     /* Wine implements pipes via temporary files, and hence stdin is
2612        effectively reading from the file. This means the prompts for
2613        more are satisfied by the next line from the input (file). To
2614        avoid this, ensure stdin is to the console                    */
2615     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2616     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2617                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2618                          FILE_ATTRIBUTE_NORMAL, 0);
2619     WINE_TRACE("No parms - working probably in pipe mode\n");
2620     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2621
2622     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2623        once you get in this bit unless due to a pipe, its going to end badly...  */
2624     wsprintfW(moreStrPage, moreFmt, moreStr);
2625
2626     WCMD_enter_paged_mode(moreStrPage);
2627     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2628       if (count == 0) break;    /* ReadFile reports success on EOF! */
2629       buffer[count] = 0;
2630       WCMD_output_asis (buffer);
2631     }
2632     WCMD_leave_paged_mode();
2633
2634     /* Restore stdin to what it was */
2635     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2636     CloseHandle(hConIn);
2637
2638     return;
2639   } else {
2640     BOOL needsPause = FALSE;
2641
2642     /* Loop through all args */
2643     WINE_TRACE("Parms supplied - working through each file\n");
2644     WCMD_enter_paged_mode(moreStrPage);
2645
2646     while (argN) {
2647       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2648       HANDLE h;
2649
2650       if (!argN) break;
2651
2652       if (needsPause) {
2653
2654         /* Wait */
2655         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2656         WCMD_leave_paged_mode();
2657         WCMD_output_asis(moreStrPage);
2658         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2659         WCMD_enter_paged_mode(moreStrPage);
2660       }
2661
2662
2663       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2664       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2665                 FILE_ATTRIBUTE_NORMAL, NULL);
2666       if (h == INVALID_HANDLE_VALUE) {
2667         WCMD_print_error ();
2668         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2669         errorlevel = 1;
2670       } else {
2671         ULONG64 curPos  = 0;
2672         ULONG64 fileLen = 0;
2673         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2674
2675         /* Get the file size */
2676         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2677         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2678
2679         needsPause = TRUE;
2680         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2681           if (count == 0) break;        /* ReadFile reports success on EOF! */
2682           buffer[count] = 0;
2683           curPos += count;
2684
2685           /* Update % count (would be used in WCMD_output_asis as prompt) */
2686           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2687
2688           WCMD_output_asis (buffer);
2689         }
2690         CloseHandle (h);
2691       }
2692     }
2693
2694     WCMD_leave_paged_mode();
2695   }
2696 }
2697
2698 /****************************************************************************
2699  * WCMD_verify
2700  *
2701  * Display verify flag.
2702  * FIXME: We don't actually do anything with the verify flag other than toggle
2703  * it...
2704  */
2705
2706 void WCMD_verify (const WCHAR *command) {
2707
2708   int count;
2709
2710   count = strlenW(command);
2711   if (count == 0) {
2712     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2713     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2714     return;
2715   }
2716   if (lstrcmpiW(command, onW) == 0) {
2717     verify_mode = TRUE;
2718     return;
2719   }
2720   else if (lstrcmpiW(command, offW) == 0) {
2721     verify_mode = FALSE;
2722     return;
2723   }
2724   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2725 }
2726
2727 /****************************************************************************
2728  * WCMD_version
2729  *
2730  * Display version info.
2731  */
2732
2733 void WCMD_version (void) {
2734
2735   WCMD_output_asis (version_string);
2736
2737 }
2738
2739 /****************************************************************************
2740  * WCMD_volume
2741  *
2742  * Display volume information (set_label = FALSE)
2743  * Additionally set volume label (set_label = TRUE)
2744  * Returns 1 on success, 0 otherwise
2745  */
2746
2747 int WCMD_volume(BOOL set_label, const WCHAR *path)
2748 {
2749   DWORD count, serial;
2750   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2751   BOOL status;
2752
2753   if (strlenW(path) == 0) {
2754     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2755     if (!status) {
2756       WCMD_print_error ();
2757       return 0;
2758     }
2759     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2760                                    &serial, NULL, NULL, NULL, 0);
2761   }
2762   else {
2763     static const WCHAR fmt[] = {'%','s','\\','\0'};
2764     if ((path[1] != ':') || (strlenW(path) != 2)) {
2765       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2766       return 0;
2767     }
2768     wsprintfW (curdir, fmt, path);
2769     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2770                                    &serial, NULL,
2771         NULL, NULL, 0);
2772   }
2773   if (!status) {
2774     WCMD_print_error ();
2775     return 0;
2776   }
2777   if (label[0] != '\0') {
2778     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2779         curdir[0], label);
2780   }
2781   else {
2782     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2783         curdir[0]);
2784   }
2785   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2786         HIWORD(serial), LOWORD(serial));
2787   if (set_label) {
2788     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2789     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2790     if (count > 1) {
2791       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2792       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2793     }
2794     if (strlenW(path) != 0) {
2795       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2796     }
2797     else {
2798       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2799     }
2800   }
2801   return 1;
2802 }
2803
2804 /**************************************************************************
2805  * WCMD_exit
2806  *
2807  * Exit either the process, or just this batch program
2808  *
2809  */
2810
2811 void WCMD_exit (CMD_LIST **cmdList) {
2812
2813     static const WCHAR parmB[] = {'/','B','\0'};
2814     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2815
2816     if (context && lstrcmpiW(quals, parmB) == 0) {
2817         errorlevel = rc;
2818         context -> skip_rest = TRUE;
2819         *cmdList = NULL;
2820     } else {
2821         ExitProcess(rc);
2822     }
2823 }
2824
2825
2826 /*****************************************************************************
2827  * WCMD_assoc
2828  *
2829  *      Lists or sets file associations  (assoc = TRUE)
2830  *      Lists or sets file types         (assoc = FALSE)
2831  */
2832 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2833
2834     HKEY    key;
2835     DWORD   accessOptions = KEY_READ;
2836     WCHAR   *newValue;
2837     LONG    rc = ERROR_SUCCESS;
2838     WCHAR    keyValue[MAXSTRING];
2839     DWORD   valueLen = MAXSTRING;
2840     HKEY    readKey;
2841     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2842                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2843
2844     /* See if parameter includes '=' */
2845     errorlevel = 0;
2846     newValue = strchrW(command, '=');
2847     if (newValue) accessOptions |= KEY_WRITE;
2848
2849     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2850     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2851                       accessOptions, &key) != ERROR_SUCCESS) {
2852       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2853       return;
2854     }
2855
2856     /* If no parameters then list all associations */
2857     if (*command == 0x00) {
2858       int index = 0;
2859
2860       /* Enumerate all the keys */
2861       while (rc != ERROR_NO_MORE_ITEMS) {
2862         WCHAR  keyName[MAXSTRING];
2863         DWORD nameLen;
2864
2865         /* Find the next value */
2866         nameLen = MAXSTRING;
2867         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2868
2869         if (rc == ERROR_SUCCESS) {
2870
2871           /* Only interested in extension ones if assoc, or others
2872              if not assoc                                          */
2873           if ((keyName[0] == '.' && assoc) ||
2874               (!(keyName[0] == '.') && (!assoc)))
2875           {
2876             WCHAR subkey[MAXSTRING];
2877             strcpyW(subkey, keyName);
2878             if (!assoc) strcatW(subkey, shOpCmdW);
2879
2880             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2881
2882               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2883               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2884               WCMD_output_asis(keyName);
2885               WCMD_output_asis(equalW);
2886               /* If no default value found, leave line empty after '=' */
2887               if (rc == ERROR_SUCCESS) {
2888                 WCMD_output_asis(keyValue);
2889               }
2890               WCMD_output_asis(newlineW);
2891               RegCloseKey(readKey);
2892             }
2893           }
2894         }
2895       }
2896
2897     } else {
2898
2899       /* Parameter supplied - if no '=' on command line, its a query */
2900       if (newValue == NULL) {
2901         WCHAR *space;
2902         WCHAR subkey[MAXSTRING];
2903
2904         /* Query terminates the parameter at the first space */
2905         strcpyW(keyValue, command);
2906         space = strchrW(keyValue, ' ');
2907         if (space) *space=0x00;
2908
2909         /* Set up key name */
2910         strcpyW(subkey, keyValue);
2911         if (!assoc) strcatW(subkey, shOpCmdW);
2912
2913         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2914
2915           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2916           WCMD_output_asis(command);
2917           WCMD_output_asis(equalW);
2918           /* If no default value found, leave line empty after '=' */
2919           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2920           WCMD_output_asis(newlineW);
2921           RegCloseKey(readKey);
2922
2923         } else {
2924           WCHAR  msgbuffer[MAXSTRING];
2925
2926           /* Load the translated 'File association not found' */
2927           if (assoc) {
2928             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2929           } else {
2930             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2931           }
2932           WCMD_output_stderr(msgbuffer, keyValue);
2933           errorlevel = 2;
2934         }
2935
2936       /* Not a query - its a set or clear of a value */
2937       } else {
2938
2939         WCHAR subkey[MAXSTRING];
2940
2941         /* Get pointer to new value */
2942         *newValue = 0x00;
2943         newValue++;
2944
2945         /* Set up key name */
2946         strcpyW(subkey, command);
2947         if (!assoc) strcatW(subkey, shOpCmdW);
2948
2949         /* If nothing after '=' then clear value - only valid for ASSOC */
2950         if (*newValue == 0x00) {
2951
2952           if (assoc) rc = RegDeleteKeyW(key, command);
2953           if (assoc && rc == ERROR_SUCCESS) {
2954             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2955
2956           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2957             WCMD_print_error();
2958             errorlevel = 2;
2959
2960           } else {
2961             WCHAR  msgbuffer[MAXSTRING];
2962
2963             /* Load the translated 'File association not found' */
2964             if (assoc) {
2965               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2966                           sizeof(msgbuffer)/sizeof(WCHAR));
2967             } else {
2968               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2969                           sizeof(msgbuffer)/sizeof(WCHAR));
2970             }
2971             WCMD_output_stderr(msgbuffer, keyValue);
2972             errorlevel = 2;
2973           }
2974
2975         /* It really is a set value = contents */
2976         } else {
2977           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2978                               accessOptions, NULL, &readKey, NULL);
2979           if (rc == ERROR_SUCCESS) {
2980             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2981                                 (LPBYTE)newValue,
2982                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
2983             RegCloseKey(readKey);
2984           }
2985
2986           if (rc != ERROR_SUCCESS) {
2987             WCMD_print_error();
2988             errorlevel = 2;
2989           } else {
2990             WCMD_output_asis(command);
2991             WCMD_output_asis(equalW);
2992             WCMD_output_asis(newValue);
2993             WCMD_output_asis(newlineW);
2994           }
2995         }
2996       }
2997     }
2998
2999     /* Clean up */
3000     RegCloseKey(key);
3001 }
3002
3003 /****************************************************************************
3004  * WCMD_color
3005  *
3006  * Colors the terminal screen.
3007  */
3008
3009 void WCMD_color (void) {
3010
3011   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3012   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3013
3014   if (param1[0] != 0x00 && strlenW(param1) > 2) {
3015     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3016     return;
3017   }
3018
3019   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3020   {
3021       COORD topLeft;
3022       DWORD screenSize;
3023       DWORD color = 0;
3024
3025       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3026
3027       topLeft.X = 0;
3028       topLeft.Y = 0;
3029
3030       /* Convert the color hex digits */
3031       if (param1[0] == 0x00) {
3032         color = defaultColor;
3033       } else {
3034         color = strtoulW(param1, NULL, 16);
3035       }
3036
3037       /* Fail if fg == bg color */
3038       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3039         errorlevel = 1;
3040         return;
3041       }
3042
3043       /* Set the current screen contents and ensure all future writes
3044          remain this color                                             */
3045       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3046       SetConsoleTextAttribute(hStdOut, color);
3047   }
3048 }