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