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