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