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