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