gdi32: Implement SelectFont as a standard driver entry point.
[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 (WCHAR *command) {
1422     struct env_stack *curdir;
1423     WCHAR *thisdir;
1424     static const WCHAR parmD[] = {'/','D','\0'};
1425
1426     if (strchrW(command, '/') != NULL) {
1427       SetLastError(ERROR_INVALID_PARAMETER);
1428       WCMD_print_error();
1429       return;
1430     }
1431
1432     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1433     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1434     if( !curdir || !thisdir ) {
1435       LocalFree(curdir);
1436       LocalFree(thisdir);
1437       WINE_ERR ("out of memory\n");
1438       return;
1439     }
1440
1441     /* Change directory using CD code with /D parameter */
1442     strcpyW(quals, parmD);
1443     GetCurrentDirectoryW (1024, thisdir);
1444     errorlevel = 0;
1445     WCMD_setshow_default(command);
1446     if (errorlevel) {
1447       LocalFree(curdir);
1448       LocalFree(thisdir);
1449       return;
1450     } else {
1451       curdir -> next    = pushd_directories;
1452       curdir -> strings = thisdir;
1453       if (pushd_directories == NULL) {
1454         curdir -> u.stackdepth = 1;
1455       } else {
1456         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1457       }
1458       pushd_directories = curdir;
1459     }
1460 }
1461
1462
1463 /*****************************************************************************
1464  * WCMD_popd
1465  *
1466  *      Pop a directory from the stack
1467  */
1468
1469 void WCMD_popd (void) {
1470     struct env_stack *temp = pushd_directories;
1471
1472     if (!pushd_directories)
1473       return;
1474
1475     /* pop the old environment from the stack, and make it the current dir */
1476     pushd_directories = temp->next;
1477     SetCurrentDirectoryW(temp->strings);
1478     LocalFree (temp->strings);
1479     LocalFree (temp);
1480 }
1481
1482 /****************************************************************************
1483  * WCMD_if
1484  *
1485  * Batch file conditional.
1486  *
1487  * On entry, cmdlist will point to command containing the IF, and optionally
1488  *   the first command to execute (if brackets not found)
1489  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1490  *   If ('s were found, execute all within that bracket
1491  *   Command may optionally be followed by an ELSE - need to skip instructions
1492  *   in the else using the same logic
1493  *
1494  * FIXME: Much more syntax checking needed!
1495  */
1496
1497 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1498
1499   int negate; /* Negate condition */
1500   int test;   /* Condition evaluation result */
1501   WCHAR condition[MAX_PATH], *command, *s;
1502   static const WCHAR notW[]    = {'n','o','t','\0'};
1503   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1504   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1505   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1506   static const WCHAR eqeqW[]   = {'=','=','\0'};
1507   static const WCHAR parmI[]   = {'/','I','\0'};
1508   int caseInsensitive = (strstrW(quals, parmI) != NULL);
1509
1510   negate = !lstrcmpiW(param1,notW);
1511   strcpyW(condition, (negate ? param2 : param1));
1512   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1513
1514   if (!lstrcmpiW (condition, errlvlW)) {
1515     test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1516     WCMD_parameter(p, 2+negate, &command, NULL);
1517   }
1518   else if (!lstrcmpiW (condition, existW)) {
1519     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1520     WCMD_parameter(p, 2+negate, &command, NULL);
1521   }
1522   else if (!lstrcmpiW (condition, defdW)) {
1523     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1524     WCMD_parameter(p, 2+negate, &command, NULL);
1525   }
1526   else if ((s = strstrW (p, eqeqW))) {
1527     /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1528     WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1529     s += 2;
1530     WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1531     WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1532     test = caseInsensitive
1533             ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1534                               leftPart, leftPartEnd-leftPart+1,
1535                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1536             : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1537                               leftPart, leftPartEnd-leftPart+1,
1538                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1539     WCMD_parameter(s, 1, &command, NULL);
1540   }
1541   else {
1542     WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1543     return;
1544   }
1545
1546   /* Process rest of IF statement which is on the same line
1547      Note: This may process all or some of the cmdList (eg a GOTO) */
1548   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1549 }
1550
1551 /****************************************************************************
1552  * WCMD_move
1553  *
1554  * Move a file, directory tree or wildcarded set of files.
1555  */
1556
1557 void WCMD_move (void)
1558 {
1559   int             status;
1560   WIN32_FIND_DATAW fd;
1561   HANDLE          hff;
1562   WCHAR            input[MAX_PATH];
1563   WCHAR            output[MAX_PATH];
1564   WCHAR            drive[10];
1565   WCHAR            dir[MAX_PATH];
1566   WCHAR            fname[MAX_PATH];
1567   WCHAR            ext[MAX_PATH];
1568
1569   if (param1[0] == 0x00) {
1570     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1571     return;
1572   }
1573
1574   /* If no destination supplied, assume current directory */
1575   if (param2[0] == 0x00) {
1576       strcpyW(param2, dotW);
1577   }
1578
1579   /* If 2nd parm is directory, then use original filename */
1580   /* Convert partial path to full path */
1581   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1582   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1583   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1584              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1585
1586   /* Split into components */
1587   WCMD_splitpath(input, drive, dir, fname, ext);
1588
1589   hff = FindFirstFileW(input, &fd);
1590   if (hff == INVALID_HANDLE_VALUE)
1591     return;
1592
1593   do {
1594     WCHAR  dest[MAX_PATH];
1595     WCHAR  src[MAX_PATH];
1596     DWORD attribs;
1597     BOOL ok = TRUE;
1598
1599     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1600
1601     /* Build src & dest name */
1602     strcpyW(src, drive);
1603     strcatW(src, dir);
1604
1605     /* See if dest is an existing directory */
1606     attribs = GetFileAttributesW(output);
1607     if (attribs != INVALID_FILE_ATTRIBUTES &&
1608        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1609       strcpyW(dest, output);
1610       strcatW(dest, slashW);
1611       strcatW(dest, fd.cFileName);
1612     } else {
1613       strcpyW(dest, output);
1614     }
1615
1616     strcatW(src, fd.cFileName);
1617
1618     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1619     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1620
1621     /* If destination exists, prompt unless /Y supplied */
1622     if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1623       BOOL force = FALSE;
1624       WCHAR copycmd[MAXSTRING];
1625       int len;
1626
1627       /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1628       if (strstrW (quals, parmNoY))
1629         force = FALSE;
1630       else if (strstrW (quals, parmY))
1631         force = TRUE;
1632       else {
1633         static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1634         len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1635         force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1636                      && ! lstrcmpiW (copycmd, parmY));
1637       }
1638
1639       /* Prompt if overwriting */
1640       if (!force) {
1641         WCHAR  question[MAXSTRING];
1642         WCHAR  yesChar[10];
1643
1644         strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1645
1646         /* Ask for confirmation */
1647         wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1648         ok = WCMD_ask_confirm(question, FALSE, NULL);
1649
1650         /* So delete the destination prior to the move */
1651         if (ok) {
1652           if (!DeleteFileW(dest)) {
1653             WCMD_print_error ();
1654             errorlevel = 1;
1655             ok = FALSE;
1656           }
1657         }
1658       }
1659     }
1660
1661     if (ok) {
1662       status = MoveFileW(src, dest);
1663     } else {
1664       status = 1; /* Anything other than 0 to prevent error msg below */
1665     }
1666
1667     if (!status) {
1668       WCMD_print_error ();
1669       errorlevel = 1;
1670     }
1671   } while (FindNextFileW(hff, &fd) != 0);
1672
1673   FindClose(hff);
1674 }
1675
1676 /****************************************************************************
1677  * WCMD_pause
1678  *
1679  * Suspend execution of a batch script until a key is typed
1680  */
1681
1682 void WCMD_pause (void)
1683 {
1684   DWORD oldmode;
1685   BOOL have_console;
1686   DWORD count;
1687   WCHAR key;
1688   HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1689
1690   have_console = GetConsoleMode(hIn, &oldmode);
1691   if (have_console)
1692       SetConsoleMode(hIn, 0);
1693
1694   WCMD_output(anykey);
1695   WCMD_ReadFile(hIn, &key, 1, &count);
1696   if (have_console)
1697     SetConsoleMode(hIn, oldmode);
1698 }
1699
1700 /****************************************************************************
1701  * WCMD_remove_dir
1702  *
1703  * Delete a directory.
1704  */
1705
1706 void WCMD_remove_dir (WCHAR *command) {
1707
1708   int   argno         = 0;
1709   int   argsProcessed = 0;
1710   WCHAR *argN          = command;
1711   static const WCHAR parmS[] = {'/','S','\0'};
1712   static const WCHAR parmQ[] = {'/','Q','\0'};
1713
1714   /* Loop through all args */
1715   while (argN) {
1716     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1717     if (argN && argN[0] != '/') {
1718       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1719                  wine_dbgstr_w(quals));
1720       argsProcessed++;
1721
1722       /* If subdirectory search not supplied, just try to remove
1723          and report error if it fails (eg if it contains a file) */
1724       if (strstrW (quals, parmS) == NULL) {
1725         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1726
1727       /* Otherwise use ShFileOp to recursively remove a directory */
1728       } else {
1729
1730         SHFILEOPSTRUCTW lpDir;
1731
1732         /* Ask first */
1733         if (strstrW (quals, parmQ) == NULL) {
1734           BOOL  ok;
1735           WCHAR  question[MAXSTRING];
1736           static const WCHAR fmt[] = {'%','s',' ','\0'};
1737
1738           /* Ask for confirmation */
1739           wsprintfW(question, fmt, thisArg);
1740           ok = WCMD_ask_confirm(question, TRUE, NULL);
1741
1742           /* Abort if answer is 'N' */
1743           if (!ok) return;
1744         }
1745
1746         /* Do the delete */
1747         lpDir.hwnd   = NULL;
1748         lpDir.pTo    = NULL;
1749         lpDir.pFrom  = thisArg;
1750         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1751         lpDir.wFunc  = FO_DELETE;
1752         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1753       }
1754     }
1755   }
1756
1757   /* Handle no valid args */
1758   if (argsProcessed == 0) {
1759     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1760     return;
1761   }
1762
1763 }
1764
1765 /****************************************************************************
1766  * WCMD_rename
1767  *
1768  * Rename a file.
1769  */
1770
1771 void WCMD_rename (void)
1772 {
1773   int             status;
1774   HANDLE          hff;
1775   WIN32_FIND_DATAW fd;
1776   WCHAR            input[MAX_PATH];
1777   WCHAR           *dotDst = NULL;
1778   WCHAR            drive[10];
1779   WCHAR            dir[MAX_PATH];
1780   WCHAR            fname[MAX_PATH];
1781   WCHAR            ext[MAX_PATH];
1782
1783   errorlevel = 0;
1784
1785   /* Must be at least two args */
1786   if (param1[0] == 0x00 || param2[0] == 0x00) {
1787     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1788     errorlevel = 1;
1789     return;
1790   }
1791
1792   /* Destination cannot contain a drive letter or directory separator */
1793   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1794       SetLastError(ERROR_INVALID_PARAMETER);
1795       WCMD_print_error();
1796       errorlevel = 1;
1797       return;
1798   }
1799
1800   /* Convert partial path to full path */
1801   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1802   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1803              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1804   dotDst = strchrW(param2, '.');
1805
1806   /* Split into components */
1807   WCMD_splitpath(input, drive, dir, fname, ext);
1808
1809   hff = FindFirstFileW(input, &fd);
1810   if (hff == INVALID_HANDLE_VALUE)
1811     return;
1812
1813  do {
1814     WCHAR  dest[MAX_PATH];
1815     WCHAR  src[MAX_PATH];
1816     WCHAR *dotSrc = NULL;
1817     int   dirLen;
1818
1819     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1820
1821     /* FIXME: If dest name or extension is *, replace with filename/ext
1822        part otherwise use supplied name. This supports:
1823           ren *.fred *.jim
1824           ren jim.* fred.* etc
1825        However, windows has a more complex algorithm supporting eg
1826           ?'s and *'s mid name                                         */
1827     dotSrc = strchrW(fd.cFileName, '.');
1828
1829     /* Build src & dest name */
1830     strcpyW(src, drive);
1831     strcatW(src, dir);
1832     strcpyW(dest, src);
1833     dirLen = strlenW(src);
1834     strcatW(src, fd.cFileName);
1835
1836     /* Build name */
1837     if (param2[0] == '*') {
1838       strcatW(dest, fd.cFileName);
1839       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1840     } else {
1841       strcatW(dest, param2);
1842       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1843     }
1844
1845     /* Build Extension */
1846     if (dotDst && (*(dotDst+1)=='*')) {
1847       if (dotSrc) strcatW(dest, dotSrc);
1848     } else if (dotDst) {
1849       if (dotDst) strcatW(dest, dotDst);
1850     }
1851
1852     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1853     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1854
1855     status = MoveFileW(src, dest);
1856
1857     if (!status) {
1858       WCMD_print_error ();
1859       errorlevel = 1;
1860     }
1861   } while (FindNextFileW(hff, &fd) != 0);
1862
1863   FindClose(hff);
1864 }
1865
1866 /*****************************************************************************
1867  * WCMD_dupenv
1868  *
1869  * Make a copy of the environment.
1870  */
1871 static WCHAR *WCMD_dupenv( const WCHAR *env )
1872 {
1873   WCHAR *env_copy;
1874   int len;
1875
1876   if( !env )
1877     return NULL;
1878
1879   len = 0;
1880   while ( env[len] )
1881     len += (strlenW(&env[len]) + 1);
1882
1883   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1884   if (!env_copy)
1885   {
1886     WINE_ERR("out of memory\n");
1887     return env_copy;
1888   }
1889   memcpy (env_copy, env, len*sizeof (WCHAR));
1890   env_copy[len] = 0;
1891
1892   return env_copy;
1893 }
1894
1895 /*****************************************************************************
1896  * WCMD_setlocal
1897  *
1898  *  setlocal pushes the environment onto a stack
1899  *  Save the environment as unicode so we don't screw anything up.
1900  */
1901 void WCMD_setlocal (const WCHAR *s) {
1902   WCHAR *env;
1903   struct env_stack *env_copy;
1904   WCHAR cwd[MAX_PATH];
1905
1906   /* DISABLEEXTENSIONS ignored */
1907
1908   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1909   if( !env_copy )
1910   {
1911     WINE_ERR ("out of memory\n");
1912     return;
1913   }
1914
1915   env = GetEnvironmentStringsW ();
1916
1917   env_copy->strings = WCMD_dupenv (env);
1918   if (env_copy->strings)
1919   {
1920     env_copy->next = saved_environment;
1921     saved_environment = env_copy;
1922
1923     /* Save the current drive letter */
1924     GetCurrentDirectoryW(MAX_PATH, cwd);
1925     env_copy->u.cwd = cwd[0];
1926   }
1927   else
1928     LocalFree (env_copy);
1929
1930   FreeEnvironmentStringsW (env);
1931
1932 }
1933
1934 /*****************************************************************************
1935  * WCMD_endlocal
1936  *
1937  *  endlocal pops the environment off a stack
1938  *  Note: When searching for '=', search from WCHAR position 1, to handle
1939  *        special internal environment variables =C:, =D: etc
1940  */
1941 void WCMD_endlocal (void) {
1942   WCHAR *env, *old, *p;
1943   struct env_stack *temp;
1944   int len, n;
1945
1946   if (!saved_environment)
1947     return;
1948
1949   /* pop the old environment from the stack */
1950   temp = saved_environment;
1951   saved_environment = temp->next;
1952
1953   /* delete the current environment, totally */
1954   env = GetEnvironmentStringsW ();
1955   old = WCMD_dupenv (GetEnvironmentStringsW ());
1956   len = 0;
1957   while (old[len]) {
1958     n = strlenW(&old[len]) + 1;
1959     p = strchrW(&old[len] + 1, '=');
1960     if (p)
1961     {
1962       *p++ = 0;
1963       SetEnvironmentVariableW (&old[len], NULL);
1964     }
1965     len += n;
1966   }
1967   LocalFree (old);
1968   FreeEnvironmentStringsW (env);
1969
1970   /* restore old environment */
1971   env = temp->strings;
1972   len = 0;
1973   while (env[len]) {
1974     n = strlenW(&env[len]) + 1;
1975     p = strchrW(&env[len] + 1, '=');
1976     if (p)
1977     {
1978       *p++ = 0;
1979       SetEnvironmentVariableW (&env[len], p);
1980     }
1981     len += n;
1982   }
1983
1984   /* Restore current drive letter */
1985   if (IsCharAlphaW(temp->u.cwd)) {
1986     WCHAR envvar[4];
1987     WCHAR cwd[MAX_PATH];
1988     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1989
1990     wsprintfW(envvar, fmt, temp->u.cwd);
1991     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1992       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1993       SetCurrentDirectoryW(cwd);
1994     }
1995   }
1996
1997   LocalFree (env);
1998   LocalFree (temp);
1999 }
2000
2001 /*****************************************************************************
2002  * WCMD_setshow_default
2003  *
2004  *      Set/Show the current default directory
2005  */
2006
2007 void WCMD_setshow_default (const WCHAR *command) {
2008
2009   BOOL status;
2010   WCHAR string[1024];
2011   WCHAR cwd[1024];
2012   WCHAR *pos;
2013   WIN32_FIND_DATAW fd;
2014   HANDLE hff;
2015   static const WCHAR parmD[] = {'/','D','\0'};
2016
2017   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2018
2019   /* Skip /D and trailing whitespace if on the front of the command line */
2020   if (CompareStringW(LOCALE_USER_DEFAULT,
2021                      NORM_IGNORECASE | SORT_STRINGSORT,
2022                      command, 2, parmD, -1) == CSTR_EQUAL) {
2023     command += 2;
2024     while (*command && (*command==' ' || *command=='\t'))
2025       command++;
2026   }
2027
2028   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2029   if (strlenW(command) == 0) {
2030     strcatW (cwd, newline);
2031     WCMD_output (cwd);
2032   }
2033   else {
2034     /* Remove any double quotes, which may be in the
2035        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2036     pos = string;
2037     while (*command) {
2038       if (*command != '"') *pos++ = *command;
2039       command++;
2040     }
2041     while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2042       pos--;
2043     *pos = 0x00;
2044
2045     /* Search for appropriate directory */
2046     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2047     hff = FindFirstFileW(string, &fd);
2048     if (hff != INVALID_HANDLE_VALUE) {
2049       do {
2050         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2051           WCHAR fpath[MAX_PATH];
2052           WCHAR drive[10];
2053           WCHAR dir[MAX_PATH];
2054           WCHAR fname[MAX_PATH];
2055           WCHAR ext[MAX_PATH];
2056           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2057
2058           /* Convert path into actual directory spec */
2059           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2060           WCMD_splitpath(fpath, drive, dir, fname, ext);
2061
2062           /* Rebuild path */
2063           wsprintfW(string, fmt, drive, dir, fd.cFileName);
2064           break;
2065         }
2066       } while (FindNextFileW(hff, &fd) != 0);
2067       FindClose(hff);
2068     }
2069
2070     /* Change to that directory */
2071     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2072
2073     status = SetCurrentDirectoryW(string);
2074     if (!status) {
2075       errorlevel = 1;
2076       WCMD_print_error ();
2077       return;
2078     } else {
2079
2080       /* Save away the actual new directory, to store as current location */
2081       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2082
2083       /* Restore old directory if drive letter would change, and
2084            CD x:\directory /D (or pushd c:\directory) not supplied */
2085       if ((strstrW(quals, parmD) == NULL) &&
2086           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2087         SetCurrentDirectoryW(cwd);
2088       }
2089     }
2090
2091     /* Set special =C: type environment variable, for drive letter of
2092        change of directory, even if path was restored due to missing
2093        /D (allows changing drive letter when not resident on that
2094        drive                                                          */
2095     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2096       WCHAR env[4];
2097       strcpyW(env, equalW);
2098       memcpy(env+1, string, 2 * sizeof(WCHAR));
2099       env[3] = 0x00;
2100       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2101       SetEnvironmentVariableW(env, string);
2102     }
2103
2104    }
2105   return;
2106 }
2107
2108 /****************************************************************************
2109  * WCMD_setshow_date
2110  *
2111  * Set/Show the system date
2112  * FIXME: Can't change date yet
2113  */
2114
2115 void WCMD_setshow_date (void) {
2116
2117   WCHAR curdate[64], buffer[64];
2118   DWORD count;
2119   static const WCHAR parmT[] = {'/','T','\0'};
2120
2121   if (strlenW(param1) == 0) {
2122     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2123                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2124       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2125       if (strstrW (quals, parmT) == NULL) {
2126         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2127         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2128         if (count > 2) {
2129           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2130         }
2131       }
2132     }
2133     else WCMD_print_error ();
2134   }
2135   else {
2136     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2137   }
2138 }
2139
2140 /****************************************************************************
2141  * WCMD_compare
2142  */
2143 static int WCMD_compare( const void *a, const void *b )
2144 {
2145     int r;
2146     const WCHAR * const *str_a = a, * const *str_b = b;
2147     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2148           *str_a, -1, *str_b, -1 );
2149     if( r == CSTR_LESS_THAN ) return -1;
2150     if( r == CSTR_GREATER_THAN ) return 1;
2151     return 0;
2152 }
2153
2154 /****************************************************************************
2155  * WCMD_setshow_sortenv
2156  *
2157  * sort variables into order for display
2158  * Optionally only display those who start with a stub
2159  * returns the count displayed
2160  */
2161 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2162 {
2163   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2164   const WCHAR **str;
2165
2166   if (stub) stublen = strlenW(stub);
2167
2168   /* count the number of strings, and the total length */
2169   while ( s[len] ) {
2170     len += (strlenW(&s[len]) + 1);
2171     count++;
2172   }
2173
2174   /* add the strings to an array */
2175   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2176   if( !str )
2177     return 0;
2178   str[0] = s;
2179   for( i=1; i<count; i++ )
2180     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2181
2182   /* sort the array */
2183   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2184
2185   /* print it */
2186   for( i=0; i<count; i++ ) {
2187     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2188                                 NORM_IGNORECASE | SORT_STRINGSORT,
2189                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2190       /* Don't display special internal variables */
2191       if (str[i][0] != '=') {
2192         WCMD_output_asis(str[i]);
2193         WCMD_output_asis(newline);
2194         displayedcount++;
2195       }
2196     }
2197   }
2198
2199   LocalFree( str );
2200   return displayedcount;
2201 }
2202
2203 /****************************************************************************
2204  * WCMD_setshow_env
2205  *
2206  * Set/Show the environment variables
2207  */
2208
2209 void WCMD_setshow_env (WCHAR *s) {
2210
2211   LPVOID env;
2212   WCHAR *p;
2213   int status;
2214   static const WCHAR parmP[] = {'/','P','\0'};
2215
2216   if (param1[0] == 0x00 && quals[0] == 0x00) {
2217     env = GetEnvironmentStringsW();
2218     WCMD_setshow_sortenv( env, NULL );
2219     return;
2220   }
2221
2222   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2223   if (CompareStringW(LOCALE_USER_DEFAULT,
2224                      NORM_IGNORECASE | SORT_STRINGSORT,
2225                      s, 2, parmP, -1) == CSTR_EQUAL) {
2226     WCHAR string[MAXSTRING];
2227     DWORD count;
2228
2229     s += 2;
2230     while (*s && (*s==' ' || *s=='\t')) s++;
2231     if (*s=='\"')
2232         WCMD_strip_quotes(s);
2233
2234     /* If no parameter, or no '=' sign, return an error */
2235     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2236       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2237       return;
2238     }
2239
2240     /* Output the prompt */
2241     *p++ = '\0';
2242     if (strlenW(p) != 0) WCMD_output(p);
2243
2244     /* Read the reply */
2245     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2246     if (count > 1) {
2247       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2248       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2249       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2250                  wine_dbgstr_w(string));
2251       status = SetEnvironmentVariableW(s, string);
2252     }
2253
2254   } else {
2255     DWORD gle;
2256
2257     if (*s=='\"')
2258         WCMD_strip_quotes(s);
2259     p = strchrW (s, '=');
2260     if (p == NULL) {
2261       env = GetEnvironmentStringsW();
2262       if (WCMD_setshow_sortenv( env, s ) == 0) {
2263         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2264         errorlevel = 1;
2265       }
2266       return;
2267     }
2268     *p++ = '\0';
2269
2270     if (strlenW(p) == 0) p = NULL;
2271     status = SetEnvironmentVariableW(s, p);
2272     gle = GetLastError();
2273     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2274       errorlevel = 1;
2275     } else if ((!status)) WCMD_print_error();
2276   }
2277 }
2278
2279 /****************************************************************************
2280  * WCMD_setshow_path
2281  *
2282  * Set/Show the path environment variable
2283  */
2284
2285 void WCMD_setshow_path (const WCHAR *command) {
2286
2287   WCHAR string[1024];
2288   DWORD status;
2289   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2290   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2291
2292   if (strlenW(param1) == 0) {
2293     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2294     if (status != 0) {
2295       WCMD_output_asis ( pathEqW);
2296       WCMD_output_asis ( string);
2297       WCMD_output_asis ( newline);
2298     }
2299     else {
2300       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2301     }
2302   }
2303   else {
2304     if (*command == '=') command++; /* Skip leading '=' */
2305     status = SetEnvironmentVariableW(pathW, command);
2306     if (!status) WCMD_print_error();
2307   }
2308 }
2309
2310 /****************************************************************************
2311  * WCMD_setshow_prompt
2312  *
2313  * Set or show the command prompt.
2314  */
2315
2316 void WCMD_setshow_prompt (void) {
2317
2318   WCHAR *s;
2319   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2320
2321   if (strlenW(param1) == 0) {
2322     SetEnvironmentVariableW(promptW, NULL);
2323   }
2324   else {
2325     s = param1;
2326     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2327     if (strlenW(s) == 0) {
2328       SetEnvironmentVariableW(promptW, NULL);
2329     }
2330     else SetEnvironmentVariableW(promptW, s);
2331   }
2332 }
2333
2334 /****************************************************************************
2335  * WCMD_setshow_time
2336  *
2337  * Set/Show the system time
2338  * FIXME: Can't change time yet
2339  */
2340
2341 void WCMD_setshow_time (void) {
2342
2343   WCHAR curtime[64], buffer[64];
2344   DWORD count;
2345   SYSTEMTIME st;
2346   static const WCHAR parmT[] = {'/','T','\0'};
2347
2348   if (strlenW(param1) == 0) {
2349     GetLocalTime(&st);
2350     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2351                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2352       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2353       if (strstrW (quals, parmT) == NULL) {
2354         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2355         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2356         if (count > 2) {
2357           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2358         }
2359       }
2360     }
2361     else WCMD_print_error ();
2362   }
2363   else {
2364     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2365   }
2366 }
2367
2368 /****************************************************************************
2369  * WCMD_shift
2370  *
2371  * Shift batch parameters.
2372  * Optional /n says where to start shifting (n=0-8)
2373  */
2374
2375 void WCMD_shift (const WCHAR *command) {
2376   int start;
2377
2378   if (context != NULL) {
2379     WCHAR *pos = strchrW(command, '/');
2380     int   i;
2381
2382     if (pos == NULL) {
2383       start = 0;
2384     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2385       start = (*(pos+1) - '0');
2386     } else {
2387       SetLastError(ERROR_INVALID_PARAMETER);
2388       WCMD_print_error();
2389       return;
2390     }
2391
2392     WINE_TRACE("Shifting variables, starting at %d\n", start);
2393     for (i=start;i<=8;i++) {
2394       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2395     }
2396     context -> shift_count[9] = context -> shift_count[9] + 1;
2397   }
2398
2399 }
2400
2401 /****************************************************************************
2402  * WCMD_title
2403  *
2404  * Set the console title
2405  */
2406 void WCMD_title (const WCHAR *command) {
2407   SetConsoleTitleW(command);
2408 }
2409
2410 /****************************************************************************
2411  * WCMD_type
2412  *
2413  * Copy a file to standard output.
2414  */
2415
2416 void WCMD_type (WCHAR *command) {
2417
2418   int   argno         = 0;
2419   WCHAR *argN          = command;
2420   BOOL  writeHeaders  = FALSE;
2421
2422   if (param1[0] == 0x00) {
2423     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2424     return;
2425   }
2426
2427   if (param2[0] != 0x00) writeHeaders = TRUE;
2428
2429   /* Loop through all args */
2430   errorlevel = 0;
2431   while (argN) {
2432     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2433
2434     HANDLE h;
2435     WCHAR buffer[512];
2436     DWORD count;
2437
2438     if (!argN) break;
2439
2440     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2441     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2442                 FILE_ATTRIBUTE_NORMAL, NULL);
2443     if (h == INVALID_HANDLE_VALUE) {
2444       WCMD_print_error ();
2445       WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2446       errorlevel = 1;
2447     } else {
2448       if (writeHeaders) {
2449         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2450         WCMD_output(fmt, thisArg);
2451       }
2452       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2453         if (count == 0) break;  /* ReadFile reports success on EOF! */
2454         buffer[count] = 0;
2455         WCMD_output_asis (buffer);
2456       }
2457       CloseHandle (h);
2458     }
2459   }
2460 }
2461
2462 /****************************************************************************
2463  * WCMD_more
2464  *
2465  * Output either a file or stdin to screen in pages
2466  */
2467
2468 void WCMD_more (WCHAR *command) {
2469
2470   int   argno         = 0;
2471   WCHAR *argN          = command;
2472   WCHAR  moreStr[100];
2473   WCHAR  moreStrPage[100];
2474   WCHAR  buffer[512];
2475   DWORD count;
2476   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2477   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2478   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2479                                     ')',' ','-','-','\n','\0'};
2480   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2481
2482   /* Prefix the NLS more with '-- ', then load the text */
2483   errorlevel = 0;
2484   strcpyW(moreStr, moreStart);
2485   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2486               (sizeof(moreStr)/sizeof(WCHAR))-3);
2487
2488   if (param1[0] == 0x00) {
2489
2490     /* Wine implements pipes via temporary files, and hence stdin is
2491        effectively reading from the file. This means the prompts for
2492        more are satisfied by the next line from the input (file). To
2493        avoid this, ensure stdin is to the console                    */
2494     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2495     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2496                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2497                          FILE_ATTRIBUTE_NORMAL, 0);
2498     WINE_TRACE("No parms - working probably in pipe mode\n");
2499     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2500
2501     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2502        once you get in this bit unless due to a pipe, its going to end badly...  */
2503     wsprintfW(moreStrPage, moreFmt, moreStr);
2504
2505     WCMD_enter_paged_mode(moreStrPage);
2506     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2507       if (count == 0) break;    /* ReadFile reports success on EOF! */
2508       buffer[count] = 0;
2509       WCMD_output_asis (buffer);
2510     }
2511     WCMD_leave_paged_mode();
2512
2513     /* Restore stdin to what it was */
2514     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2515     CloseHandle(hConIn);
2516
2517     return;
2518   } else {
2519     BOOL needsPause = FALSE;
2520
2521     /* Loop through all args */
2522     WINE_TRACE("Parms supplied - working through each file\n");
2523     WCMD_enter_paged_mode(moreStrPage);
2524
2525     while (argN) {
2526       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2527       HANDLE h;
2528
2529       if (!argN) break;
2530
2531       if (needsPause) {
2532
2533         /* Wait */
2534         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2535         WCMD_leave_paged_mode();
2536         WCMD_output_asis(moreStrPage);
2537         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2538         WCMD_enter_paged_mode(moreStrPage);
2539       }
2540
2541
2542       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2543       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2544                 FILE_ATTRIBUTE_NORMAL, NULL);
2545       if (h == INVALID_HANDLE_VALUE) {
2546         WCMD_print_error ();
2547         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2548         errorlevel = 1;
2549       } else {
2550         ULONG64 curPos  = 0;
2551         ULONG64 fileLen = 0;
2552         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2553
2554         /* Get the file size */
2555         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2556         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2557
2558         needsPause = TRUE;
2559         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2560           if (count == 0) break;        /* ReadFile reports success on EOF! */
2561           buffer[count] = 0;
2562           curPos += count;
2563
2564           /* Update % count (would be used in WCMD_output_asis as prompt) */
2565           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2566
2567           WCMD_output_asis (buffer);
2568         }
2569         CloseHandle (h);
2570       }
2571     }
2572
2573     WCMD_leave_paged_mode();
2574   }
2575 }
2576
2577 /****************************************************************************
2578  * WCMD_verify
2579  *
2580  * Display verify flag.
2581  * FIXME: We don't actually do anything with the verify flag other than toggle
2582  * it...
2583  */
2584
2585 void WCMD_verify (const WCHAR *command) {
2586
2587   int count;
2588
2589   count = strlenW(command);
2590   if (count == 0) {
2591     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2592     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2593     return;
2594   }
2595   if (lstrcmpiW(command, onW) == 0) {
2596     verify_mode = TRUE;
2597     return;
2598   }
2599   else if (lstrcmpiW(command, offW) == 0) {
2600     verify_mode = FALSE;
2601     return;
2602   }
2603   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2604 }
2605
2606 /****************************************************************************
2607  * WCMD_version
2608  *
2609  * Display version info.
2610  */
2611
2612 void WCMD_version (void) {
2613
2614   WCMD_output (version_string);
2615
2616 }
2617
2618 /****************************************************************************
2619  * WCMD_volume
2620  *
2621  * Display volume information (set_label = FALSE)
2622  * Additionally set volume label (set_label = TRUE)
2623  * Returns 1 on success, 0 otherwise
2624  */
2625
2626 int WCMD_volume(BOOL set_label, const WCHAR *path)
2627 {
2628   DWORD count, serial;
2629   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2630   BOOL status;
2631
2632   if (strlenW(path) == 0) {
2633     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2634     if (!status) {
2635       WCMD_print_error ();
2636       return 0;
2637     }
2638     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2639                                    &serial, NULL, NULL, NULL, 0);
2640   }
2641   else {
2642     static const WCHAR fmt[] = {'%','s','\\','\0'};
2643     if ((path[1] != ':') || (strlenW(path) != 2)) {
2644       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2645       return 0;
2646     }
2647     wsprintfW (curdir, fmt, path);
2648     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2649                                    &serial, NULL,
2650         NULL, NULL, 0);
2651   }
2652   if (!status) {
2653     WCMD_print_error ();
2654     return 0;
2655   }
2656   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2657         curdir[0], label, HIWORD(serial), LOWORD(serial));
2658   if (set_label) {
2659     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2660     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2661     if (count > 1) {
2662       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2663       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2664     }
2665     if (strlenW(path) != 0) {
2666       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2667     }
2668     else {
2669       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2670     }
2671   }
2672   return 1;
2673 }
2674
2675 /**************************************************************************
2676  * WCMD_exit
2677  *
2678  * Exit either the process, or just this batch program
2679  *
2680  */
2681
2682 void WCMD_exit (CMD_LIST **cmdList) {
2683
2684     static const WCHAR parmB[] = {'/','B','\0'};
2685     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2686
2687     if (context && lstrcmpiW(quals, parmB) == 0) {
2688         errorlevel = rc;
2689         context -> skip_rest = TRUE;
2690         *cmdList = NULL;
2691     } else {
2692         ExitProcess(rc);
2693     }
2694 }
2695
2696
2697 /*****************************************************************************
2698  * WCMD_assoc
2699  *
2700  *      Lists or sets file associations  (assoc = TRUE)
2701  *      Lists or sets file types         (assoc = FALSE)
2702  */
2703 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2704
2705     HKEY    key;
2706     DWORD   accessOptions = KEY_READ;
2707     WCHAR   *newValue;
2708     LONG    rc = ERROR_SUCCESS;
2709     WCHAR    keyValue[MAXSTRING];
2710     DWORD   valueLen = MAXSTRING;
2711     HKEY    readKey;
2712     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2713                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2714
2715     /* See if parameter includes '=' */
2716     errorlevel = 0;
2717     newValue = strchrW(command, '=');
2718     if (newValue) accessOptions |= KEY_WRITE;
2719
2720     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2721     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2722                       accessOptions, &key) != ERROR_SUCCESS) {
2723       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2724       return;
2725     }
2726
2727     /* If no parameters then list all associations */
2728     if (*command == 0x00) {
2729       int index = 0;
2730
2731       /* Enumerate all the keys */
2732       while (rc != ERROR_NO_MORE_ITEMS) {
2733         WCHAR  keyName[MAXSTRING];
2734         DWORD nameLen;
2735
2736         /* Find the next value */
2737         nameLen = MAXSTRING;
2738         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2739
2740         if (rc == ERROR_SUCCESS) {
2741
2742           /* Only interested in extension ones if assoc, or others
2743              if not assoc                                          */
2744           if ((keyName[0] == '.' && assoc) ||
2745               (!(keyName[0] == '.') && (!assoc)))
2746           {
2747             WCHAR subkey[MAXSTRING];
2748             strcpyW(subkey, keyName);
2749             if (!assoc) strcatW(subkey, shOpCmdW);
2750
2751             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2752
2753               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2754               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2755               WCMD_output_asis(keyName);
2756               WCMD_output_asis(equalW);
2757               /* If no default value found, leave line empty after '=' */
2758               if (rc == ERROR_SUCCESS) {
2759                 WCMD_output_asis(keyValue);
2760               }
2761               WCMD_output_asis(newline);
2762               RegCloseKey(readKey);
2763             }
2764           }
2765         }
2766       }
2767
2768     } else {
2769
2770       /* Parameter supplied - if no '=' on command line, its a query */
2771       if (newValue == NULL) {
2772         WCHAR *space;
2773         WCHAR subkey[MAXSTRING];
2774
2775         /* Query terminates the parameter at the first space */
2776         strcpyW(keyValue, command);
2777         space = strchrW(keyValue, ' ');
2778         if (space) *space=0x00;
2779
2780         /* Set up key name */
2781         strcpyW(subkey, keyValue);
2782         if (!assoc) strcatW(subkey, shOpCmdW);
2783
2784         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2785
2786           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2787           WCMD_output_asis(command);
2788           WCMD_output_asis(equalW);
2789           /* If no default value found, leave line empty after '=' */
2790           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2791           WCMD_output_asis(newline);
2792           RegCloseKey(readKey);
2793
2794         } else {
2795           WCHAR  msgbuffer[MAXSTRING];
2796           WCHAR  outbuffer[MAXSTRING];
2797
2798           /* Load the translated 'File association not found' */
2799           if (assoc) {
2800             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2801           } else {
2802             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2803           }
2804           wsprintfW(outbuffer, msgbuffer, keyValue);
2805           WCMD_output_asis_stderr(outbuffer);
2806           errorlevel = 2;
2807         }
2808
2809       /* Not a query - its a set or clear of a value */
2810       } else {
2811
2812         WCHAR subkey[MAXSTRING];
2813
2814         /* Get pointer to new value */
2815         *newValue = 0x00;
2816         newValue++;
2817
2818         /* Set up key name */
2819         strcpyW(subkey, command);
2820         if (!assoc) strcatW(subkey, shOpCmdW);
2821
2822         /* If nothing after '=' then clear value - only valid for ASSOC */
2823         if (*newValue == 0x00) {
2824
2825           if (assoc) rc = RegDeleteKeyW(key, command);
2826           if (assoc && rc == ERROR_SUCCESS) {
2827             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2828
2829           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2830             WCMD_print_error();
2831             errorlevel = 2;
2832
2833           } else {
2834             WCHAR  msgbuffer[MAXSTRING];
2835             WCHAR  outbuffer[MAXSTRING];
2836
2837             /* Load the translated 'File association not found' */
2838             if (assoc) {
2839               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2840                           sizeof(msgbuffer)/sizeof(WCHAR));
2841             } else {
2842               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2843                           sizeof(msgbuffer)/sizeof(WCHAR));
2844             }
2845             wsprintfW(outbuffer, msgbuffer, keyValue);
2846             WCMD_output_asis_stderr(outbuffer);
2847             errorlevel = 2;
2848           }
2849
2850         /* It really is a set value = contents */
2851         } else {
2852           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2853                               accessOptions, NULL, &readKey, NULL);
2854           if (rc == ERROR_SUCCESS) {
2855             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2856                                 (LPBYTE)newValue,
2857                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
2858             RegCloseKey(readKey);
2859           }
2860
2861           if (rc != ERROR_SUCCESS) {
2862             WCMD_print_error();
2863             errorlevel = 2;
2864           } else {
2865             WCMD_output_asis(command);
2866             WCMD_output_asis(equalW);
2867             WCMD_output_asis(newValue);
2868             WCMD_output_asis(newline);
2869           }
2870         }
2871       }
2872     }
2873
2874     /* Clean up */
2875     RegCloseKey(key);
2876 }
2877
2878 /****************************************************************************
2879  * WCMD_color
2880  *
2881  * Colors the terminal screen.
2882  */
2883
2884 void WCMD_color (void) {
2885
2886   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2887   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2888
2889   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2890     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2891     return;
2892   }
2893
2894   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2895   {
2896       COORD topLeft;
2897       DWORD screenSize;
2898       DWORD color = 0;
2899
2900       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2901
2902       topLeft.X = 0;
2903       topLeft.Y = 0;
2904
2905       /* Convert the color hex digits */
2906       if (param1[0] == 0x00) {
2907         color = defaultColor;
2908       } else {
2909         color = strtoulW(param1, NULL, 16);
2910       }
2911
2912       /* Fail if fg == bg color */
2913       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2914         errorlevel = 1;
2915         return;
2916       }
2917
2918       /* Set the current screen contents and ensure all future writes
2919          remain this color                                             */
2920       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2921       SetConsoleTextAttribute(hStdOut, color);
2922   }
2923 }