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