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