cmd: Improve 'attrib' builtin to handle at least setting/clearing single attributes.
[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     HeapFree(GetProcessHeap(), 0, 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 && (*cmdList)->command) {
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  */
1779
1780 void WCMD_setshow_attrib (void) {
1781
1782   DWORD count;
1783   HANDLE hff;
1784   WIN32_FIND_DATA fd;
1785   WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1786   WCHAR *name = param1;
1787   DWORD attrib_set=0;
1788   DWORD attrib_clear=0;
1789
1790   if (param1[0] == '+' || param1[0] == '-') {
1791     DWORD attrib = 0;
1792     /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1793     switch (param1[1]) {
1794     case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1795     case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1796     case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1797     case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1798     default:
1799       WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1800       return;
1801     }
1802     switch (param1[0]) {
1803     case '+': attrib_set = attrib; break;
1804     case '-': attrib_clear = attrib; break;
1805     }
1806     name = param2;
1807   }
1808
1809   if (strlenW(name) == 0) {
1810     static const WCHAR slashStarW[]  = {'\\','*','\0'};
1811
1812     GetCurrentDirectory (sizeof(param2)/sizeof(WCHAR), name);
1813     strcatW (name, slashStarW);
1814   }
1815
1816   hff = FindFirstFile (name, &fd);
1817   if (hff == INVALID_HANDLE_VALUE) {
1818     WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1819   }
1820   else {
1821     do {
1822       if (attrib_set || attrib_clear) {
1823         fd.dwFileAttributes &= ~attrib_clear;
1824         fd.dwFileAttributes |= attrib_set;
1825         if (!fd.dwFileAttributes)
1826            fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1827         SetFileAttributesW(name, fd.dwFileAttributes);
1828       } else {
1829         static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1830         if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1831           flags[0] = 'H';
1832         }
1833         if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1834           flags[1] = 'S';
1835         }
1836         if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1837           flags[2] = 'A';
1838         }
1839         if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1840           flags[3] = 'R';
1841         }
1842         if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1843           flags[4] = 'T';
1844         }
1845         if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1846           flags[5] = 'C';
1847         }
1848         WCMD_output (fmt, flags, fd.cFileName);
1849         for (count=0; count < 8; count++) flags[count] = ' ';
1850       }
1851     } while (FindNextFile(hff, &fd) != 0);
1852   }
1853   FindClose (hff);
1854 }
1855
1856 /*****************************************************************************
1857  * WCMD_setshow_default
1858  *
1859  *      Set/Show the current default directory
1860  */
1861
1862 void WCMD_setshow_default (WCHAR *command) {
1863
1864   BOOL status;
1865   WCHAR string[1024];
1866   WCHAR cwd[1024];
1867   WCHAR *pos;
1868   WIN32_FIND_DATA fd;
1869   HANDLE hff;
1870   static const WCHAR parmD[] = {'/','D','\0'};
1871
1872   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1873
1874   /* Skip /D and trailing whitespace if on the front of the command line */
1875   if (CompareString (LOCALE_USER_DEFAULT,
1876                      NORM_IGNORECASE | SORT_STRINGSORT,
1877                      command, 2, parmD, -1) == 2) {
1878     command += 2;
1879     while (*command && *command==' ') command++;
1880   }
1881
1882   GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1883   if (strlenW(command) == 0) {
1884     strcatW (cwd, newline);
1885     WCMD_output (cwd);
1886   }
1887   else {
1888     /* Remove any double quotes, which may be in the
1889        middle, eg. cd "C:\Program Files"\Microsoft is ok */
1890     pos = string;
1891     while (*command) {
1892       if (*command != '"') *pos++ = *command;
1893       command++;
1894     }
1895     *pos = 0x00;
1896
1897     /* Search for appropriate directory */
1898     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1899     hff = FindFirstFile (string, &fd);
1900     while (hff != INVALID_HANDLE_VALUE) {
1901       if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1902         WCHAR fpath[MAX_PATH];
1903         WCHAR drive[10];
1904         WCHAR dir[MAX_PATH];
1905         WCHAR fname[MAX_PATH];
1906         WCHAR ext[MAX_PATH];
1907         static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1908
1909         /* Convert path into actual directory spec */
1910         GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1911         WCMD_splitpath(fpath, drive, dir, fname, ext);
1912
1913         /* Rebuild path */
1914         wsprintf(string, fmt, drive, dir, fd.cFileName);
1915
1916         FindClose(hff);
1917         hff = INVALID_HANDLE_VALUE;
1918         break;
1919       }
1920
1921       /* Step on to next match */
1922       if (FindNextFile(hff, &fd) == 0) {
1923         FindClose(hff);
1924         hff = INVALID_HANDLE_VALUE;
1925         break;
1926       }
1927     }
1928
1929     /* Change to that directory */
1930     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1931
1932     status = SetCurrentDirectory (string);
1933     if (!status) {
1934       errorlevel = 1;
1935       WCMD_print_error ();
1936       return;
1937     } else {
1938
1939       /* Save away the actual new directory, to store as current location */
1940       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1941
1942       /* Restore old directory if drive letter would change, and
1943            CD x:\directory /D (or pushd c:\directory) not supplied */
1944       if ((strstrW(quals, parmD) == NULL) &&
1945           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1946         SetCurrentDirectory(cwd);
1947       }
1948     }
1949
1950     /* Set special =C: type environment variable, for drive letter of
1951        change of directory, even if path was restored due to missing
1952        /D (allows changing drive letter when not resident on that
1953        drive                                                          */
1954     if ((string[1] == ':') && IsCharAlpha (string[0])) {
1955       WCHAR env[4];
1956       strcpyW(env, equalW);
1957       memcpy(env+1, string, 2 * sizeof(WCHAR));
1958       env[3] = 0x00;
1959       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1960       SetEnvironmentVariable(env, string);
1961     }
1962
1963    }
1964   return;
1965 }
1966
1967 /****************************************************************************
1968  * WCMD_setshow_date
1969  *
1970  * Set/Show the system date
1971  * FIXME: Can't change date yet
1972  */
1973
1974 void WCMD_setshow_date (void) {
1975
1976   WCHAR curdate[64], buffer[64];
1977   DWORD count;
1978   static const WCHAR parmT[] = {'/','T','\0'};
1979
1980   if (strlenW(param1) == 0) {
1981     if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1982                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1983       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1984       if (strstrW (quals, parmT) == NULL) {
1985         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1986         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1987                        buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1988         if (count > 2) {
1989           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1990         }
1991       }
1992     }
1993     else WCMD_print_error ();
1994   }
1995   else {
1996     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1997   }
1998 }
1999
2000 /****************************************************************************
2001  * WCMD_compare
2002  */
2003 static int WCMD_compare( const void *a, const void *b )
2004 {
2005     int r;
2006     const WCHAR * const *str_a = a, * const *str_b = b;
2007     r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2008           *str_a, -1, *str_b, -1 );
2009     if( r == CSTR_LESS_THAN ) return -1;
2010     if( r == CSTR_GREATER_THAN ) return 1;
2011     return 0;
2012 }
2013
2014 /****************************************************************************
2015  * WCMD_setshow_sortenv
2016  *
2017  * sort variables into order for display
2018  * Optionally only display those who start with a stub
2019  * returns the count displayed
2020  */
2021 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2022 {
2023   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2024   const WCHAR **str;
2025
2026   if (stub) stublen = strlenW(stub);
2027
2028   /* count the number of strings, and the total length */
2029   while ( s[len] ) {
2030     len += (strlenW(&s[len]) + 1);
2031     count++;
2032   }
2033
2034   /* add the strings to an array */
2035   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2036   if( !str )
2037     return 0;
2038   str[0] = s;
2039   for( i=1; i<count; i++ )
2040     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2041
2042   /* sort the array */
2043   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2044
2045   /* print it */
2046   for( i=0; i<count; i++ ) {
2047     if (!stub || CompareString (LOCALE_USER_DEFAULT,
2048                                 NORM_IGNORECASE | SORT_STRINGSORT,
2049                                 str[i], stublen, stub, -1) == 2) {
2050       /* Don't display special internal variables */
2051       if (str[i][0] != '=') {
2052         WCMD_output_asis(str[i]);
2053         WCMD_output_asis(newline);
2054         displayedcount++;
2055       }
2056     }
2057   }
2058
2059   LocalFree( str );
2060   return displayedcount;
2061 }
2062
2063 /****************************************************************************
2064  * WCMD_setshow_env
2065  *
2066  * Set/Show the environment variables
2067  */
2068
2069 void WCMD_setshow_env (WCHAR *s) {
2070
2071   LPVOID env;
2072   WCHAR *p;
2073   int status;
2074   static const WCHAR parmP[] = {'/','P','\0'};
2075
2076   errorlevel = 0;
2077   if (param1[0] == 0x00 && quals[0] == 0x00) {
2078     env = GetEnvironmentStrings ();
2079     WCMD_setshow_sortenv( env, NULL );
2080     return;
2081   }
2082
2083   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2084   if (CompareString (LOCALE_USER_DEFAULT,
2085                      NORM_IGNORECASE | SORT_STRINGSORT,
2086                      s, 2, parmP, -1) == 2) {
2087     WCHAR string[MAXSTRING];
2088     DWORD count;
2089
2090     s += 2;
2091     while (*s && *s==' ') s++;
2092     if (*s=='\"')
2093         WCMD_opt_s_strip_quotes(s);
2094
2095     /* If no parameter, or no '=' sign, return an error */
2096     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2097       WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2098       return;
2099     }
2100
2101     /* Output the prompt */
2102     *p++ = '\0';
2103     if (strlenW(p) != 0) WCMD_output(p);
2104
2105     /* Read the reply */
2106     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2107                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2108     if (count > 1) {
2109       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2110       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2111       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2112                  wine_dbgstr_w(string));
2113       status = SetEnvironmentVariable (s, string);
2114     }
2115
2116   } else {
2117     DWORD gle;
2118
2119     if (*s=='\"')
2120         WCMD_opt_s_strip_quotes(s);
2121     p = strchrW (s, '=');
2122     if (p == NULL) {
2123       env = GetEnvironmentStrings ();
2124       if (WCMD_setshow_sortenv( env, s ) == 0) {
2125         WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2126         errorlevel = 1;
2127       }
2128       return;
2129     }
2130     *p++ = '\0';
2131
2132     if (strlenW(p) == 0) p = NULL;
2133     status = SetEnvironmentVariable (s, p);
2134     gle = GetLastError();
2135     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2136       errorlevel = 1;
2137     } else if ((!status)) WCMD_print_error();
2138   }
2139 }
2140
2141 /****************************************************************************
2142  * WCMD_setshow_path
2143  *
2144  * Set/Show the path environment variable
2145  */
2146
2147 void WCMD_setshow_path (WCHAR *command) {
2148
2149   WCHAR string[1024];
2150   DWORD status;
2151   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2152   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2153
2154   if (strlenW(param1) == 0) {
2155     status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2156     if (status != 0) {
2157       WCMD_output_asis ( pathEqW);
2158       WCMD_output_asis ( string);
2159       WCMD_output_asis ( newline);
2160     }
2161     else {
2162       WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2163     }
2164   }
2165   else {
2166     if (*command == '=') command++; /* Skip leading '=' */
2167     status = SetEnvironmentVariable (pathW, command);
2168     if (!status) WCMD_print_error();
2169   }
2170 }
2171
2172 /****************************************************************************
2173  * WCMD_setshow_prompt
2174  *
2175  * Set or show the command prompt.
2176  */
2177
2178 void WCMD_setshow_prompt (void) {
2179
2180   WCHAR *s;
2181   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2182
2183   if (strlenW(param1) == 0) {
2184     SetEnvironmentVariable (promptW, NULL);
2185   }
2186   else {
2187     s = param1;
2188     while ((*s == '=') || (*s == ' ')) s++;
2189     if (strlenW(s) == 0) {
2190       SetEnvironmentVariable (promptW, NULL);
2191     }
2192     else SetEnvironmentVariable (promptW, s);
2193   }
2194 }
2195
2196 /****************************************************************************
2197  * WCMD_setshow_time
2198  *
2199  * Set/Show the system time
2200  * FIXME: Can't change time yet
2201  */
2202
2203 void WCMD_setshow_time (void) {
2204
2205   WCHAR curtime[64], buffer[64];
2206   DWORD count;
2207   SYSTEMTIME st;
2208   static const WCHAR parmT[] = {'/','T','\0'};
2209
2210   if (strlenW(param1) == 0) {
2211     GetLocalTime(&st);
2212     if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2213                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2214       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2215       if (strstrW (quals, parmT) == NULL) {
2216         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2217         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2218                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2219         if (count > 2) {
2220           WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2221         }
2222       }
2223     }
2224     else WCMD_print_error ();
2225   }
2226   else {
2227     WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2228   }
2229 }
2230
2231 /****************************************************************************
2232  * WCMD_shift
2233  *
2234  * Shift batch parameters.
2235  * Optional /n says where to start shifting (n=0-8)
2236  */
2237
2238 void WCMD_shift (WCHAR *command) {
2239   int start;
2240
2241   if (context != NULL) {
2242     WCHAR *pos = strchrW(command, '/');
2243     int   i;
2244
2245     if (pos == NULL) {
2246       start = 0;
2247     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2248       start = (*(pos+1) - '0');
2249     } else {
2250       SetLastError(ERROR_INVALID_PARAMETER);
2251       WCMD_print_error();
2252       return;
2253     }
2254
2255     WINE_TRACE("Shifting variables, starting at %d\n", start);
2256     for (i=start;i<=8;i++) {
2257       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2258     }
2259     context -> shift_count[9] = context -> shift_count[9] + 1;
2260   }
2261
2262 }
2263
2264 /****************************************************************************
2265  * WCMD_title
2266  *
2267  * Set the console title
2268  */
2269 void WCMD_title (WCHAR *command) {
2270   SetConsoleTitle(command);
2271 }
2272
2273 /****************************************************************************
2274  * WCMD_type
2275  *
2276  * Copy a file to standard output.
2277  */
2278
2279 void WCMD_type (WCHAR *command) {
2280
2281   int   argno         = 0;
2282   WCHAR *argN          = command;
2283   BOOL  writeHeaders  = FALSE;
2284
2285   if (param1[0] == 0x00) {
2286     WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2287     return;
2288   }
2289
2290   if (param2[0] != 0x00) writeHeaders = TRUE;
2291
2292   /* Loop through all args */
2293   errorlevel = 0;
2294   while (argN) {
2295     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2296
2297     HANDLE h;
2298     WCHAR buffer[512];
2299     DWORD count;
2300
2301     if (!argN) break;
2302
2303     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2304     h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2305                 FILE_ATTRIBUTE_NORMAL, NULL);
2306     if (h == INVALID_HANDLE_VALUE) {
2307       WCMD_print_error ();
2308       WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2309       errorlevel = 1;
2310     } else {
2311       if (writeHeaders) {
2312         static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2313         WCMD_output(fmt, thisArg);
2314       }
2315       while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2316         if (count == 0) break;  /* ReadFile reports success on EOF! */
2317         buffer[count] = 0;
2318         WCMD_output_asis (buffer);
2319       }
2320       CloseHandle (h);
2321       if (!writeHeaders)
2322           WCMD_output_asis (newline);
2323     }
2324   }
2325 }
2326
2327 /****************************************************************************
2328  * WCMD_more
2329  *
2330  * Output either a file or stdin to screen in pages
2331  */
2332
2333 void WCMD_more (WCHAR *command) {
2334
2335   int   argno         = 0;
2336   WCHAR *argN          = command;
2337   BOOL  useinput      = FALSE;
2338   WCHAR  moreStr[100];
2339   WCHAR  moreStrPage[100];
2340   WCHAR  buffer[512];
2341   DWORD count;
2342   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2343   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2344   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2345                                     ')',' ','-','-','\n','\0'};
2346   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2347
2348   /* Prefix the NLS more with '-- ', then load the text */
2349   errorlevel = 0;
2350   strcpyW(moreStr, moreStart);
2351   LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2352               (sizeof(moreStr)/sizeof(WCHAR))-3);
2353
2354   if (param1[0] == 0x00) {
2355
2356     /* Wine implements pipes via temporary files, and hence stdin is
2357        effectively reading from the file. This means the prompts for
2358        more are satisfied by the next line from the input (file). To
2359        avoid this, ensure stdin is to the console                    */
2360     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2361     HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2362                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2363                          FILE_ATTRIBUTE_NORMAL, 0);
2364     WINE_TRACE("No parms - working probably in pipe mode\n");
2365     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2366
2367     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2368        once you get in this bit unless due to a pipe, its going to end badly...  */
2369     useinput = TRUE;
2370     wsprintf(moreStrPage, moreFmt, moreStr);
2371
2372     WCMD_enter_paged_mode(moreStrPage);
2373     while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2374       if (count == 0) break;    /* ReadFile reports success on EOF! */
2375       buffer[count] = 0;
2376       WCMD_output_asis (buffer);
2377     }
2378     WCMD_leave_paged_mode();
2379
2380     /* Restore stdin to what it was */
2381     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2382     CloseHandle(hConIn);
2383
2384     return;
2385   } else {
2386     BOOL needsPause = FALSE;
2387
2388     /* Loop through all args */
2389     WINE_TRACE("Parms supplied - working through each file\n");
2390     WCMD_enter_paged_mode(moreStrPage);
2391
2392     while (argN) {
2393       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2394       HANDLE h;
2395
2396       if (!argN) break;
2397
2398       if (needsPause) {
2399
2400         /* Wait */
2401         wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2402         WCMD_leave_paged_mode();
2403         WCMD_output_asis(moreStrPage);
2404         WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2405                        sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2406         WCMD_enter_paged_mode(moreStrPage);
2407       }
2408
2409
2410       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2411       h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2412                 FILE_ATTRIBUTE_NORMAL, NULL);
2413       if (h == INVALID_HANDLE_VALUE) {
2414         WCMD_print_error ();
2415         WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2416         errorlevel = 1;
2417       } else {
2418         ULONG64 curPos  = 0;
2419         ULONG64 fileLen = 0;
2420         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2421
2422         /* Get the file size */
2423         GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2424         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2425
2426         needsPause = TRUE;
2427         while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2428           if (count == 0) break;        /* ReadFile reports success on EOF! */
2429           buffer[count] = 0;
2430           curPos += count;
2431
2432           /* Update % count (would be used in WCMD_output_asis as prompt) */
2433           wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2434
2435           WCMD_output_asis (buffer);
2436         }
2437         CloseHandle (h);
2438       }
2439     }
2440
2441     WCMD_leave_paged_mode();
2442   }
2443 }
2444
2445 /****************************************************************************
2446  * WCMD_verify
2447  *
2448  * Display verify flag.
2449  * FIXME: We don't actually do anything with the verify flag other than toggle
2450  * it...
2451  */
2452
2453 void WCMD_verify (WCHAR *command) {
2454
2455   int count;
2456
2457   count = strlenW(command);
2458   if (count == 0) {
2459     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2460     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2461     return;
2462   }
2463   if (lstrcmpiW(command, onW) == 0) {
2464     verify_mode = 1;
2465     return;
2466   }
2467   else if (lstrcmpiW(command, offW) == 0) {
2468     verify_mode = 0;
2469     return;
2470   }
2471   else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2472 }
2473
2474 /****************************************************************************
2475  * WCMD_version
2476  *
2477  * Display version info.
2478  */
2479
2480 void WCMD_version (void) {
2481
2482   WCMD_output (version_string);
2483
2484 }
2485
2486 /****************************************************************************
2487  * WCMD_volume
2488  *
2489  * Display volume info and/or set volume label. Returns 0 if error.
2490  */
2491
2492 int WCMD_volume (int mode, WCHAR *path) {
2493
2494   DWORD count, serial;
2495   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2496   BOOL status;
2497
2498   if (strlenW(path) == 0) {
2499     status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2500     if (!status) {
2501       WCMD_print_error ();
2502       return 0;
2503     }
2504     status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2505                                    &serial, NULL, NULL, NULL, 0);
2506   }
2507   else {
2508     static const WCHAR fmt[] = {'%','s','\\','\0'};
2509     if ((path[1] != ':') || (strlenW(path) != 2)) {
2510       WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2511       return 0;
2512     }
2513     wsprintf (curdir, fmt, path);
2514     status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2515                                    &serial, NULL,
2516         NULL, NULL, 0);
2517   }
2518   if (!status) {
2519     WCMD_print_error ();
2520     return 0;
2521   }
2522   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2523         curdir[0], label, HIWORD(serial), LOWORD(serial));
2524   if (mode) {
2525     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2526     WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2527                    sizeof(string)/sizeof(WCHAR), &count, NULL);
2528     if (count > 1) {
2529       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2530       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2531     }
2532     if (strlenW(path) != 0) {
2533       if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2534     }
2535     else {
2536       if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2537     }
2538   }
2539   return 1;
2540 }
2541
2542 /**************************************************************************
2543  * WCMD_exit
2544  *
2545  * Exit either the process, or just this batch program
2546  *
2547  */
2548
2549 void WCMD_exit (CMD_LIST **cmdList) {
2550
2551     static const WCHAR parmB[] = {'/','B','\0'};
2552     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2553
2554     if (context && lstrcmpiW(quals, parmB) == 0) {
2555         errorlevel = rc;
2556         context -> skip_rest = TRUE;
2557         *cmdList = NULL;
2558     } else {
2559         ExitProcess(rc);
2560     }
2561 }
2562
2563
2564 /*****************************************************************************
2565  * WCMD_assoc
2566  *
2567  *      Lists or sets file associations  (assoc = TRUE)
2568  *      Lists or sets file types         (assoc = FALSE)
2569  */
2570 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2571
2572     HKEY    key;
2573     DWORD   accessOptions = KEY_READ;
2574     WCHAR   *newValue;
2575     LONG    rc = ERROR_SUCCESS;
2576     WCHAR    keyValue[MAXSTRING];
2577     DWORD   valueLen = MAXSTRING;
2578     HKEY    readKey;
2579     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2580                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2581
2582     /* See if parameter includes '=' */
2583     errorlevel = 0;
2584     newValue = strchrW(command, '=');
2585     if (newValue) accessOptions |= KEY_WRITE;
2586
2587     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2588     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2589                      accessOptions, &key) != ERROR_SUCCESS) {
2590       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2591       return;
2592     }
2593
2594     /* If no parameters then list all associations */
2595     if (*command == 0x00) {
2596       int index = 0;
2597
2598       /* Enumerate all the keys */
2599       while (rc != ERROR_NO_MORE_ITEMS) {
2600         WCHAR  keyName[MAXSTRING];
2601         DWORD nameLen;
2602
2603         /* Find the next value */
2604         nameLen = MAXSTRING;
2605         rc = RegEnumKeyEx(key, index++,
2606                           keyName, &nameLen,
2607                           NULL, NULL, NULL, NULL);
2608
2609         if (rc == ERROR_SUCCESS) {
2610
2611           /* Only interested in extension ones if assoc, or others
2612              if not assoc                                          */
2613           if ((keyName[0] == '.' && assoc) ||
2614               (!(keyName[0] == '.') && (!assoc)))
2615           {
2616             WCHAR subkey[MAXSTRING];
2617             strcpyW(subkey, keyName);
2618             if (!assoc) strcatW(subkey, shOpCmdW);
2619
2620             if (RegOpenKeyEx(key, subkey, 0,
2621                              accessOptions, &readKey) == ERROR_SUCCESS) {
2622
2623               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2624               rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2625                                    (LPBYTE)keyValue, &valueLen);
2626               WCMD_output_asis(keyName);
2627               WCMD_output_asis(equalW);
2628               /* If no default value found, leave line empty after '=' */
2629               if (rc == ERROR_SUCCESS) {
2630                 WCMD_output_asis(keyValue);
2631               }
2632               WCMD_output_asis(newline);
2633               RegCloseKey(readKey);
2634             }
2635           }
2636         }
2637       }
2638
2639     } else {
2640
2641       /* Parameter supplied - if no '=' on command line, its a query */
2642       if (newValue == NULL) {
2643         WCHAR *space;
2644         WCHAR subkey[MAXSTRING];
2645
2646         /* Query terminates the parameter at the first space */
2647         strcpyW(keyValue, command);
2648         space = strchrW(keyValue, ' ');
2649         if (space) *space=0x00;
2650
2651         /* Set up key name */
2652         strcpyW(subkey, keyValue);
2653         if (!assoc) strcatW(subkey, shOpCmdW);
2654
2655         if (RegOpenKeyEx(key, subkey, 0,
2656                          accessOptions, &readKey) == ERROR_SUCCESS) {
2657
2658           rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2659                                (LPBYTE)keyValue, &valueLen);
2660           WCMD_output_asis(command);
2661           WCMD_output_asis(equalW);
2662           /* If no default value found, leave line empty after '=' */
2663           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2664           WCMD_output_asis(newline);
2665           RegCloseKey(readKey);
2666
2667         } else {
2668           WCHAR  msgbuffer[MAXSTRING];
2669           WCHAR  outbuffer[MAXSTRING];
2670
2671           /* Load the translated 'File association not found' */
2672           if (assoc) {
2673             LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2674           } else {
2675             LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2676           }
2677           wsprintf(outbuffer, msgbuffer, keyValue);
2678           WCMD_output_asis(outbuffer);
2679           errorlevel = 2;
2680         }
2681
2682       /* Not a query - its a set or clear of a value */
2683       } else {
2684
2685         WCHAR subkey[MAXSTRING];
2686
2687         /* Get pointer to new value */
2688         *newValue = 0x00;
2689         newValue++;
2690
2691         /* Set up key name */
2692         strcpyW(subkey, command);
2693         if (!assoc) strcatW(subkey, shOpCmdW);
2694
2695         /* If nothing after '=' then clear value - only valid for ASSOC */
2696         if (*newValue == 0x00) {
2697
2698           if (assoc) rc = RegDeleteKey(key, command);
2699           if (assoc && rc == ERROR_SUCCESS) {
2700             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2701
2702           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2703             WCMD_print_error();
2704             errorlevel = 2;
2705
2706           } else {
2707             WCHAR  msgbuffer[MAXSTRING];
2708             WCHAR  outbuffer[MAXSTRING];
2709
2710             /* Load the translated 'File association not found' */
2711             if (assoc) {
2712               LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2713                           sizeof(msgbuffer)/sizeof(WCHAR));
2714             } else {
2715               LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2716                           sizeof(msgbuffer)/sizeof(WCHAR));
2717             }
2718             wsprintf(outbuffer, msgbuffer, keyValue);
2719             WCMD_output_asis(outbuffer);
2720             errorlevel = 2;
2721           }
2722
2723         /* It really is a set value = contents */
2724         } else {
2725           rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2726                               accessOptions, NULL, &readKey, NULL);
2727           if (rc == ERROR_SUCCESS) {
2728             rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2729                                  (LPBYTE)newValue, strlenW(newValue));
2730             RegCloseKey(readKey);
2731           }
2732
2733           if (rc != ERROR_SUCCESS) {
2734             WCMD_print_error();
2735             errorlevel = 2;
2736           } else {
2737             WCMD_output_asis(command);
2738             WCMD_output_asis(equalW);
2739             WCMD_output_asis(newValue);
2740             WCMD_output_asis(newline);
2741           }
2742         }
2743       }
2744     }
2745
2746     /* Clean up */
2747     RegCloseKey(key);
2748 }
2749
2750 /****************************************************************************
2751  * WCMD_color
2752  *
2753  * Clear the terminal screen.
2754  */
2755
2756 void WCMD_color (void) {
2757
2758   /* Emulate by filling the screen from the top left to bottom right with
2759         spaces, then moving the cursor to the top left afterwards */
2760   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2761   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2762
2763   if (param1[0] != 0x00 && strlenW(param1) > 2) {
2764     WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2765     return;
2766   }
2767
2768   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2769   {
2770       COORD topLeft;
2771       DWORD screenSize;
2772       DWORD color = 0;
2773
2774       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2775
2776       topLeft.X = 0;
2777       topLeft.Y = 0;
2778
2779       /* Convert the color hex digits */
2780       if (param1[0] == 0x00) {
2781         color = defaultColor;
2782       } else {
2783         color = strtoulW(param1, NULL, 16);
2784       }
2785
2786       /* Fail if fg == bg color */
2787       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2788         errorlevel = 1;
2789         return;
2790       }
2791
2792       /* Set the current screen contents and ensure all future writes
2793          remain this color                                             */
2794       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2795       SetConsoleTextAttribute(hStdOut, color);
2796   }
2797 }