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