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