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