cmd.exe: EXIT /b skips any residual commands.
[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  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22  * NOTES:
23  * On entry to each function, global variables quals, param1, param2 contain
24  * the qualifiers (uppercased and concatenated) and parameters entered, with
25  * environment-variable and batch parameter substitution already done.
26  */
27
28 /*
29  * FIXME:
30  * - No support for pipes, shell parameters
31  * - Lots of functionality missing from builtins
32  * - Messages etc need international support
33  */
34
35 #define WIN32_LEAN_AND_MEAN
36
37 #include "wcmd.h"
38 #include <shellapi.h>
39 #include "wine/debug.h"
40
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
42
43 void WCMD_execute (WCHAR *orig_command, WCHAR *parameter, WCHAR *substitution, CMD_LIST **cmdList);
44
45 struct env_stack *saved_environment;
46 struct env_stack *pushd_directories;
47
48 extern HINSTANCE hinst;
49 extern WCHAR inbuilt[][10];
50 extern int echo_mode, verify_mode, defaultColor;
51 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
52 extern BATCH_CONTEXT *context;
53 extern DWORD errorlevel;
54
55 static const WCHAR dotW[]    = {'.','\0'};
56 static const WCHAR dotdotW[] = {'.','.','\0'};
57 static const WCHAR slashW[]  = {'\\','\0'};
58 static const WCHAR starW[]   = {'*','\0'};
59 static const WCHAR equalW[]  = {'=','\0'};
60 static const WCHAR fslashW[] = {'/','\0'};
61 static const WCHAR onW[]  = {'O','N','\0'};
62 static const WCHAR offW[] = {'O','F','F','\0'};
63 static const WCHAR parmY[] = {'/','Y','\0'};
64 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
65 static const WCHAR nullW[] = {'\0'};
66
67 /****************************************************************************
68  * WCMD_clear_screen
69  *
70  * Clear the terminal screen.
71  */
72
73 void WCMD_clear_screen (void) {
74
75   /* Emulate by filling the screen from the top left to bottom right with
76         spaces, then moving the cursor to the top left afterwards */
77   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
78   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
79
80   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
81   {
82       COORD topLeft;
83       DWORD screenSize;
84
85       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
86
87       topLeft.X = 0;
88       topLeft.Y = 0;
89       FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
90       SetConsoleCursorPosition(hStdOut, topLeft);
91   }
92 }
93
94 /****************************************************************************
95  * WCMD_change_tty
96  *
97  * Change the default i/o device (ie redirect STDin/STDout).
98  */
99
100 void WCMD_change_tty (void) {
101
102   WCMD_output (WCMD_LoadMessage(WCMD_NYI));
103
104 }
105
106 /****************************************************************************
107  * WCMD_copy
108  *
109  * Copy a file or wildcarded set.
110  * FIXME: No wildcard support
111  */
112
113 void WCMD_copy (void) {
114
115   WIN32_FIND_DATA fd;
116   HANDLE hff;
117   BOOL force, status;
118   WCHAR outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
119   DWORD len;
120   static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
121
122   if (param1[0] == 0x00) {
123     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
124     return;
125   }
126
127   if ((strchrW(param1,'*') != NULL) && (strchrW(param1,'%') != NULL)) {
128     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
129     return;
130   }
131
132   /* If no destination supplied, assume current directory */
133   if (param2[0] == 0x00) {
134       strcpyW(param2, dotW);
135   }
136
137   GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
138   if (outpath[strlenW(outpath) - 1] == '\\')
139       outpath[strlenW(outpath) - 1] = '\0';
140   hff = FindFirstFile (outpath, &fd);
141   if (hff != INVALID_HANDLE_VALUE) {
142     if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
143       GetFullPathName (param1, sizeof(inpath)/sizeof(WCHAR), inpath, &infile);
144       strcatW (outpath, slashW);
145       strcatW (outpath, infile);
146     }
147     FindClose (hff);
148   }
149
150   /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
151   if (strstrW (quals, parmNoY))
152     force = FALSE;
153   else if (strstrW (quals, parmY))
154     force = TRUE;
155   else {
156     len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
157     force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
158   }
159
160   if (!force) {
161     hff = FindFirstFile (outpath, &fd);
162     if (hff != INVALID_HANDLE_VALUE) {
163       WCHAR buffer[MAXSTRING];
164
165       FindClose (hff);
166
167       wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
168       force = WCMD_ask_confirm(buffer, FALSE, NULL);
169     }
170     else force = TRUE;
171   }
172   if (force) {
173     status = CopyFile (param1, outpath, FALSE);
174     if (!status) WCMD_print_error ();
175   }
176 }
177
178 /****************************************************************************
179  * WCMD_create_dir
180  *
181  * Create a directory.
182  *
183  * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
184  * they do not already exist.
185  */
186
187 static BOOL create_full_path(WCHAR* path)
188 {
189     int len;
190     WCHAR *new_path;
191     BOOL ret = TRUE;
192
193     new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
194     strcpyW(new_path,path);
195
196     while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
197         new_path[len - 1] = 0;
198
199     while (!CreateDirectory(new_path,NULL))
200     {
201         WCHAR *slash;
202         DWORD last_error = GetLastError();
203         if (last_error == ERROR_ALREADY_EXISTS)
204             break;
205
206         if (last_error != ERROR_PATH_NOT_FOUND)
207         {
208             ret = FALSE;
209             break;
210         }
211
212         if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
213         {
214             ret = FALSE;
215             break;
216         }
217
218         len = slash - new_path;
219         new_path[len] = 0;
220         if (!create_full_path(new_path))
221         {
222             ret = FALSE;
223             break;
224         }
225         new_path[len] = '\\';
226     }
227     HeapFree(GetProcessHeap(),0,new_path);
228     return ret;
229 }
230
231 void WCMD_create_dir (void) {
232
233     if (param1[0] == 0x00) {
234         WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
235         return;
236     }
237     if (!create_full_path(param1)) WCMD_print_error ();
238 }
239
240 /****************************************************************************
241  * WCMD_delete
242  *
243  * Delete a file or wildcarded set.
244  *
245  * Note on /A:
246  *  - Testing shows /A is repeatable, eg. /a-r /ar matches all files
247  *  - Each set is a pattern, eg /ahr /as-r means
248  *         readonly+hidden OR nonreadonly system files
249  *  - The '-' applies to a single field, ie /a:-hr means read only
250  *         non-hidden files
251  */
252
253 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
254
255     int   argno         = 0;
256     int   argsProcessed = 0;
257     WCHAR *argN          = command;
258     BOOL  foundAny      = FALSE;
259     static const WCHAR parmA[] = {'/','A','\0'};
260     static const WCHAR parmQ[] = {'/','Q','\0'};
261     static const WCHAR parmP[] = {'/','P','\0'};
262     static const WCHAR parmS[] = {'/','S','\0'};
263     static const WCHAR parmF[] = {'/','F','\0'};
264
265     /* If not recursing, clear error flag */
266     if (expectDir) errorlevel = 0;
267
268     /* Loop through all args */
269     while (argN) {
270       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
271       WCHAR argCopy[MAX_PATH];
272
273       if (argN && argN[0] != '/') {
274
275         WIN32_FIND_DATA fd;
276         HANDLE hff;
277         WCHAR fpath[MAX_PATH];
278         WCHAR *p;
279         BOOL handleParm = TRUE;
280         BOOL found = FALSE;
281         static const WCHAR anyExt[]= {'.','*','\0'};
282
283         strcpyW(argCopy, thisArg);
284         WINE_TRACE("del: Processing arg %s (quals:%s)\n",
285                    wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
286         argsProcessed++;
287
288         /* If filename part of parameter is * or *.*, prompt unless
289            /Q supplied.                                            */
290         if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
291
292           WCHAR drive[10];
293           WCHAR dir[MAX_PATH];
294           WCHAR fname[MAX_PATH];
295           WCHAR ext[MAX_PATH];
296
297           /* Convert path into actual directory spec */
298           GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
299           WCMD_splitpath(fpath, drive, dir, fname, ext);
300
301           /* Only prompt for * and *.*, not *a, a*, *.a* etc */
302           if ((strcmpW(fname, starW) == 0) &&
303               (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
304             BOOL  ok;
305             WCHAR  question[MAXSTRING];
306             static const WCHAR fmt[] = {'%','s',' ','\0'};
307
308             /* Note: Flag as found, to avoid file not found message */
309             found = TRUE;
310
311             /* Ask for confirmation */
312             wsprintf(question, fmt, fpath);
313             ok = WCMD_ask_confirm(question, TRUE, NULL);
314
315             /* Abort if answer is 'N' */
316             if (!ok) continue;
317           }
318         }
319
320         /* First, try to delete in the current directory */
321         hff = FindFirstFile (argCopy, &fd);
322         if (hff == INVALID_HANDLE_VALUE) {
323           handleParm = FALSE;
324         } else {
325           found = TRUE;
326         }
327
328         /* Support del <dirname> by just deleting all files dirname\* */
329         if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
330                 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
331           WCHAR modifiedParm[MAX_PATH];
332           static const WCHAR slashStar[] = {'\\','*','\0'};
333
334           strcpyW(modifiedParm, argCopy);
335           strcatW(modifiedParm, slashStar);
336           FindClose(hff);
337           found = TRUE;
338           WCMD_delete(modifiedParm, FALSE);
339
340         } else if (handleParm) {
341
342           /* Build the filename to delete as <supplied directory>\<findfirst filename> */
343           strcpyW (fpath, argCopy);
344           do {
345             p = strrchrW (fpath, '\\');
346             if (p != NULL) {
347               *++p = '\0';
348               strcatW (fpath, fd.cFileName);
349             }
350             else strcpyW (fpath, fd.cFileName);
351             if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
352               BOOL  ok = TRUE;
353               WCHAR *nextA = strstrW (quals, parmA);
354
355               /* Handle attribute matching (/A) */
356               if (nextA != NULL) {
357                 ok = FALSE;
358                 while (nextA != NULL && !ok) {
359
360                   WCHAR *thisA = (nextA+2);
361                   BOOL  stillOK = TRUE;
362
363                   /* Skip optional : */
364                   if (*thisA == ':') thisA++;
365
366                   /* Parse each of the /A[:]xxx in turn */
367                   while (*thisA && *thisA != '/') {
368                     BOOL negate    = FALSE;
369                     BOOL attribute = FALSE;
370
371                     /* Match negation of attribute first */
372                     if (*thisA == '-') {
373                       negate=TRUE;
374                       thisA++;
375                     }
376
377                     /* Match attribute */
378                     switch (*thisA) {
379                     case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
380                               break;
381                     case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
382                               break;
383                     case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
384                               break;
385                     case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
386                               break;
387                     default:
388                         WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
389                     }
390
391                     /* Now check result, keeping a running boolean about whether it
392                        matches all parsed attribues so far                         */
393                     if (attribute && !negate) {
394                         stillOK = stillOK;
395                     } else if (!attribute && negate) {
396                         stillOK = stillOK;
397                     } else {
398                         stillOK = FALSE;
399                     }
400                     thisA++;
401                   }
402
403                   /* Save the running total as the final result */
404                   ok = stillOK;
405
406                   /* Step on to next /A set */
407                   nextA = strstrW (nextA+1, parmA);
408                 }
409               }
410
411               /* /P means prompt for each file */
412               if (ok && strstrW (quals, parmP) != NULL) {
413                 WCHAR  question[MAXSTRING];
414
415                 /* Ask for confirmation */
416                 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
417                 ok = WCMD_ask_confirm(question, FALSE, NULL);
418               }
419
420               /* Only proceed if ok to */
421               if (ok) {
422
423                 /* If file is read only, and /F supplied, delete it */
424                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
425                     strstrW (quals, parmF) != NULL) {
426                     SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
427                 }
428
429                 /* Now do the delete */
430                 if (!DeleteFile (fpath)) WCMD_print_error ();
431               }
432
433             }
434           } while (FindNextFile(hff, &fd) != 0);
435           FindClose (hff);
436         }
437
438         /* Now recurse into all subdirectories handling the parameter in the same way */
439         if (strstrW (quals, parmS) != NULL) {
440
441           WCHAR thisDir[MAX_PATH];
442           int cPos;
443
444           WCHAR drive[10];
445           WCHAR dir[MAX_PATH];
446           WCHAR fname[MAX_PATH];
447           WCHAR ext[MAX_PATH];
448
449           /* Convert path into actual directory spec */
450           GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
451           WCMD_splitpath(thisDir, drive, dir, fname, ext);
452
453           strcpyW(thisDir, drive);
454           strcatW(thisDir, dir);
455           cPos = strlenW(thisDir);
456
457           WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
458
459           /* Append '*' to the directory */
460           thisDir[cPos] = '*';
461           thisDir[cPos+1] = 0x00;
462
463           hff = FindFirstFile (thisDir, &fd);
464
465           /* Remove residual '*' */
466           thisDir[cPos] = 0x00;
467
468           if (hff != INVALID_HANDLE_VALUE) {
469             DIRECTORY_STACK *allDirs = NULL;
470             DIRECTORY_STACK *lastEntry = NULL;
471
472             do {
473               if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
474                   (strcmpW(fd.cFileName, dotdotW) != 0) &&
475                   (strcmpW(fd.cFileName, dotW) != 0)) {
476
477                 DIRECTORY_STACK *nextDir;
478                 WCHAR subParm[MAX_PATH];
479
480                 /* Work out search parameter in sub dir */
481                 strcpyW (subParm, thisDir);
482                 strcatW (subParm, fd.cFileName);
483                 strcatW (subParm, slashW);
484                 strcatW (subParm, fname);
485                 strcatW (subParm, ext);
486                 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
487
488                 /* Allocate memory, add to list */
489                 nextDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
490                 if (allDirs == NULL) allDirs = nextDir;
491                 if (lastEntry != NULL) lastEntry->next = nextDir;
492                 lastEntry = nextDir;
493                 nextDir->next = NULL;
494                 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
495                                              (strlenW(subParm)+1) * sizeof(WCHAR));
496                 strcpyW(nextDir->dirName, subParm);
497               }
498             } while (FindNextFile(hff, &fd) != 0);
499             FindClose (hff);
500
501             /* Go through each subdir doing the delete */
502             while (allDirs != NULL) {
503               DIRECTORY_STACK *tempDir;
504
505               tempDir = allDirs->next;
506               found |= WCMD_delete (allDirs->dirName, FALSE);
507
508               HeapFree(GetProcessHeap(),0,allDirs->dirName);
509               HeapFree(GetProcessHeap(),0,allDirs);
510               allDirs = tempDir;
511             }
512           }
513         }
514         /* Keep running total to see if any found, and if not recursing
515            issue error message                                         */
516         if (expectDir) {
517           if (!found) {
518             errorlevel = 1;
519             WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
520           }
521         }
522         foundAny |= found;
523       }
524     }
525
526     /* Handle no valid args */
527     if (argsProcessed == 0) {
528       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
529     }
530
531     return foundAny;
532 }
533
534 /****************************************************************************
535  * WCMD_echo
536  *
537  * Echo input to the screen (or not). We don't try to emulate the bugs
538  * in DOS (try typing "ECHO ON AGAIN" for an example).
539  */
540
541 void WCMD_echo (const WCHAR *command) {
542
543   int count;
544
545   if ((command[0] == '.') && (command[1] == 0)) {
546     WCMD_output (newline);
547     return;
548   }
549   if (command[0]==' ')
550     command++;
551   count = strlenW(command);
552   if (count == 0) {
553     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
554     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
555     return;
556   }
557   if (lstrcmpiW(command, onW) == 0) {
558     echo_mode = 1;
559     return;
560   }
561   if (lstrcmpiW(command, offW) == 0) {
562     echo_mode = 0;
563     return;
564   }
565   WCMD_output_asis (command);
566   WCMD_output (newline);
567
568 }
569
570 /**************************************************************************
571  * WCMD_for
572  *
573  * Batch file loop processing.
574  * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
575  * will probably work here, but the reverse is not necessarily the case...
576  */
577
578 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
579
580   WIN32_FIND_DATA fd;
581   HANDLE hff;
582   WCHAR *cmd, *item;
583   WCHAR set[MAX_PATH], param[MAX_PATH];
584   int i;
585   const WCHAR inW[] = {'i', 'n', '\0'};
586   const WCHAR doW[] = {'d', 'o', '\0'};
587
588   if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
589         || lstrcmpiW (WCMD_parameter (p, 3, NULL), doW)
590         || (param1[0] != '%')) {
591     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
592     return;
593   }
594   lstrcpynW (set, WCMD_parameter (p, 2, NULL), sizeof(set)/sizeof(WCHAR));
595   WCMD_parameter (p, 4, &cmd);
596   strcpyW (param, param1);
597
598 /*
599  *      If the parameter within the set has a wildcard then search for matching files
600  *      otherwise do a literal substitution.
601  */
602
603   i = 0;
604   while (*(item = WCMD_parameter (set, i, NULL))) {
605     static const WCHAR wildcards[] = {'*','?','\0'};
606     if (strpbrkW (item, wildcards)) {
607       hff = FindFirstFile (item, &fd);
608       if (hff == INVALID_HANDLE_VALUE) {
609         return;
610       }
611       do {
612         WCMD_execute (cmd, param, fd.cFileName, cmdList);
613       } while (FindNextFile(hff, &fd) != 0);
614       FindClose (hff);
615     }
616     else {
617       WCMD_execute (cmd, param, item, cmdList);
618     }
619     i++;
620   }
621 }
622
623 /*****************************************************************************
624  * WCMD_Execute
625  *
626  *      Execute a command after substituting variable text for the supplied parameter
627  */
628
629 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
630
631   WCHAR *new_cmd, *p, *s, *dup;
632   int size;
633
634   size = strlenW (orig_cmd);
635   new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
636   dup = s = WCMD_strdupW(orig_cmd);
637
638   while ((p = strstrW (s, param))) {
639     *p = '\0';
640     size += strlenW (subst);
641     new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
642     strcatW (new_cmd, s);
643     strcatW (new_cmd, subst);
644     s = p + strlenW (param);
645   }
646   strcatW (new_cmd, s);
647   WCMD_process_command (new_cmd, cmdList);
648   free (dup);
649   LocalFree ((HANDLE)new_cmd);
650 }
651
652
653 /**************************************************************************
654  * WCMD_give_help
655  *
656  *      Simple on-line help. Help text is stored in the resource file.
657  */
658
659 void WCMD_give_help (WCHAR *command) {
660
661   int i;
662
663   command = WCMD_strtrim_leading_spaces(command);
664   if (strlenW(command) == 0) {
665     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
666   }
667   else {
668     for (i=0; i<=WCMD_EXIT; i++) {
669       if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
670           param1, -1, inbuilt[i], -1) == 2) {
671         WCMD_output_asis (WCMD_LoadMessage(i));
672         return;
673       }
674     }
675     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
676   }
677   return;
678 }
679
680 /****************************************************************************
681  * WCMD_go_to
682  *
683  * Batch file jump instruction. Not the most efficient algorithm ;-)
684  * Prints error message if the specified label cannot be found - the file pointer is
685  * then at EOF, effectively stopping the batch file.
686  * FIXME: DOS is supposed to allow labels with spaces - we don't.
687  */
688
689 void WCMD_goto (CMD_LIST **cmdList) {
690
691   WCHAR string[MAX_PATH];
692
693   /* Do not process any more parts of a processed multipart or multilines command */
694   *cmdList = NULL;
695
696   if (param1[0] == 0x00) {
697     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
698     return;
699   }
700   if (context != NULL) {
701     WCHAR *paramStart = param1;
702     static const WCHAR eofW[] = {':','e','o','f','\0'};
703
704     /* Handle special :EOF label */
705     if (lstrcmpiW (eofW, param1) == 0) {
706       context -> skip_rest = TRUE;
707       return;
708     }
709
710     /* Support goto :label as well as goto label */
711     if (*paramStart == ':') paramStart++;
712
713     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
714     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
715       if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
716     }
717     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
718   }
719   return;
720 }
721
722 /*****************************************************************************
723  * WCMD_pushd
724  *
725  *      Push a directory onto the stack
726  */
727
728 void WCMD_pushd (WCHAR *command) {
729     struct env_stack *curdir;
730     WCHAR *thisdir;
731     static const WCHAR parmD[] = {'/','D','\0'};
732
733     if (strchrW(command, '/') != NULL) {
734       SetLastError(ERROR_INVALID_PARAMETER);
735       WCMD_print_error();
736       return;
737     }
738
739     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
740     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
741     if( !curdir || !thisdir ) {
742       LocalFree(curdir);
743       LocalFree(thisdir);
744       WINE_ERR ("out of memory\n");
745       return;
746     }
747
748     /* Change directory using CD code with /D parameter */
749     strcpyW(quals, parmD);
750     GetCurrentDirectoryW (1024, thisdir);
751     errorlevel = 0;
752     WCMD_setshow_default(command);
753     if (errorlevel) {
754       LocalFree(curdir);
755       LocalFree(thisdir);
756       return;
757     } else {
758       curdir -> next    = pushd_directories;
759       curdir -> strings = thisdir;
760       if (pushd_directories == NULL) {
761         curdir -> u.stackdepth = 1;
762       } else {
763         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
764       }
765       pushd_directories = curdir;
766     }
767 }
768
769
770 /*****************************************************************************
771  * WCMD_popd
772  *
773  *      Pop a directory from the stack
774  */
775
776 void WCMD_popd (void) {
777     struct env_stack *temp = pushd_directories;
778
779     if (!pushd_directories)
780       return;
781
782     /* pop the old environment from the stack, and make it the current dir */
783     pushd_directories = temp->next;
784     SetCurrentDirectoryW(temp->strings);
785     LocalFree (temp->strings);
786     LocalFree (temp);
787 }
788
789 /****************************************************************************
790  * WCMD_if
791  *
792  * Batch file conditional.
793  * FIXME: Much more syntax checking needed!
794  */
795
796 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
797
798   int negate = 0, test = 0;
799   WCHAR condition[MAX_PATH], *command, *s;
800   static const WCHAR notW[]    = {'n','o','t','\0'};
801   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
802   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
803   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
804   static const WCHAR eqeqW[]   = {'=','=','\0'};
805
806   if (!lstrcmpiW (param1, notW)) {
807     negate = 1;
808     strcpyW (condition, param2);
809   }
810   else {
811     strcpyW (condition, param1);
812   }
813   if (!lstrcmpiW (condition, errlvlW)) {
814     if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
815     WCMD_parameter (p, 2+negate, &command);
816   }
817   else if (!lstrcmpiW (condition, existW)) {
818     if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
819         test = 1;
820     }
821     WCMD_parameter (p, 2+negate, &command);
822   }
823   else if (!lstrcmpiW (condition, defdW)) {
824     if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
825         test = 1;
826     }
827     WCMD_parameter (p, 2+negate, &command);
828   }
829   else if ((s = strstrW (p, eqeqW))) {
830     s += 2;
831     if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
832     WCMD_parameter (s, 1, &command);
833   }
834   else {
835     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
836     return;
837   }
838   if (test != negate) {
839     command = WCMD_strdupW(command);
840     WCMD_process_command (command, cmdList);
841     free (command);
842   }
843 }
844
845 /****************************************************************************
846  * WCMD_move
847  *
848  * Move a file, directory tree or wildcarded set of files.
849  */
850
851 void WCMD_move (void) {
852
853   int             status;
854   WIN32_FIND_DATA fd;
855   HANDLE          hff;
856   WCHAR            input[MAX_PATH];
857   WCHAR            output[MAX_PATH];
858   WCHAR            drive[10];
859   WCHAR            dir[MAX_PATH];
860   WCHAR            fname[MAX_PATH];
861   WCHAR            ext[MAX_PATH];
862
863   if (param1[0] == 0x00) {
864     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
865     return;
866   }
867
868   /* If no destination supplied, assume current directory */
869   if (param2[0] == 0x00) {
870       strcpyW(param2, dotW);
871   }
872
873   /* If 2nd parm is directory, then use original filename */
874   /* Convert partial path to full path */
875   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
876   GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
877   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
878              wine_dbgstr_w(param1), wine_dbgstr_w(output));
879
880   /* Split into components */
881   WCMD_splitpath(input, drive, dir, fname, ext);
882
883   hff = FindFirstFile (input, &fd);
884   while (hff != INVALID_HANDLE_VALUE) {
885     WCHAR  dest[MAX_PATH];
886     WCHAR  src[MAX_PATH];
887     DWORD attribs;
888
889     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
890
891     /* Build src & dest name */
892     strcpyW(src, drive);
893     strcatW(src, dir);
894
895     /* See if dest is an existing directory */
896     attribs = GetFileAttributes(output);
897     if (attribs != INVALID_FILE_ATTRIBUTES &&
898        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
899       strcpyW(dest, output);
900       strcatW(dest, slashW);
901       strcatW(dest, fd.cFileName);
902     } else {
903       strcpyW(dest, output);
904     }
905
906     strcatW(src, fd.cFileName);
907
908     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
909     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
910
911     /* Check if file is read only, otherwise move it */
912     attribs = GetFileAttributes(src);
913     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
914         (attribs & FILE_ATTRIBUTE_READONLY)) {
915       SetLastError(ERROR_ACCESS_DENIED);
916       status = 0;
917     } else {
918       BOOL ok = TRUE;
919
920       /* If destination exists, prompt unless /Y supplied */
921       if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
922         BOOL force = FALSE;
923         WCHAR copycmd[MAXSTRING];
924         int len;
925
926         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
927         if (strstrW (quals, parmNoY))
928           force = FALSE;
929         else if (strstrW (quals, parmY))
930           force = TRUE;
931         else {
932           const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
933           len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
934           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
935                        && ! lstrcmpiW (copycmd, parmY));
936         }
937
938         /* Prompt if overwriting */
939         if (!force) {
940           WCHAR  question[MAXSTRING];
941           WCHAR  yesChar[10];
942
943           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
944
945           /* Ask for confirmation */
946           wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
947           ok = WCMD_ask_confirm(question, FALSE, NULL);
948
949           /* So delete the destination prior to the move */
950           if (ok) {
951             if (!DeleteFile (dest)) {
952               WCMD_print_error ();
953               errorlevel = 1;
954               ok = FALSE;
955             }
956           }
957         }
958       }
959
960       if (ok) {
961         status = MoveFile (src, dest);
962       } else {
963         status = 1; /* Anything other than 0 to prevent error msg below */
964       }
965     }
966
967     if (!status) {
968       WCMD_print_error ();
969       errorlevel = 1;
970     }
971
972     /* Step on to next match */
973     if (FindNextFile(hff, &fd) == 0) {
974       FindClose(hff);
975       hff = INVALID_HANDLE_VALUE;
976       break;
977     }
978   }
979 }
980
981 /****************************************************************************
982  * WCMD_pause
983  *
984  * Wait for keyboard input.
985  */
986
987 void WCMD_pause (void) {
988
989   DWORD count;
990   WCHAR string[32];
991
992   WCMD_output (anykey);
993   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
994                  sizeof(string)/sizeof(WCHAR), &count, NULL);
995 }
996
997 /****************************************************************************
998  * WCMD_remove_dir
999  *
1000  * Delete a directory.
1001  */
1002
1003 void WCMD_remove_dir (WCHAR *command) {
1004
1005   int   argno         = 0;
1006   int   argsProcessed = 0;
1007   WCHAR *argN          = command;
1008   static const WCHAR parmS[] = {'/','S','\0'};
1009   static const WCHAR parmQ[] = {'/','Q','\0'};
1010
1011   /* Loop through all args */
1012   while (argN) {
1013     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1014     if (argN && argN[0] != '/') {
1015       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1016                  wine_dbgstr_w(quals));
1017       argsProcessed++;
1018
1019       /* If subdirectory search not supplied, just try to remove
1020          and report error if it fails (eg if it contains a file) */
1021       if (strstrW (quals, parmS) == NULL) {
1022         if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1023
1024       /* Otherwise use ShFileOp to recursively remove a directory */
1025       } else {
1026
1027         SHFILEOPSTRUCT lpDir;
1028
1029         /* Ask first */
1030         if (strstrW (quals, parmQ) == NULL) {
1031           BOOL  ok;
1032           WCHAR  question[MAXSTRING];
1033           static const WCHAR fmt[] = {'%','s',' ','\0'};
1034
1035           /* Ask for confirmation */
1036           wsprintf(question, fmt, thisArg);
1037           ok = WCMD_ask_confirm(question, TRUE, NULL);
1038
1039           /* Abort if answer is 'N' */
1040           if (!ok) return;
1041         }
1042
1043         /* Do the delete */
1044         lpDir.hwnd   = NULL;
1045         lpDir.pTo    = NULL;
1046         lpDir.pFrom  = thisArg;
1047         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1048         lpDir.wFunc  = FO_DELETE;
1049         if (SHFileOperation(&lpDir)) WCMD_print_error ();
1050       }
1051     }
1052   }
1053
1054   /* Handle no valid args */
1055   if (argsProcessed == 0) {
1056     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1057     return;
1058   }
1059
1060 }
1061
1062 /****************************************************************************
1063  * WCMD_rename
1064  *
1065  * Rename a file.
1066  */
1067
1068 void WCMD_rename (void) {
1069
1070   int             status;
1071   HANDLE          hff;
1072   WIN32_FIND_DATA fd;
1073   WCHAR            input[MAX_PATH];
1074   WCHAR           *dotDst = NULL;
1075   WCHAR            drive[10];
1076   WCHAR            dir[MAX_PATH];
1077   WCHAR            fname[MAX_PATH];
1078   WCHAR            ext[MAX_PATH];
1079   DWORD           attribs;
1080
1081   errorlevel = 0;
1082
1083   /* Must be at least two args */
1084   if (param1[0] == 0x00 || param2[0] == 0x00) {
1085     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1086     errorlevel = 1;
1087     return;
1088   }
1089
1090   /* Destination cannot contain a drive letter or directory separator */
1091   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1092       SetLastError(ERROR_INVALID_PARAMETER);
1093       WCMD_print_error();
1094       errorlevel = 1;
1095       return;
1096   }
1097
1098   /* Convert partial path to full path */
1099   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1100   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1101              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1102   dotDst = strchrW(param2, '.');
1103
1104   /* Split into components */
1105   WCMD_splitpath(input, drive, dir, fname, ext);
1106
1107   hff = FindFirstFile (input, &fd);
1108   while (hff != INVALID_HANDLE_VALUE) {
1109     WCHAR  dest[MAX_PATH];
1110     WCHAR  src[MAX_PATH];
1111     WCHAR *dotSrc = NULL;
1112     int   dirLen;
1113
1114     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1115
1116     /* FIXME: If dest name or extension is *, replace with filename/ext
1117        part otherwise use supplied name. This supports:
1118           ren *.fred *.jim
1119           ren jim.* fred.* etc
1120        However, windows has a more complex algorithum supporting eg
1121           ?'s and *'s mid name                                         */
1122     dotSrc = strchrW(fd.cFileName, '.');
1123
1124     /* Build src & dest name */
1125     strcpyW(src, drive);
1126     strcatW(src, dir);
1127     strcpyW(dest, src);
1128     dirLen = strlenW(src);
1129     strcatW(src, fd.cFileName);
1130
1131     /* Build name */
1132     if (param2[0] == '*') {
1133       strcatW(dest, fd.cFileName);
1134       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1135     } else {
1136       strcatW(dest, param2);
1137       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1138     }
1139
1140     /* Build Extension */
1141     if (dotDst && (*(dotDst+1)=='*')) {
1142       if (dotSrc) strcatW(dest, dotSrc);
1143     } else if (dotDst) {
1144       if (dotDst) strcatW(dest, dotDst);
1145     }
1146
1147     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1148     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1149
1150     /* Check if file is read only, otherwise move it */
1151     attribs = GetFileAttributes(src);
1152     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1153         (attribs & FILE_ATTRIBUTE_READONLY)) {
1154       SetLastError(ERROR_ACCESS_DENIED);
1155       status = 0;
1156     } else {
1157       status = MoveFile (src, dest);
1158     }
1159
1160     if (!status) {
1161       WCMD_print_error ();
1162       errorlevel = 1;
1163     }
1164
1165     /* Step on to next match */
1166     if (FindNextFile(hff, &fd) == 0) {
1167       FindClose(hff);
1168       hff = INVALID_HANDLE_VALUE;
1169       break;
1170     }
1171   }
1172 }
1173
1174 /*****************************************************************************
1175  * WCMD_dupenv
1176  *
1177  * Make a copy of the environment.
1178  */
1179 static WCHAR *WCMD_dupenv( const WCHAR *env )
1180 {
1181   WCHAR *env_copy;
1182   int len;
1183
1184   if( !env )
1185     return NULL;
1186
1187   len = 0;
1188   while ( env[len] )
1189     len += (strlenW(&env[len]) + 1);
1190
1191   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1192   if (!env_copy)
1193   {
1194     WINE_ERR("out of memory\n");
1195     return env_copy;
1196   }
1197   memcpy (env_copy, env, len*sizeof (WCHAR));
1198   env_copy[len] = 0;
1199
1200   return env_copy;
1201 }
1202
1203 /*****************************************************************************
1204  * WCMD_setlocal
1205  *
1206  *  setlocal pushes the environment onto a stack
1207  *  Save the environment as unicode so we don't screw anything up.
1208  */
1209 void WCMD_setlocal (const WCHAR *s) {
1210   WCHAR *env;
1211   struct env_stack *env_copy;
1212   WCHAR cwd[MAX_PATH];
1213
1214   /* DISABLEEXTENSIONS ignored */
1215
1216   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1217   if( !env_copy )
1218   {
1219     WINE_ERR ("out of memory\n");
1220     return;
1221   }
1222
1223   env = GetEnvironmentStringsW ();
1224
1225   env_copy->strings = WCMD_dupenv (env);
1226   if (env_copy->strings)
1227   {
1228     env_copy->next = saved_environment;
1229     saved_environment = env_copy;
1230
1231     /* Save the current drive letter */
1232     GetCurrentDirectory (MAX_PATH, cwd);
1233     env_copy->u.cwd = cwd[0];
1234   }
1235   else
1236     LocalFree (env_copy);
1237
1238   FreeEnvironmentStringsW (env);
1239
1240 }
1241
1242 /*****************************************************************************
1243  * WCMD_endlocal
1244  *
1245  *  endlocal pops the environment off a stack
1246  *  Note: When searching for '=', search from WCHAR position 1, to handle
1247  *        special internal environment variables =C:, =D: etc
1248  */
1249 void WCMD_endlocal (void) {
1250   WCHAR *env, *old, *p;
1251   struct env_stack *temp;
1252   int len, n;
1253
1254   if (!saved_environment)
1255     return;
1256
1257   /* pop the old environment from the stack */
1258   temp = saved_environment;
1259   saved_environment = temp->next;
1260
1261   /* delete the current environment, totally */
1262   env = GetEnvironmentStringsW ();
1263   old = WCMD_dupenv (GetEnvironmentStringsW ());
1264   len = 0;
1265   while (old[len]) {
1266     n = strlenW(&old[len]) + 1;
1267     p = strchrW(&old[len] + 1, '=');
1268     if (p)
1269     {
1270       *p++ = 0;
1271       SetEnvironmentVariableW (&old[len], NULL);
1272     }
1273     len += n;
1274   }
1275   LocalFree (old);
1276   FreeEnvironmentStringsW (env);
1277
1278   /* restore old environment */
1279   env = temp->strings;
1280   len = 0;
1281   while (env[len]) {
1282     n = strlenW(&env[len]) + 1;
1283     p = strchrW(&env[len] + 1, '=');
1284     if (p)
1285     {
1286       *p++ = 0;
1287       SetEnvironmentVariableW (&env[len], p);
1288     }
1289     len += n;
1290   }
1291
1292   /* Restore current drive letter */
1293   if (IsCharAlpha(temp->u.cwd)) {
1294     WCHAR envvar[4];
1295     WCHAR cwd[MAX_PATH];
1296     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1297
1298     wsprintf(envvar, fmt, temp->u.cwd);
1299     if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1300       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1301       SetCurrentDirectory(cwd);
1302     }
1303   }
1304
1305   LocalFree (env);
1306   LocalFree (temp);
1307 }
1308
1309 /*****************************************************************************
1310  * WCMD_setshow_attrib
1311  *
1312  * Display and optionally sets DOS attributes on a file or directory
1313  *
1314  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1315  * As a result only the Readonly flag is correctly reported, the Archive bit
1316  * is always set and the rest are not implemented. We do the Right Thing anyway.
1317  *
1318  * FIXME: No SET functionality.
1319  *
1320  */
1321
1322 void WCMD_setshow_attrib (void) {
1323
1324   DWORD count;
1325   HANDLE hff;
1326   WIN32_FIND_DATA fd;
1327   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1328
1329   if (param1[0] == '-') {
1330     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1331     return;
1332   }
1333
1334   if (strlenW(param1) == 0) {
1335     static const WCHAR slashStarW[]  = {'\\','*','\0'};
1336
1337     GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1338     strcatW (param1, slashStarW);
1339   }
1340
1341   hff = FindFirstFile (param1, &fd);
1342   if (hff == INVALID_HANDLE_VALUE) {
1343     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1344   }
1345   else {
1346     do {
1347       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1348         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1349         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1350           flags[0] = 'H';
1351         }
1352         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1353           flags[1] = 'S';
1354         }
1355         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1356           flags[2] = 'A';
1357         }
1358         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1359           flags[3] = 'R';
1360         }
1361         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1362           flags[4] = 'T';
1363         }
1364         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1365           flags[5] = 'C';
1366         }
1367         WCMD_output (fmt, flags, fd.cFileName);
1368         for (count=0; count < 8; count++) flags[count] = ' ';
1369       }
1370     } while (FindNextFile(hff, &fd) != 0);
1371   }
1372   FindClose (hff);
1373 }
1374
1375 /*****************************************************************************
1376  * WCMD_setshow_default
1377  *
1378  *      Set/Show the current default directory
1379  */
1380
1381 void WCMD_setshow_default (WCHAR *command) {
1382
1383   BOOL status;
1384   WCHAR string[1024];
1385   WCHAR cwd[1024];
1386   WCHAR *pos;
1387   WIN32_FIND_DATA fd;
1388   HANDLE hff;
1389   static const WCHAR parmD[] = {'/','D','\0'};
1390
1391   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1392
1393   /* Skip /D and trailing whitespace if on the front of the command line */
1394   if (CompareString (LOCALE_USER_DEFAULT,
1395                      NORM_IGNORECASE | SORT_STRINGSORT,
1396                      command, 2, parmD, -1) == 2) {
1397     command += 2;
1398     while (*command && *command==' ') command++;
1399   }
1400
1401   GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1402   if (strlenW(command) == 0) {
1403     strcatW (cwd, newline);
1404     WCMD_output (cwd);
1405   }
1406   else {
1407     /* Remove any double quotes, which may be in the
1408        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1409     pos = string;
1410     while (*command) {
1411       if (*command != '"') *pos++ = *command;
1412       command++;
1413     }
1414     *pos = 0x00;
1415
1416     /* Search for approprate directory */
1417     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1418     hff = FindFirstFile (string, &fd);
1419     while (hff != INVALID_HANDLE_VALUE) {
1420       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1421         WCHAR fpath[MAX_PATH];
1422         WCHAR drive[10];
1423         WCHAR dir[MAX_PATH];
1424         WCHAR fname[MAX_PATH];
1425         WCHAR ext[MAX_PATH];
1426         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1427
1428         /* Convert path into actual directory spec */
1429         GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1430         WCMD_splitpath(fpath, drive, dir, fname, ext);
1431
1432         /* Rebuild path */
1433         wsprintf(string, fmt, drive, dir, fd.cFileName);
1434
1435         FindClose(hff);
1436         hff = INVALID_HANDLE_VALUE;
1437         break;
1438       }
1439
1440       /* Step on to next match */
1441       if (FindNextFile(hff, &fd) == 0) {
1442         FindClose(hff);
1443         hff = INVALID_HANDLE_VALUE;
1444         break;
1445       }
1446     }
1447
1448     /* Change to that directory */
1449     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1450
1451     status = SetCurrentDirectory (string);
1452     if (!status) {
1453       errorlevel = 1;
1454       WCMD_print_error ();
1455       return;
1456     } else {
1457
1458       /* Restore old directory if drive letter would change, and
1459            CD x:\directory /D (or pushd c:\directory) not supplied */
1460       if ((strstrW(quals, parmD) == NULL) &&
1461           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1462         SetCurrentDirectory(cwd);
1463       }
1464     }
1465
1466     /* Set special =C: type environment variable, for drive letter of
1467        change of directory, even if path was restored due to missing
1468        /D (allows changing drive letter when not resident on that
1469        drive                                                          */
1470     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1471       WCHAR env[4];
1472       strcpyW(env, equalW);
1473       memcpy(env+1, string, 2 * sizeof(WCHAR));
1474       env[3] = 0x00;
1475       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1476       SetEnvironmentVariable(env, string);
1477     }
1478
1479    }
1480   return;
1481 }
1482
1483 /****************************************************************************
1484  * WCMD_setshow_date
1485  *
1486  * Set/Show the system date
1487  * FIXME: Can't change date yet
1488  */
1489
1490 void WCMD_setshow_date (void) {
1491
1492   WCHAR curdate[64], buffer[64];
1493   DWORD count;
1494   static const WCHAR parmT[] = {'/','T','\0'};
1495
1496   if (strlenW(param1) == 0) {
1497     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1498                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1499       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1500       if (strstrW (quals, parmT) == NULL) {
1501         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1502         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1503                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1504         if (count > 2) {
1505           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1506         }
1507       }
1508     }
1509     else WCMD_print_error ();
1510   }
1511   else {
1512     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1513   }
1514 }
1515
1516 /****************************************************************************
1517  * WCMD_compare
1518  */
1519 static int WCMD_compare( const void *a, const void *b )
1520 {
1521     int r;
1522     const WCHAR * const *str_a = a, * const *str_b = b;
1523     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1524           *str_a, -1, *str_b, -1 );
1525     if( r == CSTR_LESS_THAN ) return -1;
1526     if( r == CSTR_GREATER_THAN ) return 1;
1527     return 0;
1528 }
1529
1530 /****************************************************************************
1531  * WCMD_setshow_sortenv
1532  *
1533  * sort variables into order for display
1534  * Optionally only display those who start with a stub
1535  * returns the count displayed
1536  */
1537 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1538 {
1539   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1540   const WCHAR **str;
1541
1542   if (stub) stublen = strlenW(stub);
1543
1544   /* count the number of strings, and the total length */
1545   while ( s[len] ) {
1546     len += (strlenW(&s[len]) + 1);
1547     count++;
1548   }
1549
1550   /* add the strings to an array */
1551   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1552   if( !str )
1553     return 0;
1554   str[0] = s;
1555   for( i=1; i<count; i++ )
1556     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1557
1558   /* sort the array */
1559   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1560
1561   /* print it */
1562   for( i=0; i<count; i++ ) {
1563     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1564                                 NORM_IGNORECASE | SORT_STRINGSORT,
1565                                 str[i], stublen, stub, -1) == 2) {
1566       /* Don't display special internal variables */
1567       if (str[i][0] != '=') {
1568         WCMD_output_asis(str[i]);
1569         WCMD_output_asis(newline);
1570         displayedcount++;
1571       }
1572     }
1573   }
1574
1575   LocalFree( str );
1576   return displayedcount;
1577 }
1578
1579 /****************************************************************************
1580  * WCMD_setshow_env
1581  *
1582  * Set/Show the environment variables
1583  */
1584
1585 void WCMD_setshow_env (WCHAR *s) {
1586
1587   LPVOID env;
1588   WCHAR *p;
1589   int status;
1590   static const WCHAR parmP[] = {'/','P','\0'};
1591
1592   errorlevel = 0;
1593   if (param1[0] == 0x00 && quals[0] == 0x00) {
1594     env = GetEnvironmentStrings ();
1595     WCMD_setshow_sortenv( env, NULL );
1596     return;
1597   }
1598
1599   /* See if /P supplied, and if so echo the prompt, and read in a reply */
1600   if (CompareString (LOCALE_USER_DEFAULT,
1601                      NORM_IGNORECASE | SORT_STRINGSORT,
1602                      s, 2, parmP, -1) == 2) {
1603     WCHAR string[MAXSTRING];
1604     DWORD count;
1605
1606     s += 2;
1607     while (*s && *s==' ') s++;
1608
1609     /* If no parameter, or no '=' sign, return an error */
1610     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1611       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1612       return;
1613     }
1614
1615     /* Output the prompt */
1616     *p++ = '\0';
1617     if (strlenW(p) != 0) WCMD_output(p);
1618
1619     /* Read the reply */
1620     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1621                    sizeof(string)/sizeof(WCHAR), &count, NULL);
1622     if (count > 1) {
1623       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1624       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1625       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1626                  wine_dbgstr_w(string));
1627       status = SetEnvironmentVariable (s, string);
1628     }
1629
1630   } else {
1631     DWORD gle;
1632     p = strchrW (s, '=');
1633     if (p == NULL) {
1634       env = GetEnvironmentStrings ();
1635       if (WCMD_setshow_sortenv( env, s ) == 0) {
1636         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1637         errorlevel = 1;
1638       }
1639       return;
1640     }
1641     *p++ = '\0';
1642
1643     if (strlenW(p) == 0) p = NULL;
1644     status = SetEnvironmentVariable (s, p);
1645     gle = GetLastError();
1646     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1647       errorlevel = 1;
1648     } else if ((!status)) WCMD_print_error();
1649   }
1650 }
1651
1652 /****************************************************************************
1653  * WCMD_setshow_path
1654  *
1655  * Set/Show the path environment variable
1656  */
1657
1658 void WCMD_setshow_path (WCHAR *command) {
1659
1660   WCHAR string[1024];
1661   DWORD status;
1662   static const WCHAR pathW[] = {'P','A','T','H','\0'};
1663   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1664
1665   if (strlenW(param1) == 0) {
1666     status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1667     if (status != 0) {
1668       WCMD_output_asis ( pathEqW);
1669       WCMD_output_asis ( string);
1670       WCMD_output_asis ( newline);
1671     }
1672     else {
1673       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1674     }
1675   }
1676   else {
1677     if (*command == '=') command++; /* Skip leading '=' */
1678     status = SetEnvironmentVariable (pathW, command);
1679     if (!status) WCMD_print_error();
1680   }
1681 }
1682
1683 /****************************************************************************
1684  * WCMD_setshow_prompt
1685  *
1686  * Set or show the command prompt.
1687  */
1688
1689 void WCMD_setshow_prompt (void) {
1690
1691   WCHAR *s;
1692   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1693
1694   if (strlenW(param1) == 0) {
1695     SetEnvironmentVariable (promptW, NULL);
1696   }
1697   else {
1698     s = param1;
1699     while ((*s == '=') || (*s == ' ')) s++;
1700     if (strlenW(s) == 0) {
1701       SetEnvironmentVariable (promptW, NULL);
1702     }
1703     else SetEnvironmentVariable (promptW, s);
1704   }
1705 }
1706
1707 /****************************************************************************
1708  * WCMD_setshow_time
1709  *
1710  * Set/Show the system time
1711  * FIXME: Can't change time yet
1712  */
1713
1714 void WCMD_setshow_time (void) {
1715
1716   WCHAR curtime[64], buffer[64];
1717   DWORD count;
1718   SYSTEMTIME st;
1719   static const WCHAR parmT[] = {'/','T','\0'};
1720
1721   if (strlenW(param1) == 0) {
1722     GetLocalTime(&st);
1723     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1724                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1725       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1726       if (strstrW (quals, parmT) == NULL) {
1727         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1728         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1729                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1730         if (count > 2) {
1731           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1732         }
1733       }
1734     }
1735     else WCMD_print_error ();
1736   }
1737   else {
1738     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1739   }
1740 }
1741
1742 /****************************************************************************
1743  * WCMD_shift
1744  *
1745  * Shift batch parameters.
1746  * Optional /n says where to start shifting (n=0-8)
1747  */
1748
1749 void WCMD_shift (WCHAR *command) {
1750   int start;
1751
1752   if (context != NULL) {
1753     WCHAR *pos = strchrW(command, '/');
1754     int   i;
1755
1756     if (pos == NULL) {
1757       start = 0;
1758     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1759       start = (*(pos+1) - '0');
1760     } else {
1761       SetLastError(ERROR_INVALID_PARAMETER);
1762       WCMD_print_error();
1763       return;
1764     }
1765
1766     WINE_TRACE("Shifting variables, starting at %d\n", start);
1767     for (i=start;i<=8;i++) {
1768       context -> shift_count[i] = context -> shift_count[i+1] + 1;
1769     }
1770     context -> shift_count[9] = context -> shift_count[9] + 1;
1771   }
1772
1773 }
1774
1775 /****************************************************************************
1776  * WCMD_title
1777  *
1778  * Set the console title
1779  */
1780 void WCMD_title (WCHAR *command) {
1781   SetConsoleTitle(command);
1782 }
1783
1784 /****************************************************************************
1785  * WCMD_type
1786  *
1787  * Copy a file to standard output.
1788  */
1789
1790 void WCMD_type (WCHAR *command) {
1791
1792   int   argno         = 0;
1793   WCHAR *argN          = command;
1794   BOOL  writeHeaders  = FALSE;
1795
1796   if (param1[0] == 0x00) {
1797     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1798     return;
1799   }
1800
1801   if (param2[0] != 0x00) writeHeaders = TRUE;
1802
1803   /* Loop through all args */
1804   errorlevel = 0;
1805   while (argN) {
1806     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1807
1808     HANDLE h;
1809     WCHAR buffer[512];
1810     DWORD count;
1811
1812     if (!argN) break;
1813
1814     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1815     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1816                 FILE_ATTRIBUTE_NORMAL, NULL);
1817     if (h == INVALID_HANDLE_VALUE) {
1818       WCMD_print_error ();
1819       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1820       errorlevel = 1;
1821     } else {
1822       if (writeHeaders) {
1823         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
1824         WCMD_output(fmt, thisArg);
1825       }
1826       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
1827         if (count == 0) break;  /* ReadFile reports success on EOF! */
1828         buffer[count] = 0;
1829         WCMD_output_asis (buffer);
1830       }
1831       CloseHandle (h);
1832     }
1833   }
1834 }
1835
1836 /****************************************************************************
1837  * WCMD_more
1838  *
1839  * Output either a file or stdin to screen in pages
1840  */
1841
1842 void WCMD_more (WCHAR *command) {
1843
1844   int   argno         = 0;
1845   WCHAR *argN          = command;
1846   BOOL  useinput      = FALSE;
1847   WCHAR  moreStr[100];
1848   WCHAR  moreStrPage[100];
1849   WCHAR  buffer[512];
1850   DWORD count;
1851   static const WCHAR moreStart[] = {'-','-',' ','\0'};
1852   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
1853   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
1854                                     ')',' ','-','-','\n','\0'};
1855   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
1856
1857   /* Prefix the NLS more with '-- ', then load the text */
1858   errorlevel = 0;
1859   strcpyW(moreStr, moreStart);
1860   LoadString (hinst, WCMD_MORESTR, &moreStr[3],
1861               (sizeof(moreStr)/sizeof(WCHAR))-3);
1862
1863   if (param1[0] == 0x00) {
1864
1865     /* Wine implements pipes via temporary files, and hence stdin is
1866        effectively reading from the file. This means the prompts for
1867        more are satistied by the next line from the input (file). To
1868        avoid this, ensure stdin is to the console                    */
1869     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
1870     HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
1871                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
1872                          FILE_ATTRIBUTE_NORMAL, 0);
1873     SetStdHandle(STD_INPUT_HANDLE, hConIn);
1874
1875     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1876        once you get in this bit unless due to a pipe, its going to end badly...  */
1877     useinput = TRUE;
1878     wsprintf(moreStrPage, moreFmt, moreStr);
1879
1880     WCMD_enter_paged_mode(moreStrPage);
1881     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1882       if (count == 0) break;    /* ReadFile reports success on EOF! */
1883       buffer[count] = 0;
1884       WCMD_output_asis (buffer);
1885     }
1886     WCMD_leave_paged_mode();
1887
1888     /* Restore stdin to what it was */
1889     SetStdHandle(STD_INPUT_HANDLE, hstdin);
1890     CloseHandle(hConIn);
1891
1892     return;
1893   } else {
1894     BOOL needsPause = FALSE;
1895
1896     /* Loop through all args */
1897     WCMD_enter_paged_mode(moreStrPage);
1898
1899     while (argN) {
1900       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1901       HANDLE h;
1902
1903       if (!argN) break;
1904
1905       if (needsPause) {
1906
1907         /* Wait */
1908         wsprintf(moreStrPage, moreFmt2, moreStr, 100);
1909         WCMD_leave_paged_mode();
1910         WCMD_output_asis(moreStrPage);
1911         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1912                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1913         WCMD_enter_paged_mode(moreStrPage);
1914       }
1915
1916
1917       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1918       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1919                 FILE_ATTRIBUTE_NORMAL, NULL);
1920       if (h == INVALID_HANDLE_VALUE) {
1921         WCMD_print_error ();
1922         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1923         errorlevel = 1;
1924       } else {
1925         ULONG64 curPos  = 0;
1926         ULONG64 fileLen = 0;
1927         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
1928
1929         /* Get the file size */
1930         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
1931         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
1932
1933         needsPause = TRUE;
1934         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1935           if (count == 0) break;        /* ReadFile reports success on EOF! */
1936           buffer[count] = 0;
1937           curPos += count;
1938
1939           /* Update % count (would be used in WCMD_output_asis as prompt) */
1940           wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
1941
1942           WCMD_output_asis (buffer);
1943         }
1944         CloseHandle (h);
1945       }
1946     }
1947
1948     WCMD_leave_paged_mode();
1949   }
1950 }
1951
1952 /****************************************************************************
1953  * WCMD_verify
1954  *
1955  * Display verify flag.
1956  * FIXME: We don't actually do anything with the verify flag other than toggle
1957  * it...
1958  */
1959
1960 void WCMD_verify (WCHAR *command) {
1961
1962   int count;
1963
1964   count = strlenW(command);
1965   if (count == 0) {
1966     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
1967     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
1968     return;
1969   }
1970   if (lstrcmpiW(command, onW) == 0) {
1971     verify_mode = 1;
1972     return;
1973   }
1974   else if (lstrcmpiW(command, offW) == 0) {
1975     verify_mode = 0;
1976     return;
1977   }
1978   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
1979 }
1980
1981 /****************************************************************************
1982  * WCMD_version
1983  *
1984  * Display version info.
1985  */
1986
1987 void WCMD_version (void) {
1988
1989   WCMD_output (version_string);
1990
1991 }
1992
1993 /****************************************************************************
1994  * WCMD_volume
1995  *
1996  * Display volume info and/or set volume label. Returns 0 if error.
1997  */
1998
1999 int WCMD_volume (int mode, WCHAR *path) {
2000
2001   DWORD count, serial;
2002   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2003   BOOL status;
2004
2005   if (strlenW(path) == 0) {
2006     status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2007     if (!status) {
2008       WCMD_print_error ();
2009       return 0;
2010     }
2011     status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2012                                    &serial, NULL, NULL, NULL, 0);
2013   }
2014   else {
2015     static const WCHAR fmt[] = {'%','s','\\','\0'};
2016     if ((path[1] != ':') || (strlenW(path) != 2)) {
2017       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2018       return 0;
2019     }
2020     wsprintf (curdir, fmt, path);
2021     status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2022                                    &serial, NULL,
2023         NULL, NULL, 0);
2024   }
2025   if (!status) {
2026     WCMD_print_error ();
2027     return 0;
2028   }
2029   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2030         curdir[0], label, HIWORD(serial), LOWORD(serial));
2031   if (mode) {
2032     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2033     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2034                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2035     if (count > 1) {
2036       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2037       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2038     }
2039     if (strlenW(path) != 0) {
2040       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2041     }
2042     else {
2043       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2044     }
2045   }
2046   return 1;
2047 }
2048
2049 /**************************************************************************
2050  * WCMD_exit
2051  *
2052  * Exit either the process, or just this batch program
2053  *
2054  */
2055
2056 void WCMD_exit (CMD_LIST **cmdList) {
2057
2058     static const WCHAR parmB[] = {'/','B','\0'};
2059     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2060
2061     if (context && lstrcmpiW(quals, parmB) == 0) {
2062         errorlevel = rc;
2063         context -> skip_rest = TRUE;
2064         *cmdList = NULL;
2065     } else {
2066         ExitProcess(rc);
2067     }
2068 }
2069
2070 /**************************************************************************
2071  * WCMD_ask_confirm
2072  *
2073  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2074  * answer.
2075  *
2076  * Returns True if Y (or A) answer is selected
2077  *         If optionAll contains a pointer, ALL is allowed, and if answered
2078  *                   set to TRUE
2079  *
2080  */
2081 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2082
2083     WCHAR  msgbuffer[MAXSTRING];
2084     WCHAR  Ybuffer[MAXSTRING];
2085     WCHAR  Nbuffer[MAXSTRING];
2086     WCHAR  Abuffer[MAXSTRING];
2087     WCHAR  answer[MAX_PATH] = {'\0'};
2088     DWORD count = 0;
2089
2090     /* Load the translated 'Are you sure', plus valid answers */
2091     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2092     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2093     LoadString (hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2094     LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2095
2096     /* Loop waiting on a Y or N */
2097     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2098       static const WCHAR startBkt[] = {' ','(','\0'};
2099       static const WCHAR endBkt[]   = {')','?','\0'};
2100
2101       WCMD_output_asis (message);
2102       if (showSureText) {
2103         WCMD_output_asis (msgbuffer);
2104       }
2105       WCMD_output_asis (startBkt);
2106       WCMD_output_asis (Ybuffer);
2107       WCMD_output_asis (fslashW);
2108       WCMD_output_asis (Nbuffer);
2109       if (optionAll) {
2110           WCMD_output_asis (fslashW);
2111           WCMD_output_asis (Abuffer);
2112       }
2113       WCMD_output_asis (endBkt);
2114       WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2115                      sizeof(answer)/sizeof(WCHAR), &count, NULL);
2116       answer[0] = toupper(answer[0]);
2117     }
2118
2119     /* Return the answer */
2120     return ((answer[0] == Ybuffer[0]) ||
2121             (optionAll && (answer[0] == Abuffer[0])));
2122 }
2123
2124 /*****************************************************************************
2125  * WCMD_assoc
2126  *
2127  *      Lists or sets file associations  (assoc = TRUE)
2128  *      Lists or sets file types         (assoc = FALSE)
2129  */
2130 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2131
2132     HKEY    key;
2133     DWORD   accessOptions = KEY_READ;
2134     WCHAR   *newValue;
2135     LONG    rc = ERROR_SUCCESS;
2136     WCHAR    keyValue[MAXSTRING];
2137     DWORD   valueLen = MAXSTRING;
2138     HKEY    readKey;
2139     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2140                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2141
2142     /* See if parameter includes '=' */
2143     errorlevel = 0;
2144     newValue = strchrW(command, '=');
2145     if (newValue) accessOptions |= KEY_WRITE;
2146
2147     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2148     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2149                      accessOptions, &key) != ERROR_SUCCESS) {
2150       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2151       return;
2152     }
2153
2154     /* If no parameters then list all associations */
2155     if (*command == 0x00) {
2156       int index = 0;
2157
2158       /* Enumerate all the keys */
2159       while (rc != ERROR_NO_MORE_ITEMS) {
2160         WCHAR  keyName[MAXSTRING];
2161         DWORD nameLen;
2162
2163         /* Find the next value */
2164         nameLen = MAXSTRING;
2165         rc = RegEnumKeyEx(key, index++,
2166                           keyName, &nameLen,
2167                           NULL, NULL, NULL, NULL);
2168
2169         if (rc == ERROR_SUCCESS) {
2170
2171           /* Only interested in extension ones if assoc, or others
2172              if not assoc                                          */
2173           if ((keyName[0] == '.' && assoc) ||
2174               (!(keyName[0] == '.') && (!assoc)))
2175           {
2176             WCHAR subkey[MAXSTRING];
2177             strcpyW(subkey, keyName);
2178             if (!assoc) strcatW(subkey, shOpCmdW);
2179
2180             if (RegOpenKeyEx(key, subkey, 0,
2181                              accessOptions, &readKey) == ERROR_SUCCESS) {
2182
2183               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2184               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2185                                    (LPBYTE)keyValue, &valueLen);
2186               WCMD_output_asis(keyName);
2187               WCMD_output_asis(equalW);
2188               /* If no default value found, leave line empty after '=' */
2189               if (rc == ERROR_SUCCESS) {
2190                 WCMD_output_asis(keyValue);
2191               }
2192               WCMD_output_asis(newline);
2193             }
2194           }
2195         }
2196       }
2197       RegCloseKey(readKey);
2198
2199     } else {
2200
2201       /* Parameter supplied - if no '=' on command line, its a query */
2202       if (newValue == NULL) {
2203         WCHAR *space;
2204         WCHAR subkey[MAXSTRING];
2205
2206         /* Query terminates the parameter at the first space */
2207         strcpyW(keyValue, command);
2208         space = strchrW(keyValue, ' ');
2209         if (space) *space=0x00;
2210
2211         /* Set up key name */
2212         strcpyW(subkey, keyValue);
2213         if (!assoc) strcatW(subkey, shOpCmdW);
2214
2215         if (RegOpenKeyEx(key, subkey, 0,
2216                          accessOptions, &readKey) == ERROR_SUCCESS) {
2217
2218           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2219                                (LPBYTE)keyValue, &valueLen);
2220           WCMD_output_asis(command);
2221           WCMD_output_asis(equalW);
2222           /* If no default value found, leave line empty after '=' */
2223           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2224           WCMD_output_asis(newline);
2225           RegCloseKey(readKey);
2226
2227         } else {
2228           WCHAR  msgbuffer[MAXSTRING];
2229           WCHAR  outbuffer[MAXSTRING];
2230
2231           /* Load the translated 'File association not found' */
2232           if (assoc) {
2233             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2234           } else {
2235             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2236           }
2237           wsprintf(outbuffer, msgbuffer, keyValue);
2238           WCMD_output_asis(outbuffer);
2239           errorlevel = 2;
2240         }
2241
2242       /* Not a query - its a set or clear of a value */
2243       } else {
2244
2245         WCHAR subkey[MAXSTRING];
2246
2247         /* Get pointer to new value */
2248         *newValue = 0x00;
2249         newValue++;
2250
2251         /* Set up key name */
2252         strcpyW(subkey, command);
2253         if (!assoc) strcatW(subkey, shOpCmdW);
2254
2255         /* If nothing after '=' then clear value - only valid for ASSOC */
2256         if (*newValue == 0x00) {
2257
2258           if (assoc) rc = RegDeleteKey(key, command);
2259           if (assoc && rc == ERROR_SUCCESS) {
2260             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2261
2262           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2263             WCMD_print_error();
2264             errorlevel = 2;
2265
2266           } else {
2267             WCHAR  msgbuffer[MAXSTRING];
2268             WCHAR  outbuffer[MAXSTRING];
2269
2270             /* Load the translated 'File association not found' */
2271             if (assoc) {
2272               LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2273                           sizeof(msgbuffer)/sizeof(WCHAR));
2274             } else {
2275               LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2276                           sizeof(msgbuffer)/sizeof(WCHAR));
2277             }
2278             wsprintf(outbuffer, msgbuffer, keyValue);
2279             WCMD_output_asis(outbuffer);
2280             errorlevel = 2;
2281           }
2282
2283         /* It really is a set value = contents */
2284         } else {
2285           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2286                               accessOptions, NULL, &readKey, NULL);
2287           if (rc == ERROR_SUCCESS) {
2288             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2289                                  (LPBYTE)newValue, strlenW(newValue));
2290             RegCloseKey(readKey);
2291           }
2292
2293           if (rc != ERROR_SUCCESS) {
2294             WCMD_print_error();
2295             errorlevel = 2;
2296           } else {
2297             WCMD_output_asis(command);
2298             WCMD_output_asis(equalW);
2299             WCMD_output_asis(newValue);
2300             WCMD_output_asis(newline);
2301           }
2302         }
2303       }
2304     }
2305
2306     /* Clean up */
2307     RegCloseKey(key);
2308 }
2309
2310 /****************************************************************************
2311  * WCMD_color
2312  *
2313  * Clear the terminal screen.
2314  */
2315
2316 void WCMD_color (void) {
2317
2318   /* Emulate by filling the screen from the top left to bottom right with
2319         spaces, then moving the cursor to the top left afterwards */
2320   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2321   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2322
2323   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2324     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2325     return;
2326   }
2327
2328   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2329   {
2330       COORD topLeft;
2331       DWORD screenSize;
2332       DWORD color = 0;
2333
2334       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2335
2336       topLeft.X = 0;
2337       topLeft.Y = 0;
2338
2339       /* Convert the color hex digits */
2340       if (param1[0] == 0x00) {
2341         color = defaultColor;
2342       } else {
2343         color = strtoulW(param1, NULL, 16);
2344       }
2345
2346       /* Fail if fg == bg color */
2347       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2348         errorlevel = 1;
2349         return;
2350       }
2351
2352       /* Set the current screen contents and ensure all future writes
2353          remain this color                                             */
2354       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2355       SetConsoleTextAttribute(hStdOut, color);
2356   }
2357 }