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