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