wininet/tests: Improve error reporting in a couple of tests.
[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  * FIXME:
24  * - No support for pipes, shell parameters
25  * - Lots of functionality missing from builtins
26  * - Messages etc need international support
27  */
28
29 #define WIN32_LEAN_AND_MEAN
30
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
36
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
40
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[]    = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[]   = {'\0'};
45 const WCHAR starW[]   = {'*','\0'};
46 const WCHAR slashW[]  = {'\\','\0'};
47 const WCHAR equalW[]  = {'=','\0'};
48 const WCHAR wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR deviceW[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt[][10] = {
52         {'C','A','L','L','\0'},
53         {'C','D','\0'},
54         {'C','H','D','I','R','\0'},
55         {'C','L','S','\0'},
56         {'C','O','P','Y','\0'},
57         {'C','T','T','Y','\0'},
58         {'D','A','T','E','\0'},
59         {'D','E','L','\0'},
60         {'D','I','R','\0'},
61         {'E','C','H','O','\0'},
62         {'E','R','A','S','E','\0'},
63         {'F','O','R','\0'},
64         {'G','O','T','O','\0'},
65         {'H','E','L','P','\0'},
66         {'I','F','\0'},
67         {'L','A','B','E','L','\0'},
68         {'M','D','\0'},
69         {'M','K','D','I','R','\0'},
70         {'M','O','V','E','\0'},
71         {'P','A','T','H','\0'},
72         {'P','A','U','S','E','\0'},
73         {'P','R','O','M','P','T','\0'},
74         {'R','E','M','\0'},
75         {'R','E','N','\0'},
76         {'R','E','N','A','M','E','\0'},
77         {'R','D','\0'},
78         {'R','M','D','I','R','\0'},
79         {'S','E','T','\0'},
80         {'S','H','I','F','T','\0'},
81         {'S','T','A','R','T','\0'},
82         {'T','I','M','E','\0'},
83         {'T','I','T','L','E','\0'},
84         {'T','Y','P','E','\0'},
85         {'V','E','R','I','F','Y','\0'},
86         {'V','E','R','\0'},
87         {'V','O','L','\0'},
88         {'E','N','D','L','O','C','A','L','\0'},
89         {'S','E','T','L','O','C','A','L','\0'},
90         {'P','U','S','H','D','\0'},
91         {'P','O','P','D','\0'},
92         {'A','S','S','O','C','\0'},
93         {'C','O','L','O','R','\0'},
94         {'F','T','Y','P','E','\0'},
95         {'M','O','R','E','\0'},
96         {'C','H','O','I','C','E','\0'},
97         {'E','X','I','T','\0'}
98 };
99 static const WCHAR externals[][10] = {
100         {'A','T','T','R','I','B','\0'},
101         {'X','C','O','P','Y','\0'}
102 };
103 static const WCHAR fslashW[] = {'/','\0'};
104 static const WCHAR onW[]  = {'O','N','\0'};
105 static const WCHAR offW[] = {'O','F','F','\0'};
106 static const WCHAR parmY[] = {'/','Y','\0'};
107 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW[]   = {'=','=','\0'};
109
110 static HINSTANCE hinst;
111 struct env_stack *saved_environment;
112 static BOOL verify_mode = FALSE;
113
114 /* set /a routines work from single character operators, but some of the
115    operators are multiple character ones, especially the assignment ones.
116    Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE     'P'
118 #define OP_NEGATIVE     'N'
119 #define OP_ASSSIGNMUL   'a'
120 #define OP_ASSSIGNDIV   'b'
121 #define OP_ASSSIGNMOD   'c'
122 #define OP_ASSSIGNADD   'd'
123 #define OP_ASSSIGNSUB   'e'
124 #define OP_ASSSIGNAND   'f'
125 #define OP_ASSSIGNNOT   'g'
126 #define OP_ASSSIGNOR    'h'
127 #define OP_ASSSIGNSHL   'i'
128 #define OP_ASSSIGNSHR   'j'
129
130 /* This maintains a stack of operators, holding both the operator precedence
131    and the single character representation of the operator in question       */
132 typedef struct _OPSTACK
133 {
134   int              precedence;
135   WCHAR            op;
136   struct _OPSTACK *next;
137 } OPSTACK;
138
139 /* This maintains a stack of values, where each value can either be a
140    numeric value, or a string represeting an environment variable     */
141 typedef struct _VARSTACK
142 {
143   BOOL              isnum;
144   WCHAR            *variable;
145   int               value;
146   struct _VARSTACK *next;
147 } VARSTACK;
148
149 /* This maintains a mapping between the calculated operator and the
150    single character representation for the assignment operators.    */
151 static struct
152 {
153   WCHAR op;
154   WCHAR calculatedop;
155 } calcassignments[] =
156 {
157   {'*', OP_ASSSIGNMUL},
158   {'/', OP_ASSSIGNDIV},
159   {'%', OP_ASSSIGNMOD},
160   {'+', OP_ASSSIGNADD},
161   {'-', OP_ASSSIGNSUB},
162   {'&', OP_ASSSIGNAND},
163   {'^', OP_ASSSIGNNOT},
164   {'|', OP_ASSSIGNOR},
165   {'<', OP_ASSSIGNSHL},
166   {'>', OP_ASSSIGNSHR},
167   {' ',' '}
168 };
169
170 /**************************************************************************
171  * WCMD_ask_confirm
172  *
173  * Issue a message and ask for confirmation, waiting on a valid answer.
174  *
175  * Returns True if Y (or A) answer is selected
176  *         If optionAll contains a pointer, ALL is allowed, and if answered
177  *                   set to TRUE
178  *
179  */
180 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
181                               BOOL *optionAll) {
182
183     UINT msgid;
184     WCHAR confirm[MAXSTRING];
185     WCHAR options[MAXSTRING];
186     WCHAR Ybuffer[MAXSTRING];
187     WCHAR Nbuffer[MAXSTRING];
188     WCHAR Abuffer[MAXSTRING];
189     WCHAR answer[MAX_PATH] = {'\0'};
190     DWORD count = 0;
191
192     /* Load the translated valid answers */
193     if (showSureText)
194       LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
195     msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
196     LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
197     LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
198     LoadStringW(hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
199     LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
200
201     /* Loop waiting on a valid answer */
202     if (optionAll)
203         *optionAll = FALSE;
204     while (1)
205     {
206       WCMD_output_asis (message);
207       if (showSureText)
208         WCMD_output_asis (confirm);
209       WCMD_output_asis (options);
210       WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
211       answer[0] = toupperW(answer[0]);
212       if (answer[0] == Ybuffer[0])
213         return TRUE;
214       if (answer[0] == Nbuffer[0])
215         return FALSE;
216       if (optionAll && answer[0] == Abuffer[0])
217       {
218         *optionAll = TRUE;
219         return TRUE;
220       }
221     }
222 }
223
224 /****************************************************************************
225  * WCMD_clear_screen
226  *
227  * Clear the terminal screen.
228  */
229
230 void WCMD_clear_screen (void) {
231
232   /* Emulate by filling the screen from the top left to bottom right with
233         spaces, then moving the cursor to the top left afterwards */
234   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
235   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
236
237   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
238   {
239       COORD topLeft;
240       DWORD screenSize;
241
242       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
243
244       topLeft.X = 0;
245       topLeft.Y = 0;
246       FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
247       SetConsoleCursorPosition(hStdOut, topLeft);
248   }
249 }
250
251 /****************************************************************************
252  * WCMD_change_tty
253  *
254  * Change the default i/o device (ie redirect STDin/STDout).
255  */
256
257 void WCMD_change_tty (void) {
258
259   WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
260
261 }
262
263 /****************************************************************************
264  * WCMD_choice
265  *
266  */
267
268 void WCMD_choice (const WCHAR * args) {
269
270     static const WCHAR bellW[] = {7,0};
271     static const WCHAR commaW[] = {',',0};
272     static const WCHAR bracket_open[] = {'[',0};
273     static const WCHAR bracket_close[] = {']','?',0};
274     WCHAR answer[16];
275     WCHAR buffer[16];
276     WCHAR *ptr = NULL;
277     WCHAR *opt_c = NULL;
278     WCHAR *my_command = NULL;
279     WCHAR opt_default = 0;
280     DWORD opt_timeout = 0;
281     DWORD count;
282     DWORD oldmode;
283     DWORD have_console;
284     BOOL opt_n = FALSE;
285     BOOL opt_s = FALSE;
286
287     have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
288     errorlevel = 0;
289
290     my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
291
292     ptr = WCMD_skip_leading_spaces(my_command);
293     while (*ptr == '/') {
294         switch (toupperW(ptr[1])) {
295             case 'C':
296                 ptr += 2;
297                 /* the colon is optional */
298                 if (*ptr == ':')
299                     ptr++;
300
301                 if (!*ptr || isspaceW(*ptr)) {
302                     WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
303                     heap_free(my_command);
304                     return;
305                 }
306
307                 /* remember the allowed keys (overwrite previous /C option) */
308                 opt_c = ptr;
309                 while (*ptr && (!isspaceW(*ptr)))
310                     ptr++;
311
312                 if (*ptr) {
313                     /* terminate allowed chars */
314                     *ptr = 0;
315                     ptr = WCMD_skip_leading_spaces(&ptr[1]);
316                 }
317                 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
318                 break;
319
320             case 'N':
321                 opt_n = TRUE;
322                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
323                 break;
324
325             case 'S':
326                 opt_s = TRUE;
327                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
328                 break;
329
330             case 'T':
331                 ptr = &ptr[2];
332                 /* the colon is optional */
333                 if (*ptr == ':')
334                     ptr++;
335
336                 opt_default = *ptr++;
337
338                 if (!opt_default || (*ptr != ',')) {
339                     WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
340                     heap_free(my_command);
341                     return;
342                 }
343                 ptr++;
344
345                 count = 0;
346                 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
347                     count++;
348                     ptr++;
349                 }
350
351                 answer[count] = 0;
352                 opt_timeout = atoiW(answer);
353
354                 ptr = WCMD_skip_leading_spaces(ptr);
355                 break;
356
357             default:
358                 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
359                 heap_free(my_command);
360                 return;
361         }
362     }
363
364     if (opt_timeout)
365         WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
366
367     if (have_console)
368         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
369
370     /* use default keys, when needed: localized versions of "Y"es and "No" */
371     if (!opt_c) {
372         LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
373         LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
374         opt_c = buffer;
375         buffer[2] = 0;
376     }
377
378     /* print the question, when needed */
379     if (*ptr)
380         WCMD_output_asis(ptr);
381
382     if (!opt_s) {
383         struprW(opt_c);
384         WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
385     }
386
387     if (!opt_n) {
388         /* print a list of all allowed answers inside brackets */
389         WCMD_output_asis(bracket_open);
390         ptr = opt_c;
391         answer[1] = 0;
392         while ((answer[0] = *ptr++)) {
393             WCMD_output_asis(answer);
394             if (*ptr)
395                 WCMD_output_asis(commaW);
396         }
397         WCMD_output_asis(bracket_close);
398     }
399
400     while (TRUE) {
401
402         /* FIXME: Add support for option /T */
403         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
404
405         if (!opt_s)
406             answer[0] = toupperW(answer[0]);
407
408         ptr = strchrW(opt_c, answer[0]);
409         if (ptr) {
410             WCMD_output_asis(answer);
411             WCMD_output_asis(newlineW);
412             if (have_console)
413                 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
414
415             errorlevel = (ptr - opt_c) + 1;
416             WINE_TRACE("answer: %d\n", errorlevel);
417             heap_free(my_command);
418             return;
419         }
420         else
421         {
422             /* key not allowed: play the bell */
423             WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
424             WCMD_output_asis(bellW);
425         }
426     }
427 }
428
429 /****************************************************************************
430  * WCMD_AppendEOF
431  *
432  * Adds an EOF onto the end of a file
433  * Returns TRUE on success
434  */
435 static BOOL WCMD_AppendEOF(WCHAR *filename)
436 {
437     HANDLE h;
438
439     char eof = '\x1a';
440
441     WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
442     h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
443                     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
444
445     if (h == NULL) {
446       WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
447       return FALSE;
448     } else {
449       SetFilePointer (h, 0, NULL, FILE_END);
450       if (!WriteFile(h, &eof, 1, NULL, NULL)) {
451         WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
452         CloseHandle(h);
453         return FALSE;
454       }
455       CloseHandle(h);
456     }
457     return TRUE;
458 }
459
460 /****************************************************************************
461  * WCMD_ManualCopy
462  *
463  * Copies from a file
464  *    optionally reading only until EOF (ascii copy)
465  *    optionally appending onto an existing file (append)
466  * Returns TRUE on success
467  */
468 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
469 {
470     HANDLE in,out;
471     BOOL   ok;
472     DWORD  bytesread, byteswritten;
473
474     WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
475                wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
476
477     in  = CreateFileW(srcname, GENERIC_READ, 0, NULL,
478                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
479     if (in == NULL) {
480       WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
481       return FALSE;
482     }
483
484     /* Open the output file, overwriting if not appending */
485     out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
486                       append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
487     if (out == NULL) {
488       WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
489       CloseHandle(in);
490       return FALSE;
491     }
492
493     /* Move to end of destination if we are going to append to it */
494     if (append) {
495       SetFilePointer(out, 0, NULL, FILE_END);
496     }
497
498     /* Loop copying data from source to destination until EOF read */
499     do
500     {
501       char buffer[MAXSTRING];
502
503       ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
504       if (ok) {
505
506         /* Stop at first EOF */
507         if (ascii) {
508           char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
509           if (ptr) bytesread = (ptr - buffer);
510         }
511
512         if (bytesread) {
513           ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
514           if (!ok || byteswritten != bytesread) {
515             WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
516                      wine_dbgstr_w(dstname), GetLastError());
517           }
518         }
519       } else {
520         WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
521                  wine_dbgstr_w(srcname), GetLastError());
522       }
523     } while (ok && bytesread > 0);
524
525     CloseHandle(out);
526     CloseHandle(in);
527     return ok;
528 }
529
530 /****************************************************************************
531  * WCMD_copy
532  *
533  * Copy a file or wildcarded set.
534  * For ascii/binary type copies, it gets complex:
535  *  Syntax on command line is
536  *   ... /a | /b   filename  /a /b {[ + filename /a /b]}  [dest /a /b]
537  *  Where first /a or /b sets 'mode in operation' until another is found
538  *  once another is found, it applies to the file preceding the /a or /b
539  *  In addition each filename can contain wildcards
540  * To make matters worse, the + may be in the same parameter (i.e. no
541  *  whitespace) or with whitespace separating it
542  *
543  * ASCII mode on read == read and stop at first EOF
544  * ASCII mode on write == append EOF to destination
545  * Binary == copy as-is
546  *
547  * Design of this is to build up a list of files which will be copied into a
548  * list, then work through the list file by file.
549  * If no destination is specified, it defaults to the name of the first file in
550  * the list, but the current directory.
551  *
552  */
553
554 void WCMD_copy(WCHAR * args) {
555
556   BOOL    opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
557   WCHAR  *thisparam;
558   int     argno = 0;
559   WCHAR  *rawarg;
560   WIN32_FIND_DATAW fd;
561   HANDLE  hff = INVALID_HANDLE_VALUE;
562   int     binarymode = -1;            /* -1 means use the default, 1 is binary, 0 ascii */
563   BOOL    concatnextfilename = FALSE; /* True if we have just processed a +             */
564   BOOL    anyconcats         = FALSE; /* Have we found any + options                    */
565   BOOL    appendfirstsource  = FALSE; /* Use first found filename as destination        */
566   BOOL    writtenoneconcat   = FALSE; /* Remember when the first concatenated file done */
567   BOOL    prompt;                     /* Prompt before overwriting                      */
568   WCHAR   destname[MAX_PATH];         /* Used in calculating the destination name       */
569   BOOL    destisdirectory = FALSE;    /* Is the destination a directory?                */
570   BOOL    status;
571   WCHAR   copycmd[4];
572   DWORD   len;
573   BOOL    dstisdevice = FALSE;
574   static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
575
576   typedef struct _COPY_FILES
577   {
578     struct _COPY_FILES *next;
579     BOOL                concatenate;
580     WCHAR              *name;
581     int                 binarycopy;
582   } COPY_FILES;
583   COPY_FILES *sourcelist    = NULL;
584   COPY_FILES *lastcopyentry = NULL;
585   COPY_FILES *destination   = NULL;
586   COPY_FILES *thiscopy      = NULL;
587   COPY_FILES *prevcopy      = NULL;
588
589   /* Assume we were successful! */
590   errorlevel = 0;
591
592   /* If no args supplied at all, report an error */
593   if (param1[0] == 0x00) {
594     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
595     errorlevel = 1;
596     return;
597   }
598
599   opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
600
601   /* Walk through all args, building up a list of files to process */
602   thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
603   while (*(thisparam)) {
604     WCHAR *pos1, *pos2;
605     BOOL inquotes;
606
607     WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
608
609     /* Handle switches */
610     if (*thisparam == '/') {
611         while (*thisparam == '/') {
612         thisparam++;
613         if (toupperW(*thisparam) == 'D') {
614           opt_d = TRUE;
615           if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
616         } else if (toupperW(*thisparam) == 'Y') {
617           opt_y = TRUE;
618         } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
619           opt_noty = TRUE;
620         } else if (toupperW(*thisparam) == 'V') {
621           opt_v = TRUE;
622           if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
623         } else if (toupperW(*thisparam) == 'N') {
624           opt_n = TRUE;
625           if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
626         } else if (toupperW(*thisparam) == 'Z') {
627           opt_z = TRUE;
628           if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
629         } else if (toupperW(*thisparam) == 'A') {
630           if (binarymode != 0) {
631             binarymode = 0;
632             WINE_TRACE("Subsequent files will be handled as ASCII\n");
633             if (destination != NULL) {
634               WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
635               destination->binarycopy = binarymode;
636             } else if (lastcopyentry != NULL) {
637               WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
638               lastcopyentry->binarycopy = binarymode;
639             }
640           }
641         } else if (toupperW(*thisparam) == 'B') {
642           if (binarymode != 1) {
643             binarymode = 1;
644             WINE_TRACE("Subsequent files will be handled as binary\n");
645             if (destination != NULL) {
646               WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
647               destination->binarycopy = binarymode;
648             } else if (lastcopyentry != NULL) {
649               WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
650               lastcopyentry->binarycopy = binarymode;
651             }
652           }
653         } else {
654           WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
655         }
656         thisparam++;
657       }
658
659       /* This parameter was purely switches, get the next one */
660       thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
661       continue;
662     }
663
664     /* We have found something which is not a switch. If could be anything of the form
665          sourcefilename (which could be destination too)
666          + (when filename + filename syntex used)
667          sourcefilename+sourcefilename
668          +sourcefilename
669          +/b[tests show windows then ignores to end of parameter]
670      */
671
672     if (*thisparam=='+') {
673       if (lastcopyentry == NULL) {
674         WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
675         errorlevel = 1;
676         goto exitreturn;
677       } else {
678         concatnextfilename = TRUE;
679         anyconcats         = TRUE;
680       }
681
682       /* Move to next thing to process */
683       thisparam++;
684       if (*thisparam == 0x00)
685         thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
686       continue;
687     }
688
689     /* We have found something to process - build a COPY_FILE block to store it */
690     thiscopy = heap_alloc(sizeof(COPY_FILES));
691
692     WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
693     thiscopy->concatenate = concatnextfilename;
694     thiscopy->binarycopy  = binarymode;
695     thiscopy->next        = NULL;
696
697     /* Time to work out the name. Allocate at least enough space (deliberately too much to
698        leave space to append \* to the end) , then copy in character by character. Strip off
699        quotes if we find them.                                                               */
700     len = strlenW(thisparam) + (sizeof(WCHAR) * 5);  /* 5 spare characters, null + \*.*      */
701     thiscopy->name = heap_alloc(len*sizeof(WCHAR));
702     memset(thiscopy->name, 0x00, len);
703
704     pos1 = thisparam;
705     pos2 = thiscopy->name;
706     inquotes = FALSE;
707     while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
708       if (*pos1 == '"') {
709         inquotes = !inquotes;
710         pos1++;
711       } else *pos2++ = *pos1++;
712     }
713     *pos2 = 0;
714     WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
715
716     /* This is either the first source, concatenated subsequent source or destination */
717     if (sourcelist == NULL) {
718       WINE_TRACE("Adding as first source part\n");
719       sourcelist = thiscopy;
720       lastcopyentry = thiscopy;
721     } else if (concatnextfilename) {
722       WINE_TRACE("Adding to source file list to be concatenated\n");
723       lastcopyentry->next = thiscopy;
724       lastcopyentry = thiscopy;
725     } else if (destination == NULL) {
726       destination = thiscopy;
727     } else {
728       /* We have processed sources and destinations and still found more to do - invalid */
729       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
730       errorlevel = 1;
731       goto exitreturn;
732     }
733     concatnextfilename    = FALSE;
734
735     /* We either need to process the rest of the parameter or move to the next */
736     if (*pos1 == '/' || *pos1 == '+') {
737       thisparam = pos1;
738       continue;
739     } else {
740       thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
741     }
742   }
743
744   /* Ensure we have at least one source file */
745   if (!sourcelist) {
746     WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
747     errorlevel = 1;
748     goto exitreturn;
749   }
750
751   /* Default whether automatic overwriting is on. If we are interactive then
752      we prompt by default, otherwise we overwrite by default
753      /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
754   if (opt_noty) prompt = TRUE;
755   else if (opt_y) prompt = FALSE;
756   else {
757     /* By default, we will force the overwrite in batch mode and ask for
758      * confirmation in interactive mode. */
759     prompt = interactive;
760     /* If COPYCMD is set, then we force the overwrite with /Y and ask for
761      * confirmation with /-Y. If COPYCMD is neither of those, then we use the
762      * default behavior. */
763     len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
764     if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
765       if (!lstrcmpiW (copycmd, parmY))
766         prompt = FALSE;
767       else if (!lstrcmpiW (copycmd, parmNoY))
768         prompt = TRUE;
769     }
770   }
771
772   /* Calculate the destination now - if none supplied, its current dir +
773      filename of first file in list*/
774   if (destination == NULL) {
775
776     WINE_TRACE("No destination supplied, so need to calculate it\n");
777     strcpyW(destname, dotW);
778     strcatW(destname, slashW);
779
780     destination = heap_alloc(sizeof(COPY_FILES));
781     if (destination == NULL) goto exitreturn;
782     destination->concatenate = FALSE;           /* Not used for destination */
783     destination->binarycopy  = binarymode;
784     destination->next        = NULL;            /* Not used for destination */
785     destination->name        = NULL;            /* To be filled in          */
786     destisdirectory          = TRUE;
787
788   } else {
789     WCHAR *filenamepart;
790     DWORD  attributes;
791
792     WINE_TRACE("Destination supplied, processing to see if file or directory\n");
793
794     /* Convert to fully qualified path/filename */
795     GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
796     WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
797
798     /* If parameter is a directory, ensure it ends in \ */
799     attributes = GetFileAttributesW(destname);
800     if ((destname[strlenW(destname) - 1] == '\\') ||
801         ((attributes != INVALID_FILE_ATTRIBUTES) &&
802          (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
803
804       destisdirectory = TRUE;
805       if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
806       WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
807     }
808   }
809
810   /* Normally, the destination is the current directory unless we are
811      concatenating, in which case its current directory plus first filename.
812      Note that if the
813      In addition by default it is a binary copy unless concatenating, when
814      the copy defaults to an ascii copy (stop at EOF). We do not know the
815      first source part yet (until we search) so flag as needing filling in. */
816
817   if (anyconcats) {
818     /* We have found an a+b type syntax, so destination has to be a filename
819        and we need to default to ascii copying. If we have been supplied a
820        directory as the destination, we need to defer calculating the name   */
821     if (destisdirectory) appendfirstsource = TRUE;
822     if (destination->binarycopy == -1) destination->binarycopy = 0;
823
824   } else if (!destisdirectory) {
825     /* We have been asked to copy to a filename. Default to ascii IF the
826        source contains wildcards (true even if only one match)           */
827     if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
828       anyconcats = TRUE;  /* We really are concatenating to a single file */
829       if (destination->binarycopy == -1) {
830         destination->binarycopy = 0;
831       }
832     } else {
833       if (destination->binarycopy == -1) {
834         destination->binarycopy = 1;
835       }
836     }
837   }
838
839   /* Save away the destination name*/
840   heap_free(destination->name);
841   destination->name = heap_strdupW(destname);
842   WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
843              wine_dbgstr_w(destname), appendfirstsource);
844
845   /* Remember if the destination is a device */
846   if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
847     WINE_TRACE("Destination is a device\n");
848     dstisdevice = TRUE;
849   }
850
851   /* Now we need to walk the set of sources, and process each name we come to.
852      If anyconcats is true, we are writing to one file, otherwise we are using
853      the source name each time.
854      If destination exists, prompt for overwrite the first time (if concatenating
855      we ask each time until yes is answered)
856      The first source file we come across must exist (when wildcards expanded)
857      and if concatenating with overwrite prompts, each source file must exist
858      until a yes is answered.                                                    */
859
860   thiscopy = sourcelist;
861   prevcopy = NULL;
862
863   while (thiscopy != NULL) {
864
865     WCHAR  srcpath[MAX_PATH];
866     const  WCHAR *srcname;
867     WCHAR *filenamepart;
868     DWORD  attributes;
869     BOOL   srcisdevice = FALSE;
870
871     /* If it was not explicit, we now know whether we are concatenating or not and
872        hence whether to copy as binary or ascii                                    */
873     if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
874
875     /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
876        to where the filename portion begins (used for wildcart expansion.              */
877     GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
878     WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
879
880     /* If parameter is a directory, ensure it ends in \* */
881     attributes = GetFileAttributesW(srcpath);
882     if (srcpath[strlenW(srcpath) - 1] == '\\') {
883
884       /* We need to know where the filename part starts, so append * and
885          recalculate the full resulting path                              */
886       strcatW(thiscopy->name, starW);
887       GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
888       WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
889
890     } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
891                (attributes != INVALID_FILE_ATTRIBUTES) &&
892                (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
893
894       /* We need to know where the filename part starts, so append \* and
895          recalculate the full resulting path                              */
896       strcatW(thiscopy->name, slashstarW);
897       GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
898       WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
899     }
900
901     WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
902                     wine_dbgstr_w(srcpath), anyconcats);
903
904     /* If the source is a device, just use it, otherwise search */
905     if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
906       WINE_TRACE("Source is a device\n");
907       srcisdevice = TRUE;
908       srcname  = &srcpath[4]; /* After the \\.\ prefix */
909     } else {
910
911       /* Loop through all source files */
912       WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
913       hff = FindFirstFileW(srcpath, &fd);
914       if (hff != INVALID_HANDLE_VALUE) {
915         srcname = fd.cFileName;
916       }
917     }
918
919     if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
920       do {
921         WCHAR outname[MAX_PATH];
922         BOOL  overwrite;
923
924         /* Skip . and .., and directories */
925         if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
926           WINE_TRACE("Skipping directories\n");
927         } else {
928
929           /* Build final destination name */
930           strcpyW(outname, destination->name);
931           if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
932
933           /* Build source name */
934           if (!srcisdevice) strcpyW(filenamepart, srcname);
935
936           /* Do we just overwrite (we do if we are writing to a device) */
937           overwrite = !prompt;
938           if (dstisdevice || (anyconcats && writtenoneconcat)) {
939             overwrite = TRUE;
940           }
941
942           WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
943           WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
944           WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
945                      thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
946
947           /* Prompt before overwriting */
948           if (!overwrite) {
949             DWORD attributes = GetFileAttributesW(outname);
950             if (attributes != INVALID_FILE_ATTRIBUTES) {
951               WCHAR* question;
952               question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
953               overwrite = WCMD_ask_confirm(question, FALSE, NULL);
954               LocalFree(question);
955             }
956             else overwrite = TRUE;
957           }
958
959           /* If we needed to save away the first filename, do it */
960           if (appendfirstsource && overwrite) {
961             heap_free(destination->name);
962             destination->name = heap_strdupW(outname);
963             WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
964             appendfirstsource = FALSE;
965             destisdirectory = FALSE;
966           }
967
968           /* Do the copy as appropriate */
969           if (overwrite) {
970             if (anyconcats && writtenoneconcat) {
971               if (thiscopy->binarycopy) {
972                 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
973               } else {
974                 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
975               }
976             } else if (!thiscopy->binarycopy) {
977               status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
978             } else if (srcisdevice) {
979               status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
980             } else {
981               status = CopyFileW(srcpath, outname, FALSE);
982             }
983             if (!status) {
984               WCMD_print_error ();
985               errorlevel = 1;
986             } else {
987               WINE_TRACE("Copied successfully\n");
988               if (anyconcats) writtenoneconcat = TRUE;
989
990               /* Append EOF if ascii destination and we are not going to add more onto the end
991                  Note: Testing shows windows has an optimization whereas if you have a binary
992                  copy of a file to a single destination (ie concatenation) then it does not add
993                  the EOF, hence the check on the source copy type below.                       */
994               if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
995                 if (!WCMD_AppendEOF(outname)) {
996                   WCMD_print_error ();
997                   errorlevel = 1;
998                 }
999               }
1000             }
1001           }
1002         }
1003       } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1004       if (!srcisdevice) FindClose (hff);
1005     } else {
1006       /* Error if the first file was not found */
1007       if (!anyconcats || (anyconcats && !writtenoneconcat)) {
1008         WCMD_print_error ();
1009         errorlevel = 1;
1010       }
1011     }
1012
1013     /* Step on to the next supplied source */
1014     thiscopy = thiscopy -> next;
1015   }
1016
1017   /* Append EOF if ascii destination and we were concatenating */
1018   if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1019     if (!WCMD_AppendEOF(destination->name)) {
1020       WCMD_print_error ();
1021       errorlevel = 1;
1022     }
1023   }
1024
1025   /* Exit out of the routine, freeing any remaining allocated memory */
1026 exitreturn:
1027
1028   thiscopy = sourcelist;
1029   while (thiscopy != NULL) {
1030     prevcopy = thiscopy;
1031     /* Free up this block*/
1032     thiscopy = thiscopy -> next;
1033     heap_free(prevcopy->name);
1034     heap_free(prevcopy);
1035   }
1036
1037   /* Free up the destination memory */
1038   if (destination) {
1039     heap_free(destination->name);
1040     heap_free(destination);
1041   }
1042
1043   return;
1044 }
1045
1046 /****************************************************************************
1047  * WCMD_create_dir
1048  *
1049  * Create a directory (and, if needed, any intermediate directories).
1050  *
1051  * Modifies its argument by replacing slashes temporarily with nulls.
1052  */
1053
1054 static BOOL create_full_path(WCHAR* path)
1055 {
1056     WCHAR *p, *start;
1057
1058     /* don't mess with drive letter portion of path, if any */
1059     start = path;
1060     if (path[1] == ':')
1061         start = path+2;
1062
1063     /* Strip trailing slashes. */
1064     for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1065         *p = 0;
1066
1067     /* Step through path, creating intermediate directories as needed. */
1068     /* First component includes drive letter, if any. */
1069     p = start;
1070     for (;;) {
1071         DWORD rv;
1072         /* Skip to end of component */
1073         while (*p == '\\') p++;
1074         while (*p && *p != '\\') p++;
1075         if (!*p) {
1076             /* path is now the original full path */
1077             return CreateDirectoryW(path, NULL);
1078         }
1079         /* Truncate path, create intermediate directory, and restore path */
1080         *p = 0;
1081         rv = CreateDirectoryW(path, NULL);
1082         *p = '\\';
1083         if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1084             return FALSE;
1085     }
1086     /* notreached */
1087     return FALSE;
1088 }
1089
1090 void WCMD_create_dir (WCHAR *args) {
1091     int   argno = 0;
1092     WCHAR *argN = args;
1093
1094     if (param1[0] == 0x00) {
1095         WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1096         return;
1097     }
1098     /* Loop through all args */
1099     while (TRUE) {
1100         WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1101         if (!argN) break;
1102         if (!create_full_path(thisArg)) {
1103             WCMD_print_error ();
1104             errorlevel = 1;
1105         }
1106     }
1107 }
1108
1109 /* Parse the /A options given by the user on the commandline
1110  * into a bitmask of wanted attributes (*wantSet),
1111  * and a bitmask of unwanted attributes (*wantClear).
1112  */
1113 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1114     static const WCHAR parmA[] = {'/','A','\0'};
1115     WCHAR *p;
1116
1117     /* both are strictly 'out' parameters */
1118     *wantSet=0;
1119     *wantClear=0;
1120
1121     /* For each /A argument */
1122     for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1123         /* Skip /A itself */
1124         p += 2;
1125
1126         /* Skip optional : */
1127         if (*p == ':') p++;
1128
1129         /* For each of the attribute specifier chars to this /A option */
1130         for (; *p != 0 && *p != '/'; p++) {
1131             BOOL negate = FALSE;
1132             DWORD mask  = 0;
1133
1134             if (*p == '-') {
1135                 negate=TRUE;
1136                 p++;
1137             }
1138
1139             /* Convert the attribute specifier to a bit in one of the masks */
1140             switch (*p) {
1141             case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1142             case 'H': mask = FILE_ATTRIBUTE_HIDDEN;   break;
1143             case 'S': mask = FILE_ATTRIBUTE_SYSTEM;   break;
1144             case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;  break;
1145             default:
1146                 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1147             }
1148             if (negate)
1149                 *wantClear |= mask;
1150             else
1151                 *wantSet |= mask;
1152         }
1153     }
1154 }
1155
1156 /* If filename part of parameter is * or *.*,
1157  * and neither /Q nor /P options were given,
1158  * prompt the user whether to proceed.
1159  * Returns FALSE if user says no, TRUE otherwise.
1160  * *pPrompted is set to TRUE if the user is prompted.
1161  * (If /P supplied, del will prompt for individual files later.)
1162  */
1163 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1164     static const WCHAR parmP[] = {'/','P','\0'};
1165     static const WCHAR parmQ[] = {'/','Q','\0'};
1166
1167     if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1168         static const WCHAR anyExt[]= {'.','*','\0'};
1169         WCHAR drive[10];
1170         WCHAR dir[MAX_PATH];
1171         WCHAR fname[MAX_PATH];
1172         WCHAR ext[MAX_PATH];
1173         WCHAR fpath[MAX_PATH];
1174
1175         /* Convert path into actual directory spec */
1176         GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1177         WCMD_splitpath(fpath, drive, dir, fname, ext);
1178
1179         /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1180         if ((strcmpW(fname, starW) == 0) &&
1181             (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1182
1183             WCHAR question[MAXSTRING];
1184             static const WCHAR fmt[] = {'%','s',' ','\0'};
1185
1186             /* Caller uses this to suppress "file not found" warning later */
1187             *pPrompted = TRUE;
1188
1189             /* Ask for confirmation */
1190             wsprintfW(question, fmt, fpath);
1191             return WCMD_ask_confirm(question, TRUE, NULL);
1192         }
1193     }
1194     /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1195     return TRUE;
1196 }
1197
1198 /* Helper function for WCMD_delete().
1199  * Deletes a single file, directory, or wildcard.
1200  * If /S was given, does it recursively.
1201  * Returns TRUE if a file was deleted.
1202  */
1203 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1204
1205     static const WCHAR parmP[] = {'/','P','\0'};
1206     static const WCHAR parmS[] = {'/','S','\0'};
1207     static const WCHAR parmF[] = {'/','F','\0'};
1208     DWORD wanted_attrs;
1209     DWORD unwanted_attrs;
1210     BOOL found = FALSE;
1211     WCHAR argCopy[MAX_PATH];
1212     WIN32_FIND_DATAW fd;
1213     HANDLE hff;
1214     WCHAR fpath[MAX_PATH];
1215     WCHAR *p;
1216     BOOL handleParm = TRUE;
1217
1218     WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1219
1220     strcpyW(argCopy, thisArg);
1221     WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1222                wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1223
1224     if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1225         /* Skip this arg if user declines to delete *.* */
1226         return FALSE;
1227     }
1228
1229     /* First, try to delete in the current directory */
1230     hff = FindFirstFileW(argCopy, &fd);
1231     if (hff == INVALID_HANDLE_VALUE) {
1232       handleParm = FALSE;
1233     } else {
1234       found = TRUE;
1235     }
1236
1237     /* Support del <dirname> by just deleting all files dirname\* */
1238     if (handleParm
1239         && (strchrW(argCopy,'*') == NULL)
1240         && (strchrW(argCopy,'?') == NULL)
1241         && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1242     {
1243       WCHAR modifiedParm[MAX_PATH];
1244       static const WCHAR slashStar[] = {'\\','*','\0'};
1245
1246       strcpyW(modifiedParm, argCopy);
1247       strcatW(modifiedParm, slashStar);
1248       FindClose(hff);
1249       found = TRUE;
1250       WCMD_delete_one(modifiedParm);
1251
1252     } else if (handleParm) {
1253
1254       /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1255       strcpyW (fpath, argCopy);
1256       do {
1257         p = strrchrW (fpath, '\\');
1258         if (p != NULL) {
1259           *++p = '\0';
1260           strcatW (fpath, fd.cFileName);
1261         }
1262         else strcpyW (fpath, fd.cFileName);
1263         if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1264           BOOL ok;
1265
1266           /* Handle attribute matching (/A) */
1267           ok =  ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1268              && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1269
1270           /* /P means prompt for each file */
1271           if (ok && strstrW (quals, parmP) != NULL) {
1272             WCHAR* question;
1273
1274             /* Ask for confirmation */
1275             question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1276             ok = WCMD_ask_confirm(question, FALSE, NULL);
1277             LocalFree(question);
1278           }
1279
1280           /* Only proceed if ok to */
1281           if (ok) {
1282
1283             /* If file is read only, and /A:r or /F supplied, delete it */
1284             if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1285                 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1286                 strstrW (quals, parmF) != NULL)) {
1287                 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1288             }
1289
1290             /* Now do the delete */
1291             if (!DeleteFileW(fpath)) WCMD_print_error ();
1292           }
1293
1294         }
1295       } while (FindNextFileW(hff, &fd) != 0);
1296       FindClose (hff);
1297     }
1298
1299     /* Now recurse into all subdirectories handling the parameter in the same way */
1300     if (strstrW (quals, parmS) != NULL) {
1301
1302       WCHAR thisDir[MAX_PATH];
1303       int cPos;
1304
1305       WCHAR drive[10];
1306       WCHAR dir[MAX_PATH];
1307       WCHAR fname[MAX_PATH];
1308       WCHAR ext[MAX_PATH];
1309
1310       /* Convert path into actual directory spec */
1311       GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1312       WCMD_splitpath(thisDir, drive, dir, fname, ext);
1313
1314       strcpyW(thisDir, drive);
1315       strcatW(thisDir, dir);
1316       cPos = strlenW(thisDir);
1317
1318       WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1319
1320       /* Append '*' to the directory */
1321       thisDir[cPos] = '*';
1322       thisDir[cPos+1] = 0x00;
1323
1324       hff = FindFirstFileW(thisDir, &fd);
1325
1326       /* Remove residual '*' */
1327       thisDir[cPos] = 0x00;
1328
1329       if (hff != INVALID_HANDLE_VALUE) {
1330         DIRECTORY_STACK *allDirs = NULL;
1331         DIRECTORY_STACK *lastEntry = NULL;
1332
1333         do {
1334           if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1335               (strcmpW(fd.cFileName, dotdotW) != 0) &&
1336               (strcmpW(fd.cFileName, dotW) != 0)) {
1337
1338             DIRECTORY_STACK *nextDir;
1339             WCHAR subParm[MAX_PATH];
1340
1341             /* Work out search parameter in sub dir */
1342             strcpyW (subParm, thisDir);
1343             strcatW (subParm, fd.cFileName);
1344             strcatW (subParm, slashW);
1345             strcatW (subParm, fname);
1346             strcatW (subParm, ext);
1347             WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1348
1349             /* Allocate memory, add to list */
1350             nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1351             if (allDirs == NULL) allDirs = nextDir;
1352             if (lastEntry != NULL) lastEntry->next = nextDir;
1353             lastEntry = nextDir;
1354             nextDir->next = NULL;
1355             nextDir->dirName = heap_strdupW(subParm);
1356           }
1357         } while (FindNextFileW(hff, &fd) != 0);
1358         FindClose (hff);
1359
1360         /* Go through each subdir doing the delete */
1361         while (allDirs != NULL) {
1362           DIRECTORY_STACK *tempDir;
1363
1364           tempDir = allDirs->next;
1365           found |= WCMD_delete_one (allDirs->dirName);
1366
1367           heap_free(allDirs->dirName);
1368           heap_free(allDirs);
1369           allDirs = tempDir;
1370         }
1371       }
1372     }
1373
1374     return found;
1375 }
1376
1377 /****************************************************************************
1378  * WCMD_delete
1379  *
1380  * Delete a file or wildcarded set.
1381  *
1382  * Note on /A:
1383  *  - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1384  *  - Each set is a pattern, eg /ahr /as-r means
1385  *         readonly+hidden OR nonreadonly system files
1386  *  - The '-' applies to a single field, ie /a:-hr means read only
1387  *         non-hidden files
1388  */
1389
1390 BOOL WCMD_delete (WCHAR *args) {
1391     int   argno;
1392     WCHAR *argN;
1393     BOOL  argsProcessed = FALSE;
1394     BOOL  foundAny      = FALSE;
1395
1396     errorlevel = 0;
1397
1398     for (argno=0; ; argno++) {
1399         BOOL found;
1400         WCHAR *thisArg;
1401
1402         argN = NULL;
1403         thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1404         if (!argN)
1405             break;       /* no more parameters */
1406         if (argN[0] == '/')
1407             continue;    /* skip options */
1408
1409         argsProcessed = TRUE;
1410         found = WCMD_delete_one(thisArg);
1411         if (!found) {
1412             errorlevel = 1;
1413             WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1414         }
1415         foundAny |= found;
1416     }
1417
1418     /* Handle no valid args */
1419     if (!argsProcessed)
1420         WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1421
1422     return foundAny;
1423 }
1424
1425 /*
1426  * WCMD_strtrim
1427  *
1428  * Returns a trimmed version of s with all leading and trailing whitespace removed
1429  * Pre: s non NULL
1430  *
1431  */
1432 static WCHAR *WCMD_strtrim(const WCHAR *s)
1433 {
1434     DWORD len = strlenW(s);
1435     const WCHAR *start = s;
1436     WCHAR* result;
1437
1438     result = heap_alloc((len + 1) * sizeof(WCHAR));
1439
1440     while (isspaceW(*start)) start++;
1441     if (*start) {
1442         const WCHAR *end = s + len - 1;
1443         while (end > start && isspaceW(*end)) end--;
1444         memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1445         result[end - start + 1] = '\0';
1446     } else {
1447         result[0] = '\0';
1448     }
1449
1450     return result;
1451 }
1452
1453 /****************************************************************************
1454  * WCMD_echo
1455  *
1456  * Echo input to the screen (or not). We don't try to emulate the bugs
1457  * in DOS (try typing "ECHO ON AGAIN" for an example).
1458  */
1459
1460 void WCMD_echo (const WCHAR *args)
1461 {
1462   int count;
1463   const WCHAR *origcommand = args;
1464   WCHAR *trimmed;
1465
1466   if (   args[0]==' ' || args[0]=='\t' || args[0]=='.'
1467       || args[0]==':' || args[0]==';')
1468     args++;
1469
1470   trimmed = WCMD_strtrim(args);
1471   if (!trimmed) return;
1472
1473   count = strlenW(trimmed);
1474   if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1475                  && origcommand[0]!=';') {
1476     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1477     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1478     heap_free(trimmed);
1479     return;
1480   }
1481
1482   if (lstrcmpiW(trimmed, onW) == 0)
1483     echo_mode = TRUE;
1484   else if (lstrcmpiW(trimmed, offW) == 0)
1485     echo_mode = FALSE;
1486   else {
1487     WCMD_output_asis (args);
1488     WCMD_output_asis (newlineW);
1489   }
1490   heap_free(trimmed);
1491 }
1492
1493 /*****************************************************************************
1494  * WCMD_part_execute
1495  *
1496  * Execute a command, and any && or bracketed follow on to the command. The
1497  * first command to be executed may not be at the front of the
1498  * commands->thiscommand string (eg. it may point after a DO or ELSE)
1499  */
1500 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1501                               BOOL isIF, BOOL executecmds)
1502 {
1503   CMD_LIST *curPosition = *cmdList;
1504   int myDepth = (*cmdList)->bracketDepth;
1505
1506   WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList, wine_dbgstr_w(firstcmd),
1507              executecmds);
1508
1509   /* Skip leading whitespace between condition and the command */
1510   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1511
1512   /* Process the first command, if there is one */
1513   if (executecmds && firstcmd && *firstcmd) {
1514     WCHAR *command = heap_strdupW(firstcmd);
1515     WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1516     heap_free(command);
1517   }
1518
1519
1520   /* If it didn't move the position, step to next command */
1521   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1522
1523   /* Process any other parts of the command */
1524   if (*cmdList) {
1525     BOOL processThese = executecmds;
1526
1527     while (*cmdList) {
1528       static const WCHAR ifElse[] = {'e','l','s','e'};
1529
1530       /* execute all appropriate commands */
1531       curPosition = *cmdList;
1532
1533       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1534                  *cmdList,
1535                  (*cmdList)->prevDelim,
1536                  (*cmdList)->bracketDepth, myDepth);
1537
1538       /* Execute any statements appended to the line */
1539       /* FIXME: Only if previous call worked for && or failed for || */
1540       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1541           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1542         if (processThese && (*cmdList)->command) {
1543           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1544                         cmdList, FALSE);
1545         }
1546         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1547
1548       /* Execute any appended to the statement with (...) */
1549       } else if ((*cmdList)->bracketDepth > myDepth) {
1550         if (processThese) {
1551           *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1552           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1553         }
1554         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1555
1556       /* End of the command - does 'ELSE ' follow as the next command? */
1557       } else {
1558         if (isIF
1559             && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1560                                      (*cmdList)->command)) {
1561
1562           /* Swap between if and else processing */
1563           processThese = !processThese;
1564
1565           /* Process the ELSE part */
1566           if (processThese) {
1567             const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1568             WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1569
1570             /* Skip leading whitespace between condition and the command */
1571             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1572             if (*cmd) {
1573               WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1574             }
1575           }
1576           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1577         } else {
1578           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1579           break;
1580         }
1581       }
1582     }
1583   }
1584   return;
1585 }
1586
1587 /*****************************************************************************
1588  * WCMD_parse_forf_options
1589  *
1590  * Parses the for /f 'options', extracting the values and validating the
1591  * keywords. Note all keywords are optional.
1592  * Parameters:
1593  *  options    [I] The unparsed parameter string
1594  *  eol        [O] Set to the comment character (eol=x)
1595  *  skip       [O] Set to the number of lines to skip (skip=xx)
1596  *  delims     [O] Set to the token delimiters (delims=)
1597  *  tokens     [O] Set to the requested tokens, as provided (tokens=)
1598  *  usebackq   [O] Set to TRUE if usebackq found
1599  *
1600  * Returns TRUE on success, FALSE on syntax error
1601  *
1602  */
1603 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1604                                     WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1605 {
1606
1607   WCHAR *pos = options;
1608   int    len = strlenW(pos);
1609   static const WCHAR eolW[] = {'e','o','l','='};
1610   static const WCHAR skipW[] = {'s','k','i','p','='};
1611   static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1612   static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1613   static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1614   static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1615   static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1616
1617   /* Initialize to defaults */
1618   strcpyW(delims, forf_defaultdelims);
1619   strcpyW(tokens, forf_defaulttokens);
1620   *eol      = 0;
1621   *skip     = 0;
1622   *usebackq = FALSE;
1623
1624   /* Strip (optional) leading and trailing quotes */
1625   if ((*pos == '"') && (pos[len-1] == '"')) {
1626     pos[len-1] = 0;
1627     pos++;
1628   }
1629
1630   /* Process each keyword */
1631   while (pos && *pos) {
1632     if (*pos == ' ' || *pos == '\t') {
1633       pos++;
1634
1635     /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1636     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1637                        pos, sizeof(eolW)/sizeof(WCHAR),
1638                        eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1639       *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1640       pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1641       WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1642
1643     /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1644     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1645                        pos, sizeof(skipW)/sizeof(WCHAR),
1646                        skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1647       WCHAR *nextchar = NULL;
1648       pos = pos + sizeof(skipW)/sizeof(WCHAR);
1649       *skip = strtoulW(pos, &nextchar, 0);
1650       WINE_TRACE("Found skip as %d lines\n", *skip);
1651       pos = nextchar;
1652
1653     /* Save if usebackq semantics are in effect */
1654     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1655                        pos, sizeof(usebackqW)/sizeof(WCHAR),
1656                        usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1657       *usebackq = TRUE;
1658       pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1659       WINE_TRACE("Found usebackq\n");
1660
1661     /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1662        if you finish the optionsroot string with delims= otherwise the space is
1663        just a token delimiter!                                                     */
1664     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1665                        pos, sizeof(delimsW)/sizeof(WCHAR),
1666                        delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1667       int i=0;
1668
1669       pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1670       while (*pos && *pos != ' ') {
1671         delims[i++] = *pos;
1672         pos++;
1673       }
1674       if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1675       delims[i++] = 0; /* Null terminate the delims */
1676       WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1677
1678     /* Save the tokens being requested */
1679     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1680                        pos, sizeof(tokensW)/sizeof(WCHAR),
1681                        tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1682       int i=0;
1683
1684       pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1685       while (*pos && *pos != ' ') {
1686         tokens[i++] = *pos;
1687         pos++;
1688       }
1689       tokens[i++] = 0; /* Null terminate the tokens */
1690       WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1691
1692     } else {
1693       WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1694       return FALSE;
1695     }
1696   }
1697   return TRUE;
1698 }
1699
1700 /*****************************************************************************
1701  * WCMD_add_dirstowalk
1702  *
1703  * When recursing through directories (for /r), we need to add to the list of
1704  * directories still to walk, any subdirectories of the one we are processing.
1705  *
1706  * Parameters
1707  *  options    [I] The remaining list of directories still to process
1708  *
1709  * Note this routine inserts the subdirectories found between the entry being
1710  * processed, and any other directory still to be processed, mimicing what
1711  * Windows does
1712  */
1713 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1714   DIRECTORY_STACK *remainingDirs = dirsToWalk;
1715   WCHAR fullitem[MAX_PATH];
1716   WIN32_FIND_DATAW fd;
1717   HANDLE hff;
1718
1719   /* Build a generic search and add all directories on the list of directories
1720      still to walk                                                             */
1721   strcpyW(fullitem, dirsToWalk->dirName);
1722   strcatW(fullitem, slashstarW);
1723   hff = FindFirstFileW(fullitem, &fd);
1724   if (hff != INVALID_HANDLE_VALUE) {
1725     do {
1726       WINE_TRACE("Looking for subdirectories\n");
1727       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1728           (strcmpW(fd.cFileName, dotdotW) != 0) &&
1729           (strcmpW(fd.cFileName, dotW) != 0))
1730       {
1731         /* Allocate memory, add to list */
1732         DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1733         WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1734         toWalk->next = remainingDirs->next;
1735         remainingDirs->next = toWalk;
1736         remainingDirs = toWalk;
1737         toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1738         strcpyW(toWalk->dirName, dirsToWalk->dirName);
1739         strcatW(toWalk->dirName, slashW);
1740         strcatW(toWalk->dirName, fd.cFileName);
1741         WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1742                    toWalk, toWalk->next);
1743       }
1744     } while (FindNextFileW(hff, &fd) != 0);
1745     WINE_TRACE("Finished adding all subdirectories\n");
1746     FindClose (hff);
1747   }
1748 }
1749
1750 /**************************************************************************
1751  * WCMD_for_nexttoken
1752  *
1753  * Parse the token= line, identifying the next highest number not processed
1754  * so far. Count how many tokens are referred (including duplicates) and
1755  * optionally return that, plus optionally indicate if the tokens= line
1756  * ends in a star.
1757  *
1758  * Parameters:
1759  *  lasttoken    [I]    - Identifies the token index of the last one
1760  *                           returned so far (-1 used for first loop)
1761  *  tokenstr     [I]    - The specified tokens= line
1762  *  firstCmd     [O]    - Optionally indicate how many tokens are listed
1763  *  doAll        [O]    - Optionally indicate if line ends with *
1764  *  duplicates   [O]    - Optionally indicate if there is any evidence of
1765  *                           overlaying tokens in the string
1766  * Note the caller should keep a running track of duplicates as the tokens
1767  * are recursively passed. If any have duplicates, then the * token should
1768  * not be honoured.
1769  */
1770 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1771                               int *totalfound, BOOL *doall,
1772                               BOOL *duplicates)
1773 {
1774   WCHAR *pos = tokenstr;
1775   int    nexttoken = -1;
1776
1777   if (totalfound) *totalfound = 0;
1778   if (doall) *doall = FALSE;
1779   if (duplicates) *duplicates = FALSE;
1780
1781   WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1782              wine_dbgstr_w(tokenstr), nexttoken);
1783
1784   /* Loop through the token string, parsing it. Valid syntax is:
1785      token=m or x-y with comma delimiter and optionally * to finish*/
1786   while (*pos) {
1787     int nextnumber1, nextnumber2 = -1;
1788     WCHAR *nextchar;
1789
1790     /* Get the next number */
1791     nextnumber1 = strtoulW(pos, &nextchar, 10);
1792
1793     /* If it is followed by a minus, its a range, so get the next one as well */
1794     if (*nextchar == '-') {
1795       nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1796
1797       /* We want to return the lowest number that is higher than lasttoken
1798          but only if range is positive                                     */
1799       if (nextnumber2 >= nextnumber1 &&
1800           lasttoken < nextnumber2) {
1801
1802         int nextvalue;
1803         if (nexttoken == -1) {
1804           nextvalue = max(nextnumber1, (lasttoken+1));
1805         } else {
1806           nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1807         }
1808
1809         /* Flag if duplicates identified */
1810         if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1811
1812         nexttoken = nextvalue;
1813       }
1814
1815       /* Update the running total for the whole range */
1816       if (nextnumber2 >= nextnumber1 && totalfound) {
1817         *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1818       }
1819
1820     } else {
1821       if (totalfound) (*totalfound)++;
1822
1823       /* See if the number found is one we have already seen */
1824       if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1825
1826       /* We want to return the lowest number that is higher than lasttoken */
1827       if (lasttoken < nextnumber1 &&
1828          ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1829         nexttoken = nextnumber1;
1830       }
1831
1832     }
1833
1834     /* Remember if it is followed by a star, and if it is indicate a need to
1835        show all tokens, unless a duplicate has been found                    */
1836     if (*nextchar == '*') {
1837       if (doall) *doall = TRUE;
1838       if (totalfound) (*totalfound)++;
1839     }
1840
1841     /* Step on to the next character */
1842     pos = nextchar;
1843     if (*pos) pos++;
1844   }
1845
1846   /* Return result */
1847   if (nexttoken == -1) nexttoken = lasttoken;
1848   WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1849   if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1850   if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1851   if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1852   return nexttoken;
1853 }
1854
1855 /**************************************************************************
1856  * WCMD_parse_line
1857  *
1858  * When parsing file or string contents (for /f), once the string to parse
1859  * has been identified, handle the various options and call the do part
1860  * if appropriate.
1861  *
1862  * Parameters:
1863  *  cmdStart     [I]    - Identifies the list of commands making up the
1864  *                           for loop body (especially if brackets in use)
1865  *  firstCmd     [I]    - The textual start of the command after the DO
1866  *                           which is within the first item of cmdStart
1867  *  cmdEnd       [O]    - Identifies where to continue after the DO
1868  *  variable     [I]    - The variable identified on the for line
1869  *  buffer       [I]    - The string to parse
1870  *  doExecuted   [O]    - Set to TRUE if the DO is ever executed once
1871  *  forf_skip    [I/O]  - How many lines to skip first
1872  *  forf_eol     [I]    - The 'end of line' (comment) character
1873  *  forf_delims  [I]    - The delimiters to use when breaking the string apart
1874  *  forf_tokens  [I]    - The tokens to use when breaking the string apart
1875  */
1876 static void WCMD_parse_line(CMD_LIST    *cmdStart,
1877                             const WCHAR *firstCmd,
1878                             CMD_LIST   **cmdEnd,
1879                             const WCHAR  variable,
1880                             WCHAR       *buffer,
1881                             BOOL        *doExecuted,
1882                             int         *forf_skip,
1883                             WCHAR        forf_eol,
1884                             WCHAR       *forf_delims,
1885                             WCHAR       *forf_tokens) {
1886
1887   WCHAR *parm;
1888   FOR_CONTEXT oldcontext;
1889   int varidx, varoffset;
1890   int nexttoken, lasttoken = -1;
1891   BOOL starfound = FALSE;
1892   BOOL thisduplicate = FALSE;
1893   BOOL anyduplicates = FALSE;
1894   int  totalfound;
1895
1896   /* Skip lines if requested */
1897   if (*forf_skip) {
1898     (*forf_skip)--;
1899     return;
1900   }
1901
1902   /* Save away any existing for variable context (e.g. nested for loops) */
1903   oldcontext = forloopcontext;
1904
1905   /* Extract the parameters based on the tokens= value (There will always
1906      be some value, as if it is not supplied, it defaults to tokens=1).
1907      Rough logic:
1908      Count how many tokens are named in the line, identify the lowest
1909      Empty (set to null terminated string) that number of named variables
1910      While lasttoken != nextlowest
1911        %letter = parameter number 'nextlowest'
1912        letter++ (if >26 or >52 abort)
1913        Go through token= string finding next lowest number
1914      If token ends in * set %letter = raw position of token(nextnumber+1)
1915    */
1916   lasttoken = -1;
1917   nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1918                                  NULL, &thisduplicate);
1919   varidx = FOR_VAR_IDX(variable);
1920
1921   /* Empty out variables */
1922   for (varoffset=0;
1923        varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1924        varoffset++) {
1925     forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1926     /* Stop if we walk beyond z or Z */
1927     if (((varidx+varoffset) % 26) == 0) break;
1928   }
1929
1930   /* Loop extracting the tokens */
1931   varoffset = 0;
1932   WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1933   while (varidx >= 0 && (nexttoken > lasttoken)) {
1934     anyduplicates |= thisduplicate;
1935
1936     /* Extract the token number requested and set into the next variable context */
1937     parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1938     WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1939                varidx + varoffset, wine_dbgstr_w(parm));
1940     if (varidx >=0) {
1941       forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1942       varoffset++;
1943       if (((varidx + varoffset) %26) == 0) break;
1944     }
1945
1946     /* Find the next token */
1947     lasttoken = nexttoken;
1948     nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1949                                    &starfound, &thisduplicate);
1950   }
1951
1952   /* If all the rest of the tokens were requested, and there is still space in
1953      the variable range, write them now                                        */
1954   if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1955     nexttoken++;
1956     WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1957     WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1958                varidx + varoffset, wine_dbgstr_w(parm));
1959     forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1960   }
1961
1962   /* Execute the body of the foor loop with these values */
1963   if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1964     CMD_LIST *thisCmdStart = cmdStart;
1965     *doExecuted = TRUE;
1966     WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1967     *cmdEnd = thisCmdStart;
1968   }
1969
1970   /* Free the duplicated strings, and restore the context */
1971   if (varidx >=0) {
1972     int i;
1973     for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1974       if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1975           (forloopcontext.variable[i] != nullW)) {
1976         heap_free(forloopcontext.variable[i]);
1977       }
1978     }
1979   }
1980
1981   /* Restore the original for variable contextx */
1982   forloopcontext = oldcontext;
1983 }
1984
1985 /**************************************************************************
1986  * WCMD_forf_getinputhandle
1987  *
1988  * Return a file handle which can be used for reading the input lines,
1989  * either to a specific file (which may be quote delimited as we have to
1990  * read the parameters in raw mode) or to a command which we need to
1991  * execute. The command being executed runs in its own shell and stores
1992  * its data in a temporary file.
1993  *
1994  * Parameters:
1995  *  usebackq     [I]    - Indicates whether usebackq is in effect or not
1996  *  itemStr      [I]    - The item to be handled, either a filename or
1997  *                           whole command string to execute
1998  *  iscmd        [I]    - Identifies whether this is a command or not
1999  *
2000  * Returns a file handle which can be used to read the input lines from.
2001  */
2002 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2003   WCHAR  temp_str[MAX_PATH];
2004   WCHAR  temp_file[MAX_PATH];
2005   WCHAR  temp_cmd[MAXSTRING];
2006   HANDLE hinput = INVALID_HANDLE_VALUE;
2007   static const WCHAR redirOutW[]  = {'>','%','s','\0'};
2008   static const WCHAR cmdW[]       = {'C','M','D','\0'};
2009   static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2010                                      '/','C',' ','"','%','s','"','\0'};
2011
2012   /* Remove leading and trailing character */
2013   if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2014       (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2015       (!iscmd && (itemstr[0] == '"' && usebackq)))
2016   {
2017     itemstr[strlenW(itemstr)-1] = 0x00;
2018     itemstr++;
2019   }
2020
2021   if (iscmd) {
2022     /* Get temp filename */
2023     GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2024     GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2025
2026     /* Redirect output to the temporary file */
2027     wsprintfW(temp_str, redirOutW, temp_file);
2028     wsprintfW(temp_cmd, cmdslashcW, itemstr);
2029     WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2030                wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2031     WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2032
2033     /* Open the file, read line by line and process */
2034     hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2035                         NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2036
2037   } else {
2038     /* Open the file, read line by line and process */
2039     WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2040     hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2041                         NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2042   }
2043   return hinput;
2044 }
2045
2046 /**************************************************************************
2047  * WCMD_for
2048  *
2049  * Batch file loop processing.
2050  *
2051  * On entry: cmdList       contains the syntax up to the set
2052  *           next cmdList and all in that bracket contain the set data
2053  *           next cmdlist  contains the DO cmd
2054  *           following that is either brackets or && entries (as per if)
2055  *
2056  */
2057
2058 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2059
2060   WIN32_FIND_DATAW fd;
2061   HANDLE hff;
2062   int i;
2063   static const WCHAR inW[] = {'i','n'};
2064   static const WCHAR doW[] = {'d','o'};
2065   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2066   WCHAR variable[4];
2067   int   varidx = -1;
2068   WCHAR *oldvariablevalue;
2069   WCHAR *firstCmd;
2070   int thisDepth;
2071   WCHAR optionsRoot[MAX_PATH];
2072   DIRECTORY_STACK *dirsToWalk = NULL;
2073   BOOL   expandDirs  = FALSE;
2074   BOOL   useNumbers  = FALSE;
2075   BOOL   doFileset   = FALSE;
2076   BOOL   doRecurse   = FALSE;
2077   BOOL   doExecuted  = FALSE;  /* Has the 'do' part been executed */
2078   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2079   int    itemNum;
2080   CMD_LIST *thisCmdStart;
2081   int    parameterNo = 0;
2082   WCHAR  forf_eol = 0;
2083   int    forf_skip = 0;
2084   WCHAR  forf_delims[256];
2085   WCHAR  forf_tokens[MAXSTRING];
2086   BOOL   forf_usebackq = FALSE;
2087
2088   /* Handle optional qualifiers (multiple are allowed) */
2089   WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2090
2091   optionsRoot[0] = 0;
2092   while (thisArg && *thisArg == '/') {
2093       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2094       thisArg++;
2095       switch (toupperW(*thisArg)) {
2096       case 'D': expandDirs = TRUE; break;
2097       case 'L': useNumbers = TRUE; break;
2098
2099       /* Recursive is special case - /R can have an optional path following it                */
2100       /* filenamesets are another special case - /F can have an optional options following it */
2101       case 'R':
2102       case 'F':
2103           {
2104               /* When recursing directories, use current directory as the starting point unless
2105                  subsequently overridden */
2106               doRecurse = (toupperW(*thisArg) == 'R');
2107               if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2108
2109               doFileset = (toupperW(*thisArg) == 'F');
2110
2111               /* Retrieve next parameter to see if is root/options (raw form required
2112                  with for /f, or unquoted in for /r)                                  */
2113               thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2114
2115               /* Next parm is either qualifier, path/options or variable -
2116                  only care about it if it is the path/options              */
2117               if (thisArg && *thisArg != '/' && *thisArg != '%') {
2118                   parameterNo++;
2119                   strcpyW(optionsRoot, thisArg);
2120               }
2121               break;
2122           }
2123       default:
2124           WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2125       }
2126
2127       /* Step to next token */
2128       thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2129   }
2130
2131   /* Ensure line continues with variable */
2132   if (!*thisArg || *thisArg != '%') {
2133       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2134       return;
2135   }
2136
2137   /* With for /f parse the options if provided */
2138   if (doFileset) {
2139     if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2140                                  forf_delims, forf_tokens, &forf_usebackq))
2141     {
2142       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2143       return;
2144     }
2145
2146   /* Set up the list of directories to recurse if we are going to */
2147   } else if (doRecurse) {
2148        /* Allocate memory, add to list */
2149        dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2150        dirsToWalk->next = NULL;
2151        dirsToWalk->dirName = heap_strdupW(optionsRoot);
2152        WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2153   }
2154
2155   /* Variable should follow */
2156   strcpyW(variable, thisArg);
2157   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2158   varidx = FOR_VAR_IDX(variable[1]);
2159
2160   /* Ensure line continues with IN */
2161   thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2162   if (!thisArg
2163        || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2164                            thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2165                            sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2166       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2167       return;
2168   }
2169
2170   /* Save away where the set of data starts and the variable */
2171   thisDepth = (*cmdList)->bracketDepth;
2172   *cmdList = (*cmdList)->nextcommand;
2173   setStart = (*cmdList);
2174
2175   /* Skip until the close bracket */
2176   WINE_TRACE("Searching %p as the set\n", *cmdList);
2177   while (*cmdList &&
2178          (*cmdList)->command != NULL &&
2179          (*cmdList)->bracketDepth > thisDepth) {
2180     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2181     *cmdList = (*cmdList)->nextcommand;
2182   }
2183
2184   /* Skip the close bracket, if there is one */
2185   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2186
2187   /* Syntax error if missing close bracket, or nothing following it
2188      and once we have the complete set, we expect a DO              */
2189   WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2190   if ((*cmdList == NULL)
2191       || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2192
2193       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2194       return;
2195   }
2196
2197   cmdEnd   = *cmdList;
2198
2199   /* Loop repeatedly per-directory we are potentially walking, when in for /r
2200      mode, or once for the rest of the time.                                  */
2201   do {
2202
2203     /* Save away the starting position for the commands (and offset for the
2204        first one)                                                           */
2205     cmdStart = *cmdList;
2206     firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2207     itemNum  = 0;
2208
2209     /* If we are recursing directories (ie /R), add all sub directories now, then
2210        prefix the root when searching for the item */
2211     if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2212
2213     thisSet = setStart;
2214     /* Loop through all set entries */
2215     while (thisSet &&
2216            thisSet->command != NULL &&
2217            thisSet->bracketDepth >= thisDepth) {
2218
2219       /* Loop through all entries on the same line */
2220       WCHAR *item;
2221       WCHAR *itemStart;
2222       WCHAR buffer[MAXSTRING];
2223
2224       WINE_TRACE("Processing for set %p\n", thisSet);
2225       i = 0;
2226       while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2227
2228         /*
2229          * If the parameter within the set has a wildcard then search for matching files
2230          * otherwise do a literal substitution.
2231          */
2232         static const WCHAR wildcards[] = {'*','?','\0'};
2233         thisCmdStart = cmdStart;
2234
2235         itemNum++;
2236         WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2237
2238         if (!useNumbers && !doFileset) {
2239             WCHAR fullitem[MAX_PATH];
2240
2241             /* Now build the item to use / search for in the specified directory,
2242                as it is fully qualified in the /R case */
2243             if (dirsToWalk) {
2244               strcpyW(fullitem, dirsToWalk->dirName);
2245               strcatW(fullitem, slashW);
2246               strcatW(fullitem, item);
2247             } else {
2248               strcpyW(fullitem, item);
2249             }
2250
2251             if (strpbrkW (fullitem, wildcards)) {
2252
2253               hff = FindFirstFileW(fullitem, &fd);
2254               if (hff != INVALID_HANDLE_VALUE) {
2255                 do {
2256                   BOOL isDirectory = FALSE;
2257
2258                   if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2259
2260                   /* Handle as files or dirs appropriately, but ignore . and .. */
2261                   if (isDirectory == expandDirs &&
2262                       (strcmpW(fd.cFileName, dotdotW) != 0) &&
2263                       (strcmpW(fd.cFileName, dotW) != 0))
2264                   {
2265                       thisCmdStart = cmdStart;
2266                       WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2267
2268                       if (doRecurse) {
2269                           strcpyW(fullitem, dirsToWalk->dirName);
2270                           strcatW(fullitem, slashW);
2271                           strcatW(fullitem, fd.cFileName);
2272                       } else {
2273                           strcpyW(fullitem, fd.cFileName);
2274                       }
2275                       doExecuted = TRUE;
2276
2277                       /* Save away any existing for variable context (e.g. nested for loops)
2278                          and restore it after executing the body of this for loop           */
2279                       if (varidx >= 0) {
2280                         oldvariablevalue = forloopcontext.variable[varidx];
2281                         forloopcontext.variable[varidx] = fullitem;
2282                       }
2283                       WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2284                       if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2285
2286                       cmdEnd = thisCmdStart;
2287                   }
2288                 } while (FindNextFileW(hff, &fd) != 0);
2289                 FindClose (hff);
2290               }
2291             } else {
2292               doExecuted = TRUE;
2293
2294               /* Save away any existing for variable context (e.g. nested for loops)
2295                  and restore it after executing the body of this for loop           */
2296               if (varidx >= 0) {
2297                 oldvariablevalue = forloopcontext.variable[varidx];
2298                 forloopcontext.variable[varidx] = fullitem;
2299               }
2300               WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2301               if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2302
2303               cmdEnd = thisCmdStart;
2304             }
2305
2306         } else if (useNumbers) {
2307             /* Convert the first 3 numbers to signed longs and save */
2308             if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2309             /* else ignore them! */
2310
2311         /* Filesets - either a list of files, or a command to run and parse the output */
2312         } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2313                                  (forf_usebackq && *itemStart != '\''))) {
2314
2315             HANDLE input;
2316             WCHAR *itemparm;
2317
2318             WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2319                        wine_dbgstr_w(item));
2320
2321             /* If backquote or single quote, we need to launch that command
2322                and parse the results - use a temporary file                 */
2323             if ((forf_usebackq && *itemStart == '`') ||
2324                 (!forf_usebackq && *itemStart == '\'')) {
2325
2326               /* Use itemstart because the command is the whole set, not just the first token */
2327               itemparm = itemStart;
2328             } else {
2329
2330               /* Use item because the file to process is just the first item in the set */
2331               itemparm = item;
2332             }
2333             input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2334
2335             /* Process the input file */
2336             if (input == INVALID_HANDLE_VALUE) {
2337               WCMD_print_error ();
2338               WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2339               errorlevel = 1;
2340               return; /* FOR loop aborts at first failure here */
2341
2342             } else {
2343
2344               /* Read line by line until end of file */
2345               while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2346                 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2347                                 &forf_skip, forf_eol, forf_delims, forf_tokens);
2348                 buffer[0] = 0;
2349               }
2350               CloseHandle (input);
2351             }
2352
2353             /* When we have processed the item as a whole command, abort future set processing */
2354             if (itemparm==itemStart) {
2355               thisSet = NULL;
2356               break;
2357             }
2358
2359         /* Filesets - A string literal */
2360         } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2361                                  (forf_usebackq && *itemStart == '\''))) {
2362
2363           /* Remove leading and trailing character, ready to parse with delims= delimiters
2364              Note that the last quote is removed from the set and the string terminates
2365              there to mimic windows                                                        */
2366           WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2367           if (strend) {
2368             *strend = 0x00;
2369             itemStart++;
2370           }
2371
2372           /* Copy the item away from the global buffer used by WCMD_parameter */
2373           strcpyW(buffer, itemStart);
2374           WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2375                             &forf_skip, forf_eol, forf_delims, forf_tokens);
2376
2377           /* Only one string can be supplied in the whole set, abort future set processing */
2378           thisSet = NULL;
2379           break;
2380         }
2381
2382         WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2383         i++;
2384       }
2385
2386       /* Move onto the next set line */
2387       if (thisSet) thisSet = thisSet->nextcommand;
2388     }
2389
2390     /* If /L is provided, now run the for loop */
2391     if (useNumbers) {
2392         WCHAR thisNum[20];
2393         static const WCHAR fmt[] = {'%','d','\0'};
2394
2395         WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2396                    numbers[0], numbers[2], numbers[1]);
2397         for (i=numbers[0];
2398              (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2399              i=i + numbers[1]) {
2400
2401             sprintfW(thisNum, fmt, i);
2402             WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2403
2404             thisCmdStart = cmdStart;
2405             doExecuted = TRUE;
2406
2407             /* Save away any existing for variable context (e.g. nested for loops)
2408                and restore it after executing the body of this for loop           */
2409             if (varidx >= 0) {
2410               oldvariablevalue = forloopcontext.variable[varidx];
2411               forloopcontext.variable[varidx] = thisNum;
2412             }
2413             WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2414             if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2415         }
2416         cmdEnd = thisCmdStart;
2417     }
2418
2419     /* If we are walking directories, move on to any which remain */
2420     if (dirsToWalk != NULL) {
2421       DIRECTORY_STACK *nextDir = dirsToWalk->next;
2422       heap_free(dirsToWalk->dirName);
2423       heap_free(dirsToWalk);
2424       dirsToWalk = nextDir;
2425       if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2426                                  wine_dbgstr_w(dirsToWalk->dirName));
2427       else WINE_TRACE("Finished all directories.\n");
2428     }
2429
2430   } while (dirsToWalk != NULL);
2431
2432   /* Now skip over the do part if we did not perform the for loop so far.
2433      We store in cmdEnd the next command after the do block, but we only
2434      know this if something was run. If it has not been, we need to calculate
2435      it.                                                                      */
2436   if (!doExecuted) {
2437     thisCmdStart = cmdStart;
2438     WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2439     WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2440     cmdEnd = thisCmdStart;
2441   }
2442
2443   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2444      all processing, OR it should be pointing to the end of && processing OR
2445      it should be pointing at the NULL end of bracket for the DO. The return
2446      value needs to be the NEXT command to execute, which it either is, or
2447      we need to step over the closing bracket                                  */
2448   *cmdList = cmdEnd;
2449   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2450 }
2451
2452 /**************************************************************************
2453  * WCMD_give_help
2454  *
2455  *      Simple on-line help. Help text is stored in the resource file.
2456  */
2457
2458 void WCMD_give_help (const WCHAR *args)
2459 {
2460   size_t i;
2461
2462   args = WCMD_skip_leading_spaces((WCHAR*) args);
2463   if (strlenW(args) == 0) {
2464     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2465   }
2466   else {
2467     /* Display help message for builtin commands */
2468     for (i=0; i<=WCMD_EXIT; i++) {
2469       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2470           args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2471         WCMD_output_asis (WCMD_LoadMessage(i));
2472         return;
2473       }
2474     }
2475     /* Launch the command with the /? option for external commands shipped with cmd.exe */
2476     for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2477       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2478           args, -1, externals[i], -1) == CSTR_EQUAL) {
2479         WCHAR cmd[128];
2480         static const WCHAR helpW[] = {' ', '/','?','\0'};
2481         strcpyW(cmd, args);
2482         strcatW(cmd, helpW);
2483         WCMD_run_program(cmd, FALSE);
2484         return;
2485       }
2486     }
2487     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2488   }
2489   return;
2490 }
2491
2492 /****************************************************************************
2493  * WCMD_go_to
2494  *
2495  * Batch file jump instruction. Not the most efficient algorithm ;-)
2496  * Prints error message if the specified label cannot be found - the file pointer is
2497  * then at EOF, effectively stopping the batch file.
2498  * FIXME: DOS is supposed to allow labels with spaces - we don't.
2499  */
2500
2501 void WCMD_goto (CMD_LIST **cmdList) {
2502
2503   WCHAR string[MAX_PATH];
2504   WCHAR current[MAX_PATH];
2505
2506   /* Do not process any more parts of a processed multipart or multilines command */
2507   if (cmdList) *cmdList = NULL;
2508
2509   if (context != NULL) {
2510     WCHAR *paramStart = param1, *str;
2511     static const WCHAR eofW[] = {':','e','o','f','\0'};
2512
2513     if (param1[0] == 0x00) {
2514       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2515       return;
2516     }
2517
2518     /* Handle special :EOF label */
2519     if (lstrcmpiW (eofW, param1) == 0) {
2520       context -> skip_rest = TRUE;
2521       return;
2522     }
2523
2524     /* Support goto :label as well as goto label */
2525     if (*paramStart == ':') paramStart++;
2526
2527     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2528     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2529       str = string;
2530       while (isspaceW (*str)) str++;
2531       if (*str == ':') {
2532         DWORD index = 0;
2533         str++;
2534         while (((current[index] = str[index])) && (!isspaceW (current[index])))
2535             index++;
2536
2537         /* ignore space at the end */
2538         current[index] = 0;
2539         if (lstrcmpiW (current, paramStart) == 0) return;
2540       }
2541     }
2542     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2543   }
2544   return;
2545 }
2546
2547 /*****************************************************************************
2548  * WCMD_pushd
2549  *
2550  *      Push a directory onto the stack
2551  */
2552
2553 void WCMD_pushd (const WCHAR *args)
2554 {
2555     struct env_stack *curdir;
2556     WCHAR *thisdir;
2557     static const WCHAR parmD[] = {'/','D','\0'};
2558
2559     if (strchrW(args, '/') != NULL) {
2560       SetLastError(ERROR_INVALID_PARAMETER);
2561       WCMD_print_error();
2562       return;
2563     }
2564
2565     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2566     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2567     if( !curdir || !thisdir ) {
2568       LocalFree(curdir);
2569       LocalFree(thisdir);
2570       WINE_ERR ("out of memory\n");
2571       return;
2572     }
2573
2574     /* Change directory using CD code with /D parameter */
2575     strcpyW(quals, parmD);
2576     GetCurrentDirectoryW (1024, thisdir);
2577     errorlevel = 0;
2578     WCMD_setshow_default(args);
2579     if (errorlevel) {
2580       LocalFree(curdir);
2581       LocalFree(thisdir);
2582       return;
2583     } else {
2584       curdir -> next    = pushd_directories;
2585       curdir -> strings = thisdir;
2586       if (pushd_directories == NULL) {
2587         curdir -> u.stackdepth = 1;
2588       } else {
2589         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2590       }
2591       pushd_directories = curdir;
2592     }
2593 }
2594
2595
2596 /*****************************************************************************
2597  * WCMD_popd
2598  *
2599  *      Pop a directory from the stack
2600  */
2601
2602 void WCMD_popd (void) {
2603     struct env_stack *temp = pushd_directories;
2604
2605     if (!pushd_directories)
2606       return;
2607
2608     /* pop the old environment from the stack, and make it the current dir */
2609     pushd_directories = temp->next;
2610     SetCurrentDirectoryW(temp->strings);
2611     LocalFree (temp->strings);
2612     LocalFree (temp);
2613 }
2614
2615 /*******************************************************************
2616  * evaluate_if_comparison
2617  *
2618  * Evaluates an "if" comparison operation
2619  *
2620  * PARAMS
2621  *  leftOperand     [I] left operand, non NULL
2622  *  operator        [I] "if" binary comparison operator, non NULL
2623  *  rightOperand    [I] right operand, non NULL
2624  *  caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2625  *
2626  * RETURNS
2627  *  Success:  1 if operator applied to the operands evaluates to TRUE
2628  *            0 if operator applied to the operands evaluates to FALSE
2629  *  Failure: -1 if operator is not recognized
2630  */
2631 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2632                                   const WCHAR *rightOperand, int caseInsensitive)
2633 {
2634     WCHAR *endptr_leftOp, *endptr_rightOp;
2635     long int leftOperand_int, rightOperand_int;
2636     BOOL int_operands;
2637     static const WCHAR lssW[]  = {'l','s','s','\0'};
2638     static const WCHAR leqW[]  = {'l','e','q','\0'};
2639     static const WCHAR equW[]  = {'e','q','u','\0'};
2640     static const WCHAR neqW[]  = {'n','e','q','\0'};
2641     static const WCHAR geqW[]  = {'g','e','q','\0'};
2642     static const WCHAR gtrW[]  = {'g','t','r','\0'};
2643
2644     /* == is a special case, as it always compares strings */
2645     if (!lstrcmpiW(operator, eqeqW))
2646         return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2647                                : lstrcmpW (leftOperand, rightOperand) == 0;
2648
2649     /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2650     leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2651     rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2652     int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2653
2654     /* Perform actual (integer or string) comparison */
2655     if (!lstrcmpiW(operator, lssW)) {
2656         if (int_operands)
2657             return leftOperand_int < rightOperand_int;
2658         else
2659             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2660                                    : lstrcmpW (leftOperand, rightOperand) < 0;
2661     }
2662
2663     if (!lstrcmpiW(operator, leqW)) {
2664         if (int_operands)
2665             return leftOperand_int <= rightOperand_int;
2666         else
2667             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2668                                    : lstrcmpW (leftOperand, rightOperand) <= 0;
2669     }
2670
2671     if (!lstrcmpiW(operator, equW)) {
2672         if (int_operands)
2673             return leftOperand_int == rightOperand_int;
2674         else
2675             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2676                                    : lstrcmpW (leftOperand, rightOperand) == 0;
2677     }
2678
2679     if (!lstrcmpiW(operator, neqW)) {
2680         if (int_operands)
2681             return leftOperand_int != rightOperand_int;
2682         else
2683             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2684                                    : lstrcmpW (leftOperand, rightOperand) != 0;
2685     }
2686
2687     if (!lstrcmpiW(operator, geqW)) {
2688         if (int_operands)
2689             return leftOperand_int >= rightOperand_int;
2690         else
2691             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2692                                    : lstrcmpW (leftOperand, rightOperand) >= 0;
2693     }
2694
2695     if (!lstrcmpiW(operator, gtrW)) {
2696         if (int_operands)
2697             return leftOperand_int > rightOperand_int;
2698         else
2699             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2700                                    : lstrcmpW (leftOperand, rightOperand) > 0;
2701     }
2702
2703     return -1;
2704 }
2705
2706 /****************************************************************************
2707  * WCMD_if
2708  *
2709  * Batch file conditional.
2710  *
2711  * On entry, cmdlist will point to command containing the IF, and optionally
2712  *   the first command to execute (if brackets not found)
2713  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
2714  *   If ('s were found, execute all within that bracket
2715  *   Command may optionally be followed by an ELSE - need to skip instructions
2716  *   in the else using the same logic
2717  *
2718  * FIXME: Much more syntax checking needed!
2719  */
2720 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2721 {
2722   int negate; /* Negate condition */
2723   int test;   /* Condition evaluation result */
2724   WCHAR condition[MAX_PATH], *command;
2725   static const WCHAR notW[]    = {'n','o','t','\0'};
2726   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2727   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
2728   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
2729   static const WCHAR parmI[]   = {'/','I','\0'};
2730   int caseInsensitive = (strstrW(quals, parmI) != NULL);
2731
2732   negate = !lstrcmpiW(param1,notW);
2733   strcpyW(condition, (negate ? param2 : param1));
2734   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2735
2736   if (!lstrcmpiW (condition, errlvlW)) {
2737     WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2738     WCHAR *endptr;
2739     long int param_int = strtolW(param, &endptr, 10);
2740     if (*endptr) goto syntax_err;
2741     test = ((long int)errorlevel >= param_int);
2742     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2743   }
2744   else if (!lstrcmpiW (condition, existW)) {
2745     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2746              != INVALID_FILE_ATTRIBUTES);
2747     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2748   }
2749   else if (!lstrcmpiW (condition, defdW)) {
2750     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2751                                     NULL, 0) > 0);
2752     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2753   }
2754   else { /* comparison operation */
2755     WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2756     WCHAR *paramStart;
2757
2758     strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2759     if (!*leftOperand)
2760       goto syntax_err;
2761
2762     /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2763     p = paramStart + strlenW(leftOperand);
2764     while (*p == ' ' || *p == '\t')
2765       p++;
2766
2767     if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2768       strcpyW(operator, eqeqW);
2769     else {
2770       strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2771       if (!*operator) goto syntax_err;
2772     }
2773     p += strlenW(operator);
2774
2775     strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2776     if (!*rightOperand)
2777       goto syntax_err;
2778
2779     test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2780     if (test == -1)
2781       goto syntax_err;
2782
2783     p = paramStart + strlenW(rightOperand);
2784     WCMD_parameter(p, 0, &command, FALSE, FALSE);
2785   }
2786
2787   /* Process rest of IF statement which is on the same line
2788      Note: This may process all or some of the cmdList (eg a GOTO) */
2789   WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2790   return;
2791
2792 syntax_err:
2793   WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2794 }
2795
2796 /****************************************************************************
2797  * WCMD_move
2798  *
2799  * Move a file, directory tree or wildcarded set of files.
2800  */
2801
2802 void WCMD_move (void)
2803 {
2804   int             status;
2805   WIN32_FIND_DATAW fd;
2806   HANDLE          hff;
2807   WCHAR            input[MAX_PATH];
2808   WCHAR            output[MAX_PATH];
2809   WCHAR            drive[10];
2810   WCHAR            dir[MAX_PATH];
2811   WCHAR            fname[MAX_PATH];
2812   WCHAR            ext[MAX_PATH];
2813
2814   if (param1[0] == 0x00) {
2815     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2816     return;
2817   }
2818
2819   /* If no destination supplied, assume current directory */
2820   if (param2[0] == 0x00) {
2821       strcpyW(param2, dotW);
2822   }
2823
2824   /* If 2nd parm is directory, then use original filename */
2825   /* Convert partial path to full path */
2826   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2827   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2828   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2829              wine_dbgstr_w(param1), wine_dbgstr_w(output));
2830
2831   /* Split into components */
2832   WCMD_splitpath(input, drive, dir, fname, ext);
2833
2834   hff = FindFirstFileW(input, &fd);
2835   if (hff == INVALID_HANDLE_VALUE)
2836     return;
2837
2838   do {
2839     WCHAR  dest[MAX_PATH];
2840     WCHAR  src[MAX_PATH];
2841     DWORD attribs;
2842     BOOL ok = TRUE;
2843
2844     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2845
2846     /* Build src & dest name */
2847     strcpyW(src, drive);
2848     strcatW(src, dir);
2849
2850     /* See if dest is an existing directory */
2851     attribs = GetFileAttributesW(output);
2852     if (attribs != INVALID_FILE_ATTRIBUTES &&
2853        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2854       strcpyW(dest, output);
2855       strcatW(dest, slashW);
2856       strcatW(dest, fd.cFileName);
2857     } else {
2858       strcpyW(dest, output);
2859     }
2860
2861     strcatW(src, fd.cFileName);
2862
2863     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2864     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
2865
2866     /* If destination exists, prompt unless /Y supplied */
2867     if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2868       BOOL force = FALSE;
2869       WCHAR copycmd[MAXSTRING];
2870       DWORD len;
2871
2872       /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2873       if (strstrW (quals, parmNoY))
2874         force = FALSE;
2875       else if (strstrW (quals, parmY))
2876         force = TRUE;
2877       else {
2878         static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2879         len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2880         force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2881                      && ! lstrcmpiW (copycmd, parmY));
2882       }
2883
2884       /* Prompt if overwriting */
2885       if (!force) {
2886         WCHAR* question;
2887
2888         /* Ask for confirmation */
2889         question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2890         ok = WCMD_ask_confirm(question, FALSE, NULL);
2891         LocalFree(question);
2892
2893         /* So delete the destination prior to the move */
2894         if (ok) {
2895           if (!DeleteFileW(dest)) {
2896             WCMD_print_error ();
2897             errorlevel = 1;
2898             ok = FALSE;
2899           }
2900         }
2901       }
2902     }
2903
2904     if (ok) {
2905       status = MoveFileW(src, dest);
2906     } else {
2907       status = 1; /* Anything other than 0 to prevent error msg below */
2908     }
2909
2910     if (!status) {
2911       WCMD_print_error ();
2912       errorlevel = 1;
2913     }
2914   } while (FindNextFileW(hff, &fd) != 0);
2915
2916   FindClose(hff);
2917 }
2918
2919 /****************************************************************************
2920  * WCMD_pause
2921  *
2922  * Suspend execution of a batch script until a key is typed
2923  */
2924
2925 void WCMD_pause (void)
2926 {
2927   DWORD oldmode;
2928   BOOL have_console;
2929   DWORD count;
2930   WCHAR key;
2931   HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2932
2933   have_console = GetConsoleMode(hIn, &oldmode);
2934   if (have_console)
2935       SetConsoleMode(hIn, 0);
2936
2937   WCMD_output_asis(anykey);
2938   WCMD_ReadFile(hIn, &key, 1, &count);
2939   if (have_console)
2940     SetConsoleMode(hIn, oldmode);
2941 }
2942
2943 /****************************************************************************
2944  * WCMD_remove_dir
2945  *
2946  * Delete a directory.
2947  */
2948
2949 void WCMD_remove_dir (WCHAR *args) {
2950
2951   int   argno         = 0;
2952   int   argsProcessed = 0;
2953   WCHAR *argN          = args;
2954   static const WCHAR parmS[] = {'/','S','\0'};
2955   static const WCHAR parmQ[] = {'/','Q','\0'};
2956
2957   /* Loop through all args */
2958   while (argN) {
2959     WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2960     if (argN && argN[0] != '/') {
2961       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2962                  wine_dbgstr_w(quals));
2963       argsProcessed++;
2964
2965       /* If subdirectory search not supplied, just try to remove
2966          and report error if it fails (eg if it contains a file) */
2967       if (strstrW (quals, parmS) == NULL) {
2968         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2969
2970       /* Otherwise use ShFileOp to recursively remove a directory */
2971       } else {
2972
2973         SHFILEOPSTRUCTW lpDir;
2974
2975         /* Ask first */
2976         if (strstrW (quals, parmQ) == NULL) {
2977           BOOL  ok;
2978           WCHAR  question[MAXSTRING];
2979           static const WCHAR fmt[] = {'%','s',' ','\0'};
2980
2981           /* Ask for confirmation */
2982           wsprintfW(question, fmt, thisArg);
2983           ok = WCMD_ask_confirm(question, TRUE, NULL);
2984
2985           /* Abort if answer is 'N' */
2986           if (!ok) return;
2987         }
2988
2989         /* Do the delete */
2990         lpDir.hwnd   = NULL;
2991         lpDir.pTo    = NULL;
2992         lpDir.pFrom  = thisArg;
2993         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2994         lpDir.wFunc  = FO_DELETE;
2995
2996         /* SHFileOperationW needs file list with a double null termination */
2997         thisArg[lstrlenW(thisArg) + 1] = 0x00;
2998
2999         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3000       }
3001     }
3002   }
3003
3004   /* Handle no valid args */
3005   if (argsProcessed == 0) {
3006     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3007     return;
3008   }
3009
3010 }
3011
3012 /****************************************************************************
3013  * WCMD_rename
3014  *
3015  * Rename a file.
3016  */
3017
3018 void WCMD_rename (void)
3019 {
3020   int             status;
3021   HANDLE          hff;
3022   WIN32_FIND_DATAW fd;
3023   WCHAR            input[MAX_PATH];
3024   WCHAR           *dotDst = NULL;
3025   WCHAR            drive[10];
3026   WCHAR            dir[MAX_PATH];
3027   WCHAR            fname[MAX_PATH];
3028   WCHAR            ext[MAX_PATH];
3029
3030   errorlevel = 0;
3031
3032   /* Must be at least two args */
3033   if (param1[0] == 0x00 || param2[0] == 0x00) {
3034     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3035     errorlevel = 1;
3036     return;
3037   }
3038
3039   /* Destination cannot contain a drive letter or directory separator */
3040   if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3041       SetLastError(ERROR_INVALID_PARAMETER);
3042       WCMD_print_error();
3043       errorlevel = 1;
3044       return;
3045   }
3046
3047   /* Convert partial path to full path */
3048   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3049   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3050              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3051   dotDst = strchrW(param2, '.');
3052
3053   /* Split into components */
3054   WCMD_splitpath(input, drive, dir, fname, ext);
3055
3056   hff = FindFirstFileW(input, &fd);
3057   if (hff == INVALID_HANDLE_VALUE)
3058     return;
3059
3060  do {
3061     WCHAR  dest[MAX_PATH];
3062     WCHAR  src[MAX_PATH];
3063     WCHAR *dotSrc = NULL;
3064     int   dirLen;
3065
3066     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3067
3068     /* FIXME: If dest name or extension is *, replace with filename/ext
3069        part otherwise use supplied name. This supports:
3070           ren *.fred *.jim
3071           ren jim.* fred.* etc
3072        However, windows has a more complex algorithm supporting eg
3073           ?'s and *'s mid name                                         */
3074     dotSrc = strchrW(fd.cFileName, '.');
3075
3076     /* Build src & dest name */
3077     strcpyW(src, drive);
3078     strcatW(src, dir);
3079     strcpyW(dest, src);
3080     dirLen = strlenW(src);
3081     strcatW(src, fd.cFileName);
3082
3083     /* Build name */
3084     if (param2[0] == '*') {
3085       strcatW(dest, fd.cFileName);
3086       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3087     } else {
3088       strcatW(dest, param2);
3089       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3090     }
3091
3092     /* Build Extension */
3093     if (dotDst && (*(dotDst+1)=='*')) {
3094       if (dotSrc) strcatW(dest, dotSrc);
3095     } else if (dotDst) {
3096       if (dotDst) strcatW(dest, dotDst);
3097     }
3098
3099     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3100     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
3101
3102     status = MoveFileW(src, dest);
3103
3104     if (!status) {
3105       WCMD_print_error ();
3106       errorlevel = 1;
3107     }
3108   } while (FindNextFileW(hff, &fd) != 0);
3109
3110   FindClose(hff);
3111 }
3112
3113 /*****************************************************************************
3114  * WCMD_dupenv
3115  *
3116  * Make a copy of the environment.
3117  */
3118 static WCHAR *WCMD_dupenv( const WCHAR *env )
3119 {
3120   WCHAR *env_copy;
3121   int len;
3122
3123   if( !env )
3124     return NULL;
3125
3126   len = 0;
3127   while ( env[len] )
3128     len += (strlenW(&env[len]) + 1);
3129
3130   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3131   if (!env_copy)
3132   {
3133     WINE_ERR("out of memory\n");
3134     return env_copy;
3135   }
3136   memcpy (env_copy, env, len*sizeof (WCHAR));
3137   env_copy[len] = 0;
3138
3139   return env_copy;
3140 }
3141
3142 /*****************************************************************************
3143  * WCMD_setlocal
3144  *
3145  *  setlocal pushes the environment onto a stack
3146  *  Save the environment as unicode so we don't screw anything up.
3147  */
3148 void WCMD_setlocal (const WCHAR *s) {
3149   WCHAR *env;
3150   struct env_stack *env_copy;
3151   WCHAR cwd[MAX_PATH];
3152   BOOL newdelay;
3153   static const WCHAR ondelayW[]     = {'E','N','A','B','L','E','D','E','L','A',
3154                                        'Y','E','D','E','X','P','A','N','S','I',
3155                                        'O','N','\0'};
3156   static const WCHAR offdelayW[]    = {'D','I','S','A','B','L','E','D','E','L',
3157                                        'A','Y','E','D','E','X','P','A','N','S',
3158                                        'I','O','N','\0'};
3159
3160   /* setlocal does nothing outside of batch programs */
3161   if (!context) return;
3162
3163   /* DISABLEEXTENSIONS ignored */
3164
3165   /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3166      (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3167   if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3168     newdelay = TRUE;
3169   } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3170     newdelay = FALSE;
3171   } else {
3172     newdelay = delayedsubst;
3173   }
3174   WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3175
3176   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3177   if( !env_copy )
3178   {
3179     WINE_ERR ("out of memory\n");
3180     return;
3181   }
3182
3183   env = GetEnvironmentStringsW ();
3184   env_copy->strings = WCMD_dupenv (env);
3185   if (env_copy->strings)
3186   {
3187     env_copy->batchhandle = context->h;
3188     env_copy->next = saved_environment;
3189     env_copy->delayedsubst = delayedsubst;
3190     delayedsubst = newdelay;
3191     saved_environment = env_copy;
3192
3193     /* Save the current drive letter */
3194     GetCurrentDirectoryW(MAX_PATH, cwd);
3195     env_copy->u.cwd = cwd[0];
3196   }
3197   else
3198     LocalFree (env_copy);
3199
3200   FreeEnvironmentStringsW (env);
3201
3202 }
3203
3204 /*****************************************************************************
3205  * WCMD_endlocal
3206  *
3207  *  endlocal pops the environment off a stack
3208  *  Note: When searching for '=', search from WCHAR position 1, to handle
3209  *        special internal environment variables =C:, =D: etc
3210  */
3211 void WCMD_endlocal (void) {
3212   WCHAR *env, *old, *p;
3213   struct env_stack *temp;
3214   int len, n;
3215
3216   /* setlocal does nothing outside of batch programs */
3217   if (!context) return;
3218
3219   /* setlocal needs a saved environment from within the same context (batch
3220      program) as it was saved in                                            */
3221   if (!saved_environment || saved_environment->batchhandle != context->h)
3222     return;
3223
3224   /* pop the old environment from the stack */
3225   temp = saved_environment;
3226   saved_environment = temp->next;
3227
3228   /* delete the current environment, totally */
3229   env = GetEnvironmentStringsW ();
3230   old = WCMD_dupenv (env);
3231   len = 0;
3232   while (old[len]) {
3233     n = strlenW(&old[len]) + 1;
3234     p = strchrW(&old[len] + 1, '=');
3235     if (p)
3236     {
3237       *p++ = 0;
3238       SetEnvironmentVariableW (&old[len], NULL);
3239     }
3240     len += n;
3241   }
3242   LocalFree (old);
3243   FreeEnvironmentStringsW (env);
3244
3245   /* restore old environment */
3246   env = temp->strings;
3247   len = 0;
3248   delayedsubst = temp->delayedsubst;
3249   WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3250   while (env[len]) {
3251     n = strlenW(&env[len]) + 1;
3252     p = strchrW(&env[len] + 1, '=');
3253     if (p)
3254     {
3255       *p++ = 0;
3256       SetEnvironmentVariableW (&env[len], p);
3257     }
3258     len += n;
3259   }
3260
3261   /* Restore current drive letter */
3262   if (IsCharAlphaW(temp->u.cwd)) {
3263     WCHAR envvar[4];
3264     WCHAR cwd[MAX_PATH];
3265     static const WCHAR fmt[] = {'=','%','c',':','\0'};
3266
3267     wsprintfW(envvar, fmt, temp->u.cwd);
3268     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3269       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3270       SetCurrentDirectoryW(cwd);
3271     }
3272   }
3273
3274   LocalFree (env);
3275   LocalFree (temp);
3276 }
3277
3278 /*****************************************************************************
3279  * WCMD_setshow_default
3280  *
3281  *      Set/Show the current default directory
3282  */
3283
3284 void WCMD_setshow_default (const WCHAR *args) {
3285
3286   BOOL status;
3287   WCHAR string[1024];
3288   WCHAR cwd[1024];
3289   WCHAR *pos;
3290   WIN32_FIND_DATAW fd;
3291   HANDLE hff;
3292   static const WCHAR parmD[] = {'/','D','\0'};
3293
3294   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3295
3296   /* Skip /D and trailing whitespace if on the front of the command line */
3297   if (CompareStringW(LOCALE_USER_DEFAULT,
3298                      NORM_IGNORECASE | SORT_STRINGSORT,
3299                      args, 2, parmD, -1) == CSTR_EQUAL) {
3300     args += 2;
3301     while (*args && (*args==' ' || *args=='\t'))
3302       args++;
3303   }
3304
3305   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3306   if (strlenW(args) == 0) {
3307     strcatW (cwd, newlineW);
3308     WCMD_output_asis (cwd);
3309   }
3310   else {
3311     /* Remove any double quotes, which may be in the
3312        middle, eg. cd "C:\Program Files"\Microsoft is ok */
3313     pos = string;
3314     while (*args) {
3315       if (*args != '"') *pos++ = *args;
3316       args++;
3317     }
3318     while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3319       pos--;
3320     *pos = 0x00;
3321
3322     /* Search for appropriate directory */
3323     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3324     hff = FindFirstFileW(string, &fd);
3325     if (hff != INVALID_HANDLE_VALUE) {
3326       do {
3327         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3328           WCHAR fpath[MAX_PATH];
3329           WCHAR drive[10];
3330           WCHAR dir[MAX_PATH];
3331           WCHAR fname[MAX_PATH];
3332           WCHAR ext[MAX_PATH];
3333           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3334
3335           /* Convert path into actual directory spec */
3336           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3337           WCMD_splitpath(fpath, drive, dir, fname, ext);
3338
3339           /* Rebuild path */
3340           wsprintfW(string, fmt, drive, dir, fd.cFileName);
3341           break;
3342         }
3343       } while (FindNextFileW(hff, &fd) != 0);
3344       FindClose(hff);
3345     }
3346
3347     /* Change to that directory */
3348     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3349
3350     status = SetCurrentDirectoryW(string);
3351     if (!status) {
3352       errorlevel = 1;
3353       WCMD_print_error ();
3354       return;
3355     } else {
3356
3357       /* Save away the actual new directory, to store as current location */
3358       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3359
3360       /* Restore old directory if drive letter would change, and
3361            CD x:\directory /D (or pushd c:\directory) not supplied */
3362       if ((strstrW(quals, parmD) == NULL) &&
3363           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3364         SetCurrentDirectoryW(cwd);
3365       }
3366     }
3367
3368     /* Set special =C: type environment variable, for drive letter of
3369        change of directory, even if path was restored due to missing
3370        /D (allows changing drive letter when not resident on that
3371        drive                                                          */
3372     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3373       WCHAR env[4];
3374       strcpyW(env, equalW);
3375       memcpy(env+1, string, 2 * sizeof(WCHAR));
3376       env[3] = 0x00;
3377       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3378       SetEnvironmentVariableW(env, string);
3379     }
3380
3381    }
3382   return;
3383 }
3384
3385 /****************************************************************************
3386  * WCMD_setshow_date
3387  *
3388  * Set/Show the system date
3389  * FIXME: Can't change date yet
3390  */
3391
3392 void WCMD_setshow_date (void) {
3393
3394   WCHAR curdate[64], buffer[64];
3395   DWORD count;
3396   static const WCHAR parmT[] = {'/','T','\0'};
3397
3398   if (strlenW(param1) == 0) {
3399     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3400                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3401       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3402       if (strstrW (quals, parmT) == NULL) {
3403         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3404         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3405         if (count > 2) {
3406           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3407         }
3408       }
3409     }
3410     else WCMD_print_error ();
3411   }
3412   else {
3413     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3414   }
3415 }
3416
3417 /****************************************************************************
3418  * WCMD_compare
3419  * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3420  *       the equals sign.
3421  */
3422 static int WCMD_compare( const void *a, const void *b )
3423 {
3424     int r;
3425     const WCHAR * const *str_a = a, * const *str_b = b;
3426     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3427           *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3428     if( r == CSTR_LESS_THAN ) return -1;
3429     if( r == CSTR_GREATER_THAN ) return 1;
3430     return 0;
3431 }
3432
3433 /****************************************************************************
3434  * WCMD_setshow_sortenv
3435  *
3436  * sort variables into order for display
3437  * Optionally only display those who start with a stub
3438  * returns the count displayed
3439  */
3440 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3441 {
3442   UINT count=0, len=0, i, displayedcount=0, stublen=0;
3443   const WCHAR **str;
3444
3445   if (stub) stublen = strlenW(stub);
3446
3447   /* count the number of strings, and the total length */
3448   while ( s[len] ) {
3449     len += (strlenW(&s[len]) + 1);
3450     count++;
3451   }
3452
3453   /* add the strings to an array */
3454   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3455   if( !str )
3456     return 0;
3457   str[0] = s;
3458   for( i=1; i<count; i++ )
3459     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3460
3461   /* sort the array */
3462   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3463
3464   /* print it */
3465   for( i=0; i<count; i++ ) {
3466     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3467                                 NORM_IGNORECASE | SORT_STRINGSORT,
3468                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3469       /* Don't display special internal variables */
3470       if (str[i][0] != '=') {
3471         WCMD_output_asis(str[i]);
3472         WCMD_output_asis(newlineW);
3473         displayedcount++;
3474       }
3475     }
3476   }
3477
3478   LocalFree( str );
3479   return displayedcount;
3480 }
3481
3482 /****************************************************************************
3483  * WCMD_getprecedence
3484  * Return the precedence of a particular operator
3485  */
3486 static int WCMD_getprecedence(const WCHAR in)
3487 {
3488   switch (in) {
3489     case '!':
3490     case '~':
3491     case OP_POSITIVE:
3492     case OP_NEGATIVE:
3493       return 8;
3494     case '*':
3495     case '/':
3496     case '%':
3497       return 7;
3498     case '+':
3499     case '-':
3500       return 6;
3501     case '<':
3502     case '>':
3503       return 5;
3504     case '&':
3505       return 4;
3506     case '^':
3507       return 3;
3508     case '|':
3509       return 2;
3510     case '=':
3511     case OP_ASSSIGNMUL:
3512     case OP_ASSSIGNDIV:
3513     case OP_ASSSIGNMOD:
3514     case OP_ASSSIGNADD:
3515     case OP_ASSSIGNSUB:
3516     case OP_ASSSIGNAND:
3517     case OP_ASSSIGNNOT:
3518     case OP_ASSSIGNOR:
3519     case OP_ASSSIGNSHL:
3520     case OP_ASSSIGNSHR:
3521       return 1;
3522     default:
3523       return 0;
3524   }
3525 }
3526
3527 /****************************************************************************
3528  * WCMD_pushnumber
3529  * Push either a number or name (environment variable) onto the supplied
3530  * stack
3531  */
3532 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3533   VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3534   thisstack->isnum = (var == NULL);
3535   if (var) {
3536     thisstack->variable = var;
3537     WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3538   } else {
3539     thisstack->value = num;
3540     WINE_TRACE("Pushed number %d\n", num);
3541   }
3542   thisstack->next = *varstack;
3543   *varstack = thisstack;
3544 }
3545
3546 /****************************************************************************
3547  * WCMD_peeknumber
3548  * Returns the value of the top number or environment variable on the stack
3549  * and leaves the item on the stack.
3550  */
3551 static int WCMD_peeknumber(VARSTACK **varstack) {
3552   int result = 0;
3553   VARSTACK *thisvar;
3554
3555   if (varstack) {
3556     thisvar = *varstack;
3557     if (!thisvar->isnum) {
3558       WCHAR tmpstr[MAXSTRING];
3559       if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3560         result = strtoulW(tmpstr,NULL,0);
3561       }
3562       WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3563     } else {
3564       result = thisvar->value;
3565     }
3566   }
3567   WINE_TRACE("Peeked number %d\n", result);
3568   return result;
3569 }
3570
3571 /****************************************************************************
3572  * WCMD_popnumber
3573  * Returns the value of the top number or environment variable on the stack
3574  * and removes the item from the stack.
3575  */
3576 static int WCMD_popnumber(VARSTACK **varstack) {
3577   int result = 0;
3578   VARSTACK *thisvar;
3579
3580   if (varstack) {
3581     thisvar = *varstack;
3582     result = WCMD_peeknumber(varstack);
3583     if (!thisvar->isnum) heap_free(thisvar->variable);
3584     *varstack = thisvar->next;
3585     heap_free(thisvar);
3586   }
3587   WINE_TRACE("Popped number %d\n", result);
3588   return result;
3589 }
3590
3591 /****************************************************************************
3592  * WCMD_pushoperator
3593  * Push an operator onto the supplied stack
3594  */
3595 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3596   OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3597   thisstack->precedence = precedence;
3598   thisstack->op = op;
3599   thisstack->next = *opstack;
3600   WINE_TRACE("Pushed operator %c\n", op);
3601   *opstack = thisstack;
3602 }
3603
3604 /****************************************************************************
3605  * WCMD_popoperator
3606  * Returns the operator from the top of the stack and removes the item from
3607  * the stack.
3608  */
3609 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3610   WCHAR result = 0;
3611   OPSTACK *thisop;
3612
3613   if (opstack) {
3614     thisop = *opstack;
3615     result = thisop->op;
3616     *opstack = thisop->next;
3617     heap_free(thisop);
3618   }
3619   WINE_TRACE("Popped operator %c\n", result);
3620   return result;
3621 }
3622
3623 /****************************************************************************
3624  * WCMD_reduce
3625  * Actions the top operator on the stack against the first and sometimes
3626  * second value on the variable stack, and pushes the result
3627  * Returns non-zero on error.
3628  */
3629 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3630   OPSTACK *thisop;
3631   int var1,var2;
3632   int rc = 0;
3633
3634   if (!*opstack || !*varstack) {
3635     WINE_TRACE("No operators for the reduce\n");
3636     return WCMD_NOOPERATOR;
3637   }
3638
3639   /* Remove the top operator */
3640   thisop = *opstack;
3641   *opstack = (*opstack)->next;
3642   WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop->op);
3643
3644   /* One variable operators */
3645   var1 = WCMD_popnumber(varstack);
3646   switch (thisop->op) {
3647   case '!': WCMD_pushnumber(NULL, !var1, varstack);
3648             break;
3649   case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3650             break;
3651   case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3652             break;
3653   case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3654             break;
3655   }
3656
3657   /* Two variable operators */
3658   if (!*varstack) {
3659     WINE_TRACE("No operands left for the reduce?\n");
3660     return WCMD_NOOPERAND;
3661   }
3662   switch (thisop->op) {
3663   case '!':
3664   case '~':
3665   case OP_POSITIVE:
3666   case OP_NEGATIVE:
3667             break; /* Handled above */
3668   case '*': var2 = WCMD_popnumber(varstack);
3669             WCMD_pushnumber(NULL, var2*var1, varstack);
3670             break;
3671   case '/': var2 = WCMD_popnumber(varstack);
3672             if (var1 == 0) return WCMD_DIVIDEBYZERO;
3673             WCMD_pushnumber(NULL, var2/var1, varstack);
3674             break;
3675   case '+': var2 = WCMD_popnumber(varstack);
3676             WCMD_pushnumber(NULL, var2+var1, varstack);
3677             break;
3678   case '-': var2 = WCMD_popnumber(varstack);
3679             WCMD_pushnumber(NULL, var2-var1, varstack);
3680             break;
3681   case '&': var2 = WCMD_popnumber(varstack);
3682             WCMD_pushnumber(NULL, var2&var1, varstack);
3683             break;
3684   case '%': var2 = WCMD_popnumber(varstack);
3685             if (var1 == 0) return WCMD_DIVIDEBYZERO;
3686             WCMD_pushnumber(NULL, var2%var1, varstack);
3687             break;
3688   case '^': var2 = WCMD_popnumber(varstack);
3689             WCMD_pushnumber(NULL, var2^var1, varstack);
3690             break;
3691   case '<': var2 = WCMD_popnumber(varstack);
3692             /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3693                which differs from the compiler (for example gcc) so being explicit. */
3694             if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3695               WCMD_pushnumber(NULL, 0, varstack);
3696             } else {
3697               WCMD_pushnumber(NULL, var2<<var1, varstack);
3698             }
3699             break;
3700   case '>': var2 = WCMD_popnumber(varstack);
3701             WCMD_pushnumber(NULL, var2>>var1, varstack);
3702             break;
3703   case '|': var2 = WCMD_popnumber(varstack);
3704             WCMD_pushnumber(NULL, var2|var1, varstack);
3705             break;
3706
3707   case OP_ASSSIGNMUL:
3708   case OP_ASSSIGNDIV:
3709   case OP_ASSSIGNMOD:
3710   case OP_ASSSIGNADD:
3711   case OP_ASSSIGNSUB:
3712   case OP_ASSSIGNAND:
3713   case OP_ASSSIGNNOT:
3714   case OP_ASSSIGNOR:
3715   case OP_ASSSIGNSHL:
3716   case OP_ASSSIGNSHR:
3717         {
3718           int i = 0;
3719
3720           /* The left of an equals must be one variable */
3721           if (!(*varstack) || (*varstack)->isnum) {
3722             return WCMD_NOOPERAND;
3723           }
3724
3725           /* Make the number stack grow by inserting the value of the variable */
3726           var2 = WCMD_peeknumber(varstack);
3727           WCMD_pushnumber(NULL, var2, varstack);
3728           WCMD_pushnumber(NULL, var1, varstack);
3729
3730           /* Make the operand stack grow by pushing the assign operator plus the
3731              operator to perform                                                 */
3732           while (calcassignments[i].op != ' ' &&
3733                  calcassignments[i].calculatedop != thisop->op) {
3734             i++;
3735           }
3736           if (calcassignments[i].calculatedop == ' ') {
3737             WINE_ERR("Unexpected operator %c\n", thisop->op);
3738             return WCMD_NOOPERATOR;
3739           }
3740           WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3741           WCMD_pushoperator(calcassignments[i].op,
3742                             WCMD_getprecedence(calcassignments[i].op), opstack);
3743           break;
3744         }
3745
3746   case '=':
3747         {
3748           WCHAR  intFormat[] = {'%','d','\0'};
3749           WCHAR  result[MAXSTRING];
3750
3751           /* Build the result, then push it onto the stack */
3752           sprintfW(result, intFormat, var1);
3753           WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3754                      wine_dbgstr_w(result));
3755           SetEnvironmentVariableW((*varstack)->variable, result);
3756           var2 = WCMD_popnumber(varstack);
3757           WCMD_pushnumber(NULL, var1, varstack);
3758           break;
3759         }
3760
3761   default:  WINE_ERR("Unrecognized operator %c\n", thisop->op);
3762   }
3763
3764   heap_free(thisop);
3765   return rc;
3766 }
3767
3768
3769 /****************************************************************************
3770  * WCMD_handleExpression
3771  * Handles an expression provided to set /a - If it finds brackets, it uses
3772  * recursion to process the parts in brackets.
3773  */
3774 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3775 {
3776   static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3777                                      '+','<','>','&','^','|','=',',','\0' };
3778   int       rc = 0;
3779   WCHAR    *pos;
3780   BOOL      lastwasnumber = FALSE;  /* FALSE makes a minus at the start of the expression easier to handle */
3781   OPSTACK  *opstackhead = NULL;
3782   VARSTACK *varstackhead = NULL;
3783   WCHAR     foundhalf = 0;
3784
3785   /* Initialize */
3786   WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3787   pos = *expr;
3788
3789   /* Iterate through until whole expression is processed */
3790   while (pos && *pos) {
3791     BOOL treatasnumber;
3792
3793     /* Skip whitespace to get to the next character to process*/
3794     while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3795     if (!*pos) goto exprreturn;
3796
3797     /* If we have found anything other than an operator then its a number/variable */
3798     if (strchrW(mathDelims, *pos) == NULL) {
3799       WCHAR *parmstart, *parm, *dupparm;
3800       WCHAR *nextpos;
3801
3802       /* Cannot have an expression with var/number twice, without an operator
3803          in-between, nor or number following a half constructed << or >> operator */
3804       if (lastwasnumber || foundhalf) {
3805         rc = WCMD_NOOPERATOR;
3806         goto exprerrorreturn;
3807       }
3808       lastwasnumber = TRUE;
3809
3810       if (isdigitW(*pos)) {
3811         /* For a number - just push it onto the stack */
3812         int num = strtoulW(pos, &nextpos, 0);
3813         WCMD_pushnumber(NULL, num, &varstackhead);
3814         pos = nextpos;
3815
3816         /* Verify the number was validly formed */
3817         if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3818           rc = WCMD_BADHEXOCT;
3819           goto exprerrorreturn;
3820         }
3821       } else {
3822
3823         /* For a variable - just push it onto the stack */
3824         parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3825         dupparm = heap_strdupW(parm);
3826         WCMD_pushnumber(dupparm, 0, &varstackhead);
3827         pos = parmstart + strlenW(dupparm);
3828       }
3829       continue;
3830     }
3831
3832     /* We have found an operator. Some operators are one character, some two, and the minus
3833        and plus signs need special processing as they can be either operators or just influence
3834        the parameter which follows them                                                         */
3835     if (foundhalf && (*pos != foundhalf)) {
3836       /* Badly constructed operator pair */
3837       rc = WCMD_NOOPERATOR;
3838       goto exprerrorreturn;
3839     }
3840
3841     treatasnumber = FALSE; /* We are processing an operand */
3842     switch (*pos) {
3843
3844     /* > and < are special as they are double character operators (and spaces can be between them!)
3845        If we see these for the first time, set a flag, and second time around we continue.
3846        Note these double character operators are stored as just one of the characters on the stack */
3847     case '>':
3848     case '<': if (!foundhalf) {
3849                 foundhalf = *pos;
3850                 pos++;
3851                 break;
3852               }
3853               /* We have found the rest, so clear up the knowledge of the half completed part and
3854                  drop through to normal operator processing                                       */
3855               foundhalf = 0;
3856               /* drop through */
3857
3858     case '=': if (*pos=='=') {
3859                 /* = is special cased as if the last was an operator then we may have e.g. += or
3860                    *= etc which we need to handle by replacing the operator that is on the stack
3861                    with a calculated assignment equivalent                                       */
3862                 if (!lastwasnumber && opstackhead) {
3863                   int i = 0;
3864                   while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3865                     i++;
3866                   }
3867                   if (calcassignments[i].op == ' ') {
3868                     rc = WCMD_NOOPERAND;
3869                     goto exprerrorreturn;
3870                   } else {
3871                     /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3872                        when the general operator handling happens further down.                   */
3873                     *pos = calcassignments[i].calculatedop;
3874                     WCMD_popoperator(&opstackhead);
3875                   }
3876                 }
3877               }
3878               /* Drop though */
3879
3880     /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3881        so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3882     case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3883               /* drop through */
3884     case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3885               /* drop through */
3886
3887     /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3888     case '!': /* drop through */
3889     case '~': /* drop through */
3890     case '/': /* drop through */
3891     case '%': /* drop through */
3892     case '&': /* drop through */
3893     case '^': /* drop through */
3894     case '*': /* drop through */
3895     case '|':
3896                /* General code for handling most of the operators - look at the
3897                   precedence of the top item on the stack, and see if we need to
3898                   action the stack before we push something else onto it.        */
3899                {
3900                  int precedence = WCMD_getprecedence(*pos);
3901                  WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3902                             precedence, !opstackhead?-1:opstackhead->precedence);
3903
3904                  /* In general, for things with the same precedence, reduce immediately
3905                     except for assignments and unary operators which do not             */
3906                  while (!rc && opstackhead &&
3907                         ((opstackhead->precedence > precedence) ||
3908                          ((opstackhead->precedence == precedence) &&
3909                             (precedence != 1) && (precedence != 8)))) {
3910                    rc = WCMD_reduce(&opstackhead, &varstackhead);
3911                  }
3912                  if (rc) goto exprerrorreturn;
3913                  WCMD_pushoperator(*pos, precedence, &opstackhead);
3914                  pos++;
3915                  break;
3916                }
3917
3918     /* comma means start a new expression, ie calculate what we have */
3919     case ',':
3920                {
3921                  int prevresult = -1;
3922                  WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3923                  while (!rc && opstackhead) {
3924                    rc = WCMD_reduce(&opstackhead, &varstackhead);
3925                  }
3926                  if (rc) goto exprerrorreturn;
3927                  /* If we have anything other than one number left, error
3928                     otherwise throw the number away                      */
3929                  if (!varstackhead || varstackhead->next) {
3930                    rc = WCMD_NOOPERATOR;
3931                    goto exprerrorreturn;
3932                  }
3933                  prevresult = WCMD_popnumber(&varstackhead);
3934                  WINE_TRACE("Expression resolved to %d\n", prevresult);
3935                  heap_free(varstackhead);
3936                  varstackhead = NULL;
3937                  pos++;
3938                  break;
3939                }
3940
3941     /* Open bracket - use iteration to parse the inner expression, then continue */
3942     case '(' : {
3943                  int exprresult = 0;
3944                  pos++;
3945                  rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
3946                  if (rc) goto exprerrorreturn;
3947                  WCMD_pushnumber(NULL, exprresult, &varstackhead);
3948                  break;
3949                }
3950
3951     /* Close bracket - we have finished this depth, calculate and return */
3952     case ')' : {
3953                  pos++;
3954                  treatasnumber = TRUE; /* Things in brackets result in a number */
3955                  if (depth == 0) {
3956                    rc = WCMD_BADPAREN;
3957                    goto exprerrorreturn;
3958                  }
3959                  goto exprreturn;
3960                }
3961
3962     default:
3963         WINE_ERR("Unrecognized operator %c\n", *pos);
3964         pos++;
3965     }
3966     lastwasnumber = treatasnumber;
3967   }
3968
3969 exprreturn:
3970   *expr = pos;
3971
3972   /* We need to reduce until we have a single number (or variable) on the
3973      stack and set the return value to that                               */
3974   while (!rc && opstackhead) {
3975     rc = WCMD_reduce(&opstackhead, &varstackhead);
3976   }
3977   if (rc) goto exprerrorreturn;
3978
3979   /* If we have anything other than one number left, error
3980       otherwise throw the number away                      */
3981   if (!varstackhead || varstackhead->next) {
3982     rc = WCMD_NOOPERATOR;
3983     goto exprerrorreturn;
3984   }
3985
3986   /* Now get the number (and convert if its just a variable name) */
3987   *ret = WCMD_popnumber(&varstackhead);
3988
3989 exprerrorreturn:
3990   /* Free all remaining memory */
3991   while (opstackhead) WCMD_popoperator(&opstackhead);
3992   while (varstackhead) WCMD_popnumber(&varstackhead);
3993
3994   WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
3995   return rc;
3996 }
3997
3998 /****************************************************************************
3999  * WCMD_setshow_env
4000  *
4001  * Set/Show the environment variables
4002  */
4003
4004 void WCMD_setshow_env (WCHAR *s) {
4005
4006   LPVOID env;
4007   WCHAR *p;
4008   int status;
4009   static const WCHAR parmP[] = {'/','P','\0'};
4010   static const WCHAR parmA[] = {'/','A','\0'};
4011   WCHAR string[MAXSTRING];
4012
4013   if (param1[0] == 0x00 && quals[0] == 0x00) {
4014     env = GetEnvironmentStringsW();
4015     WCMD_setshow_sortenv( env, NULL );
4016     return;
4017   }
4018
4019   /* See if /P supplied, and if so echo the prompt, and read in a reply */
4020   if (CompareStringW(LOCALE_USER_DEFAULT,
4021                      NORM_IGNORECASE | SORT_STRINGSORT,
4022                      s, 2, parmP, -1) == CSTR_EQUAL) {
4023     DWORD count;
4024
4025     s += 2;
4026     while (*s && (*s==' ' || *s=='\t')) s++;
4027     if (*s=='\"')
4028         WCMD_strip_quotes(s);
4029
4030     /* If no parameter, or no '=' sign, return an error */
4031     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4032       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4033       return;
4034     }
4035
4036     /* Output the prompt */
4037     *p++ = '\0';
4038     if (strlenW(p) != 0) WCMD_output_asis(p);
4039
4040     /* Read the reply */
4041     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4042     if (count > 1) {
4043       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4044       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4045       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4046                  wine_dbgstr_w(string));
4047       status = SetEnvironmentVariableW(s, string);
4048     }
4049
4050   /* See if /A supplied, and if so calculate the results of all the expressions */
4051   } else if (CompareStringW(LOCALE_USER_DEFAULT,
4052                             NORM_IGNORECASE | SORT_STRINGSORT,
4053                             s, 2, parmA, -1) == CSTR_EQUAL) {
4054     /* /A supplied, so evaluate expressions and set variables appropriately */
4055     /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result  */
4056     /* of the final computation                                             */
4057     int result = 0;
4058     int rc = 0;
4059     WCHAR *thisexpr;
4060     WCHAR *src,*dst;
4061
4062     /* Remove all quotes before doing any calculations */
4063     thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4064     src = s+2;
4065     dst = thisexpr;
4066     while (*src) {
4067       if (*src != '"') *dst++ = *src;
4068       src++;
4069     }
4070     *dst = 0;
4071
4072     /* Now calculate the results of the expression */
4073     src = thisexpr;
4074     rc = WCMD_handleExpression(&src, &result, 0);
4075     heap_free(thisexpr);
4076
4077     /* If parsing failed, issue the error message */
4078     if (rc > 0) {
4079       WCMD_output_stderr(WCMD_LoadMessage(rc));
4080       return;
4081     }
4082
4083     /* If we have no context (interactive or cmd.exe /c) print the final result */
4084     if (!context) {
4085       static const WCHAR fmt[] = {'%','d','\0'};
4086       sprintfW(string, fmt, result);
4087       WCMD_output_asis(string);
4088     }
4089
4090   } else {
4091     DWORD gle;
4092
4093     if (*s=='\"')
4094         WCMD_strip_quotes(s);
4095     p = strchrW (s, '=');
4096     if (p == NULL) {
4097       env = GetEnvironmentStringsW();
4098       if (WCMD_setshow_sortenv( env, s ) == 0) {
4099         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4100         errorlevel = 1;
4101       }
4102       return;
4103     }
4104     *p++ = '\0';
4105
4106     if (strlenW(p) == 0) p = NULL;
4107     WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4108                wine_dbgstr_w(p));
4109     status = SetEnvironmentVariableW(s, p);
4110     gle = GetLastError();
4111     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4112       errorlevel = 1;
4113     } else if ((!status)) WCMD_print_error();
4114     else errorlevel = 0;
4115   }
4116 }
4117
4118 /****************************************************************************
4119  * WCMD_setshow_path
4120  *
4121  * Set/Show the path environment variable
4122  */
4123
4124 void WCMD_setshow_path (const WCHAR *args) {
4125
4126   WCHAR string[1024];
4127   DWORD status;
4128   static const WCHAR pathW[] = {'P','A','T','H','\0'};
4129   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4130
4131   if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4132     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4133     if (status != 0) {
4134       WCMD_output_asis ( pathEqW);
4135       WCMD_output_asis ( string);
4136       WCMD_output_asis ( newlineW);
4137     }
4138     else {
4139       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4140     }
4141   }
4142   else {
4143     if (*args == '=') args++; /* Skip leading '=' */
4144     status = SetEnvironmentVariableW(pathW, args);
4145     if (!status) WCMD_print_error();
4146   }
4147 }
4148
4149 /****************************************************************************
4150  * WCMD_setshow_prompt
4151  *
4152  * Set or show the command prompt.
4153  */
4154
4155 void WCMD_setshow_prompt (void) {
4156
4157   WCHAR *s;
4158   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4159
4160   if (strlenW(param1) == 0) {
4161     SetEnvironmentVariableW(promptW, NULL);
4162   }
4163   else {
4164     s = param1;
4165     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4166     if (strlenW(s) == 0) {
4167       SetEnvironmentVariableW(promptW, NULL);
4168     }
4169     else SetEnvironmentVariableW(promptW, s);
4170   }
4171 }
4172
4173 /****************************************************************************
4174  * WCMD_setshow_time
4175  *
4176  * Set/Show the system time
4177  * FIXME: Can't change time yet
4178  */
4179
4180 void WCMD_setshow_time (void) {
4181
4182   WCHAR curtime[64], buffer[64];
4183   DWORD count;
4184   SYSTEMTIME st;
4185   static const WCHAR parmT[] = {'/','T','\0'};
4186
4187   if (strlenW(param1) == 0) {
4188     GetLocalTime(&st);
4189     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4190                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4191       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4192       if (strstrW (quals, parmT) == NULL) {
4193         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4194         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4195         if (count > 2) {
4196           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4197         }
4198       }
4199     }
4200     else WCMD_print_error ();
4201   }
4202   else {
4203     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4204   }
4205 }
4206
4207 /****************************************************************************
4208  * WCMD_shift
4209  *
4210  * Shift batch parameters.
4211  * Optional /n says where to start shifting (n=0-8)
4212  */
4213
4214 void WCMD_shift (const WCHAR *args) {
4215   int start;
4216
4217   if (context != NULL) {
4218     WCHAR *pos = strchrW(args, '/');
4219     int   i;
4220
4221     if (pos == NULL) {
4222       start = 0;
4223     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4224       start = (*(pos+1) - '0');
4225     } else {
4226       SetLastError(ERROR_INVALID_PARAMETER);
4227       WCMD_print_error();
4228       return;
4229     }
4230
4231     WINE_TRACE("Shifting variables, starting at %d\n", start);
4232     for (i=start;i<=8;i++) {
4233       context -> shift_count[i] = context -> shift_count[i+1] + 1;
4234     }
4235     context -> shift_count[9] = context -> shift_count[9] + 1;
4236   }
4237
4238 }
4239
4240 /****************************************************************************
4241  * WCMD_start
4242  */
4243 void WCMD_start(const WCHAR *args)
4244 {
4245     static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4246                                  '\\','s','t','a','r','t','.','e','x','e',0};
4247     WCHAR file[MAX_PATH];
4248     WCHAR *cmdline;
4249     STARTUPINFOW st;
4250     PROCESS_INFORMATION pi;
4251
4252     GetWindowsDirectoryW( file, MAX_PATH );
4253     strcatW( file, exeW );
4254     cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
4255     strcpyW( cmdline, file );
4256     strcatW( cmdline, spaceW );
4257     strcatW( cmdline, args );
4258
4259     memset( &st, 0, sizeof(STARTUPINFOW) );
4260     st.cb = sizeof(STARTUPINFOW);
4261
4262     if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4263     {
4264         WaitForSingleObject( pi.hProcess, INFINITE );
4265         GetExitCodeProcess( pi.hProcess, &errorlevel );
4266         if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4267         CloseHandle(pi.hProcess);
4268         CloseHandle(pi.hThread);
4269     }
4270     else
4271     {
4272         SetLastError(ERROR_FILE_NOT_FOUND);
4273         WCMD_print_error ();
4274         errorlevel = 9009;
4275     }
4276     heap_free(cmdline);
4277 }
4278
4279 /****************************************************************************
4280  * WCMD_title
4281  *
4282  * Set the console title
4283  */
4284 void WCMD_title (const WCHAR *args) {
4285   SetConsoleTitleW(args);
4286 }
4287
4288 /****************************************************************************
4289  * WCMD_type
4290  *
4291  * Copy a file to standard output.
4292  */
4293
4294 void WCMD_type (WCHAR *args) {
4295
4296   int   argno         = 0;
4297   WCHAR *argN          = args;
4298   BOOL  writeHeaders  = FALSE;
4299
4300   if (param1[0] == 0x00) {
4301     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4302     return;
4303   }
4304
4305   if (param2[0] != 0x00) writeHeaders = TRUE;
4306
4307   /* Loop through all args */
4308   errorlevel = 0;
4309   while (argN) {
4310     WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4311
4312     HANDLE h;
4313     WCHAR buffer[512];
4314     DWORD count;
4315
4316     if (!argN) break;
4317
4318     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4319     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4320                 FILE_ATTRIBUTE_NORMAL, NULL);
4321     if (h == INVALID_HANDLE_VALUE) {
4322       WCMD_print_error ();
4323       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4324       errorlevel = 1;
4325     } else {
4326       if (writeHeaders) {
4327         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4328         WCMD_output(fmt, thisArg);
4329       }
4330       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4331         if (count == 0) break;  /* ReadFile reports success on EOF! */
4332         buffer[count] = 0;
4333         WCMD_output_asis (buffer);
4334       }
4335       CloseHandle (h);
4336     }
4337   }
4338 }
4339
4340 /****************************************************************************
4341  * WCMD_more
4342  *
4343  * Output either a file or stdin to screen in pages
4344  */
4345
4346 void WCMD_more (WCHAR *args) {
4347
4348   int   argno         = 0;
4349   WCHAR *argN         = args;
4350   WCHAR  moreStr[100];
4351   WCHAR  moreStrPage[100];
4352   WCHAR  buffer[512];
4353   DWORD count;
4354   static const WCHAR moreStart[] = {'-','-',' ','\0'};
4355   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
4356   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
4357                                     ')',' ','-','-','\n','\0'};
4358   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
4359
4360   /* Prefix the NLS more with '-- ', then load the text */
4361   errorlevel = 0;
4362   strcpyW(moreStr, moreStart);
4363   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4364               (sizeof(moreStr)/sizeof(WCHAR))-3);
4365
4366   if (param1[0] == 0x00) {
4367
4368     /* Wine implements pipes via temporary files, and hence stdin is
4369        effectively reading from the file. This means the prompts for
4370        more are satisfied by the next line from the input (file). To
4371        avoid this, ensure stdin is to the console                    */
4372     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
4373     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4374                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
4375                          FILE_ATTRIBUTE_NORMAL, 0);
4376     WINE_TRACE("No parms - working probably in pipe mode\n");
4377     SetStdHandle(STD_INPUT_HANDLE, hConIn);
4378
4379     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4380        once you get in this bit unless due to a pipe, its going to end badly...  */
4381     wsprintfW(moreStrPage, moreFmt, moreStr);
4382
4383     WCMD_enter_paged_mode(moreStrPage);
4384     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4385       if (count == 0) break;    /* ReadFile reports success on EOF! */
4386       buffer[count] = 0;
4387       WCMD_output_asis (buffer);
4388     }
4389     WCMD_leave_paged_mode();
4390
4391     /* Restore stdin to what it was */
4392     SetStdHandle(STD_INPUT_HANDLE, hstdin);
4393     CloseHandle(hConIn);
4394
4395     return;
4396   } else {
4397     BOOL needsPause = FALSE;
4398
4399     /* Loop through all args */
4400     WINE_TRACE("Parms supplied - working through each file\n");
4401     WCMD_enter_paged_mode(moreStrPage);
4402
4403     while (argN) {
4404       WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4405       HANDLE h;
4406
4407       if (!argN) break;
4408
4409       if (needsPause) {
4410
4411         /* Wait */
4412         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4413         WCMD_leave_paged_mode();
4414         WCMD_output_asis(moreStrPage);
4415         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4416         WCMD_enter_paged_mode(moreStrPage);
4417       }
4418
4419
4420       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4421       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4422                 FILE_ATTRIBUTE_NORMAL, NULL);
4423       if (h == INVALID_HANDLE_VALUE) {
4424         WCMD_print_error ();
4425         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4426         errorlevel = 1;
4427       } else {
4428         ULONG64 curPos  = 0;
4429         ULONG64 fileLen = 0;
4430         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
4431
4432         /* Get the file size */
4433         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4434         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4435
4436         needsPause = TRUE;
4437         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4438           if (count == 0) break;        /* ReadFile reports success on EOF! */
4439           buffer[count] = 0;
4440           curPos += count;
4441
4442           /* Update % count (would be used in WCMD_output_asis as prompt) */
4443           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4444
4445           WCMD_output_asis (buffer);
4446         }
4447         CloseHandle (h);
4448       }
4449     }
4450
4451     WCMD_leave_paged_mode();
4452   }
4453 }
4454
4455 /****************************************************************************
4456  * WCMD_verify
4457  *
4458  * Display verify flag.
4459  * FIXME: We don't actually do anything with the verify flag other than toggle
4460  * it...
4461  */
4462
4463 void WCMD_verify (const WCHAR *args) {
4464
4465   int count;
4466
4467   count = strlenW(args);
4468   if (count == 0) {
4469     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4470     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4471     return;
4472   }
4473   if (lstrcmpiW(args, onW) == 0) {
4474     verify_mode = TRUE;
4475     return;
4476   }
4477   else if (lstrcmpiW(args, offW) == 0) {
4478     verify_mode = FALSE;
4479     return;
4480   }
4481   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4482 }
4483
4484 /****************************************************************************
4485  * WCMD_version
4486  *
4487  * Display version info.
4488  */
4489
4490 void WCMD_version (void) {
4491
4492   WCMD_output_asis (version_string);
4493
4494 }
4495
4496 /****************************************************************************
4497  * WCMD_volume
4498  *
4499  * Display volume information (set_label = FALSE)
4500  * Additionally set volume label (set_label = TRUE)
4501  * Returns 1 on success, 0 otherwise
4502  */
4503
4504 int WCMD_volume(BOOL set_label, const WCHAR *path)
4505 {
4506   DWORD count, serial;
4507   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4508   BOOL status;
4509
4510   if (strlenW(path) == 0) {
4511     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4512     if (!status) {
4513       WCMD_print_error ();
4514       return 0;
4515     }
4516     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4517                                    &serial, NULL, NULL, NULL, 0);
4518   }
4519   else {
4520     static const WCHAR fmt[] = {'%','s','\\','\0'};
4521     if ((path[1] != ':') || (strlenW(path) != 2)) {
4522       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4523       return 0;
4524     }
4525     wsprintfW (curdir, fmt, path);
4526     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4527                                    &serial, NULL,
4528         NULL, NULL, 0);
4529   }
4530   if (!status) {
4531     WCMD_print_error ();
4532     return 0;
4533   }
4534   if (label[0] != '\0') {
4535     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4536         curdir[0], label);
4537   }
4538   else {
4539     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4540         curdir[0]);
4541   }
4542   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4543         HIWORD(serial), LOWORD(serial));
4544   if (set_label) {
4545     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4546     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4547     if (count > 1) {
4548       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
4549       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4550     }
4551     if (strlenW(path) != 0) {
4552       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4553     }
4554     else {
4555       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4556     }
4557   }
4558   return 1;
4559 }
4560
4561 /**************************************************************************
4562  * WCMD_exit
4563  *
4564  * Exit either the process, or just this batch program
4565  *
4566  */
4567
4568 void WCMD_exit (CMD_LIST **cmdList) {
4569
4570     static const WCHAR parmB[] = {'/','B','\0'};
4571     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4572
4573     if (context && lstrcmpiW(quals, parmB) == 0) {
4574         errorlevel = rc;
4575         context -> skip_rest = TRUE;
4576         *cmdList = NULL;
4577     } else {
4578         ExitProcess(rc);
4579     }
4580 }
4581
4582
4583 /*****************************************************************************
4584  * WCMD_assoc
4585  *
4586  *      Lists or sets file associations  (assoc = TRUE)
4587  *      Lists or sets file types         (assoc = FALSE)
4588  */
4589 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4590
4591     HKEY    key;
4592     DWORD   accessOptions = KEY_READ;
4593     WCHAR   *newValue;
4594     LONG    rc = ERROR_SUCCESS;
4595     WCHAR    keyValue[MAXSTRING];
4596     DWORD   valueLen = MAXSTRING;
4597     HKEY    readKey;
4598     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4599                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4600
4601     /* See if parameter includes '=' */
4602     errorlevel = 0;
4603     newValue = strchrW(args, '=');
4604     if (newValue) accessOptions |= KEY_WRITE;
4605
4606     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4607     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4608                       accessOptions, &key) != ERROR_SUCCESS) {
4609       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4610       return;
4611     }
4612
4613     /* If no parameters then list all associations */
4614     if (*args == 0x00) {
4615       int index = 0;
4616
4617       /* Enumerate all the keys */
4618       while (rc != ERROR_NO_MORE_ITEMS) {
4619         WCHAR  keyName[MAXSTRING];
4620         DWORD nameLen;
4621
4622         /* Find the next value */
4623         nameLen = MAXSTRING;
4624         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4625
4626         if (rc == ERROR_SUCCESS) {
4627
4628           /* Only interested in extension ones if assoc, or others
4629              if not assoc                                          */
4630           if ((keyName[0] == '.' && assoc) ||
4631               (!(keyName[0] == '.') && (!assoc)))
4632           {
4633             WCHAR subkey[MAXSTRING];
4634             strcpyW(subkey, keyName);
4635             if (!assoc) strcatW(subkey, shOpCmdW);
4636
4637             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4638
4639               valueLen = sizeof(keyValue)/sizeof(WCHAR);
4640               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4641               WCMD_output_asis(keyName);
4642               WCMD_output_asis(equalW);
4643               /* If no default value found, leave line empty after '=' */
4644               if (rc == ERROR_SUCCESS) {
4645                 WCMD_output_asis(keyValue);
4646               }
4647               WCMD_output_asis(newlineW);
4648               RegCloseKey(readKey);
4649             }
4650           }
4651         }
4652       }
4653
4654     } else {
4655
4656       /* Parameter supplied - if no '=' on command line, its a query */
4657       if (newValue == NULL) {
4658         WCHAR *space;
4659         WCHAR subkey[MAXSTRING];
4660
4661         /* Query terminates the parameter at the first space */
4662         strcpyW(keyValue, args);
4663         space = strchrW(keyValue, ' ');
4664         if (space) *space=0x00;
4665
4666         /* Set up key name */
4667         strcpyW(subkey, keyValue);
4668         if (!assoc) strcatW(subkey, shOpCmdW);
4669
4670         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4671
4672           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4673           WCMD_output_asis(args);
4674           WCMD_output_asis(equalW);
4675           /* If no default value found, leave line empty after '=' */
4676           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4677           WCMD_output_asis(newlineW);
4678           RegCloseKey(readKey);
4679
4680         } else {
4681           WCHAR  msgbuffer[MAXSTRING];
4682
4683           /* Load the translated 'File association not found' */
4684           if (assoc) {
4685             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4686           } else {
4687             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4688           }
4689           WCMD_output_stderr(msgbuffer, keyValue);
4690           errorlevel = 2;
4691         }
4692
4693       /* Not a query - its a set or clear of a value */
4694       } else {
4695
4696         WCHAR subkey[MAXSTRING];
4697
4698         /* Get pointer to new value */
4699         *newValue = 0x00;
4700         newValue++;
4701
4702         /* Set up key name */
4703         strcpyW(subkey, args);
4704         if (!assoc) strcatW(subkey, shOpCmdW);
4705
4706         /* If nothing after '=' then clear value - only valid for ASSOC */
4707         if (*newValue == 0x00) {
4708
4709           if (assoc) rc = RegDeleteKeyW(key, args);
4710           if (assoc && rc == ERROR_SUCCESS) {
4711             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4712
4713           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4714             WCMD_print_error();
4715             errorlevel = 2;
4716
4717           } else {
4718             WCHAR  msgbuffer[MAXSTRING];
4719
4720             /* Load the translated 'File association not found' */
4721             if (assoc) {
4722               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4723                           sizeof(msgbuffer)/sizeof(WCHAR));
4724             } else {
4725               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4726                           sizeof(msgbuffer)/sizeof(WCHAR));
4727             }
4728             WCMD_output_stderr(msgbuffer, keyValue);
4729             errorlevel = 2;
4730           }
4731
4732         /* It really is a set value = contents */
4733         } else {
4734           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4735                               accessOptions, NULL, &readKey, NULL);
4736           if (rc == ERROR_SUCCESS) {
4737             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4738                                 (LPBYTE)newValue,
4739                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
4740             RegCloseKey(readKey);
4741           }
4742
4743           if (rc != ERROR_SUCCESS) {
4744             WCMD_print_error();
4745             errorlevel = 2;
4746           } else {
4747             WCMD_output_asis(args);
4748             WCMD_output_asis(equalW);
4749             WCMD_output_asis(newValue);
4750             WCMD_output_asis(newlineW);
4751           }
4752         }
4753       }
4754     }
4755
4756     /* Clean up */
4757     RegCloseKey(key);
4758 }
4759
4760 /****************************************************************************
4761  * WCMD_color
4762  *
4763  * Colors the terminal screen.
4764  */
4765
4766 void WCMD_color (void) {
4767
4768   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4769   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4770
4771   if (param1[0] != 0x00 && strlenW(param1) > 2) {
4772     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4773     return;
4774   }
4775
4776   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4777   {
4778       COORD topLeft;
4779       DWORD screenSize;
4780       DWORD color = 0;
4781
4782       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4783
4784       topLeft.X = 0;
4785       topLeft.Y = 0;
4786
4787       /* Convert the color hex digits */
4788       if (param1[0] == 0x00) {
4789         color = defaultColor;
4790       } else {
4791         color = strtoulW(param1, NULL, 16);
4792       }
4793
4794       /* Fail if fg == bg color */
4795       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4796         errorlevel = 1;
4797         return;
4798       }
4799
4800       /* Set the current screen contents and ensure all future writes
4801          remain this color                                             */
4802       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4803       SetConsoleTextAttribute(hStdOut, color);
4804   }
4805 }