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