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