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