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