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