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