cmd: Allow a batch file to delete itself.
[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, NULL);
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, NULL);
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]=='.'
868       || command[0]==':' || command[0]==';')
869     command++;
870
871   count = strlenW(command);
872   if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
873                  && origcommand[0]!=';') {
874     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
875     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
876     return;
877   }
878   if (lstrcmpiW(command, onW) == 0) {
879     echo_mode = 1;
880     return;
881   }
882   if (lstrcmpiW(command, offW) == 0) {
883     echo_mode = 0;
884     return;
885   }
886   WCMD_output_asis (command);
887   WCMD_output (newline);
888
889 }
890
891 /**************************************************************************
892  * WCMD_for
893  *
894  * Batch file loop processing.
895  *
896  * On entry: cmdList       contains the syntax up to the set
897  *           next cmdList and all in that bracket contain the set data
898  *           next cmdlist  contains the DO cmd
899  *           following that is either brackets or && entries (as per if)
900  *
901  */
902
903 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
904
905   WIN32_FIND_DATAW fd;
906   HANDLE hff;
907   int i;
908   static const WCHAR inW[] = {'i','n'};
909   static const WCHAR doW[] = {'d','o'};
910   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
911   WCHAR variable[4];
912   WCHAR *firstCmd;
913   int thisDepth;
914
915   WCHAR *curPos = p;
916   BOOL   expandDirs  = FALSE;
917   BOOL   useNumbers  = FALSE;
918   BOOL   doFileset   = FALSE;
919   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
920   int    itemNum;
921   CMD_LIST *thisCmdStart;
922
923
924   /* Handle optional qualifiers (multiple are allowed) */
925   while (*curPos && *curPos == '/') {
926       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
927       curPos++;
928       switch (toupperW(*curPos)) {
929       case 'D': curPos++; expandDirs = TRUE; break;
930       case 'L': curPos++; useNumbers = TRUE; break;
931
932       /* Recursive is special case - /R can have an optional path following it                */
933       /* filenamesets are another special case - /F can have an optional options following it */
934       case 'R':
935       case 'F':
936           {
937               BOOL isRecursive = (*curPos == 'R');
938
939               if (!isRecursive)
940                   doFileset = TRUE;
941
942               /* Skip whitespace */
943               curPos++;
944               while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
945
946               /* Next parm is either qualifier, path/options or variable -
947                  only care about it if it is the path/options              */
948               if (*curPos && *curPos != '/' && *curPos != '%') {
949                   if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
950                   else {
951                       static unsigned int once;
952                       if (!once++) WINE_FIXME("/F needs to handle options\n");
953                   }
954               }
955               break;
956           }
957       default:
958           WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
959           curPos++;
960       }
961
962       /* Skip whitespace between qualifiers */
963       while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
964   }
965
966   /* Skip whitespace before variable */
967   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
968
969   /* Ensure line continues with variable */
970   if (!*curPos || *curPos != '%') {
971       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
972       return;
973   }
974
975   /* Variable should follow */
976   i = 0;
977   while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
978   memcpy(&variable[0], curPos, i*sizeof(WCHAR));
979   variable[i] = 0x00;
980   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
981   curPos = &curPos[i];
982
983   /* Skip whitespace before IN */
984   while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
985
986   /* Ensure line continues with IN */
987   if (!*curPos
988        || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
989
990       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
991       return;
992   }
993
994   /* Save away where the set of data starts and the variable */
995   thisDepth = (*cmdList)->bracketDepth;
996   *cmdList = (*cmdList)->nextcommand;
997   setStart = (*cmdList);
998
999   /* Skip until the close bracket */
1000   WINE_TRACE("Searching %p as the set\n", *cmdList);
1001   while (*cmdList &&
1002          (*cmdList)->command != NULL &&
1003          (*cmdList)->bracketDepth > thisDepth) {
1004     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1005     *cmdList = (*cmdList)->nextcommand;
1006   }
1007
1008   /* Skip the close bracket, if there is one */
1009   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1010
1011   /* Syntax error if missing close bracket, or nothing following it
1012      and once we have the complete set, we expect a DO              */
1013   WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1014   if ((*cmdList == NULL)
1015       || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1016
1017       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1018       return;
1019   }
1020
1021   /* Save away the starting position for the commands (and offset for the
1022      first one                                                           */
1023   cmdStart = *cmdList;
1024   cmdEnd   = *cmdList;
1025   firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1026   itemNum  = 0;
1027
1028   thisSet = setStart;
1029   /* Loop through all set entries */
1030   while (thisSet &&
1031          thisSet->command != NULL &&
1032          thisSet->bracketDepth >= thisDepth) {
1033
1034     /* Loop through all entries on the same line */
1035     WCHAR *item;
1036     WCHAR *itemStart;
1037
1038     WINE_TRACE("Processing for set %p\n", thisSet);
1039     i = 0;
1040     while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1041
1042       /*
1043        * If the parameter within the set has a wildcard then search for matching files
1044        * otherwise do a literal substitution.
1045        */
1046       static const WCHAR wildcards[] = {'*','?','\0'};
1047       thisCmdStart = cmdStart;
1048
1049       itemNum++;
1050       WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1051
1052       if (!useNumbers && !doFileset) {
1053           if (strpbrkW (item, wildcards)) {
1054             hff = FindFirstFileW(item, &fd);
1055             if (hff != INVALID_HANDLE_VALUE) {
1056               do {
1057                 BOOL isDirectory = FALSE;
1058
1059                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1060
1061                 /* Handle as files or dirs appropriately, but ignore . and .. */
1062                 if (isDirectory == expandDirs &&
1063                     (strcmpW(fd.cFileName, dotdotW) != 0) &&
1064                     (strcmpW(fd.cFileName, dotW) != 0))
1065                 {
1066                   thisCmdStart = cmdStart;
1067                   WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1068                   WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1069                                                fd.cFileName, FALSE, TRUE);
1070                 }
1071
1072               } while (FindNextFileW(hff, &fd) != 0);
1073               FindClose (hff);
1074             }
1075           } else {
1076             WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1077           }
1078
1079       } else if (useNumbers) {
1080           /* Convert the first 3 numbers to signed longs and save */
1081           if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1082           /* else ignore them! */
1083
1084       /* Filesets - either a list of files, or a command to run and parse the output */
1085       } else if (doFileset && *itemStart != '"') {
1086
1087           HANDLE input;
1088           WCHAR temp_file[MAX_PATH];
1089
1090           WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1091                      wine_dbgstr_w(item));
1092
1093           /* If backquote or single quote, we need to launch that command
1094              and parse the results - use a temporary file                 */
1095           if (*itemStart == '`' || *itemStart == '\'') {
1096
1097               WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1098               static const WCHAR redirOut[] = {'>','%','s','\0'};
1099               static const WCHAR cmdW[]     = {'C','M','D','\0'};
1100
1101               /* Remove trailing character */
1102               itemStart[strlenW(itemStart)-1] = 0x00;
1103
1104               /* Get temp filename */
1105               GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1106               GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1107
1108               /* Execute program and redirect output */
1109               wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1110               WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1111
1112               /* Open the file, read line by line and process */
1113               input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1114                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1115           } else {
1116
1117               /* Open the file, read line by line and process */
1118               input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1119                                   NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1120           }
1121
1122           /* Process the input file */
1123           if (input == INVALID_HANDLE_VALUE) {
1124             WCMD_print_error ();
1125             WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1126             errorlevel = 1;
1127             return; /* FOR loop aborts at first failure here */
1128
1129           } else {
1130
1131             WCHAR buffer[MAXSTRING] = {'\0'};
1132             WCHAR *where, *parm;
1133
1134             while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1135
1136               /* Skip blank lines*/
1137               parm = WCMD_parameter (buffer, 0, &where, NULL);
1138               WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1139                          wine_dbgstr_w(buffer));
1140
1141               if (where) {
1142                   /* FIXME: The following should be moved into its own routine and
1143                      reused for the string literal parsing below                  */
1144                   thisCmdStart = cmdStart;
1145                   WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1146                   cmdEnd = thisCmdStart;
1147               }
1148
1149               buffer[0] = 0x00;
1150
1151             }
1152             CloseHandle (input);
1153           }
1154
1155           /* Delete the temporary file */
1156           if (*itemStart == '`' || *itemStart == '\'') {
1157               DeleteFileW(temp_file);
1158           }
1159
1160       /* Filesets - A string literal */
1161       } else if (doFileset && *itemStart == '"') {
1162           WCHAR buffer[MAXSTRING] = {'\0'};
1163           WCHAR *where, *parm;
1164
1165           /* Skip blank lines, and re-extract parameter now string has quotes removed */
1166           strcpyW(buffer, item);
1167           parm = WCMD_parameter (buffer, 0, &where, NULL);
1168           WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1169                        wine_dbgstr_w(buffer));
1170
1171           if (where) {
1172               /* FIXME: The following should be moved into its own routine and
1173                  reused for the string literal parsing below                  */
1174               thisCmdStart = cmdStart;
1175               WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1176               cmdEnd = thisCmdStart;
1177           }
1178       }
1179
1180       WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1181       cmdEnd = thisCmdStart;
1182       i++;
1183     }
1184
1185     /* Move onto the next set line */
1186     thisSet = thisSet->nextcommand;
1187   }
1188
1189   /* If /L is provided, now run the for loop */
1190   if (useNumbers) {
1191       WCHAR thisNum[20];
1192       static const WCHAR fmt[] = {'%','d','\0'};
1193
1194       WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1195                  numbers[0], numbers[2], numbers[1]);
1196       for (i=numbers[0];
1197            (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1198            i=i + numbers[1]) {
1199
1200           sprintfW(thisNum, fmt, i);
1201           WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1202
1203           thisCmdStart = cmdStart;
1204           WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1205           cmdEnd = thisCmdStart;
1206       }
1207   }
1208
1209   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1210      all processing, OR it should be pointing to the end of && processing OR
1211      it should be pointing at the NULL end of bracket for the DO. The return
1212      value needs to be the NEXT command to execute, which it either is, or
1213      we need to step over the closing bracket                                  */
1214   *cmdList = cmdEnd;
1215   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1216 }
1217
1218
1219 /*****************************************************************************
1220  * WCMD_part_execute
1221  *
1222  * Execute a command, and any && or bracketed follow on to the command. The
1223  * first command to be executed may not be at the front of the
1224  * commands->thiscommand string (eg. it may point after a DO or ELSE)
1225  */
1226 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1227                               const WCHAR *variable, const WCHAR *value,
1228                               BOOL isIF, BOOL conditionTRUE) {
1229
1230   CMD_LIST *curPosition = *cmdList;
1231   int myDepth = (*cmdList)->bracketDepth;
1232
1233   WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1234              cmdList, wine_dbgstr_w(firstcmd),
1235              wine_dbgstr_w(variable), wine_dbgstr_w(value),
1236              conditionTRUE);
1237
1238   /* Skip leading whitespace between condition and the command */
1239   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1240
1241   /* Process the first command, if there is one */
1242   if (conditionTRUE && firstcmd && *firstcmd) {
1243     WCHAR *command = WCMD_strdupW(firstcmd);
1244     WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1245     HeapFree(GetProcessHeap(), 0, command);
1246   }
1247
1248
1249   /* If it didn't move the position, step to next command */
1250   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1251
1252   /* Process any other parts of the command */
1253   if (*cmdList) {
1254     BOOL processThese = TRUE;
1255
1256     if (isIF) processThese = conditionTRUE;
1257
1258     while (*cmdList) {
1259       static const WCHAR ifElse[] = {'e','l','s','e'};
1260
1261       /* execute all appropriate commands */
1262       curPosition = *cmdList;
1263
1264       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1265                  *cmdList,
1266                  (*cmdList)->prevDelim,
1267                  (*cmdList)->bracketDepth, myDepth);
1268
1269       /* Execute any statements appended to the line */
1270       /* FIXME: Only if previous call worked for && or failed for || */
1271       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1272           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1273         if (processThese && (*cmdList)->command) {
1274           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1275                         value, cmdList);
1276         }
1277         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1278
1279       /* Execute any appended to the statement with (...) */
1280       } else if ((*cmdList)->bracketDepth > myDepth) {
1281         if (processThese) {
1282           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1283           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1284         }
1285         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1286
1287       /* End of the command - does 'ELSE ' follow as the next command? */
1288       } else {
1289         if (isIF
1290             && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1291                                      (*cmdList)->command)) {
1292
1293           /* Swap between if and else processing */
1294           processThese = !processThese;
1295
1296           /* Process the ELSE part */
1297           if (processThese) {
1298             const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1299             WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1300
1301             /* Skip leading whitespace between condition and the command */
1302             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1303             if (*cmd) {
1304               WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1305             }
1306           }
1307           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1308         } else {
1309           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1310           break;
1311         }
1312       }
1313     }
1314   }
1315   return;
1316 }
1317
1318 /**************************************************************************
1319  * WCMD_give_help
1320  *
1321  *      Simple on-line help. Help text is stored in the resource file.
1322  */
1323
1324 void WCMD_give_help (const WCHAR *command) {
1325
1326   int i;
1327
1328   command = WCMD_skip_leading_spaces((WCHAR*) command);
1329   if (strlenW(command) == 0) {
1330     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1331   }
1332   else {
1333     /* Display help message for builtin commands */
1334     for (i=0; i<=WCMD_EXIT; i++) {
1335       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1336           command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1337         WCMD_output_asis (WCMD_LoadMessage(i));
1338         return;
1339       }
1340     }
1341     /* Launch the command with the /? option for external commands shipped with cmd.exe */
1342     for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1343       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1344           command, -1, externals[i], -1) == CSTR_EQUAL) {
1345         WCHAR cmd[128];
1346         static const WCHAR helpW[] = {' ', '/','?','\0'};
1347         strcpyW(cmd, command);
1348         strcatW(cmd, helpW);
1349         WCMD_run_program(cmd, 0);
1350         return;
1351       }
1352     }
1353     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1354   }
1355   return;
1356 }
1357
1358 /****************************************************************************
1359  * WCMD_go_to
1360  *
1361  * Batch file jump instruction. Not the most efficient algorithm ;-)
1362  * Prints error message if the specified label cannot be found - the file pointer is
1363  * then at EOF, effectively stopping the batch file.
1364  * FIXME: DOS is supposed to allow labels with spaces - we don't.
1365  */
1366
1367 void WCMD_goto (CMD_LIST **cmdList) {
1368
1369   WCHAR string[MAX_PATH];
1370   WCHAR current[MAX_PATH];
1371
1372   /* Do not process any more parts of a processed multipart or multilines command */
1373   if (cmdList) *cmdList = NULL;
1374
1375   if (context != NULL) {
1376     WCHAR *paramStart = param1, *str;
1377     static const WCHAR eofW[] = {':','e','o','f','\0'};
1378
1379     if (param1[0] == 0x00) {
1380       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1381       return;
1382     }
1383
1384     /* Handle special :EOF label */
1385     if (lstrcmpiW (eofW, param1) == 0) {
1386       context -> skip_rest = TRUE;
1387       return;
1388     }
1389
1390     /* Support goto :label as well as goto label */
1391     if (*paramStart == ':') paramStart++;
1392
1393     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1394     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1395       str = string;
1396       while (isspaceW (*str)) str++;
1397       if (*str == ':') {
1398         DWORD index = 0;
1399         str++;
1400         while (((current[index] = str[index])) && (!isspaceW (current[index])))
1401             index++;
1402
1403         /* ignore space at the end */
1404         current[index] = 0;
1405         if (lstrcmpiW (current, paramStart) == 0) return;
1406       }
1407     }
1408     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1409   }
1410   return;
1411 }
1412
1413 /*****************************************************************************
1414  * WCMD_pushd
1415  *
1416  *      Push a directory onto the stack
1417  */
1418
1419 void WCMD_pushd (WCHAR *command) {
1420     struct env_stack *curdir;
1421     WCHAR *thisdir;
1422     static const WCHAR parmD[] = {'/','D','\0'};
1423
1424     if (strchrW(command, '/') != NULL) {
1425       SetLastError(ERROR_INVALID_PARAMETER);
1426       WCMD_print_error();
1427       return;
1428     }
1429
1430     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1431     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1432     if( !curdir || !thisdir ) {
1433       LocalFree(curdir);
1434       LocalFree(thisdir);
1435       WINE_ERR ("out of memory\n");
1436       return;
1437     }
1438
1439     /* Change directory using CD code with /D parameter */
1440     strcpyW(quals, parmD);
1441     GetCurrentDirectoryW (1024, thisdir);
1442     errorlevel = 0;
1443     WCMD_setshow_default(command);
1444     if (errorlevel) {
1445       LocalFree(curdir);
1446       LocalFree(thisdir);
1447       return;
1448     } else {
1449       curdir -> next    = pushd_directories;
1450       curdir -> strings = thisdir;
1451       if (pushd_directories == NULL) {
1452         curdir -> u.stackdepth = 1;
1453       } else {
1454         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1455       }
1456       pushd_directories = curdir;
1457     }
1458 }
1459
1460
1461 /*****************************************************************************
1462  * WCMD_popd
1463  *
1464  *      Pop a directory from the stack
1465  */
1466
1467 void WCMD_popd (void) {
1468     struct env_stack *temp = pushd_directories;
1469
1470     if (!pushd_directories)
1471       return;
1472
1473     /* pop the old environment from the stack, and make it the current dir */
1474     pushd_directories = temp->next;
1475     SetCurrentDirectoryW(temp->strings);
1476     LocalFree (temp->strings);
1477     LocalFree (temp);
1478 }
1479
1480 /****************************************************************************
1481  * WCMD_if
1482  *
1483  * Batch file conditional.
1484  *
1485  * On entry, cmdlist will point to command containing the IF, and optionally
1486  *   the first command to execute (if brackets not found)
1487  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1488  *   If ('s were found, execute all within that bracket
1489  *   Command may optionally be followed by an ELSE - need to skip instructions
1490  *   in the else using the same logic
1491  *
1492  * FIXME: Much more syntax checking needed!
1493  */
1494
1495 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1496
1497   int negate; /* Negate condition */
1498   int test;   /* Condition evaluation result */
1499   WCHAR condition[MAX_PATH], *command, *s;
1500   static const WCHAR notW[]    = {'n','o','t','\0'};
1501   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1502   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1503   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1504   static const WCHAR eqeqW[]   = {'=','=','\0'};
1505   static const WCHAR parmI[]   = {'/','I','\0'};
1506   int caseInsensitive = (strstrW(quals, parmI) != NULL);
1507
1508   negate = !lstrcmpiW(param1,notW);
1509   strcpyW(condition, (negate ? param2 : param1));
1510   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1511
1512   if (!lstrcmpiW (condition, errlvlW)) {
1513     test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1514     WCMD_parameter(p, 2+negate, &command, NULL);
1515   }
1516   else if (!lstrcmpiW (condition, existW)) {
1517     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1518     WCMD_parameter(p, 2+negate, &command, NULL);
1519   }
1520   else if (!lstrcmpiW (condition, defdW)) {
1521     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1522     WCMD_parameter(p, 2+negate, &command, NULL);
1523   }
1524   else if ((s = strstrW (p, eqeqW))) {
1525     /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1526     WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1527     s += 2;
1528     WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1529     WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1530     test = caseInsensitive
1531             ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1532                               leftPart, leftPartEnd-leftPart+1,
1533                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1534             : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1535                               leftPart, leftPartEnd-leftPart+1,
1536                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1537     WCMD_parameter(s, 1, &command, NULL);
1538   }
1539   else {
1540     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1541     return;
1542   }
1543
1544   /* Process rest of IF statement which is on the same line
1545      Note: This may process all or some of the cmdList (eg a GOTO) */
1546   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1547 }
1548
1549 /****************************************************************************
1550  * WCMD_move
1551  *
1552  * Move a file, directory tree or wildcarded set of files.
1553  */
1554
1555 void WCMD_move (void) {
1556
1557   int             status;
1558   WIN32_FIND_DATAW fd;
1559   HANDLE          hff;
1560   WCHAR            input[MAX_PATH];
1561   WCHAR            output[MAX_PATH];
1562   WCHAR            drive[10];
1563   WCHAR            dir[MAX_PATH];
1564   WCHAR            fname[MAX_PATH];
1565   WCHAR            ext[MAX_PATH];
1566
1567   if (param1[0] == 0x00) {
1568     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1569     return;
1570   }
1571
1572   /* If no destination supplied, assume current directory */
1573   if (param2[0] == 0x00) {
1574       strcpyW(param2, dotW);
1575   }
1576
1577   /* If 2nd parm is directory, then use original filename */
1578   /* Convert partial path to full path */
1579   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1580   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1581   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1582              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1583
1584   /* Split into components */
1585   WCMD_splitpath(input, drive, dir, fname, ext);
1586
1587   hff = FindFirstFileW(input, &fd);
1588   while (hff != INVALID_HANDLE_VALUE) {
1589     WCHAR  dest[MAX_PATH];
1590     WCHAR  src[MAX_PATH];
1591     DWORD attribs;
1592
1593     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1594
1595     /* Build src & dest name */
1596     strcpyW(src, drive);
1597     strcatW(src, dir);
1598
1599     /* See if dest is an existing directory */
1600     attribs = GetFileAttributesW(output);
1601     if (attribs != INVALID_FILE_ATTRIBUTES &&
1602        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1603       strcpyW(dest, output);
1604       strcatW(dest, slashW);
1605       strcatW(dest, fd.cFileName);
1606     } else {
1607       strcpyW(dest, output);
1608     }
1609
1610     strcatW(src, fd.cFileName);
1611
1612     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1613     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1614
1615     /* Check if file is read only, otherwise move it */
1616     attribs = GetFileAttributesW(src);
1617     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1618         (attribs & FILE_ATTRIBUTE_READONLY)) {
1619       SetLastError(ERROR_ACCESS_DENIED);
1620       status = 0;
1621     } else {
1622       BOOL ok = TRUE;
1623
1624       /* If destination exists, prompt unless /Y supplied */
1625       if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1626         BOOL force = FALSE;
1627         WCHAR copycmd[MAXSTRING];
1628         int len;
1629
1630         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1631         if (strstrW (quals, parmNoY))
1632           force = FALSE;
1633         else if (strstrW (quals, parmY))
1634           force = TRUE;
1635         else {
1636           static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1637           len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1638           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1639                        && ! lstrcmpiW (copycmd, parmY));
1640         }
1641
1642         /* Prompt if overwriting */
1643         if (!force) {
1644           WCHAR  question[MAXSTRING];
1645           WCHAR  yesChar[10];
1646
1647           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1648
1649           /* Ask for confirmation */
1650           wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1651           ok = WCMD_ask_confirm(question, FALSE, NULL);
1652
1653           /* So delete the destination prior to the move */
1654           if (ok) {
1655             if (!DeleteFileW(dest)) {
1656               WCMD_print_error ();
1657               errorlevel = 1;
1658               ok = FALSE;
1659             }
1660           }
1661         }
1662       }
1663
1664       if (ok) {
1665         status = MoveFileW(src, dest);
1666       } else {
1667         status = 1; /* Anything other than 0 to prevent error msg below */
1668       }
1669     }
1670
1671     if (!status) {
1672       WCMD_print_error ();
1673       errorlevel = 1;
1674     }
1675
1676     /* Step on to next match */
1677     if (FindNextFileW(hff, &fd) == 0) {
1678       FindClose(hff);
1679       hff = INVALID_HANDLE_VALUE;
1680       break;
1681     }
1682   }
1683 }
1684
1685 /****************************************************************************
1686  * WCMD_pause
1687  *
1688  * Wait for keyboard input.
1689  */
1690
1691 void WCMD_pause (void) {
1692
1693   DWORD count;
1694   WCHAR string[32];
1695
1696   WCMD_output (anykey);
1697   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1698                  sizeof(string)/sizeof(WCHAR), &count, NULL);
1699 }
1700
1701 /****************************************************************************
1702  * WCMD_remove_dir
1703  *
1704  * Delete a directory.
1705  */
1706
1707 void WCMD_remove_dir (WCHAR *command) {
1708
1709   int   argno         = 0;
1710   int   argsProcessed = 0;
1711   WCHAR *argN          = command;
1712   static const WCHAR parmS[] = {'/','S','\0'};
1713   static const WCHAR parmQ[] = {'/','Q','\0'};
1714
1715   /* Loop through all args */
1716   while (argN) {
1717     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1718     if (argN && argN[0] != '/') {
1719       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1720                  wine_dbgstr_w(quals));
1721       argsProcessed++;
1722
1723       /* If subdirectory search not supplied, just try to remove
1724          and report error if it fails (eg if it contains a file) */
1725       if (strstrW (quals, parmS) == NULL) {
1726         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1727
1728       /* Otherwise use ShFileOp to recursively remove a directory */
1729       } else {
1730
1731         SHFILEOPSTRUCTW lpDir;
1732
1733         /* Ask first */
1734         if (strstrW (quals, parmQ) == NULL) {
1735           BOOL  ok;
1736           WCHAR  question[MAXSTRING];
1737           static const WCHAR fmt[] = {'%','s',' ','\0'};
1738
1739           /* Ask for confirmation */
1740           wsprintfW(question, fmt, thisArg);
1741           ok = WCMD_ask_confirm(question, TRUE, NULL);
1742
1743           /* Abort if answer is 'N' */
1744           if (!ok) return;
1745         }
1746
1747         /* Do the delete */
1748         lpDir.hwnd   = NULL;
1749         lpDir.pTo    = NULL;
1750         lpDir.pFrom  = thisArg;
1751         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1752         lpDir.wFunc  = FO_DELETE;
1753         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1754       }
1755     }
1756   }
1757
1758   /* Handle no valid args */
1759   if (argsProcessed == 0) {
1760     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1761     return;
1762   }
1763
1764 }
1765
1766 /****************************************************************************
1767  * WCMD_rename
1768  *
1769  * Rename a file.
1770  */
1771
1772 void WCMD_rename (void) {
1773
1774   int             status;
1775   HANDLE          hff;
1776   WIN32_FIND_DATAW fd;
1777   WCHAR            input[MAX_PATH];
1778   WCHAR           *dotDst = NULL;
1779   WCHAR            drive[10];
1780   WCHAR            dir[MAX_PATH];
1781   WCHAR            fname[MAX_PATH];
1782   WCHAR            ext[MAX_PATH];
1783   DWORD           attribs;
1784
1785   errorlevel = 0;
1786
1787   /* Must be at least two args */
1788   if (param1[0] == 0x00 || param2[0] == 0x00) {
1789     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1790     errorlevel = 1;
1791     return;
1792   }
1793
1794   /* Destination cannot contain a drive letter or directory separator */
1795   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1796       SetLastError(ERROR_INVALID_PARAMETER);
1797       WCMD_print_error();
1798       errorlevel = 1;
1799       return;
1800   }
1801
1802   /* Convert partial path to full path */
1803   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1804   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1805              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1806   dotDst = strchrW(param2, '.');
1807
1808   /* Split into components */
1809   WCMD_splitpath(input, drive, dir, fname, ext);
1810
1811   hff = FindFirstFileW(input, &fd);
1812   while (hff != INVALID_HANDLE_VALUE) {
1813     WCHAR  dest[MAX_PATH];
1814     WCHAR  src[MAX_PATH];
1815     WCHAR *dotSrc = NULL;
1816     int   dirLen;
1817
1818     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1819
1820     /* FIXME: If dest name or extension is *, replace with filename/ext
1821        part otherwise use supplied name. This supports:
1822           ren *.fred *.jim
1823           ren jim.* fred.* etc
1824        However, windows has a more complex algorithm supporting eg
1825           ?'s and *'s mid name                                         */
1826     dotSrc = strchrW(fd.cFileName, '.');
1827
1828     /* Build src & dest name */
1829     strcpyW(src, drive);
1830     strcatW(src, dir);
1831     strcpyW(dest, src);
1832     dirLen = strlenW(src);
1833     strcatW(src, fd.cFileName);
1834
1835     /* Build name */
1836     if (param2[0] == '*') {
1837       strcatW(dest, fd.cFileName);
1838       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1839     } else {
1840       strcatW(dest, param2);
1841       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1842     }
1843
1844     /* Build Extension */
1845     if (dotDst && (*(dotDst+1)=='*')) {
1846       if (dotSrc) strcatW(dest, dotSrc);
1847     } else if (dotDst) {
1848       if (dotDst) strcatW(dest, dotDst);
1849     }
1850
1851     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1852     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1853
1854     /* Check if file is read only, otherwise move it */
1855     attribs = GetFileAttributesW(src);
1856     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1857         (attribs & FILE_ATTRIBUTE_READONLY)) {
1858       SetLastError(ERROR_ACCESS_DENIED);
1859       status = 0;
1860     } else {
1861       status = MoveFileW(src, dest);
1862     }
1863
1864     if (!status) {
1865       WCMD_print_error ();
1866       errorlevel = 1;
1867     }
1868
1869     /* Step on to next match */
1870     if (FindNextFileW(hff, &fd) == 0) {
1871       FindClose(hff);
1872       hff = INVALID_HANDLE_VALUE;
1873       break;
1874     }
1875   }
1876 }
1877
1878 /*****************************************************************************
1879  * WCMD_dupenv
1880  *
1881  * Make a copy of the environment.
1882  */
1883 static WCHAR *WCMD_dupenv( const WCHAR *env )
1884 {
1885   WCHAR *env_copy;
1886   int len;
1887
1888   if( !env )
1889     return NULL;
1890
1891   len = 0;
1892   while ( env[len] )
1893     len += (strlenW(&env[len]) + 1);
1894
1895   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1896   if (!env_copy)
1897   {
1898     WINE_ERR("out of memory\n");
1899     return env_copy;
1900   }
1901   memcpy (env_copy, env, len*sizeof (WCHAR));
1902   env_copy[len] = 0;
1903
1904   return env_copy;
1905 }
1906
1907 /*****************************************************************************
1908  * WCMD_setlocal
1909  *
1910  *  setlocal pushes the environment onto a stack
1911  *  Save the environment as unicode so we don't screw anything up.
1912  */
1913 void WCMD_setlocal (const WCHAR *s) {
1914   WCHAR *env;
1915   struct env_stack *env_copy;
1916   WCHAR cwd[MAX_PATH];
1917
1918   /* DISABLEEXTENSIONS ignored */
1919
1920   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1921   if( !env_copy )
1922   {
1923     WINE_ERR ("out of memory\n");
1924     return;
1925   }
1926
1927   env = GetEnvironmentStringsW ();
1928
1929   env_copy->strings = WCMD_dupenv (env);
1930   if (env_copy->strings)
1931   {
1932     env_copy->next = saved_environment;
1933     saved_environment = env_copy;
1934
1935     /* Save the current drive letter */
1936     GetCurrentDirectoryW(MAX_PATH, cwd);
1937     env_copy->u.cwd = cwd[0];
1938   }
1939   else
1940     LocalFree (env_copy);
1941
1942   FreeEnvironmentStringsW (env);
1943
1944 }
1945
1946 /*****************************************************************************
1947  * WCMD_endlocal
1948  *
1949  *  endlocal pops the environment off a stack
1950  *  Note: When searching for '=', search from WCHAR position 1, to handle
1951  *        special internal environment variables =C:, =D: etc
1952  */
1953 void WCMD_endlocal (void) {
1954   WCHAR *env, *old, *p;
1955   struct env_stack *temp;
1956   int len, n;
1957
1958   if (!saved_environment)
1959     return;
1960
1961   /* pop the old environment from the stack */
1962   temp = saved_environment;
1963   saved_environment = temp->next;
1964
1965   /* delete the current environment, totally */
1966   env = GetEnvironmentStringsW ();
1967   old = WCMD_dupenv (GetEnvironmentStringsW ());
1968   len = 0;
1969   while (old[len]) {
1970     n = strlenW(&old[len]) + 1;
1971     p = strchrW(&old[len] + 1, '=');
1972     if (p)
1973     {
1974       *p++ = 0;
1975       SetEnvironmentVariableW (&old[len], NULL);
1976     }
1977     len += n;
1978   }
1979   LocalFree (old);
1980   FreeEnvironmentStringsW (env);
1981
1982   /* restore old environment */
1983   env = temp->strings;
1984   len = 0;
1985   while (env[len]) {
1986     n = strlenW(&env[len]) + 1;
1987     p = strchrW(&env[len] + 1, '=');
1988     if (p)
1989     {
1990       *p++ = 0;
1991       SetEnvironmentVariableW (&env[len], p);
1992     }
1993     len += n;
1994   }
1995
1996   /* Restore current drive letter */
1997   if (IsCharAlphaW(temp->u.cwd)) {
1998     WCHAR envvar[4];
1999     WCHAR cwd[MAX_PATH];
2000     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2001
2002     wsprintfW(envvar, fmt, temp->u.cwd);
2003     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2004       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2005       SetCurrentDirectoryW(cwd);
2006     }
2007   }
2008
2009   LocalFree (env);
2010   LocalFree (temp);
2011 }
2012
2013 /*****************************************************************************
2014  * WCMD_setshow_default
2015  *
2016  *      Set/Show the current default directory
2017  */
2018
2019 void WCMD_setshow_default (const WCHAR *command) {
2020
2021   BOOL status;
2022   WCHAR string[1024];
2023   WCHAR cwd[1024];
2024   WCHAR *pos;
2025   WIN32_FIND_DATAW fd;
2026   HANDLE hff;
2027   static const WCHAR parmD[] = {'/','D','\0'};
2028
2029   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2030
2031   /* Skip /D and trailing whitespace if on the front of the command line */
2032   if (CompareStringW(LOCALE_USER_DEFAULT,
2033                      NORM_IGNORECASE | SORT_STRINGSORT,
2034                      command, 2, parmD, -1) == CSTR_EQUAL) {
2035     command += 2;
2036     while (*command && (*command==' ' || *command=='\t'))
2037       command++;
2038   }
2039
2040   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2041   if (strlenW(command) == 0) {
2042     strcatW (cwd, newline);
2043     WCMD_output (cwd);
2044   }
2045   else {
2046     /* Remove any double quotes, which may be in the
2047        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2048     pos = string;
2049     while (*command) {
2050       if (*command != '"') *pos++ = *command;
2051       command++;
2052     }
2053     while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2054       pos--;
2055     *pos = 0x00;
2056
2057     /* Search for appropriate directory */
2058     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2059     hff = FindFirstFileW(string, &fd);
2060     while (hff != INVALID_HANDLE_VALUE) {
2061       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2062         WCHAR fpath[MAX_PATH];
2063         WCHAR drive[10];
2064         WCHAR dir[MAX_PATH];
2065         WCHAR fname[MAX_PATH];
2066         WCHAR ext[MAX_PATH];
2067         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2068
2069         /* Convert path into actual directory spec */
2070         GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2071         WCMD_splitpath(fpath, drive, dir, fname, ext);
2072
2073         /* Rebuild path */
2074         wsprintfW(string, fmt, drive, dir, fd.cFileName);
2075
2076         FindClose(hff);
2077         hff = INVALID_HANDLE_VALUE;
2078         break;
2079       }
2080
2081       /* Step on to next match */
2082       if (FindNextFileW(hff, &fd) == 0) {
2083         FindClose(hff);
2084         hff = INVALID_HANDLE_VALUE;
2085         break;
2086       }
2087     }
2088
2089     /* Change to that directory */
2090     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2091
2092     status = SetCurrentDirectoryW(string);
2093     if (!status) {
2094       errorlevel = 1;
2095       WCMD_print_error ();
2096       return;
2097     } else {
2098
2099       /* Save away the actual new directory, to store as current location */
2100       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2101
2102       /* Restore old directory if drive letter would change, and
2103            CD x:\directory /D (or pushd c:\directory) not supplied */
2104       if ((strstrW(quals, parmD) == NULL) &&
2105           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2106         SetCurrentDirectoryW(cwd);
2107       }
2108     }
2109
2110     /* Set special =C: type environment variable, for drive letter of
2111        change of directory, even if path was restored due to missing
2112        /D (allows changing drive letter when not resident on that
2113        drive                                                          */
2114     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2115       WCHAR env[4];
2116       strcpyW(env, equalW);
2117       memcpy(env+1, string, 2 * sizeof(WCHAR));
2118       env[3] = 0x00;
2119       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2120       SetEnvironmentVariableW(env, string);
2121     }
2122
2123    }
2124   return;
2125 }
2126
2127 /****************************************************************************
2128  * WCMD_setshow_date
2129  *
2130  * Set/Show the system date
2131  * FIXME: Can't change date yet
2132  */
2133
2134 void WCMD_setshow_date (void) {
2135
2136   WCHAR curdate[64], buffer[64];
2137   DWORD count;
2138   static const WCHAR parmT[] = {'/','T','\0'};
2139
2140   if (strlenW(param1) == 0) {
2141     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2142                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2143       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2144       if (strstrW (quals, parmT) == NULL) {
2145         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2146         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2147                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2148         if (count > 2) {
2149           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2150         }
2151       }
2152     }
2153     else WCMD_print_error ();
2154   }
2155   else {
2156     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2157   }
2158 }
2159
2160 /****************************************************************************
2161  * WCMD_compare
2162  */
2163 static int WCMD_compare( const void *a, const void *b )
2164 {
2165     int r;
2166     const WCHAR * const *str_a = a, * const *str_b = b;
2167     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2168           *str_a, -1, *str_b, -1 );
2169     if( r == CSTR_LESS_THAN ) return -1;
2170     if( r == CSTR_GREATER_THAN ) return 1;
2171     return 0;
2172 }
2173
2174 /****************************************************************************
2175  * WCMD_setshow_sortenv
2176  *
2177  * sort variables into order for display
2178  * Optionally only display those who start with a stub
2179  * returns the count displayed
2180  */
2181 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2182 {
2183   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2184   const WCHAR **str;
2185
2186   if (stub) stublen = strlenW(stub);
2187
2188   /* count the number of strings, and the total length */
2189   while ( s[len] ) {
2190     len += (strlenW(&s[len]) + 1);
2191     count++;
2192   }
2193
2194   /* add the strings to an array */
2195   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2196   if( !str )
2197     return 0;
2198   str[0] = s;
2199   for( i=1; i<count; i++ )
2200     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2201
2202   /* sort the array */
2203   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2204
2205   /* print it */
2206   for( i=0; i<count; i++ ) {
2207     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2208                                 NORM_IGNORECASE | SORT_STRINGSORT,
2209                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2210       /* Don't display special internal variables */
2211       if (str[i][0] != '=') {
2212         WCMD_output_asis(str[i]);
2213         WCMD_output_asis(newline);
2214         displayedcount++;
2215       }
2216     }
2217   }
2218
2219   LocalFree( str );
2220   return displayedcount;
2221 }
2222
2223 /****************************************************************************
2224  * WCMD_setshow_env
2225  *
2226  * Set/Show the environment variables
2227  */
2228
2229 void WCMD_setshow_env (WCHAR *s) {
2230
2231   LPVOID env;
2232   WCHAR *p;
2233   int status;
2234   static const WCHAR parmP[] = {'/','P','\0'};
2235
2236   if (param1[0] == 0x00 && quals[0] == 0x00) {
2237     env = GetEnvironmentStringsW();
2238     WCMD_setshow_sortenv( env, NULL );
2239     return;
2240   }
2241
2242   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2243   if (CompareStringW(LOCALE_USER_DEFAULT,
2244                      NORM_IGNORECASE | SORT_STRINGSORT,
2245                      s, 2, parmP, -1) == CSTR_EQUAL) {
2246     WCHAR string[MAXSTRING];
2247     DWORD count;
2248
2249     s += 2;
2250     while (*s && (*s==' ' || *s=='\t')) s++;
2251     if (*s=='\"')
2252         WCMD_opt_s_strip_quotes(s);
2253
2254     /* If no parameter, or no '=' sign, return an error */
2255     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2256       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2257       return;
2258     }
2259
2260     /* Output the prompt */
2261     *p++ = '\0';
2262     if (strlenW(p) != 0) WCMD_output(p);
2263
2264     /* Read the reply */
2265     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2266                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2267     if (count > 1) {
2268       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2269       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2270       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2271                  wine_dbgstr_w(string));
2272       status = SetEnvironmentVariableW(s, string);
2273     }
2274
2275   } else {
2276     DWORD gle;
2277
2278     if (*s=='\"')
2279         WCMD_opt_s_strip_quotes(s);
2280     p = strchrW (s, '=');
2281     if (p == NULL) {
2282       env = GetEnvironmentStringsW();
2283       if (WCMD_setshow_sortenv( env, s ) == 0) {
2284         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2285         errorlevel = 1;
2286       }
2287       return;
2288     }
2289     *p++ = '\0';
2290
2291     if (strlenW(p) == 0) p = NULL;
2292     status = SetEnvironmentVariableW(s, p);
2293     gle = GetLastError();
2294     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2295       errorlevel = 1;
2296     } else if ((!status)) WCMD_print_error();
2297   }
2298 }
2299
2300 /****************************************************************************
2301  * WCMD_setshow_path
2302  *
2303  * Set/Show the path environment variable
2304  */
2305
2306 void WCMD_setshow_path (const WCHAR *command) {
2307
2308   WCHAR string[1024];
2309   DWORD status;
2310   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2311   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2312
2313   if (strlenW(param1) == 0) {
2314     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2315     if (status != 0) {
2316       WCMD_output_asis ( pathEqW);
2317       WCMD_output_asis ( string);
2318       WCMD_output_asis ( newline);
2319     }
2320     else {
2321       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2322     }
2323   }
2324   else {
2325     if (*command == '=') command++; /* Skip leading '=' */
2326     status = SetEnvironmentVariableW(pathW, command);
2327     if (!status) WCMD_print_error();
2328   }
2329 }
2330
2331 /****************************************************************************
2332  * WCMD_setshow_prompt
2333  *
2334  * Set or show the command prompt.
2335  */
2336
2337 void WCMD_setshow_prompt (void) {
2338
2339   WCHAR *s;
2340   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2341
2342   if (strlenW(param1) == 0) {
2343     SetEnvironmentVariableW(promptW, NULL);
2344   }
2345   else {
2346     s = param1;
2347     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2348     if (strlenW(s) == 0) {
2349       SetEnvironmentVariableW(promptW, NULL);
2350     }
2351     else SetEnvironmentVariableW(promptW, s);
2352   }
2353 }
2354
2355 /****************************************************************************
2356  * WCMD_setshow_time
2357  *
2358  * Set/Show the system time
2359  * FIXME: Can't change time yet
2360  */
2361
2362 void WCMD_setshow_time (void) {
2363
2364   WCHAR curtime[64], buffer[64];
2365   DWORD count;
2366   SYSTEMTIME st;
2367   static const WCHAR parmT[] = {'/','T','\0'};
2368
2369   if (strlenW(param1) == 0) {
2370     GetLocalTime(&st);
2371     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2372                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2373       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2374       if (strstrW (quals, parmT) == NULL) {
2375         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2376         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2377                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2378         if (count > 2) {
2379           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2380         }
2381       }
2382     }
2383     else WCMD_print_error ();
2384   }
2385   else {
2386     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2387   }
2388 }
2389
2390 /****************************************************************************
2391  * WCMD_shift
2392  *
2393  * Shift batch parameters.
2394  * Optional /n says where to start shifting (n=0-8)
2395  */
2396
2397 void WCMD_shift (const WCHAR *command) {
2398   int start;
2399
2400   if (context != NULL) {
2401     WCHAR *pos = strchrW(command, '/');
2402     int   i;
2403
2404     if (pos == NULL) {
2405       start = 0;
2406     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2407       start = (*(pos+1) - '0');
2408     } else {
2409       SetLastError(ERROR_INVALID_PARAMETER);
2410       WCMD_print_error();
2411       return;
2412     }
2413
2414     WINE_TRACE("Shifting variables, starting at %d\n", start);
2415     for (i=start;i<=8;i++) {
2416       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2417     }
2418     context -> shift_count[9] = context -> shift_count[9] + 1;
2419   }
2420
2421 }
2422
2423 /****************************************************************************
2424  * WCMD_title
2425  *
2426  * Set the console title
2427  */
2428 void WCMD_title (const WCHAR *command) {
2429   SetConsoleTitleW(command);
2430 }
2431
2432 /****************************************************************************
2433  * WCMD_type
2434  *
2435  * Copy a file to standard output.
2436  */
2437
2438 void WCMD_type (WCHAR *command) {
2439
2440   int   argno         = 0;
2441   WCHAR *argN          = command;
2442   BOOL  writeHeaders  = FALSE;
2443
2444   if (param1[0] == 0x00) {
2445     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2446     return;
2447   }
2448
2449   if (param2[0] != 0x00) writeHeaders = TRUE;
2450
2451   /* Loop through all args */
2452   errorlevel = 0;
2453   while (argN) {
2454     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2455
2456     HANDLE h;
2457     WCHAR buffer[512];
2458     DWORD count;
2459
2460     if (!argN) break;
2461
2462     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2463     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2464                 FILE_ATTRIBUTE_NORMAL, NULL);
2465     if (h == INVALID_HANDLE_VALUE) {
2466       WCMD_print_error ();
2467       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2468       errorlevel = 1;
2469     } else {
2470       if (writeHeaders) {
2471         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2472         WCMD_output(fmt, thisArg);
2473       }
2474       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2475         if (count == 0) break;  /* ReadFile reports success on EOF! */
2476         buffer[count] = 0;
2477         WCMD_output_asis (buffer);
2478       }
2479       CloseHandle (h);
2480     }
2481   }
2482 }
2483
2484 /****************************************************************************
2485  * WCMD_more
2486  *
2487  * Output either a file or stdin to screen in pages
2488  */
2489
2490 void WCMD_more (WCHAR *command) {
2491
2492   int   argno         = 0;
2493   WCHAR *argN          = command;
2494   WCHAR  moreStr[100];
2495   WCHAR  moreStrPage[100];
2496   WCHAR  buffer[512];
2497   DWORD count;
2498   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2499   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2500   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2501                                     ')',' ','-','-','\n','\0'};
2502   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2503
2504   /* Prefix the NLS more with '-- ', then load the text */
2505   errorlevel = 0;
2506   strcpyW(moreStr, moreStart);
2507   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2508               (sizeof(moreStr)/sizeof(WCHAR))-3);
2509
2510   if (param1[0] == 0x00) {
2511
2512     /* Wine implements pipes via temporary files, and hence stdin is
2513        effectively reading from the file. This means the prompts for
2514        more are satisfied by the next line from the input (file). To
2515        avoid this, ensure stdin is to the console                    */
2516     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2517     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2518                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2519                          FILE_ATTRIBUTE_NORMAL, 0);
2520     WINE_TRACE("No parms - working probably in pipe mode\n");
2521     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2522
2523     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2524        once you get in this bit unless due to a pipe, its going to end badly...  */
2525     wsprintfW(moreStrPage, moreFmt, moreStr);
2526
2527     WCMD_enter_paged_mode(moreStrPage);
2528     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2529       if (count == 0) break;    /* ReadFile reports success on EOF! */
2530       buffer[count] = 0;
2531       WCMD_output_asis (buffer);
2532     }
2533     WCMD_leave_paged_mode();
2534
2535     /* Restore stdin to what it was */
2536     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2537     CloseHandle(hConIn);
2538
2539     return;
2540   } else {
2541     BOOL needsPause = FALSE;
2542
2543     /* Loop through all args */
2544     WINE_TRACE("Parms supplied - working through each file\n");
2545     WCMD_enter_paged_mode(moreStrPage);
2546
2547     while (argN) {
2548       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2549       HANDLE h;
2550
2551       if (!argN) break;
2552
2553       if (needsPause) {
2554
2555         /* Wait */
2556         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2557         WCMD_leave_paged_mode();
2558         WCMD_output_asis(moreStrPage);
2559         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2560                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2561         WCMD_enter_paged_mode(moreStrPage);
2562       }
2563
2564
2565       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2566       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2567                 FILE_ATTRIBUTE_NORMAL, NULL);
2568       if (h == INVALID_HANDLE_VALUE) {
2569         WCMD_print_error ();
2570         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2571         errorlevel = 1;
2572       } else {
2573         ULONG64 curPos  = 0;
2574         ULONG64 fileLen = 0;
2575         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2576
2577         /* Get the file size */
2578         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2579         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2580
2581         needsPause = TRUE;
2582         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2583           if (count == 0) break;        /* ReadFile reports success on EOF! */
2584           buffer[count] = 0;
2585           curPos += count;
2586
2587           /* Update % count (would be used in WCMD_output_asis as prompt) */
2588           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2589
2590           WCMD_output_asis (buffer);
2591         }
2592         CloseHandle (h);
2593       }
2594     }
2595
2596     WCMD_leave_paged_mode();
2597   }
2598 }
2599
2600 /****************************************************************************
2601  * WCMD_verify
2602  *
2603  * Display verify flag.
2604  * FIXME: We don't actually do anything with the verify flag other than toggle
2605  * it...
2606  */
2607
2608 void WCMD_verify (const WCHAR *command) {
2609
2610   int count;
2611
2612   count = strlenW(command);
2613   if (count == 0) {
2614     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2615     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2616     return;
2617   }
2618   if (lstrcmpiW(command, onW) == 0) {
2619     verify_mode = 1;
2620     return;
2621   }
2622   else if (lstrcmpiW(command, offW) == 0) {
2623     verify_mode = 0;
2624     return;
2625   }
2626   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2627 }
2628
2629 /****************************************************************************
2630  * WCMD_version
2631  *
2632  * Display version info.
2633  */
2634
2635 void WCMD_version (void) {
2636
2637   WCMD_output (version_string);
2638
2639 }
2640
2641 /****************************************************************************
2642  * WCMD_volume
2643  *
2644  * Display volume info and/or set volume label. Returns 0 if error.
2645  */
2646
2647 int WCMD_volume (int mode, const WCHAR *path) {
2648
2649   DWORD count, serial;
2650   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2651   BOOL status;
2652
2653   if (strlenW(path) == 0) {
2654     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2655     if (!status) {
2656       WCMD_print_error ();
2657       return 0;
2658     }
2659     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2660                                    &serial, NULL, NULL, NULL, 0);
2661   }
2662   else {
2663     static const WCHAR fmt[] = {'%','s','\\','\0'};
2664     if ((path[1] != ':') || (strlenW(path) != 2)) {
2665       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2666       return 0;
2667     }
2668     wsprintfW (curdir, fmt, path);
2669     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2670                                    &serial, NULL,
2671         NULL, NULL, 0);
2672   }
2673   if (!status) {
2674     WCMD_print_error ();
2675     return 0;
2676   }
2677   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2678         curdir[0], label, HIWORD(serial), LOWORD(serial));
2679   if (mode) {
2680     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2681     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2682                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2683     if (count > 1) {
2684       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2685       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2686     }
2687     if (strlenW(path) != 0) {
2688       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2689     }
2690     else {
2691       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2692     }
2693   }
2694   return 1;
2695 }
2696
2697 /**************************************************************************
2698  * WCMD_exit
2699  *
2700  * Exit either the process, or just this batch program
2701  *
2702  */
2703
2704 void WCMD_exit (CMD_LIST **cmdList) {
2705
2706     static const WCHAR parmB[] = {'/','B','\0'};
2707     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2708
2709     if (context && lstrcmpiW(quals, parmB) == 0) {
2710         errorlevel = rc;
2711         context -> skip_rest = TRUE;
2712         *cmdList = NULL;
2713     } else {
2714         ExitProcess(rc);
2715     }
2716 }
2717
2718
2719 /*****************************************************************************
2720  * WCMD_assoc
2721  *
2722  *      Lists or sets file associations  (assoc = TRUE)
2723  *      Lists or sets file types         (assoc = FALSE)
2724  */
2725 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2726
2727     HKEY    key;
2728     DWORD   accessOptions = KEY_READ;
2729     WCHAR   *newValue;
2730     LONG    rc = ERROR_SUCCESS;
2731     WCHAR    keyValue[MAXSTRING];
2732     DWORD   valueLen = MAXSTRING;
2733     HKEY    readKey;
2734     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2735                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2736
2737     /* See if parameter includes '=' */
2738     errorlevel = 0;
2739     newValue = strchrW(command, '=');
2740     if (newValue) accessOptions |= KEY_WRITE;
2741
2742     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2743     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2744                       accessOptions, &key) != ERROR_SUCCESS) {
2745       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2746       return;
2747     }
2748
2749     /* If no parameters then list all associations */
2750     if (*command == 0x00) {
2751       int index = 0;
2752
2753       /* Enumerate all the keys */
2754       while (rc != ERROR_NO_MORE_ITEMS) {
2755         WCHAR  keyName[MAXSTRING];
2756         DWORD nameLen;
2757
2758         /* Find the next value */
2759         nameLen = MAXSTRING;
2760         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2761
2762         if (rc == ERROR_SUCCESS) {
2763
2764           /* Only interested in extension ones if assoc, or others
2765              if not assoc                                          */
2766           if ((keyName[0] == '.' && assoc) ||
2767               (!(keyName[0] == '.') && (!assoc)))
2768           {
2769             WCHAR subkey[MAXSTRING];
2770             strcpyW(subkey, keyName);
2771             if (!assoc) strcatW(subkey, shOpCmdW);
2772
2773             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2774
2775               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2776               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2777               WCMD_output_asis(keyName);
2778               WCMD_output_asis(equalW);
2779               /* If no default value found, leave line empty after '=' */
2780               if (rc == ERROR_SUCCESS) {
2781                 WCMD_output_asis(keyValue);
2782               }
2783               WCMD_output_asis(newline);
2784               RegCloseKey(readKey);
2785             }
2786           }
2787         }
2788       }
2789
2790     } else {
2791
2792       /* Parameter supplied - if no '=' on command line, its a query */
2793       if (newValue == NULL) {
2794         WCHAR *space;
2795         WCHAR subkey[MAXSTRING];
2796
2797         /* Query terminates the parameter at the first space */
2798         strcpyW(keyValue, command);
2799         space = strchrW(keyValue, ' ');
2800         if (space) *space=0x00;
2801
2802         /* Set up key name */
2803         strcpyW(subkey, keyValue);
2804         if (!assoc) strcatW(subkey, shOpCmdW);
2805
2806         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2807
2808           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2809           WCMD_output_asis(command);
2810           WCMD_output_asis(equalW);
2811           /* If no default value found, leave line empty after '=' */
2812           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2813           WCMD_output_asis(newline);
2814           RegCloseKey(readKey);
2815
2816         } else {
2817           WCHAR  msgbuffer[MAXSTRING];
2818           WCHAR  outbuffer[MAXSTRING];
2819
2820           /* Load the translated 'File association not found' */
2821           if (assoc) {
2822             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2823           } else {
2824             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2825           }
2826           wsprintfW(outbuffer, msgbuffer, keyValue);
2827           WCMD_output_asis_stderr(outbuffer);
2828           errorlevel = 2;
2829         }
2830
2831       /* Not a query - its a set or clear of a value */
2832       } else {
2833
2834         WCHAR subkey[MAXSTRING];
2835
2836         /* Get pointer to new value */
2837         *newValue = 0x00;
2838         newValue++;
2839
2840         /* Set up key name */
2841         strcpyW(subkey, command);
2842         if (!assoc) strcatW(subkey, shOpCmdW);
2843
2844         /* If nothing after '=' then clear value - only valid for ASSOC */
2845         if (*newValue == 0x00) {
2846
2847           if (assoc) rc = RegDeleteKeyW(key, command);
2848           if (assoc && rc == ERROR_SUCCESS) {
2849             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2850
2851           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2852             WCMD_print_error();
2853             errorlevel = 2;
2854
2855           } else {
2856             WCHAR  msgbuffer[MAXSTRING];
2857             WCHAR  outbuffer[MAXSTRING];
2858
2859             /* Load the translated 'File association not found' */
2860             if (assoc) {
2861               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2862                           sizeof(msgbuffer)/sizeof(WCHAR));
2863             } else {
2864               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2865                           sizeof(msgbuffer)/sizeof(WCHAR));
2866             }
2867             wsprintfW(outbuffer, msgbuffer, keyValue);
2868             WCMD_output_asis_stderr(outbuffer);
2869             errorlevel = 2;
2870           }
2871
2872         /* It really is a set value = contents */
2873         } else {
2874           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2875                               accessOptions, NULL, &readKey, NULL);
2876           if (rc == ERROR_SUCCESS) {
2877             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2878                                 (LPBYTE)newValue,
2879                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
2880             RegCloseKey(readKey);
2881           }
2882
2883           if (rc != ERROR_SUCCESS) {
2884             WCMD_print_error();
2885             errorlevel = 2;
2886           } else {
2887             WCMD_output_asis(command);
2888             WCMD_output_asis(equalW);
2889             WCMD_output_asis(newValue);
2890             WCMD_output_asis(newline);
2891           }
2892         }
2893       }
2894     }
2895
2896     /* Clean up */
2897     RegCloseKey(key);
2898 }
2899
2900 /****************************************************************************
2901  * WCMD_color
2902  *
2903  * Clear the terminal screen.
2904  */
2905
2906 void WCMD_color (void) {
2907
2908   /* Emulate by filling the screen from the top left to bottom right with
2909         spaces, then moving the cursor to the top left afterwards */
2910   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2911   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2912
2913   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2914     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2915     return;
2916   }
2917
2918   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2919   {
2920       COORD topLeft;
2921       DWORD screenSize;
2922       DWORD color = 0;
2923
2924       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2925
2926       topLeft.X = 0;
2927       topLeft.Y = 0;
2928
2929       /* Convert the color hex digits */
2930       if (param1[0] == 0x00) {
2931         color = defaultColor;
2932       } else {
2933         color = strtoulW(param1, NULL, 16);
2934       }
2935
2936       /* Fail if fg == bg color */
2937       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2938         errorlevel = 1;
2939         return;
2940       }
2941
2942       /* Set the current screen contents and ensure all future writes
2943          remain this color                                             */
2944       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2945       SetConsoleTextAttribute(hStdOut, color);
2946   }
2947 }