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