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