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