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