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