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