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