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