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