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