cmd.exe: Fix 'help garbage' command post-unicode support.
[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);
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) {
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);
613       } while (FindNextFile(hff, &fd) != 0);
614       FindClose (hff);
615 }
616     else {
617       WCMD_execute (cmd, param, item);
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) {
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);
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 (void) {
690
691   WCHAR string[MAX_PATH];
692
693   if (param1[0] == 0x00) {
694     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
695     return;
696   }
697   if (context != NULL) {
698     WCHAR *paramStart = param1;
699     static const WCHAR eofW[] = {':','e','o','f','\0'};
700
701     /* Handle special :EOF label */
702     if (lstrcmpiW (eofW, param1) == 0) {
703       context -> skip_rest = TRUE;
704       return;
705     }
706
707     /* Support goto :label as well as goto label */
708     if (*paramStart == ':') paramStart++;
709
710     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
711     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
712       if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
713     }
714     WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
715   }
716   return;
717 }
718
719 /*****************************************************************************
720  * WCMD_pushd
721  *
722  *      Push a directory onto the stack
723  */
724
725 void WCMD_pushd (WCHAR *command) {
726     struct env_stack *curdir;
727     WCHAR *thisdir;
728     static const WCHAR parmD[] = {'/','D','\0'};
729
730     if (strchrW(command, '/') != NULL) {
731       SetLastError(ERROR_INVALID_PARAMETER);
732       WCMD_print_error();
733       return;
734     }
735
736     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
737     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
738     if( !curdir || !thisdir ) {
739       LocalFree(curdir);
740       LocalFree(thisdir);
741       WINE_ERR ("out of memory\n");
742       return;
743     }
744
745     /* Change directory using CD code with /D parameter */
746     strcpyW(quals, parmD);
747     GetCurrentDirectoryW (1024, thisdir);
748     errorlevel = 0;
749     WCMD_setshow_default(command);
750     if (errorlevel) {
751       LocalFree(curdir);
752       LocalFree(thisdir);
753       return;
754     } else {
755       curdir -> next    = pushd_directories;
756       curdir -> strings = thisdir;
757       if (pushd_directories == NULL) {
758         curdir -> u.stackdepth = 1;
759       } else {
760         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
761       }
762       pushd_directories = curdir;
763     }
764 }
765
766
767 /*****************************************************************************
768  * WCMD_popd
769  *
770  *      Pop a directory from the stack
771  */
772
773 void WCMD_popd (void) {
774     struct env_stack *temp = pushd_directories;
775
776     if (!pushd_directories)
777       return;
778
779     /* pop the old environment from the stack, and make it the current dir */
780     pushd_directories = temp->next;
781     SetCurrentDirectoryW(temp->strings);
782     LocalFree (temp->strings);
783     LocalFree (temp);
784 }
785
786 /****************************************************************************
787  * WCMD_if
788  *
789  * Batch file conditional.
790  * FIXME: Much more syntax checking needed!
791  */
792
793 void WCMD_if (WCHAR *p) {
794
795   int negate = 0, test = 0;
796   WCHAR condition[MAX_PATH], *command, *s;
797   static const WCHAR notW[]    = {'n','o','t','\0'};
798   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
799   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
800   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
801   static const WCHAR eqeqW[]   = {'=','=','\0'};
802
803   if (!lstrcmpiW (param1, notW)) {
804     negate = 1;
805     strcpyW (condition, param2);
806   }
807   else {
808     strcpyW (condition, param1);
809   }
810   if (!lstrcmpiW (condition, errlvlW)) {
811     if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
812     WCMD_parameter (p, 2+negate, &command);
813   }
814   else if (!lstrcmpiW (condition, existW)) {
815     if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
816         test = 1;
817     }
818     WCMD_parameter (p, 2+negate, &command);
819   }
820   else if (!lstrcmpiW (condition, defdW)) {
821     if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
822         test = 1;
823     }
824     WCMD_parameter (p, 2+negate, &command);
825   }
826   else if ((s = strstrW (p, eqeqW))) {
827     s += 2;
828     if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
829     WCMD_parameter (s, 1, &command);
830   }
831   else {
832     WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
833     return;
834   }
835   if (test != negate) {
836     command = WCMD_strdupW(command);
837     WCMD_process_command (command);
838     free (command);
839   }
840 }
841
842 /****************************************************************************
843  * WCMD_move
844  *
845  * Move a file, directory tree or wildcarded set of files.
846  */
847
848 void WCMD_move (void) {
849
850   int             status;
851   WIN32_FIND_DATA fd;
852   HANDLE          hff;
853   WCHAR            input[MAX_PATH];
854   WCHAR            output[MAX_PATH];
855   WCHAR            drive[10];
856   WCHAR            dir[MAX_PATH];
857   WCHAR            fname[MAX_PATH];
858   WCHAR            ext[MAX_PATH];
859
860   if (param1[0] == 0x00) {
861     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
862     return;
863   }
864
865   /* If no destination supplied, assume current directory */
866   if (param2[0] == 0x00) {
867       strcpyW(param2, dotW);
868   }
869
870   /* If 2nd parm is directory, then use original filename */
871   /* Convert partial path to full path */
872   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
873   GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
874   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
875              wine_dbgstr_w(param1), wine_dbgstr_w(output));
876
877   /* Split into components */
878   WCMD_splitpath(input, drive, dir, fname, ext);
879
880   hff = FindFirstFile (input, &fd);
881   while (hff != INVALID_HANDLE_VALUE) {
882     WCHAR  dest[MAX_PATH];
883     WCHAR  src[MAX_PATH];
884     DWORD attribs;
885
886     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
887
888     /* Build src & dest name */
889     strcpyW(src, drive);
890     strcatW(src, dir);
891
892     /* See if dest is an existing directory */
893     attribs = GetFileAttributes(output);
894     if (attribs != INVALID_FILE_ATTRIBUTES &&
895        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
896       strcpyW(dest, output);
897       strcatW(dest, slashW);
898       strcatW(dest, fd.cFileName);
899     } else {
900       strcpyW(dest, output);
901     }
902
903     strcatW(src, fd.cFileName);
904
905     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
906     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
907
908     /* Check if file is read only, otherwise move it */
909     attribs = GetFileAttributes(src);
910     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
911         (attribs & FILE_ATTRIBUTE_READONLY)) {
912       SetLastError(ERROR_ACCESS_DENIED);
913       status = 0;
914     } else {
915       BOOL ok = TRUE;
916
917       /* If destination exists, prompt unless /Y supplied */
918       if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
919         BOOL force = FALSE;
920         WCHAR copycmd[MAXSTRING];
921         int len;
922
923         /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
924         if (strstrW (quals, parmNoY))
925           force = FALSE;
926         else if (strstrW (quals, parmY))
927           force = TRUE;
928         else {
929           const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
930           len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
931           force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
932                        && ! lstrcmpiW (copycmd, parmY));
933         }
934
935         /* Prompt if overwriting */
936         if (!force) {
937           WCHAR  question[MAXSTRING];
938           WCHAR  yesChar[10];
939
940           strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
941
942           /* Ask for confirmation */
943           wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
944           ok = WCMD_ask_confirm(question, FALSE, NULL);
945
946           /* So delete the destination prior to the move */
947           if (ok) {
948             if (!DeleteFile (dest)) {
949               WCMD_print_error ();
950               errorlevel = 1;
951               ok = FALSE;
952             }
953           }
954         }
955       }
956
957       if (ok) {
958         status = MoveFile (src, dest);
959       } else {
960         status = 1; /* Anything other than 0 to prevent error msg below */
961       }
962     }
963
964     if (!status) {
965       WCMD_print_error ();
966       errorlevel = 1;
967     }
968
969     /* Step on to next match */
970     if (FindNextFile(hff, &fd) == 0) {
971       FindClose(hff);
972       hff = INVALID_HANDLE_VALUE;
973       break;
974     }
975   }
976 }
977
978 /****************************************************************************
979  * WCMD_pause
980  *
981  * Wait for keyboard input.
982  */
983
984 void WCMD_pause (void) {
985
986   DWORD count;
987   WCHAR string[32];
988
989   WCMD_output (anykey);
990   WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
991                  sizeof(string)/sizeof(WCHAR), &count, NULL);
992 }
993
994 /****************************************************************************
995  * WCMD_remove_dir
996  *
997  * Delete a directory.
998  */
999
1000 void WCMD_remove_dir (WCHAR *command) {
1001
1002   int   argno         = 0;
1003   int   argsProcessed = 0;
1004   WCHAR *argN          = command;
1005   static const WCHAR parmS[] = {'/','S','\0'};
1006   static const WCHAR parmQ[] = {'/','Q','\0'};
1007
1008   /* Loop through all args */
1009   while (argN) {
1010     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1011     if (argN && argN[0] != '/') {
1012       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1013                  wine_dbgstr_w(quals));
1014       argsProcessed++;
1015
1016       /* If subdirectory search not supplied, just try to remove
1017          and report error if it fails (eg if it contains a file) */
1018       if (strstrW (quals, parmS) == NULL) {
1019         if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1020
1021       /* Otherwise use ShFileOp to recursively remove a directory */
1022       } else {
1023
1024         SHFILEOPSTRUCT lpDir;
1025
1026         /* Ask first */
1027         if (strstrW (quals, parmQ) == NULL) {
1028           BOOL  ok;
1029           WCHAR  question[MAXSTRING];
1030           static const WCHAR fmt[] = {'%','s',' ','\0'};
1031
1032           /* Ask for confirmation */
1033           wsprintf(question, fmt, thisArg);
1034           ok = WCMD_ask_confirm(question, TRUE, NULL);
1035
1036           /* Abort if answer is 'N' */
1037           if (!ok) return;
1038         }
1039
1040         /* Do the delete */
1041         lpDir.hwnd   = NULL;
1042         lpDir.pTo    = NULL;
1043         lpDir.pFrom  = thisArg;
1044         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1045         lpDir.wFunc  = FO_DELETE;
1046         if (SHFileOperation(&lpDir)) WCMD_print_error ();
1047       }
1048     }
1049   }
1050
1051   /* Handle no valid args */
1052   if (argsProcessed == 0) {
1053     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1054     return;
1055   }
1056
1057 }
1058
1059 /****************************************************************************
1060  * WCMD_rename
1061  *
1062  * Rename a file.
1063  */
1064
1065 void WCMD_rename (void) {
1066
1067   int             status;
1068   HANDLE          hff;
1069   WIN32_FIND_DATA fd;
1070   WCHAR            input[MAX_PATH];
1071   WCHAR           *dotDst = NULL;
1072   WCHAR            drive[10];
1073   WCHAR            dir[MAX_PATH];
1074   WCHAR            fname[MAX_PATH];
1075   WCHAR            ext[MAX_PATH];
1076   DWORD           attribs;
1077
1078   errorlevel = 0;
1079
1080   /* Must be at least two args */
1081   if (param1[0] == 0x00 || param2[0] == 0x00) {
1082     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1083     errorlevel = 1;
1084     return;
1085   }
1086
1087   /* Destination cannot contain a drive letter or directory separator */
1088   if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1089       SetLastError(ERROR_INVALID_PARAMETER);
1090       WCMD_print_error();
1091       errorlevel = 1;
1092       return;
1093   }
1094
1095   /* Convert partial path to full path */
1096   GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1097   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1098              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1099   dotDst = strchrW(param2, '.');
1100
1101   /* Split into components */
1102   WCMD_splitpath(input, drive, dir, fname, ext);
1103
1104   hff = FindFirstFile (input, &fd);
1105   while (hff != INVALID_HANDLE_VALUE) {
1106     WCHAR  dest[MAX_PATH];
1107     WCHAR  src[MAX_PATH];
1108     WCHAR *dotSrc = NULL;
1109     int   dirLen;
1110
1111     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1112
1113     /* FIXME: If dest name or extension is *, replace with filename/ext
1114        part otherwise use supplied name. This supports:
1115           ren *.fred *.jim
1116           ren jim.* fred.* etc
1117        However, windows has a more complex algorithum supporting eg
1118           ?'s and *'s mid name                                         */
1119     dotSrc = strchrW(fd.cFileName, '.');
1120
1121     /* Build src & dest name */
1122     strcpyW(src, drive);
1123     strcatW(src, dir);
1124     strcpyW(dest, src);
1125     dirLen = strlenW(src);
1126     strcatW(src, fd.cFileName);
1127
1128     /* Build name */
1129     if (param2[0] == '*') {
1130       strcatW(dest, fd.cFileName);
1131       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1132     } else {
1133       strcatW(dest, param2);
1134       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1135     }
1136
1137     /* Build Extension */
1138     if (dotDst && (*(dotDst+1)=='*')) {
1139       if (dotSrc) strcatW(dest, dotSrc);
1140     } else if (dotDst) {
1141       if (dotDst) strcatW(dest, dotDst);
1142     }
1143
1144     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1145     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1146
1147     /* Check if file is read only, otherwise move it */
1148     attribs = GetFileAttributes(src);
1149     if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1150         (attribs & FILE_ATTRIBUTE_READONLY)) {
1151       SetLastError(ERROR_ACCESS_DENIED);
1152       status = 0;
1153     } else {
1154       status = MoveFile (src, dest);
1155     }
1156
1157     if (!status) {
1158       WCMD_print_error ();
1159       errorlevel = 1;
1160     }
1161
1162     /* Step on to next match */
1163     if (FindNextFile(hff, &fd) == 0) {
1164       FindClose(hff);
1165       hff = INVALID_HANDLE_VALUE;
1166       break;
1167     }
1168   }
1169 }
1170
1171 /*****************************************************************************
1172  * WCMD_dupenv
1173  *
1174  * Make a copy of the environment.
1175  */
1176 static WCHAR *WCMD_dupenv( const WCHAR *env )
1177 {
1178   WCHAR *env_copy;
1179   int len;
1180
1181   if( !env )
1182     return NULL;
1183
1184   len = 0;
1185   while ( env[len] )
1186     len += (strlenW(&env[len]) + 1);
1187
1188   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1189   if (!env_copy)
1190   {
1191     WINE_ERR("out of memory\n");
1192     return env_copy;
1193   }
1194   memcpy (env_copy, env, len*sizeof (WCHAR));
1195   env_copy[len] = 0;
1196
1197   return env_copy;
1198 }
1199
1200 /*****************************************************************************
1201  * WCMD_setlocal
1202  *
1203  *  setlocal pushes the environment onto a stack
1204  *  Save the environment as unicode so we don't screw anything up.
1205  */
1206 void WCMD_setlocal (const WCHAR *s) {
1207   WCHAR *env;
1208   struct env_stack *env_copy;
1209   WCHAR cwd[MAX_PATH];
1210
1211   /* DISABLEEXTENSIONS ignored */
1212
1213   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1214   if( !env_copy )
1215   {
1216     WINE_ERR ("out of memory\n");
1217     return;
1218   }
1219
1220   env = GetEnvironmentStringsW ();
1221
1222   env_copy->strings = WCMD_dupenv (env);
1223   if (env_copy->strings)
1224   {
1225     env_copy->next = saved_environment;
1226     saved_environment = env_copy;
1227
1228     /* Save the current drive letter */
1229     GetCurrentDirectory (MAX_PATH, cwd);
1230     env_copy->u.cwd = cwd[0];
1231   }
1232   else
1233     LocalFree (env_copy);
1234
1235   FreeEnvironmentStringsW (env);
1236
1237 }
1238
1239 /*****************************************************************************
1240  * WCMD_endlocal
1241  *
1242  *  endlocal pops the environment off a stack
1243  *  Note: When searching for '=', search from WCHAR position 1, to handle
1244  *        special internal environment variables =C:, =D: etc
1245  */
1246 void WCMD_endlocal (void) {
1247   WCHAR *env, *old, *p;
1248   struct env_stack *temp;
1249   int len, n;
1250
1251   if (!saved_environment)
1252     return;
1253
1254   /* pop the old environment from the stack */
1255   temp = saved_environment;
1256   saved_environment = temp->next;
1257
1258   /* delete the current environment, totally */
1259   env = GetEnvironmentStringsW ();
1260   old = WCMD_dupenv (GetEnvironmentStringsW ());
1261   len = 0;
1262   while (old[len]) {
1263     n = strlenW(&old[len]) + 1;
1264     p = strchrW(&old[len] + 1, '=');
1265     if (p)
1266     {
1267       *p++ = 0;
1268       SetEnvironmentVariableW (&old[len], NULL);
1269     }
1270     len += n;
1271   }
1272   LocalFree (old);
1273   FreeEnvironmentStringsW (env);
1274
1275   /* restore old environment */
1276   env = temp->strings;
1277   len = 0;
1278   while (env[len]) {
1279     n = strlenW(&env[len]) + 1;
1280     p = strchrW(&env[len] + 1, '=');
1281     if (p)
1282     {
1283       *p++ = 0;
1284       SetEnvironmentVariableW (&env[len], p);
1285     }
1286     len += n;
1287   }
1288
1289   /* Restore current drive letter */
1290   if (IsCharAlpha(temp->u.cwd)) {
1291     WCHAR envvar[4];
1292     WCHAR cwd[MAX_PATH];
1293     static const WCHAR fmt[] = {'=','%','c',':','\0'};
1294
1295     wsprintf(envvar, fmt, temp->u.cwd);
1296     if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1297       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1298       SetCurrentDirectory(cwd);
1299     }
1300   }
1301
1302   LocalFree (env);
1303   LocalFree (temp);
1304 }
1305
1306 /*****************************************************************************
1307  * WCMD_setshow_attrib
1308  *
1309  * Display and optionally sets DOS attributes on a file or directory
1310  *
1311  * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1312  * As a result only the Readonly flag is correctly reported, the Archive bit
1313  * is always set and the rest are not implemented. We do the Right Thing anyway.
1314  *
1315  * FIXME: No SET functionality.
1316  *
1317  */
1318
1319 void WCMD_setshow_attrib (void) {
1320
1321   DWORD count;
1322   HANDLE hff;
1323   WIN32_FIND_DATA fd;
1324   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1325
1326   if (param1[0] == '-') {
1327     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1328     return;
1329   }
1330
1331   if (strlenW(param1) == 0) {
1332     static const WCHAR slashStarW[]  = {'\\','*','\0'};
1333
1334     GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1335     strcatW (param1, slashStarW);
1336   }
1337
1338   hff = FindFirstFile (param1, &fd);
1339   if (hff == INVALID_HANDLE_VALUE) {
1340     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1341   }
1342   else {
1343     do {
1344       if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1345         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1346         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1347           flags[0] = 'H';
1348         }
1349         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1350           flags[1] = 'S';
1351         }
1352         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1353           flags[2] = 'A';
1354         }
1355         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1356           flags[3] = 'R';
1357         }
1358         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1359           flags[4] = 'T';
1360         }
1361         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1362           flags[5] = 'C';
1363         }
1364         WCMD_output (fmt, flags, fd.cFileName);
1365         for (count=0; count < 8; count++) flags[count] = ' ';
1366       }
1367     } while (FindNextFile(hff, &fd) != 0);
1368   }
1369   FindClose (hff);
1370 }
1371
1372 /*****************************************************************************
1373  * WCMD_setshow_default
1374  *
1375  *      Set/Show the current default directory
1376  */
1377
1378 void WCMD_setshow_default (WCHAR *command) {
1379
1380   BOOL status;
1381   WCHAR string[1024];
1382   WCHAR cwd[1024];
1383   WCHAR *pos;
1384   WIN32_FIND_DATA fd;
1385   HANDLE hff;
1386   static const WCHAR parmD[] = {'/','D','\0'};
1387
1388   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1389
1390   /* Skip /D and trailing whitespace if on the front of the command line */
1391   if (CompareString (LOCALE_USER_DEFAULT,
1392                      NORM_IGNORECASE | SORT_STRINGSORT,
1393                      command, 2, parmD, -1) == 2) {
1394     command += 2;
1395     while (*command && *command==' ') command++;
1396   }
1397
1398   GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1399   if (strlenW(command) == 0) {
1400     strcatW (cwd, newline);
1401     WCMD_output (cwd);
1402   }
1403   else {
1404     /* Remove any double quotes, which may be in the
1405        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1406     pos = string;
1407     while (*command) {
1408       if (*command != '"') *pos++ = *command;
1409       command++;
1410     }
1411     *pos = 0x00;
1412
1413     /* Search for approprate directory */
1414     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1415     hff = FindFirstFile (string, &fd);
1416     while (hff != INVALID_HANDLE_VALUE) {
1417       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1418         WCHAR fpath[MAX_PATH];
1419         WCHAR drive[10];
1420         WCHAR dir[MAX_PATH];
1421         WCHAR fname[MAX_PATH];
1422         WCHAR ext[MAX_PATH];
1423         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1424
1425         /* Convert path into actual directory spec */
1426         GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1427         WCMD_splitpath(fpath, drive, dir, fname, ext);
1428
1429         /* Rebuild path */
1430         wsprintf(string, fmt, drive, dir, fd.cFileName);
1431
1432         FindClose(hff);
1433         hff = INVALID_HANDLE_VALUE;
1434         break;
1435       }
1436
1437       /* Step on to next match */
1438       if (FindNextFile(hff, &fd) == 0) {
1439         FindClose(hff);
1440         hff = INVALID_HANDLE_VALUE;
1441         break;
1442       }
1443     }
1444
1445     /* Change to that directory */
1446     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1447
1448     status = SetCurrentDirectory (string);
1449     if (!status) {
1450       errorlevel = 1;
1451       WCMD_print_error ();
1452       return;
1453     } else {
1454
1455       /* Restore old directory if drive letter would change, and
1456            CD x:\directory /D (or pushd c:\directory) not supplied */
1457       if ((strstrW(quals, parmD) == NULL) &&
1458           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1459         SetCurrentDirectory(cwd);
1460       }
1461     }
1462
1463     /* Set special =C: type environment variable, for drive letter of
1464        change of directory, even if path was restored due to missing
1465        /D (allows changing drive letter when not resident on that
1466        drive                                                          */
1467     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1468       WCHAR env[4];
1469       strcpyW(env, equalW);
1470       memcpy(env+1, string, 2 * sizeof(WCHAR));
1471       env[3] = 0x00;
1472       WINE_FIXME("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1473       SetEnvironmentVariable(env, string);
1474     }
1475
1476    }
1477   return;
1478 }
1479
1480 /****************************************************************************
1481  * WCMD_setshow_date
1482  *
1483  * Set/Show the system date
1484  * FIXME: Can't change date yet
1485  */
1486
1487 void WCMD_setshow_date (void) {
1488
1489   WCHAR curdate[64], buffer[64];
1490   DWORD count;
1491   static const WCHAR parmT[] = {'/','T','\0'};
1492
1493   if (strlenW(param1) == 0) {
1494     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1495                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1496       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1497       if (strstrW (quals, parmT) == NULL) {
1498         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1499         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1500                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1501         if (count > 2) {
1502           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1503         }
1504       }
1505     }
1506     else WCMD_print_error ();
1507   }
1508   else {
1509     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1510   }
1511 }
1512
1513 /****************************************************************************
1514  * WCMD_compare
1515  */
1516 static int WCMD_compare( const void *a, const void *b )
1517 {
1518     int r;
1519     const WCHAR * const *str_a = a, * const *str_b = b;
1520     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1521           *str_a, -1, *str_b, -1 );
1522     if( r == CSTR_LESS_THAN ) return -1;
1523     if( r == CSTR_GREATER_THAN ) return 1;
1524     return 0;
1525 }
1526
1527 /****************************************************************************
1528  * WCMD_setshow_sortenv
1529  *
1530  * sort variables into order for display
1531  * Optionally only display those who start with a stub
1532  * returns the count displayed
1533  */
1534 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1535 {
1536   UINT count=0, len=0, i, displayedcount=0, stublen=0;
1537   const WCHAR **str;
1538
1539   if (stub) stublen = strlenW(stub);
1540
1541   /* count the number of strings, and the total length */
1542   while ( s[len] ) {
1543     len += (strlenW(&s[len]) + 1);
1544     count++;
1545   }
1546
1547   /* add the strings to an array */
1548   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1549   if( !str )
1550     return 0;
1551   str[0] = s;
1552   for( i=1; i<count; i++ )
1553     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1554
1555   /* sort the array */
1556   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1557
1558   /* print it */
1559   for( i=0; i<count; i++ ) {
1560     if (!stub || CompareString (LOCALE_USER_DEFAULT,
1561                                 NORM_IGNORECASE | SORT_STRINGSORT,
1562                                 str[i], stublen, stub, -1) == 2) {
1563       /* Don't display special internal variables */
1564       if (str[i][0] != '=') {
1565         WCMD_output_asis(str[i]);
1566         WCMD_output_asis(newline);
1567         displayedcount++;
1568       }
1569     }
1570   }
1571
1572   LocalFree( str );
1573   return displayedcount;
1574 }
1575
1576 /****************************************************************************
1577  * WCMD_setshow_env
1578  *
1579  * Set/Show the environment variables
1580  */
1581
1582 void WCMD_setshow_env (WCHAR *s) {
1583
1584   LPVOID env;
1585   WCHAR *p;
1586   int status;
1587   static const WCHAR parmP[] = {'/','P','\0'};
1588
1589   errorlevel = 0;
1590   if (param1[0] == 0x00 && quals[0] == 0x00) {
1591     env = GetEnvironmentStrings ();
1592     WCMD_setshow_sortenv( env, NULL );
1593     return;
1594   }
1595
1596   /* See if /P supplied, and if so echo the prompt, and read in a reply */
1597   if (CompareString (LOCALE_USER_DEFAULT,
1598                      NORM_IGNORECASE | SORT_STRINGSORT,
1599                      s, 2, parmP, -1) == 2) {
1600     WCHAR string[MAXSTRING];
1601     DWORD count;
1602
1603     s += 2;
1604     while (*s && *s==' ') s++;
1605
1606     /* If no parameter, or no '=' sign, return an error */
1607     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1608       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1609       return;
1610     }
1611
1612     /* Output the prompt */
1613     *p++ = '\0';
1614     if (strlenW(p) != 0) WCMD_output(p);
1615
1616     /* Read the reply */
1617     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1618                    sizeof(string)/sizeof(WCHAR), &count, NULL);
1619     if (count > 1) {
1620       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1621       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1622       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1623                  wine_dbgstr_w(string));
1624       status = SetEnvironmentVariable (s, string);
1625     }
1626
1627   } else {
1628     DWORD gle;
1629     p = strchrW (s, '=');
1630     if (p == NULL) {
1631       env = GetEnvironmentStrings ();
1632       if (WCMD_setshow_sortenv( env, s ) == 0) {
1633         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1634         errorlevel = 1;
1635       }
1636       return;
1637     }
1638     *p++ = '\0';
1639
1640     if (strlenW(p) == 0) p = NULL;
1641     status = SetEnvironmentVariable (s, p);
1642     gle = GetLastError();
1643     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1644       errorlevel = 1;
1645     } else if ((!status)) WCMD_print_error();
1646   }
1647 }
1648
1649 /****************************************************************************
1650  * WCMD_setshow_path
1651  *
1652  * Set/Show the path environment variable
1653  */
1654
1655 void WCMD_setshow_path (WCHAR *command) {
1656
1657   WCHAR string[1024];
1658   DWORD status;
1659   static const WCHAR pathW[] = {'P','A','T','H','\0'};
1660   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1661
1662   if (strlenW(param1) == 0) {
1663     status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1664     if (status != 0) {
1665       WCMD_output_asis ( pathEqW);
1666       WCMD_output_asis ( string);
1667       WCMD_output_asis ( newline);
1668     }
1669     else {
1670       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1671     }
1672   }
1673   else {
1674     if (*command == '=') command++; /* Skip leading '=' */
1675     status = SetEnvironmentVariable (pathW, command);
1676     if (!status) WCMD_print_error();
1677   }
1678 }
1679
1680 /****************************************************************************
1681  * WCMD_setshow_prompt
1682  *
1683  * Set or show the command prompt.
1684  */
1685
1686 void WCMD_setshow_prompt (void) {
1687
1688   WCHAR *s;
1689   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1690
1691   if (strlenW(param1) == 0) {
1692     SetEnvironmentVariable (promptW, NULL);
1693   }
1694   else {
1695     s = param1;
1696     while ((*s == '=') || (*s == ' ')) s++;
1697     if (strlenW(s) == 0) {
1698       SetEnvironmentVariable (promptW, NULL);
1699     }
1700     else SetEnvironmentVariable (promptW, s);
1701   }
1702 }
1703
1704 /****************************************************************************
1705  * WCMD_setshow_time
1706  *
1707  * Set/Show the system time
1708  * FIXME: Can't change time yet
1709  */
1710
1711 void WCMD_setshow_time (void) {
1712
1713   WCHAR curtime[64], buffer[64];
1714   DWORD count;
1715   SYSTEMTIME st;
1716   static const WCHAR parmT[] = {'/','T','\0'};
1717
1718   if (strlenW(param1) == 0) {
1719     GetLocalTime(&st);
1720     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1721                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1722       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1723       if (strstrW (quals, parmT) == NULL) {
1724         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1725         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1726                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1727         if (count > 2) {
1728           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1729         }
1730       }
1731     }
1732     else WCMD_print_error ();
1733   }
1734   else {
1735     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1736   }
1737 }
1738
1739 /****************************************************************************
1740  * WCMD_shift
1741  *
1742  * Shift batch parameters.
1743  * Optional /n says where to start shifting (n=0-8)
1744  */
1745
1746 void WCMD_shift (WCHAR *command) {
1747   int start;
1748
1749   if (context != NULL) {
1750     WCHAR *pos = strchrW(command, '/');
1751     int   i;
1752
1753     if (pos == NULL) {
1754       start = 0;
1755     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1756       start = (*(pos+1) - '0');
1757     } else {
1758       SetLastError(ERROR_INVALID_PARAMETER);
1759       WCMD_print_error();
1760       return;
1761     }
1762
1763     WINE_TRACE("Shifting variables, starting at %d\n", start);
1764     for (i=start;i<=8;i++) {
1765       context -> shift_count[i] = context -> shift_count[i+1] + 1;
1766     }
1767     context -> shift_count[9] = context -> shift_count[9] + 1;
1768   }
1769
1770 }
1771
1772 /****************************************************************************
1773  * WCMD_title
1774  *
1775  * Set the console title
1776  */
1777 void WCMD_title (WCHAR *command) {
1778   SetConsoleTitle(command);
1779 }
1780
1781 /****************************************************************************
1782  * WCMD_type
1783  *
1784  * Copy a file to standard output.
1785  */
1786
1787 void WCMD_type (WCHAR *command) {
1788
1789   int   argno         = 0;
1790   WCHAR *argN          = command;
1791   BOOL  writeHeaders  = FALSE;
1792
1793   if (param1[0] == 0x00) {
1794     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1795     return;
1796   }
1797
1798   if (param2[0] != 0x00) writeHeaders = TRUE;
1799
1800   /* Loop through all args */
1801   errorlevel = 0;
1802   while (argN) {
1803     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1804
1805     HANDLE h;
1806     WCHAR buffer[512];
1807     DWORD count;
1808
1809     if (!argN) break;
1810
1811     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1812     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1813                 FILE_ATTRIBUTE_NORMAL, NULL);
1814     if (h == INVALID_HANDLE_VALUE) {
1815       WCMD_print_error ();
1816       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1817       errorlevel = 1;
1818     } else {
1819       if (writeHeaders) {
1820         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
1821         WCMD_output(fmt, thisArg);
1822       }
1823       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
1824         if (count == 0) break;  /* ReadFile reports success on EOF! */
1825         buffer[count] = 0;
1826         WCMD_output_asis (buffer);
1827       }
1828       CloseHandle (h);
1829     }
1830   }
1831 }
1832
1833 /****************************************************************************
1834  * WCMD_more
1835  *
1836  * Output either a file or stdin to screen in pages
1837  */
1838
1839 void WCMD_more (WCHAR *command) {
1840
1841   int   argno         = 0;
1842   WCHAR *argN          = command;
1843   BOOL  useinput      = FALSE;
1844   WCHAR  moreStr[100];
1845   WCHAR  moreStrPage[100];
1846   WCHAR  buffer[512];
1847   DWORD count;
1848   static const WCHAR moreStart[] = {'-','-',' ','\0'};
1849   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
1850   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
1851                                     ')',' ','-','-','\n','\0'};
1852   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
1853
1854   /* Prefix the NLS more with '-- ', then load the text */
1855   errorlevel = 0;
1856   strcpyW(moreStr, moreStart);
1857   LoadString (hinst, WCMD_MORESTR, &moreStr[3],
1858               (sizeof(moreStr)/sizeof(WCHAR))-3);
1859
1860   if (param1[0] == 0x00) {
1861
1862     /* Wine implements pipes via temporary files, and hence stdin is
1863        effectively reading from the file. This means the prompts for
1864        more are satistied by the next line from the input (file). To
1865        avoid this, ensure stdin is to the console                    */
1866     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
1867     HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
1868                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
1869                          FILE_ATTRIBUTE_NORMAL, 0);
1870     SetStdHandle(STD_INPUT_HANDLE, hConIn);
1871
1872     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1873        once you get in this bit unless due to a pipe, its going to end badly...  */
1874     useinput = TRUE;
1875     wsprintf(moreStrPage, moreFmt, moreStr);
1876
1877     WCMD_enter_paged_mode(moreStrPage);
1878     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1879       if (count == 0) break;    /* ReadFile reports success on EOF! */
1880       buffer[count] = 0;
1881       WCMD_output_asis (buffer);
1882     }
1883     WCMD_leave_paged_mode();
1884
1885     /* Restore stdin to what it was */
1886     SetStdHandle(STD_INPUT_HANDLE, hstdin);
1887     CloseHandle(hConIn);
1888
1889     return;
1890   } else {
1891     BOOL needsPause = FALSE;
1892
1893     /* Loop through all args */
1894     WCMD_enter_paged_mode(moreStrPage);
1895
1896     while (argN) {
1897       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1898       HANDLE h;
1899
1900       if (!argN) break;
1901
1902       if (needsPause) {
1903
1904         /* Wait */
1905         wsprintf(moreStrPage, moreFmt2, moreStr, 100);
1906         WCMD_leave_paged_mode();
1907         WCMD_output_asis(moreStrPage);
1908         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1909                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1910         WCMD_enter_paged_mode(moreStrPage);
1911       }
1912
1913
1914       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1915       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1916                 FILE_ATTRIBUTE_NORMAL, NULL);
1917       if (h == INVALID_HANDLE_VALUE) {
1918         WCMD_print_error ();
1919         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1920         errorlevel = 1;
1921       } else {
1922         ULONG64 curPos  = 0;
1923         ULONG64 fileLen = 0;
1924         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
1925
1926         /* Get the file size */
1927         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
1928         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
1929
1930         needsPause = TRUE;
1931         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1932           if (count == 0) break;        /* ReadFile reports success on EOF! */
1933           buffer[count] = 0;
1934           curPos += count;
1935
1936           /* Update % count (would be used in WCMD_output_asis as prompt) */
1937           wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
1938
1939           WCMD_output_asis (buffer);
1940         }
1941         CloseHandle (h);
1942       }
1943     }
1944
1945     WCMD_leave_paged_mode();
1946   }
1947 }
1948
1949 /****************************************************************************
1950  * WCMD_verify
1951  *
1952  * Display verify flag.
1953  * FIXME: We don't actually do anything with the verify flag other than toggle
1954  * it...
1955  */
1956
1957 void WCMD_verify (WCHAR *command) {
1958
1959   int count;
1960
1961   count = strlenW(command);
1962   if (count == 0) {
1963     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
1964     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
1965     return;
1966   }
1967   if (lstrcmpiW(command, onW) == 0) {
1968     verify_mode = 1;
1969     return;
1970   }
1971   else if (lstrcmpiW(command, offW) == 0) {
1972     verify_mode = 0;
1973     return;
1974   }
1975   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
1976 }
1977
1978 /****************************************************************************
1979  * WCMD_version
1980  *
1981  * Display version info.
1982  */
1983
1984 void WCMD_version (void) {
1985
1986   WCMD_output (version_string);
1987
1988 }
1989
1990 /****************************************************************************
1991  * WCMD_volume
1992  *
1993  * Display volume info and/or set volume label. Returns 0 if error.
1994  */
1995
1996 int WCMD_volume (int mode, WCHAR *path) {
1997
1998   DWORD count, serial;
1999   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2000   BOOL status;
2001
2002   if (strlenW(path) == 0) {
2003     status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2004     if (!status) {
2005       WCMD_print_error ();
2006       return 0;
2007     }
2008     status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2009                                    &serial, NULL, NULL, NULL, 0);
2010   }
2011   else {
2012     static const WCHAR fmt[] = {'%','s','\\','\0'};
2013     if ((path[1] != ':') || (strlenW(path) != 2)) {
2014       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2015       return 0;
2016     }
2017     wsprintf (curdir, fmt, path);
2018     status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2019                                    &serial, NULL,
2020         NULL, NULL, 0);
2021   }
2022   if (!status) {
2023     WCMD_print_error ();
2024     return 0;
2025   }
2026   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2027         curdir[0], label, HIWORD(serial), LOWORD(serial));
2028   if (mode) {
2029     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2030     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2031                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2032     if (count > 1) {
2033       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2034       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2035     }
2036     if (strlenW(path) != 0) {
2037       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2038     }
2039     else {
2040       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2041     }
2042   }
2043   return 1;
2044 }
2045
2046 /**************************************************************************
2047  * WCMD_exit
2048  *
2049  * Exit either the process, or just this batch program
2050  *
2051  */
2052
2053 void WCMD_exit (void) {
2054
2055     static const WCHAR parmB[] = {'/','B','\0'};
2056     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2057
2058     if (context && lstrcmpiW(quals, parmB) == 0) {
2059         errorlevel = rc;
2060         context -> skip_rest = TRUE;
2061     } else {
2062         ExitProcess(rc);
2063     }
2064 }
2065
2066 /**************************************************************************
2067  * WCMD_ask_confirm
2068  *
2069  * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2070  * answer.
2071  *
2072  * Returns True if Y (or A) answer is selected
2073  *         If optionAll contains a pointer, ALL is allowed, and if answered
2074  *                   set to TRUE
2075  *
2076  */
2077 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2078
2079     WCHAR  msgbuffer[MAXSTRING];
2080     WCHAR  Ybuffer[MAXSTRING];
2081     WCHAR  Nbuffer[MAXSTRING];
2082     WCHAR  Abuffer[MAXSTRING];
2083     WCHAR  answer[MAX_PATH] = {'\0'};
2084     DWORD count = 0;
2085
2086     /* Load the translated 'Are you sure', plus valid answers */
2087     LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2088     LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2089     LoadString (hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2090     LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2091
2092     /* Loop waiting on a Y or N */
2093     while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2094       static const WCHAR startBkt[] = {' ','(','\0'};
2095       static const WCHAR endBkt[]   = {')','?','\0'};
2096
2097       WCMD_output_asis (message);
2098       if (showSureText) {
2099         WCMD_output_asis (msgbuffer);
2100       }
2101       WCMD_output_asis (startBkt);
2102       WCMD_output_asis (Ybuffer);
2103       WCMD_output_asis (fslashW);
2104       WCMD_output_asis (Nbuffer);
2105       if (optionAll) {
2106           WCMD_output_asis (fslashW);
2107           WCMD_output_asis (Abuffer);
2108       }
2109       WCMD_output_asis (endBkt);
2110       WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2111                      sizeof(answer)/sizeof(WCHAR), &count, NULL);
2112       answer[0] = toupper(answer[0]);
2113     }
2114
2115     /* Return the answer */
2116     return ((answer[0] == Ybuffer[0]) ||
2117             (optionAll && (answer[0] == Abuffer[0])));
2118 }
2119
2120 /*****************************************************************************
2121  * WCMD_assoc
2122  *
2123  *      Lists or sets file associations  (assoc = TRUE)
2124  *      Lists or sets file types         (assoc = FALSE)
2125  */
2126 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2127
2128     HKEY    key;
2129     DWORD   accessOptions = KEY_READ;
2130     WCHAR   *newValue;
2131     LONG    rc = ERROR_SUCCESS;
2132     WCHAR    keyValue[MAXSTRING];
2133     DWORD   valueLen = MAXSTRING;
2134     HKEY    readKey;
2135     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2136                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2137
2138     /* See if parameter includes '=' */
2139     errorlevel = 0;
2140     newValue = strchrW(command, '=');
2141     if (newValue) accessOptions |= KEY_WRITE;
2142
2143     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2144     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2145                      accessOptions, &key) != ERROR_SUCCESS) {
2146       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2147       return;
2148     }
2149
2150     /* If no parameters then list all associations */
2151     if (*command == 0x00) {
2152       int index = 0;
2153
2154       /* Enumerate all the keys */
2155       while (rc != ERROR_NO_MORE_ITEMS) {
2156         WCHAR  keyName[MAXSTRING];
2157         DWORD nameLen;
2158
2159         /* Find the next value */
2160         nameLen = MAXSTRING;
2161         rc = RegEnumKeyEx(key, index++,
2162                           keyName, &nameLen,
2163                           NULL, NULL, NULL, NULL);
2164
2165         if (rc == ERROR_SUCCESS) {
2166
2167           /* Only interested in extension ones if assoc, or others
2168              if not assoc                                          */
2169           if ((keyName[0] == '.' && assoc) ||
2170               (!(keyName[0] == '.') && (!assoc)))
2171           {
2172             WCHAR subkey[MAXSTRING];
2173             strcpyW(subkey, keyName);
2174             if (!assoc) strcatW(subkey, shOpCmdW);
2175
2176             if (RegOpenKeyEx(key, subkey, 0,
2177                              accessOptions, &readKey) == ERROR_SUCCESS) {
2178
2179               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2180               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2181                                    (LPBYTE)keyValue, &valueLen);
2182               WCMD_output_asis(keyName);
2183               WCMD_output_asis(equalW);
2184               /* If no default value found, leave line empty after '=' */
2185               if (rc == ERROR_SUCCESS) {
2186                 WCMD_output_asis(keyValue);
2187               }
2188               WCMD_output_asis(newline);
2189             }
2190           }
2191         }
2192       }
2193       RegCloseKey(readKey);
2194
2195     } else {
2196
2197       /* Parameter supplied - if no '=' on command line, its a query */
2198       if (newValue == NULL) {
2199         WCHAR *space;
2200         WCHAR subkey[MAXSTRING];
2201
2202         /* Query terminates the parameter at the first space */
2203         strcpyW(keyValue, command);
2204         space = strchrW(keyValue, ' ');
2205         if (space) *space=0x00;
2206
2207         /* Set up key name */
2208         strcpyW(subkey, keyValue);
2209         if (!assoc) strcatW(subkey, shOpCmdW);
2210
2211         if (RegOpenKeyEx(key, subkey, 0,
2212                          accessOptions, &readKey) == ERROR_SUCCESS) {
2213
2214           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2215                                (LPBYTE)keyValue, &valueLen);
2216           WCMD_output_asis(command);
2217           WCMD_output_asis(equalW);
2218           /* If no default value found, leave line empty after '=' */
2219           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2220           WCMD_output_asis(newline);
2221           RegCloseKey(readKey);
2222
2223         } else {
2224           WCHAR  msgbuffer[MAXSTRING];
2225           WCHAR  outbuffer[MAXSTRING];
2226
2227           /* Load the translated 'File association not found' */
2228           if (assoc) {
2229             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2230           } else {
2231             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2232           }
2233           wsprintf(outbuffer, msgbuffer, keyValue);
2234           WCMD_output_asis(outbuffer);
2235           errorlevel = 2;
2236         }
2237
2238       /* Not a query - its a set or clear of a value */
2239       } else {
2240
2241         WCHAR subkey[MAXSTRING];
2242
2243         /* Get pointer to new value */
2244         *newValue = 0x00;
2245         newValue++;
2246
2247         /* Set up key name */
2248         strcpyW(subkey, command);
2249         if (!assoc) strcatW(subkey, shOpCmdW);
2250
2251         /* If nothing after '=' then clear value - only valid for ASSOC */
2252         if (*newValue == 0x00) {
2253
2254           if (assoc) rc = RegDeleteKey(key, command);
2255           if (assoc && rc == ERROR_SUCCESS) {
2256             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2257
2258           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2259             WCMD_print_error();
2260             errorlevel = 2;
2261
2262           } else {
2263             WCHAR  msgbuffer[MAXSTRING];
2264             WCHAR  outbuffer[MAXSTRING];
2265
2266             /* Load the translated 'File association not found' */
2267             if (assoc) {
2268               LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2269                           sizeof(msgbuffer)/sizeof(WCHAR));
2270             } else {
2271               LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2272                           sizeof(msgbuffer)/sizeof(WCHAR));
2273             }
2274             wsprintf(outbuffer, msgbuffer, keyValue);
2275             WCMD_output_asis(outbuffer);
2276             errorlevel = 2;
2277           }
2278
2279         /* It really is a set value = contents */
2280         } else {
2281           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2282                               accessOptions, NULL, &readKey, NULL);
2283           if (rc == ERROR_SUCCESS) {
2284             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2285                                  (LPBYTE)newValue, strlenW(newValue));
2286             RegCloseKey(readKey);
2287           }
2288
2289           if (rc != ERROR_SUCCESS) {
2290             WCMD_print_error();
2291             errorlevel = 2;
2292           } else {
2293             WCMD_output_asis(command);
2294             WCMD_output_asis(equalW);
2295             WCMD_output_asis(newValue);
2296             WCMD_output_asis(newline);
2297           }
2298         }
2299       }
2300     }
2301
2302     /* Clean up */
2303     RegCloseKey(key);
2304 }
2305
2306 /****************************************************************************
2307  * WCMD_color
2308  *
2309  * Clear the terminal screen.
2310  */
2311
2312 void WCMD_color (void) {
2313
2314   /* Emulate by filling the screen from the top left to bottom right with
2315         spaces, then moving the cursor to the top left afterwards */
2316   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2317   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2318
2319   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2320     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2321     return;
2322   }
2323
2324   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2325   {
2326       COORD topLeft;
2327       DWORD screenSize;
2328       DWORD color = 0;
2329
2330       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2331
2332       topLeft.X = 0;
2333       topLeft.Y = 0;
2334
2335       /* Convert the color hex digits */
2336       if (param1[0] == 0x00) {
2337         color = defaultColor;
2338       } else {
2339         color = strtoulW(param1, NULL, 16);
2340       }
2341
2342       /* Fail if fg == bg color */
2343       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2344         errorlevel = 1;
2345         return;
2346       }
2347
2348       /* Set the current screen contents and ensure all future writes
2349          remain this color                                             */
2350       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2351       SetConsoleTextAttribute(hStdOut, color);
2352   }
2353 }