wined3d: Use the np2_fixup to find out if a RECT texture is used.
[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(%p), doIt(%d)\n",
1507              cmdList, wine_dbgstr_w(firstcmd),
1508              executecmds);
1509
1510   /* Skip leading whitespace between condition and the command */
1511   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1512
1513   /* Process the first command, if there is one */
1514   if (executecmds && firstcmd && *firstcmd) {
1515     WCHAR *command = heap_strdupW(firstcmd);
1516     WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1517     heap_free(command);
1518   }
1519
1520
1521   /* If it didn't move the position, step to next command */
1522   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1523
1524   /* Process any other parts of the command */
1525   if (*cmdList) {
1526     BOOL processThese = executecmds;
1527
1528     while (*cmdList) {
1529       static const WCHAR ifElse[] = {'e','l','s','e'};
1530
1531       /* execute all appropriate commands */
1532       curPosition = *cmdList;
1533
1534       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1535                  *cmdList,
1536                  (*cmdList)->prevDelim,
1537                  (*cmdList)->bracketDepth, myDepth);
1538
1539       /* Execute any statements appended to the line */
1540       /* FIXME: Only if previous call worked for && or failed for || */
1541       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1542           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1543         if (processThese && (*cmdList)->command) {
1544           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1545                         cmdList, FALSE);
1546         }
1547         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1548
1549       /* Execute any appended to the statement with (...) */
1550       } else if ((*cmdList)->bracketDepth > myDepth) {
1551         if (processThese) {
1552           *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1553           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1554         }
1555         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1556
1557       /* End of the command - does 'ELSE ' follow as the next command? */
1558       } else {
1559         if (isIF
1560             && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1561                                      (*cmdList)->command)) {
1562
1563           /* Swap between if and else processing */
1564           processThese = !processThese;
1565
1566           /* Process the ELSE part */
1567           if (processThese) {
1568             const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1569             WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1570
1571             /* Skip leading whitespace between condition and the command */
1572             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1573             if (*cmd) {
1574               WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1575             }
1576           }
1577           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1578         } else {
1579           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1580           break;
1581         }
1582       }
1583     }
1584   }
1585   return;
1586 }
1587
1588 /*****************************************************************************
1589  * WCMD_parse_forf_options
1590  *
1591  * Parses the for /f 'options', extracting the values and validating the
1592  * keywords. Note all keywords are optional.
1593  * Parameters:
1594  *  options    [I] The unparsed parameter string
1595  *  eol        [O] Set to the comment character (eol=x)
1596  *  skip       [O] Set to the number of lines to skip (skip=xx)
1597  *  delims     [O] Set to the token delimiters (delims=)
1598  *  tokens     [O] Set to the requested tokens, as provided (tokens=)
1599  *  usebackq   [O] Set to TRUE if usebackq found
1600  *
1601  * Returns TRUE on success, FALSE on syntax error
1602  *
1603  */
1604 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1605                                     WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1606 {
1607
1608   WCHAR *pos = options;
1609   int    len = strlenW(pos);
1610   static const WCHAR eolW[] = {'e','o','l','='};
1611   static const WCHAR skipW[] = {'s','k','i','p','='};
1612   static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1613   static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1614   static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1615   static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1616   static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1617
1618   /* Initialize to defaults */
1619   strcpyW(delims, forf_defaultdelims);
1620   strcpyW(tokens, forf_defaulttokens);
1621   *eol      = 0;
1622   *skip     = 0;
1623   *usebackq = FALSE;
1624
1625   /* Strip (optional) leading and trailing quotes */
1626   if ((*pos == '"') && (pos[len-1] == '"')) {
1627     pos[len-1] = 0;
1628     pos++;
1629   }
1630
1631   /* Process each keyword */
1632   while (pos && *pos) {
1633     if (*pos == ' ' || *pos == '\t') {
1634       pos++;
1635
1636     /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1637     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1638                        pos, sizeof(eolW)/sizeof(WCHAR),
1639                        eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1640       *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1641       pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1642       WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1643
1644     /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1645     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1646                        pos, sizeof(skipW)/sizeof(WCHAR),
1647                        skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1648       WCHAR *nextchar = NULL;
1649       pos = pos + sizeof(skipW)/sizeof(WCHAR);
1650       *skip = strtoulW(pos, &nextchar, 0);
1651       WINE_TRACE("Found skip as %d lines\n", *skip);
1652       pos = nextchar;
1653
1654     /* Save if usebackq semantics are in effect */
1655     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1656                        pos, sizeof(usebackqW)/sizeof(WCHAR),
1657                        usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1658       *usebackq = TRUE;
1659       pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1660       WINE_TRACE("Found usebackq\n");
1661
1662     /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1663        if you finish the optionsroot string with delims= otherwise the space is
1664        just a token delimiter!                                                     */
1665     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1666                        pos, sizeof(delimsW)/sizeof(WCHAR),
1667                        delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1668       int i=0;
1669
1670       pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1671       while (*pos && *pos != ' ') {
1672         delims[i++] = *pos;
1673         pos++;
1674       }
1675       if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1676       delims[i++] = 0; /* Null terminate the delims */
1677       WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1678
1679     /* Save the tokens being requested */
1680     } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1681                        pos, sizeof(tokensW)/sizeof(WCHAR),
1682                        tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1683       int i=0;
1684
1685       pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1686       while (*pos && *pos != ' ') {
1687         tokens[i++] = *pos;
1688         pos++;
1689       }
1690       tokens[i++] = 0; /* Null terminate the tokens */
1691       WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1692
1693     } else {
1694       WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1695       return FALSE;
1696     }
1697   }
1698   return TRUE;
1699 }
1700
1701 /*****************************************************************************
1702  * WCMD_add_dirstowalk
1703  *
1704  * When recursing through directories (for /r), we need to add to the list of
1705  * directories still to walk, any subdirectories of the one we are processing.
1706  *
1707  * Parameters
1708  *  options    [I] The remaining list of directories still to process
1709  *
1710  * Note this routine inserts the subdirectories found between the entry being
1711  * processed, and any other directory still to be processed, mimicing what
1712  * Windows does
1713  */
1714 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1715   DIRECTORY_STACK *remainingDirs = dirsToWalk;
1716   WCHAR fullitem[MAX_PATH];
1717   WIN32_FIND_DATAW fd;
1718   HANDLE hff;
1719
1720   /* Build a generic search and add all directories on the list of directories
1721      still to walk                                                             */
1722   strcpyW(fullitem, dirsToWalk->dirName);
1723   strcatW(fullitem, slashstarW);
1724   hff = FindFirstFileW(fullitem, &fd);
1725   if (hff != INVALID_HANDLE_VALUE) {
1726     do {
1727       WINE_TRACE("Looking for subdirectories\n");
1728       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1729           (strcmpW(fd.cFileName, dotdotW) != 0) &&
1730           (strcmpW(fd.cFileName, dotW) != 0))
1731       {
1732         /* Allocate memory, add to list */
1733         DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1734         WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1735         toWalk->next = remainingDirs->next;
1736         remainingDirs->next = toWalk;
1737         remainingDirs = toWalk;
1738         toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1739         strcpyW(toWalk->dirName, dirsToWalk->dirName);
1740         strcatW(toWalk->dirName, slashW);
1741         strcatW(toWalk->dirName, fd.cFileName);
1742         WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1743                    toWalk, toWalk->next);
1744       }
1745     } while (FindNextFileW(hff, &fd) != 0);
1746     WINE_TRACE("Finished adding all subdirectories\n");
1747     FindClose (hff);
1748   }
1749 }
1750
1751 /**************************************************************************
1752  * WCMD_for_nexttoken
1753  *
1754  * Parse the token= line, identifying the next highest number not processed
1755  * so far. Count how many tokens are referred (including duplicates) and
1756  * optionally return that, plus optionally indicate if the tokens= line
1757  * ends in a star.
1758  *
1759  * Parameters:
1760  *  lasttoken    [I]    - Identifies the token index of the last one
1761  *                           returned so far (-1 used for first loop)
1762  *  tokenstr     [I]    - The specified tokens= line
1763  *  firstCmd     [O]    - Optionally indicate how many tokens are listed
1764  *  doAll        [O]    - Optionally indicate if line ends with *
1765  *  duplicates   [O]    - Optionally indicate if there is any evidence of
1766  *                           overlaying tokens in the string
1767  * Note the caller should keep a running track of duplicates as the tokens
1768  * are recursively passed. If any have duplicates, then the * token should
1769  * not be honoured.
1770  */
1771 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1772                               int *totalfound, BOOL *doall,
1773                               BOOL *duplicates)
1774 {
1775   WCHAR *pos = tokenstr;
1776   int    nexttoken = -1;
1777
1778   if (totalfound) *totalfound = 0;
1779   if (doall) *doall = FALSE;
1780   if (duplicates) *duplicates = FALSE;
1781
1782   WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1783              wine_dbgstr_w(tokenstr), nexttoken);
1784
1785   /* Loop through the token string, parsing it. Valid syntax is:
1786      token=m or x-y with comma delimiter and optionally * to finish*/
1787   while (*pos) {
1788     int nextnumber1, nextnumber2 = -1;
1789     WCHAR *nextchar;
1790
1791     /* Get the next number */
1792     nextnumber1 = strtoulW(pos, &nextchar, 10);
1793
1794     /* If it is followed by a minus, its a range, so get the next one as well */
1795     if (*nextchar == '-') {
1796       nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1797
1798       /* We want to return the lowest number that is higher than lasttoken
1799          but only if range is positive                                     */
1800       if (nextnumber2 >= nextnumber1 &&
1801           lasttoken < nextnumber2) {
1802
1803         int nextvalue;
1804         if (nexttoken == -1) {
1805           nextvalue = max(nextnumber1, (lasttoken+1));
1806         } else {
1807           nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1808         }
1809
1810         /* Flag if duplicates identified */
1811         if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1812
1813         nexttoken = nextvalue;
1814       }
1815
1816       /* Update the running total for the whole range */
1817       if (nextnumber2 >= nextnumber1 && totalfound) {
1818         *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1819       }
1820
1821     } else {
1822       if (totalfound) (*totalfound)++;
1823
1824       /* See if the number found is one we have already seen */
1825       if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1826
1827       /* We want to return the lowest number that is higher than lasttoken */
1828       if (lasttoken < nextnumber1 &&
1829          ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1830         nexttoken = nextnumber1;
1831       }
1832
1833     }
1834
1835     /* Remember if it is followed by a star, and if it is indicate a need to
1836        show all tokens, unless a duplicate has been found                    */
1837     if (*nextchar == '*') {
1838       if (doall) *doall = TRUE;
1839       if (totalfound) (*totalfound)++;
1840     }
1841
1842     /* Step on to the next character */
1843     pos = nextchar;
1844     if (*pos) pos++;
1845   }
1846
1847   /* Return result */
1848   if (nexttoken == -1) nexttoken = lasttoken;
1849   WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1850   if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1851   if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1852   if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1853   return nexttoken;
1854 }
1855
1856 /**************************************************************************
1857  * WCMD_parse_line
1858  *
1859  * When parsing file or string contents (for /f), once the string to parse
1860  * has been identified, handle the various options and call the do part
1861  * if appropriate.
1862  *
1863  * Parameters:
1864  *  cmdStart     [I]    - Identifies the list of commands making up the
1865  *                           for loop body (especially if brackets in use)
1866  *  firstCmd     [I]    - The textual start of the command after the DO
1867  *                           which is within the first item of cmdStart
1868  *  cmdEnd       [O]    - Identifies where to continue after the DO
1869  *  variable     [I]    - The variable identified on the for line
1870  *  buffer       [I]    - The string to parse
1871  *  doExecuted   [O]    - Set to TRUE if the DO is ever executed once
1872  *  forf_skip    [I/O]  - How many lines to skip first
1873  *  forf_eol     [I]    - The 'end of line' (comment) character
1874  *  forf_delims  [I]    - The delimiters to use when breaking the string apart
1875  *  forf_tokens  [I]    - The tokens to use when breaking the string apart
1876  */
1877 static void WCMD_parse_line(CMD_LIST    *cmdStart,
1878                             const WCHAR *firstCmd,
1879                             CMD_LIST   **cmdEnd,
1880                             const WCHAR  variable,
1881                             WCHAR       *buffer,
1882                             BOOL        *doExecuted,
1883                             int         *forf_skip,
1884                             WCHAR        forf_eol,
1885                             WCHAR       *forf_delims,
1886                             WCHAR       *forf_tokens) {
1887
1888   WCHAR *parm;
1889   FOR_CONTEXT oldcontext;
1890   int varidx, varoffset;
1891   int nexttoken, lasttoken = -1;
1892   BOOL starfound = FALSE;
1893   BOOL thisduplicate = FALSE;
1894   BOOL anyduplicates = FALSE;
1895   int  totalfound;
1896
1897   /* Skip lines if requested */
1898   if (*forf_skip) {
1899     (*forf_skip)--;
1900     return;
1901   }
1902
1903   /* Save away any existing for variable context (e.g. nested for loops) */
1904   oldcontext = forloopcontext;
1905
1906   /* Extract the parameters based on the tokens= value (There will always
1907      be some value, as if it is not supplied, it defaults to tokens=1).
1908      Rough logic:
1909      Count how many tokens are named in the line, identify the lowest
1910      Empty (set to null terminated string) that number of named variables
1911      While lasttoken != nextlowest
1912        %letter = parameter number 'nextlowest'
1913        letter++ (if >26 or >52 abort)
1914        Go through token= string finding next lowest number
1915      If token ends in * set %letter = raw position of token(nextnumber+1)
1916    */
1917   lasttoken = -1;
1918   nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1919                                  NULL, &thisduplicate);
1920   varidx = FOR_VAR_IDX(variable);
1921
1922   /* Empty out variables */
1923   for (varoffset=0;
1924        varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1925        varoffset++) {
1926     forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1927     /* Stop if we walk beyond z or Z */
1928     if (((varidx+varoffset) % 26) == 0) break;
1929   }
1930
1931   /* Loop extracting the tokens */
1932   varoffset = 0;
1933   WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1934   while (varidx >= 0 && (nexttoken > lasttoken)) {
1935     anyduplicates |= thisduplicate;
1936
1937     /* Extract the token number requested and set into the next variable context */
1938     parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1939     WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1940                varidx + varoffset, wine_dbgstr_w(parm));
1941     if (varidx >=0) {
1942       forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1943       varoffset++;
1944       if (((varidx + varoffset) %26) == 0) break;
1945     }
1946
1947     /* Find the next token */
1948     lasttoken = nexttoken;
1949     nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1950                                    &starfound, &thisduplicate);
1951   }
1952
1953   /* If all the rest of the tokens were requested, and there is still space in
1954      the variable range, write them now                                        */
1955   if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1956     nexttoken++;
1957     WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1958     WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1959                varidx + varoffset, wine_dbgstr_w(parm));
1960     forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1961   }
1962
1963   /* Execute the body of the foor loop with these values */
1964   if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1965     CMD_LIST *thisCmdStart = cmdStart;
1966     *doExecuted = TRUE;
1967     WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1968     *cmdEnd = thisCmdStart;
1969   }
1970
1971   /* Free the duplicated strings, and restore the context */
1972   if (varidx >=0) {
1973     int i;
1974     for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1975       if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1976           (forloopcontext.variable[i] != nullW)) {
1977         heap_free(forloopcontext.variable[i]);
1978       }
1979     }
1980   }
1981
1982   /* Restore the original for variable contextx */
1983   forloopcontext = oldcontext;
1984 }
1985
1986 /**************************************************************************
1987  * WCMD_forf_getinputhandle
1988  *
1989  * Return a file handle which can be used for reading the input lines,
1990  * either to a specific file (which may be quote delimited as we have to
1991  * read the parameters in raw mode) or to a command which we need to
1992  * execute. The command being executed runs in its own shell and stores
1993  * its data in a temporary file.
1994  *
1995  * Parameters:
1996  *  usebackq     [I]    - Indicates whether usebackq is in effect or not
1997  *  itemStr      [I]    - The item to be handled, either a filename or
1998  *                           whole command string to execute
1999  *  iscmd        [I]    - Identifies whether this is a command or not
2000  *
2001  * Returns a file handle which can be used to read the input lines from.
2002  */
2003 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2004   WCHAR  temp_str[MAX_PATH];
2005   WCHAR  temp_file[MAX_PATH];
2006   WCHAR  temp_cmd[MAXSTRING];
2007   HANDLE hinput = INVALID_HANDLE_VALUE;
2008   static const WCHAR redirOutW[]  = {'>','%','s','\0'};
2009   static const WCHAR cmdW[]       = {'C','M','D','\0'};
2010   static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2011                                      '/','C',' ','"','%','s','"','\0'};
2012
2013   /* Remove leading and trailing character */
2014   if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2015       (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2016       (!iscmd && (itemstr[0] == '"' && usebackq)))
2017   {
2018     itemstr[strlenW(itemstr)-1] = 0x00;
2019     itemstr++;
2020   }
2021
2022   if (iscmd) {
2023     /* Get temp filename */
2024     GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2025     GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2026
2027     /* Redirect output to the temporary file */
2028     wsprintfW(temp_str, redirOutW, temp_file);
2029     wsprintfW(temp_cmd, cmdslashcW, itemstr);
2030     WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2031                wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2032     WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2033
2034     /* Open the file, read line by line and process */
2035     hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2036                         NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2037
2038   } else {
2039     /* Open the file, read line by line and process */
2040     WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2041     hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2042                         NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2043   }
2044   return hinput;
2045 }
2046
2047 /**************************************************************************
2048  * WCMD_for
2049  *
2050  * Batch file loop processing.
2051  *
2052  * On entry: cmdList       contains the syntax up to the set
2053  *           next cmdList and all in that bracket contain the set data
2054  *           next cmdlist  contains the DO cmd
2055  *           following that is either brackets or && entries (as per if)
2056  *
2057  */
2058
2059 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2060
2061   WIN32_FIND_DATAW fd;
2062   HANDLE hff;
2063   int i;
2064   static const WCHAR inW[] = {'i','n'};
2065   static const WCHAR doW[] = {'d','o'};
2066   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2067   WCHAR variable[4];
2068   int   varidx = -1;
2069   WCHAR *oldvariablevalue;
2070   WCHAR *firstCmd;
2071   int thisDepth;
2072   WCHAR optionsRoot[MAX_PATH];
2073   DIRECTORY_STACK *dirsToWalk = NULL;
2074   BOOL   expandDirs  = FALSE;
2075   BOOL   useNumbers  = FALSE;
2076   BOOL   doFileset   = FALSE;
2077   BOOL   doRecurse   = FALSE;
2078   BOOL   doExecuted  = FALSE;  /* Has the 'do' part been executed */
2079   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2080   int    itemNum;
2081   CMD_LIST *thisCmdStart;
2082   int    parameterNo = 0;
2083   WCHAR  forf_eol = 0;
2084   int    forf_skip = 0;
2085   WCHAR  forf_delims[256];
2086   WCHAR  forf_tokens[MAXSTRING];
2087   BOOL   forf_usebackq = FALSE;
2088
2089   /* Handle optional qualifiers (multiple are allowed) */
2090   WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2091
2092   optionsRoot[0] = 0;
2093   while (thisArg && *thisArg == '/') {
2094       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2095       thisArg++;
2096       switch (toupperW(*thisArg)) {
2097       case 'D': expandDirs = TRUE; break;
2098       case 'L': useNumbers = TRUE; break;
2099
2100       /* Recursive is special case - /R can have an optional path following it                */
2101       /* filenamesets are another special case - /F can have an optional options following it */
2102       case 'R':
2103       case 'F':
2104           {
2105               /* When recursing directories, use current directory as the starting point unless
2106                  subsequently overridden */
2107               doRecurse = (toupperW(*thisArg) == 'R');
2108               if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2109
2110               doFileset = (toupperW(*thisArg) == 'F');
2111
2112               /* Retrieve next parameter to see if is root/options (raw form required
2113                  with for /f, or unquoted in for /r)                                  */
2114               thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2115
2116               /* Next parm is either qualifier, path/options or variable -
2117                  only care about it if it is the path/options              */
2118               if (thisArg && *thisArg != '/' && *thisArg != '%') {
2119                   parameterNo++;
2120                   strcpyW(optionsRoot, thisArg);
2121               }
2122               break;
2123           }
2124       default:
2125           WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2126       }
2127
2128       /* Step to next token */
2129       thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2130   }
2131
2132   /* Ensure line continues with variable */
2133   if (!*thisArg || *thisArg != '%') {
2134       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2135       return;
2136   }
2137
2138   /* With for /f parse the options if provided */
2139   if (doFileset) {
2140     if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2141                                  forf_delims, forf_tokens, &forf_usebackq))
2142     {
2143       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2144       return;
2145     }
2146
2147   /* Set up the list of directories to recurse if we are going to */
2148   } else if (doRecurse) {
2149        /* Allocate memory, add to list */
2150        dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2151        dirsToWalk->next = NULL;
2152        dirsToWalk->dirName = heap_strdupW(optionsRoot);
2153        WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2154   }
2155
2156   /* Variable should follow */
2157   strcpyW(variable, thisArg);
2158   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2159   varidx = FOR_VAR_IDX(variable[1]);
2160
2161   /* Ensure line continues with IN */
2162   thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2163   if (!thisArg
2164        || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2165                            thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2166                            sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2167       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2168       return;
2169   }
2170
2171   /* Save away where the set of data starts and the variable */
2172   thisDepth = (*cmdList)->bracketDepth;
2173   *cmdList = (*cmdList)->nextcommand;
2174   setStart = (*cmdList);
2175
2176   /* Skip until the close bracket */
2177   WINE_TRACE("Searching %p as the set\n", *cmdList);
2178   while (*cmdList &&
2179          (*cmdList)->command != NULL &&
2180          (*cmdList)->bracketDepth > thisDepth) {
2181     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2182     *cmdList = (*cmdList)->nextcommand;
2183   }
2184
2185   /* Skip the close bracket, if there is one */
2186   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2187
2188   /* Syntax error if missing close bracket, or nothing following it
2189      and once we have the complete set, we expect a DO              */
2190   WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2191   if ((*cmdList == NULL)
2192       || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2193
2194       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2195       return;
2196   }
2197
2198   cmdEnd   = *cmdList;
2199
2200   /* Loop repeatedly per-directory we are potentially walking, when in for /r
2201      mode, or once for the rest of the time.                                  */
2202   do {
2203
2204     /* Save away the starting position for the commands (and offset for the
2205        first one)                                                           */
2206     cmdStart = *cmdList;
2207     firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2208     itemNum  = 0;
2209
2210     /* If we are recursing directories (ie /R), add all sub directories now, then
2211        prefix the root when searching for the item */
2212     if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2213
2214     thisSet = setStart;
2215     /* Loop through all set entries */
2216     while (thisSet &&
2217            thisSet->command != NULL &&
2218            thisSet->bracketDepth >= thisDepth) {
2219
2220       /* Loop through all entries on the same line */
2221       WCHAR *item;
2222       WCHAR *itemStart;
2223       WCHAR buffer[MAXSTRING];
2224
2225       WINE_TRACE("Processing for set %p\n", thisSet);
2226       i = 0;
2227       while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2228
2229         /*
2230          * If the parameter within the set has a wildcard then search for matching files
2231          * otherwise do a literal substitution.
2232          */
2233         static const WCHAR wildcards[] = {'*','?','\0'};
2234         thisCmdStart = cmdStart;
2235
2236         itemNum++;
2237         WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2238
2239         if (!useNumbers && !doFileset) {
2240             WCHAR fullitem[MAX_PATH];
2241
2242             /* Now build the item to use / search for in the specified directory,
2243                as it is fully qualified in the /R case */
2244             if (dirsToWalk) {
2245               strcpyW(fullitem, dirsToWalk->dirName);
2246               strcatW(fullitem, slashW);
2247               strcatW(fullitem, item);
2248             } else {
2249               strcpyW(fullitem, item);
2250             }
2251
2252             if (strpbrkW (fullitem, wildcards)) {
2253
2254               hff = FindFirstFileW(fullitem, &fd);
2255               if (hff != INVALID_HANDLE_VALUE) {
2256                 do {
2257                   BOOL isDirectory = FALSE;
2258
2259                   if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2260
2261                   /* Handle as files or dirs appropriately, but ignore . and .. */
2262                   if (isDirectory == expandDirs &&
2263                       (strcmpW(fd.cFileName, dotdotW) != 0) &&
2264                       (strcmpW(fd.cFileName, dotW) != 0))
2265                   {
2266                       thisCmdStart = cmdStart;
2267                       WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2268
2269                       if (doRecurse) {
2270                           strcpyW(fullitem, dirsToWalk->dirName);
2271                           strcatW(fullitem, slashW);
2272                           strcatW(fullitem, fd.cFileName);
2273                       } else {
2274                           strcpyW(fullitem, fd.cFileName);
2275                       }
2276                       doExecuted = TRUE;
2277
2278                       /* Save away any existing for variable context (e.g. nested for loops)
2279                          and restore it after executing the body of this for loop           */
2280                       if (varidx >= 0) {
2281                         oldvariablevalue = forloopcontext.variable[varidx];
2282                         forloopcontext.variable[varidx] = fullitem;
2283                       }
2284                       WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2285                       if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2286
2287                       cmdEnd = thisCmdStart;
2288                   }
2289                 } while (FindNextFileW(hff, &fd) != 0);
2290                 FindClose (hff);
2291               }
2292             } else {
2293               doExecuted = TRUE;
2294
2295               /* Save away any existing for variable context (e.g. nested for loops)
2296                  and restore it after executing the body of this for loop           */
2297               if (varidx >= 0) {
2298                 oldvariablevalue = forloopcontext.variable[varidx];
2299                 forloopcontext.variable[varidx] = fullitem;
2300               }
2301               WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2302               if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2303
2304               cmdEnd = thisCmdStart;
2305             }
2306
2307         } else if (useNumbers) {
2308             /* Convert the first 3 numbers to signed longs and save */
2309             if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2310             /* else ignore them! */
2311
2312         /* Filesets - either a list of files, or a command to run and parse the output */
2313         } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2314                                  (forf_usebackq && *itemStart != '\''))) {
2315
2316             HANDLE input;
2317             WCHAR *itemparm;
2318
2319             WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2320                        wine_dbgstr_w(item));
2321
2322             /* If backquote or single quote, we need to launch that command
2323                and parse the results - use a temporary file                 */
2324             if ((forf_usebackq && *itemStart == '`') ||
2325                 (!forf_usebackq && *itemStart == '\'')) {
2326
2327               /* Use itemstart because the command is the whole set, not just the first token */
2328               itemparm = itemStart;
2329             } else {
2330
2331               /* Use item because the file to process is just the first item in the set */
2332               itemparm = item;
2333             }
2334             input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2335
2336             /* Process the input file */
2337             if (input == INVALID_HANDLE_VALUE) {
2338               WCMD_print_error ();
2339               WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2340               errorlevel = 1;
2341               return; /* FOR loop aborts at first failure here */
2342
2343             } else {
2344
2345               /* Read line by line until end of file */
2346               while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2347                 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2348                                 &forf_skip, forf_eol, forf_delims, forf_tokens);
2349                 buffer[0] = 0;
2350               }
2351               CloseHandle (input);
2352             }
2353
2354             /* When we have processed the item as a whole command, abort future set processing */
2355             if (itemparm==itemStart) {
2356               thisSet = NULL;
2357               break;
2358             }
2359
2360         /* Filesets - A string literal */
2361         } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2362                                  (forf_usebackq && *itemStart == '\''))) {
2363
2364           /* Remove leading and trailing character, ready to parse with delims= delimiters
2365              Note that the last quote is removed from the set and the string terminates
2366              there to mimic windows                                                        */
2367           WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2368           if (strend) {
2369             *strend = 0x00;
2370             itemStart++;
2371           }
2372
2373           /* Copy the item away from the global buffer used by WCMD_parameter */
2374           strcpyW(buffer, itemStart);
2375           WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2376                             &forf_skip, forf_eol, forf_delims, forf_tokens);
2377
2378           /* Only one string can be supplied in the whole set, abort future set processing */
2379           thisSet = NULL;
2380           break;
2381         }
2382
2383         WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2384         i++;
2385       }
2386
2387       /* Move onto the next set line */
2388       if (thisSet) thisSet = thisSet->nextcommand;
2389     }
2390
2391     /* If /L is provided, now run the for loop */
2392     if (useNumbers) {
2393         WCHAR thisNum[20];
2394         static const WCHAR fmt[] = {'%','d','\0'};
2395
2396         WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2397                    numbers[0], numbers[2], numbers[1]);
2398         for (i=numbers[0];
2399              (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2400              i=i + numbers[1]) {
2401
2402             sprintfW(thisNum, fmt, i);
2403             WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2404
2405             thisCmdStart = cmdStart;
2406             doExecuted = TRUE;
2407
2408             /* Save away any existing for variable context (e.g. nested for loops)
2409                and restore it after executing the body of this for loop           */
2410             if (varidx >= 0) {
2411               oldvariablevalue = forloopcontext.variable[varidx];
2412               forloopcontext.variable[varidx] = thisNum;
2413             }
2414             WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2415             if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2416         }
2417         cmdEnd = thisCmdStart;
2418     }
2419
2420     /* If we are walking directories, move on to any which remain */
2421     if (dirsToWalk != NULL) {
2422       DIRECTORY_STACK *nextDir = dirsToWalk->next;
2423       heap_free(dirsToWalk->dirName);
2424       heap_free(dirsToWalk);
2425       dirsToWalk = nextDir;
2426       if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2427                                  wine_dbgstr_w(dirsToWalk->dirName));
2428       else WINE_TRACE("Finished all directories.\n");
2429     }
2430
2431   } while (dirsToWalk != NULL);
2432
2433   /* Now skip over the do part if we did not perform the for loop so far.
2434      We store in cmdEnd the next command after the do block, but we only
2435      know this if something was run. If it has not been, we need to calculate
2436      it.                                                                      */
2437   if (!doExecuted) {
2438     thisCmdStart = cmdStart;
2439     WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2440     WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2441     cmdEnd = thisCmdStart;
2442   }
2443
2444   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2445      all processing, OR it should be pointing to the end of && processing OR
2446      it should be pointing at the NULL end of bracket for the DO. The return
2447      value needs to be the NEXT command to execute, which it either is, or
2448      we need to step over the closing bracket                                  */
2449   *cmdList = cmdEnd;
2450   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2451 }
2452
2453 /**************************************************************************
2454  * WCMD_give_help
2455  *
2456  *      Simple on-line help. Help text is stored in the resource file.
2457  */
2458
2459 void WCMD_give_help (const WCHAR *args)
2460 {
2461   size_t i;
2462
2463   args = WCMD_skip_leading_spaces((WCHAR*) args);
2464   if (strlenW(args) == 0) {
2465     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2466   }
2467   else {
2468     /* Display help message for builtin commands */
2469     for (i=0; i<=WCMD_EXIT; i++) {
2470       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2471           args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2472         WCMD_output_asis (WCMD_LoadMessage(i));
2473         return;
2474       }
2475     }
2476     /* Launch the command with the /? option for external commands shipped with cmd.exe */
2477     for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2478       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2479           args, -1, externals[i], -1) == CSTR_EQUAL) {
2480         WCHAR cmd[128];
2481         static const WCHAR helpW[] = {' ', '/','?','\0'};
2482         strcpyW(cmd, args);
2483         strcatW(cmd, helpW);
2484         WCMD_run_program(cmd, FALSE);
2485         return;
2486       }
2487     }
2488     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2489   }
2490   return;
2491 }
2492
2493 /****************************************************************************
2494  * WCMD_go_to
2495  *
2496  * Batch file jump instruction. Not the most efficient algorithm ;-)
2497  * Prints error message if the specified label cannot be found - the file pointer is
2498  * then at EOF, effectively stopping the batch file.
2499  * FIXME: DOS is supposed to allow labels with spaces - we don't.
2500  */
2501
2502 void WCMD_goto (CMD_LIST **cmdList) {
2503
2504   WCHAR string[MAX_PATH];
2505   WCHAR current[MAX_PATH];
2506
2507   /* Do not process any more parts of a processed multipart or multilines command */
2508   if (cmdList) *cmdList = NULL;
2509
2510   if (context != NULL) {
2511     WCHAR *paramStart = param1, *str;
2512     static const WCHAR eofW[] = {':','e','o','f','\0'};
2513
2514     if (param1[0] == 0x00) {
2515       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2516       return;
2517     }
2518
2519     /* Handle special :EOF label */
2520     if (lstrcmpiW (eofW, param1) == 0) {
2521       context -> skip_rest = TRUE;
2522       return;
2523     }
2524
2525     /* Support goto :label as well as goto label */
2526     if (*paramStart == ':') paramStart++;
2527
2528     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2529     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2530       str = string;
2531       while (isspaceW (*str)) str++;
2532       if (*str == ':') {
2533         DWORD index = 0;
2534         str++;
2535         while (((current[index] = str[index])) && (!isspaceW (current[index])))
2536             index++;
2537
2538         /* ignore space at the end */
2539         current[index] = 0;
2540         if (lstrcmpiW (current, paramStart) == 0) return;
2541       }
2542     }
2543     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2544   }
2545   return;
2546 }
2547
2548 /*****************************************************************************
2549  * WCMD_pushd
2550  *
2551  *      Push a directory onto the stack
2552  */
2553
2554 void WCMD_pushd (const WCHAR *args)
2555 {
2556     struct env_stack *curdir;
2557     WCHAR *thisdir;
2558     static const WCHAR parmD[] = {'/','D','\0'};
2559
2560     if (strchrW(args, '/') != NULL) {
2561       SetLastError(ERROR_INVALID_PARAMETER);
2562       WCMD_print_error();
2563       return;
2564     }
2565
2566     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2567     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2568     if( !curdir || !thisdir ) {
2569       LocalFree(curdir);
2570       LocalFree(thisdir);
2571       WINE_ERR ("out of memory\n");
2572       return;
2573     }
2574
2575     /* Change directory using CD code with /D parameter */
2576     strcpyW(quals, parmD);
2577     GetCurrentDirectoryW (1024, thisdir);
2578     errorlevel = 0;
2579     WCMD_setshow_default(args);
2580     if (errorlevel) {
2581       LocalFree(curdir);
2582       LocalFree(thisdir);
2583       return;
2584     } else {
2585       curdir -> next    = pushd_directories;
2586       curdir -> strings = thisdir;
2587       if (pushd_directories == NULL) {
2588         curdir -> u.stackdepth = 1;
2589       } else {
2590         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2591       }
2592       pushd_directories = curdir;
2593     }
2594 }
2595
2596
2597 /*****************************************************************************
2598  * WCMD_popd
2599  *
2600  *      Pop a directory from the stack
2601  */
2602
2603 void WCMD_popd (void) {
2604     struct env_stack *temp = pushd_directories;
2605
2606     if (!pushd_directories)
2607       return;
2608
2609     /* pop the old environment from the stack, and make it the current dir */
2610     pushd_directories = temp->next;
2611     SetCurrentDirectoryW(temp->strings);
2612     LocalFree (temp->strings);
2613     LocalFree (temp);
2614 }
2615
2616 /*******************************************************************
2617  * evaluate_if_comparison
2618  *
2619  * Evaluates an "if" comparison operation
2620  *
2621  * PARAMS
2622  *  leftOperand     [I] left operand, non NULL
2623  *  operator        [I] "if" binary comparison operator, non NULL
2624  *  rightOperand    [I] right operand, non NULL
2625  *  caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2626  *
2627  * RETURNS
2628  *  Success:  1 if operator applied to the operands evaluates to TRUE
2629  *            0 if operator applied to the operands evaluates to FALSE
2630  *  Failure: -1 if operator is not recognized
2631  */
2632 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2633                                   const WCHAR *rightOperand, int caseInsensitive)
2634 {
2635     WCHAR *endptr_leftOp, *endptr_rightOp;
2636     long int leftOperand_int, rightOperand_int;
2637     BOOL int_operands;
2638     static const WCHAR lssW[]  = {'l','s','s','\0'};
2639     static const WCHAR leqW[]  = {'l','e','q','\0'};
2640     static const WCHAR equW[]  = {'e','q','u','\0'};
2641     static const WCHAR neqW[]  = {'n','e','q','\0'};
2642     static const WCHAR geqW[]  = {'g','e','q','\0'};
2643     static const WCHAR gtrW[]  = {'g','t','r','\0'};
2644
2645     /* == is a special case, as it always compares strings */
2646     if (!lstrcmpiW(operator, eqeqW))
2647         return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2648                                : lstrcmpW (leftOperand, rightOperand) == 0;
2649
2650     /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2651     leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2652     rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2653     int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2654
2655     /* Perform actual (integer or string) comparison */
2656     if (!lstrcmpiW(operator, lssW)) {
2657         if (int_operands)
2658             return leftOperand_int < rightOperand_int;
2659         else
2660             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2661                                    : lstrcmpW (leftOperand, rightOperand) < 0;
2662     }
2663
2664     if (!lstrcmpiW(operator, leqW)) {
2665         if (int_operands)
2666             return leftOperand_int <= rightOperand_int;
2667         else
2668             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2669                                    : lstrcmpW (leftOperand, rightOperand) <= 0;
2670     }
2671
2672     if (!lstrcmpiW(operator, equW)) {
2673         if (int_operands)
2674             return leftOperand_int == rightOperand_int;
2675         else
2676             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2677                                    : lstrcmpW (leftOperand, rightOperand) == 0;
2678     }
2679
2680     if (!lstrcmpiW(operator, neqW)) {
2681         if (int_operands)
2682             return leftOperand_int != rightOperand_int;
2683         else
2684             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2685                                    : lstrcmpW (leftOperand, rightOperand) != 0;
2686     }
2687
2688     if (!lstrcmpiW(operator, geqW)) {
2689         if (int_operands)
2690             return leftOperand_int >= rightOperand_int;
2691         else
2692             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2693                                    : lstrcmpW (leftOperand, rightOperand) >= 0;
2694     }
2695
2696     if (!lstrcmpiW(operator, gtrW)) {
2697         if (int_operands)
2698             return leftOperand_int > rightOperand_int;
2699         else
2700             return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2701                                    : lstrcmpW (leftOperand, rightOperand) > 0;
2702     }
2703
2704     return -1;
2705 }
2706
2707 /****************************************************************************
2708  * WCMD_if
2709  *
2710  * Batch file conditional.
2711  *
2712  * On entry, cmdlist will point to command containing the IF, and optionally
2713  *   the first command to execute (if brackets not found)
2714  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
2715  *   If ('s were found, execute all within that bracket
2716  *   Command may optionally be followed by an ELSE - need to skip instructions
2717  *   in the else using the same logic
2718  *
2719  * FIXME: Much more syntax checking needed!
2720  */
2721 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2722 {
2723   int negate; /* Negate condition */
2724   int test;   /* Condition evaluation result */
2725   WCHAR condition[MAX_PATH], *command;
2726   static const WCHAR notW[]    = {'n','o','t','\0'};
2727   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2728   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
2729   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
2730   static const WCHAR parmI[]   = {'/','I','\0'};
2731   int caseInsensitive = (strstrW(quals, parmI) != NULL);
2732
2733   negate = !lstrcmpiW(param1,notW);
2734   strcpyW(condition, (negate ? param2 : param1));
2735   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2736
2737   if (!lstrcmpiW (condition, errlvlW)) {
2738     WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2739     WCHAR *endptr;
2740     long int param_int = strtolW(param, &endptr, 10);
2741     if (*endptr) goto syntax_err;
2742     test = ((long int)errorlevel >= param_int);
2743     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2744   }
2745   else if (!lstrcmpiW (condition, existW)) {
2746     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2747              != INVALID_FILE_ATTRIBUTES);
2748     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2749   }
2750   else if (!lstrcmpiW (condition, defdW)) {
2751     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2752                                     NULL, 0) > 0);
2753     WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2754   }
2755   else { /* comparison operation */
2756     WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2757     WCHAR *paramStart;
2758
2759     strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2760     if (!*leftOperand)
2761       goto syntax_err;
2762
2763     /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2764     p = paramStart + strlenW(leftOperand);
2765     while (*p == ' ' || *p == '\t')
2766       p++;
2767
2768     if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2769       strcpyW(operator, eqeqW);
2770     else {
2771       strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2772       if (!*operator) goto syntax_err;
2773     }
2774     p += strlenW(operator);
2775
2776     strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2777     if (!*rightOperand)
2778       goto syntax_err;
2779
2780     test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2781     if (test == -1)
2782       goto syntax_err;
2783
2784     p = paramStart + strlenW(rightOperand);
2785     WCMD_parameter(p, 0, &command, FALSE, FALSE);
2786   }
2787
2788   /* Process rest of IF statement which is on the same line
2789      Note: This may process all or some of the cmdList (eg a GOTO) */
2790   WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2791   return;
2792
2793 syntax_err:
2794   WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2795 }
2796
2797 /****************************************************************************
2798  * WCMD_move
2799  *
2800  * Move a file, directory tree or wildcarded set of files.
2801  */
2802
2803 void WCMD_move (void)
2804 {
2805   int             status;
2806   WIN32_FIND_DATAW fd;
2807   HANDLE          hff;
2808   WCHAR            input[MAX_PATH];
2809   WCHAR            output[MAX_PATH];
2810   WCHAR            drive[10];
2811   WCHAR            dir[MAX_PATH];
2812   WCHAR            fname[MAX_PATH];
2813   WCHAR            ext[MAX_PATH];
2814
2815   if (param1[0] == 0x00) {
2816     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2817     return;
2818   }
2819
2820   /* If no destination supplied, assume current directory */
2821   if (param2[0] == 0x00) {
2822       strcpyW(param2, dotW);
2823   }
2824
2825   /* If 2nd parm is directory, then use original filename */
2826   /* Convert partial path to full path */
2827   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2828   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2829   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2830              wine_dbgstr_w(param1), wine_dbgstr_w(output));
2831
2832   /* Split into components */
2833   WCMD_splitpath(input, drive, dir, fname, ext);
2834
2835   hff = FindFirstFileW(input, &fd);
2836   if (hff == INVALID_HANDLE_VALUE)
2837     return;
2838
2839   do {
2840     WCHAR  dest[MAX_PATH];
2841     WCHAR  src[MAX_PATH];
2842     DWORD attribs;
2843     BOOL ok = TRUE;
2844
2845     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2846
2847     /* Build src & dest name */
2848     strcpyW(src, drive);
2849     strcatW(src, dir);
2850
2851     /* See if dest is an existing directory */
2852     attribs = GetFileAttributesW(output);
2853     if (attribs != INVALID_FILE_ATTRIBUTES &&
2854        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2855       strcpyW(dest, output);
2856       strcatW(dest, slashW);
2857       strcatW(dest, fd.cFileName);
2858     } else {
2859       strcpyW(dest, output);
2860     }
2861
2862     strcatW(src, fd.cFileName);
2863
2864     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2865     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
2866
2867     /* If destination exists, prompt unless /Y supplied */
2868     if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2869       BOOL force = FALSE;
2870       WCHAR copycmd[MAXSTRING];
2871       DWORD len;
2872
2873       /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2874       if (strstrW (quals, parmNoY))
2875         force = FALSE;
2876       else if (strstrW (quals, parmY))
2877         force = TRUE;
2878       else {
2879         static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2880         len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2881         force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2882                      && ! lstrcmpiW (copycmd, parmY));
2883       }
2884
2885       /* Prompt if overwriting */
2886       if (!force) {
2887         WCHAR* question;
2888
2889         /* Ask for confirmation */
2890         question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2891         ok = WCMD_ask_confirm(question, FALSE, NULL);
2892         LocalFree(question);
2893
2894         /* So delete the destination prior to the move */
2895         if (ok) {
2896           if (!DeleteFileW(dest)) {
2897             WCMD_print_error ();
2898             errorlevel = 1;
2899             ok = FALSE;
2900           }
2901         }
2902       }
2903     }
2904
2905     if (ok) {
2906       status = MoveFileW(src, dest);
2907     } else {
2908       status = 1; /* Anything other than 0 to prevent error msg below */
2909     }
2910
2911     if (!status) {
2912       WCMD_print_error ();
2913       errorlevel = 1;
2914     }
2915   } while (FindNextFileW(hff, &fd) != 0);
2916
2917   FindClose(hff);
2918 }
2919
2920 /****************************************************************************
2921  * WCMD_pause
2922  *
2923  * Suspend execution of a batch script until a key is typed
2924  */
2925
2926 void WCMD_pause (void)
2927 {
2928   DWORD oldmode;
2929   BOOL have_console;
2930   DWORD count;
2931   WCHAR key;
2932   HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2933
2934   have_console = GetConsoleMode(hIn, &oldmode);
2935   if (have_console)
2936       SetConsoleMode(hIn, 0);
2937
2938   WCMD_output_asis(anykey);
2939   WCMD_ReadFile(hIn, &key, 1, &count);
2940   if (have_console)
2941     SetConsoleMode(hIn, oldmode);
2942 }
2943
2944 /****************************************************************************
2945  * WCMD_remove_dir
2946  *
2947  * Delete a directory.
2948  */
2949
2950 void WCMD_remove_dir (WCHAR *args) {
2951
2952   int   argno         = 0;
2953   int   argsProcessed = 0;
2954   WCHAR *argN          = args;
2955   static const WCHAR parmS[] = {'/','S','\0'};
2956   static const WCHAR parmQ[] = {'/','Q','\0'};
2957
2958   /* Loop through all args */
2959   while (argN) {
2960     WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2961     if (argN && argN[0] != '/') {
2962       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2963                  wine_dbgstr_w(quals));
2964       argsProcessed++;
2965
2966       /* If subdirectory search not supplied, just try to remove
2967          and report error if it fails (eg if it contains a file) */
2968       if (strstrW (quals, parmS) == NULL) {
2969         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2970
2971       /* Otherwise use ShFileOp to recursively remove a directory */
2972       } else {
2973
2974         SHFILEOPSTRUCTW lpDir;
2975
2976         /* Ask first */
2977         if (strstrW (quals, parmQ) == NULL) {
2978           BOOL  ok;
2979           WCHAR  question[MAXSTRING];
2980           static const WCHAR fmt[] = {'%','s',' ','\0'};
2981
2982           /* Ask for confirmation */
2983           wsprintfW(question, fmt, thisArg);
2984           ok = WCMD_ask_confirm(question, TRUE, NULL);
2985
2986           /* Abort if answer is 'N' */
2987           if (!ok) return;
2988         }
2989
2990         /* Do the delete */
2991         lpDir.hwnd   = NULL;
2992         lpDir.pTo    = NULL;
2993         lpDir.pFrom  = thisArg;
2994         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2995         lpDir.wFunc  = FO_DELETE;
2996
2997         /* SHFileOperationW needs file list with a double null termination */
2998         thisArg[lstrlenW(thisArg) + 1] = 0x00;
2999
3000         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3001       }
3002     }
3003   }
3004
3005   /* Handle no valid args */
3006   if (argsProcessed == 0) {
3007     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3008     return;
3009   }
3010
3011 }
3012
3013 /****************************************************************************
3014  * WCMD_rename
3015  *
3016  * Rename a file.
3017  */
3018
3019 void WCMD_rename (void)
3020 {
3021   int             status;
3022   HANDLE          hff;
3023   WIN32_FIND_DATAW fd;
3024   WCHAR            input[MAX_PATH];
3025   WCHAR           *dotDst = NULL;
3026   WCHAR            drive[10];
3027   WCHAR            dir[MAX_PATH];
3028   WCHAR            fname[MAX_PATH];
3029   WCHAR            ext[MAX_PATH];
3030
3031   errorlevel = 0;
3032
3033   /* Must be at least two args */
3034   if (param1[0] == 0x00 || param2[0] == 0x00) {
3035     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3036     errorlevel = 1;
3037     return;
3038   }
3039
3040   /* Destination cannot contain a drive letter or directory separator */
3041   if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3042       SetLastError(ERROR_INVALID_PARAMETER);
3043       WCMD_print_error();
3044       errorlevel = 1;
3045       return;
3046   }
3047
3048   /* Convert partial path to full path */
3049   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3050   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3051              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3052   dotDst = strchrW(param2, '.');
3053
3054   /* Split into components */
3055   WCMD_splitpath(input, drive, dir, fname, ext);
3056
3057   hff = FindFirstFileW(input, &fd);
3058   if (hff == INVALID_HANDLE_VALUE)
3059     return;
3060
3061  do {
3062     WCHAR  dest[MAX_PATH];
3063     WCHAR  src[MAX_PATH];
3064     WCHAR *dotSrc = NULL;
3065     int   dirLen;
3066
3067     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3068
3069     /* FIXME: If dest name or extension is *, replace with filename/ext
3070        part otherwise use supplied name. This supports:
3071           ren *.fred *.jim
3072           ren jim.* fred.* etc
3073        However, windows has a more complex algorithm supporting eg
3074           ?'s and *'s mid name                                         */
3075     dotSrc = strchrW(fd.cFileName, '.');
3076
3077     /* Build src & dest name */
3078     strcpyW(src, drive);
3079     strcatW(src, dir);
3080     strcpyW(dest, src);
3081     dirLen = strlenW(src);
3082     strcatW(src, fd.cFileName);
3083
3084     /* Build name */
3085     if (param2[0] == '*') {
3086       strcatW(dest, fd.cFileName);
3087       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3088     } else {
3089       strcatW(dest, param2);
3090       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3091     }
3092
3093     /* Build Extension */
3094     if (dotDst && (*(dotDst+1)=='*')) {
3095       if (dotSrc) strcatW(dest, dotSrc);
3096     } else if (dotDst) {
3097       if (dotDst) strcatW(dest, dotDst);
3098     }
3099
3100     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3101     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
3102
3103     status = MoveFileW(src, dest);
3104
3105     if (!status) {
3106       WCMD_print_error ();
3107       errorlevel = 1;
3108     }
3109   } while (FindNextFileW(hff, &fd) != 0);
3110
3111   FindClose(hff);
3112 }
3113
3114 /*****************************************************************************
3115  * WCMD_dupenv
3116  *
3117  * Make a copy of the environment.
3118  */
3119 static WCHAR *WCMD_dupenv( const WCHAR *env )
3120 {
3121   WCHAR *env_copy;
3122   int len;
3123
3124   if( !env )
3125     return NULL;
3126
3127   len = 0;
3128   while ( env[len] )
3129     len += (strlenW(&env[len]) + 1);
3130
3131   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3132   if (!env_copy)
3133   {
3134     WINE_ERR("out of memory\n");
3135     return env_copy;
3136   }
3137   memcpy (env_copy, env, len*sizeof (WCHAR));
3138   env_copy[len] = 0;
3139
3140   return env_copy;
3141 }
3142
3143 /*****************************************************************************
3144  * WCMD_setlocal
3145  *
3146  *  setlocal pushes the environment onto a stack
3147  *  Save the environment as unicode so we don't screw anything up.
3148  */
3149 void WCMD_setlocal (const WCHAR *s) {
3150   WCHAR *env;
3151   struct env_stack *env_copy;
3152   WCHAR cwd[MAX_PATH];
3153   BOOL newdelay;
3154   static const WCHAR ondelayW[]     = {'E','N','A','B','L','E','D','E','L','A',
3155                                        'Y','E','D','E','X','P','A','N','S','I',
3156                                        'O','N','\0'};
3157   static const WCHAR offdelayW[]    = {'D','I','S','A','B','L','E','D','E','L',
3158                                        'A','Y','E','D','E','X','P','A','N','S',
3159                                        'I','O','N','\0'};
3160
3161   /* setlocal does nothing outside of batch programs */
3162   if (!context) return;
3163
3164   /* DISABLEEXTENSIONS ignored */
3165
3166   /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3167      (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3168   if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3169     newdelay = TRUE;
3170   } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3171     newdelay = FALSE;
3172   } else {
3173     newdelay = delayedsubst;
3174   }
3175   WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3176
3177   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3178   if( !env_copy )
3179   {
3180     WINE_ERR ("out of memory\n");
3181     return;
3182   }
3183
3184   env = GetEnvironmentStringsW ();
3185   env_copy->strings = WCMD_dupenv (env);
3186   if (env_copy->strings)
3187   {
3188     env_copy->batchhandle = context->h;
3189     env_copy->next = saved_environment;
3190     env_copy->delayedsubst = delayedsubst;
3191     delayedsubst = newdelay;
3192     saved_environment = env_copy;
3193
3194     /* Save the current drive letter */
3195     GetCurrentDirectoryW(MAX_PATH, cwd);
3196     env_copy->u.cwd = cwd[0];
3197   }
3198   else
3199     LocalFree (env_copy);
3200
3201   FreeEnvironmentStringsW (env);
3202
3203 }
3204
3205 /*****************************************************************************
3206  * WCMD_endlocal
3207  *
3208  *  endlocal pops the environment off a stack
3209  *  Note: When searching for '=', search from WCHAR position 1, to handle
3210  *        special internal environment variables =C:, =D: etc
3211  */
3212 void WCMD_endlocal (void) {
3213   WCHAR *env, *old, *p;
3214   struct env_stack *temp;
3215   int len, n;
3216
3217   /* setlocal does nothing outside of batch programs */
3218   if (!context) return;
3219
3220   /* setlocal needs a saved environment from within the same context (batch
3221      program) as it was saved in                                            */
3222   if (!saved_environment || saved_environment->batchhandle != context->h)
3223     return;
3224
3225   /* pop the old environment from the stack */
3226   temp = saved_environment;
3227   saved_environment = temp->next;
3228
3229   /* delete the current environment, totally */
3230   env = GetEnvironmentStringsW ();
3231   old = WCMD_dupenv (env);
3232   len = 0;
3233   while (old[len]) {
3234     n = strlenW(&old[len]) + 1;
3235     p = strchrW(&old[len] + 1, '=');
3236     if (p)
3237     {
3238       *p++ = 0;
3239       SetEnvironmentVariableW (&old[len], NULL);
3240     }
3241     len += n;
3242   }
3243   LocalFree (old);
3244   FreeEnvironmentStringsW (env);
3245
3246   /* restore old environment */
3247   env = temp->strings;
3248   len = 0;
3249   delayedsubst = temp->delayedsubst;
3250   WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3251   while (env[len]) {
3252     n = strlenW(&env[len]) + 1;
3253     p = strchrW(&env[len] + 1, '=');
3254     if (p)
3255     {
3256       *p++ = 0;
3257       SetEnvironmentVariableW (&env[len], p);
3258     }
3259     len += n;
3260   }
3261
3262   /* Restore current drive letter */
3263   if (IsCharAlphaW(temp->u.cwd)) {
3264     WCHAR envvar[4];
3265     WCHAR cwd[MAX_PATH];
3266     static const WCHAR fmt[] = {'=','%','c',':','\0'};
3267
3268     wsprintfW(envvar, fmt, temp->u.cwd);
3269     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3270       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3271       SetCurrentDirectoryW(cwd);
3272     }
3273   }
3274
3275   LocalFree (env);
3276   LocalFree (temp);
3277 }
3278
3279 /*****************************************************************************
3280  * WCMD_setshow_default
3281  *
3282  *      Set/Show the current default directory
3283  */
3284
3285 void WCMD_setshow_default (const WCHAR *args) {
3286
3287   BOOL status;
3288   WCHAR string[1024];
3289   WCHAR cwd[1024];
3290   WCHAR *pos;
3291   WIN32_FIND_DATAW fd;
3292   HANDLE hff;
3293   static const WCHAR parmD[] = {'/','D','\0'};
3294
3295   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3296
3297   /* Skip /D and trailing whitespace if on the front of the command line */
3298   if (CompareStringW(LOCALE_USER_DEFAULT,
3299                      NORM_IGNORECASE | SORT_STRINGSORT,
3300                      args, 2, parmD, -1) == CSTR_EQUAL) {
3301     args += 2;
3302     while (*args && (*args==' ' || *args=='\t'))
3303       args++;
3304   }
3305
3306   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3307   if (strlenW(args) == 0) {
3308     strcatW (cwd, newlineW);
3309     WCMD_output_asis (cwd);
3310   }
3311   else {
3312     /* Remove any double quotes, which may be in the
3313        middle, eg. cd "C:\Program Files"\Microsoft is ok */
3314     pos = string;
3315     while (*args) {
3316       if (*args != '"') *pos++ = *args;
3317       args++;
3318     }
3319     while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3320       pos--;
3321     *pos = 0x00;
3322
3323     /* Search for appropriate directory */
3324     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3325     hff = FindFirstFileW(string, &fd);
3326     if (hff != INVALID_HANDLE_VALUE) {
3327       do {
3328         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3329           WCHAR fpath[MAX_PATH];
3330           WCHAR drive[10];
3331           WCHAR dir[MAX_PATH];
3332           WCHAR fname[MAX_PATH];
3333           WCHAR ext[MAX_PATH];
3334           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3335
3336           /* Convert path into actual directory spec */
3337           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3338           WCMD_splitpath(fpath, drive, dir, fname, ext);
3339
3340           /* Rebuild path */
3341           wsprintfW(string, fmt, drive, dir, fd.cFileName);
3342           break;
3343         }
3344       } while (FindNextFileW(hff, &fd) != 0);
3345       FindClose(hff);
3346     }
3347
3348     /* Change to that directory */
3349     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3350
3351     status = SetCurrentDirectoryW(string);
3352     if (!status) {
3353       errorlevel = 1;
3354       WCMD_print_error ();
3355       return;
3356     } else {
3357
3358       /* Save away the actual new directory, to store as current location */
3359       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3360
3361       /* Restore old directory if drive letter would change, and
3362            CD x:\directory /D (or pushd c:\directory) not supplied */
3363       if ((strstrW(quals, parmD) == NULL) &&
3364           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3365         SetCurrentDirectoryW(cwd);
3366       }
3367     }
3368
3369     /* Set special =C: type environment variable, for drive letter of
3370        change of directory, even if path was restored due to missing
3371        /D (allows changing drive letter when not resident on that
3372        drive                                                          */
3373     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3374       WCHAR env[4];
3375       strcpyW(env, equalW);
3376       memcpy(env+1, string, 2 * sizeof(WCHAR));
3377       env[3] = 0x00;
3378       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3379       SetEnvironmentVariableW(env, string);
3380     }
3381
3382    }
3383   return;
3384 }
3385
3386 /****************************************************************************
3387  * WCMD_setshow_date
3388  *
3389  * Set/Show the system date
3390  * FIXME: Can't change date yet
3391  */
3392
3393 void WCMD_setshow_date (void) {
3394
3395   WCHAR curdate[64], buffer[64];
3396   DWORD count;
3397   static const WCHAR parmT[] = {'/','T','\0'};
3398
3399   if (strlenW(param1) == 0) {
3400     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3401                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3402       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3403       if (strstrW (quals, parmT) == NULL) {
3404         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3405         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3406         if (count > 2) {
3407           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3408         }
3409       }
3410     }
3411     else WCMD_print_error ();
3412   }
3413   else {
3414     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3415   }
3416 }
3417
3418 /****************************************************************************
3419  * WCMD_compare
3420  * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3421  *       the equals sign.
3422  */
3423 static int WCMD_compare( const void *a, const void *b )
3424 {
3425     int r;
3426     const WCHAR * const *str_a = a, * const *str_b = b;
3427     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3428           *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3429     if( r == CSTR_LESS_THAN ) return -1;
3430     if( r == CSTR_GREATER_THAN ) return 1;
3431     return 0;
3432 }
3433
3434 /****************************************************************************
3435  * WCMD_setshow_sortenv
3436  *
3437  * sort variables into order for display
3438  * Optionally only display those who start with a stub
3439  * returns the count displayed
3440  */
3441 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3442 {
3443   UINT count=0, len=0, i, displayedcount=0, stublen=0;
3444   const WCHAR **str;
3445
3446   if (stub) stublen = strlenW(stub);
3447
3448   /* count the number of strings, and the total length */
3449   while ( s[len] ) {
3450     len += (strlenW(&s[len]) + 1);
3451     count++;
3452   }
3453
3454   /* add the strings to an array */
3455   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3456   if( !str )
3457     return 0;
3458   str[0] = s;
3459   for( i=1; i<count; i++ )
3460     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3461
3462   /* sort the array */
3463   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3464
3465   /* print it */
3466   for( i=0; i<count; i++ ) {
3467     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3468                                 NORM_IGNORECASE | SORT_STRINGSORT,
3469                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3470       /* Don't display special internal variables */
3471       if (str[i][0] != '=') {
3472         WCMD_output_asis(str[i]);
3473         WCMD_output_asis(newlineW);
3474         displayedcount++;
3475       }
3476     }
3477   }
3478
3479   LocalFree( str );
3480   return displayedcount;
3481 }
3482
3483 /****************************************************************************
3484  * WCMD_getprecedence
3485  * Return the precedence of a particular operator
3486  */
3487 static int WCMD_getprecedence(const WCHAR in)
3488 {
3489   switch (in) {
3490     case '!':
3491     case '~':
3492     case OP_POSITIVE:
3493     case OP_NEGATIVE:
3494       return 8;
3495     case '*':
3496     case '/':
3497     case '%':
3498       return 7;
3499     case '+':
3500     case '-':
3501       return 6;
3502     case '<':
3503     case '>':
3504       return 5;
3505     case '&':
3506       return 4;
3507     case '^':
3508       return 3;
3509     case '|':
3510       return 2;
3511     case '=':
3512     case OP_ASSSIGNMUL:
3513     case OP_ASSSIGNDIV:
3514     case OP_ASSSIGNMOD:
3515     case OP_ASSSIGNADD:
3516     case OP_ASSSIGNSUB:
3517     case OP_ASSSIGNAND:
3518     case OP_ASSSIGNNOT:
3519     case OP_ASSSIGNOR:
3520     case OP_ASSSIGNSHL:
3521     case OP_ASSSIGNSHR:
3522       return 1;
3523     default:
3524       return 0;
3525   }
3526 }
3527
3528 /****************************************************************************
3529  * WCMD_pushnumber
3530  * Push either a number or name (environment variable) onto the supplied
3531  * stack
3532  */
3533 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3534   VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3535   thisstack->isnum = (var == NULL);
3536   if (var) {
3537     thisstack->variable = var;
3538     WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3539   } else {
3540     thisstack->value = num;
3541     WINE_TRACE("Pushed number %d\n", num);
3542   }
3543   thisstack->next = *varstack;
3544   *varstack = thisstack;
3545 }
3546
3547 /****************************************************************************
3548  * WCMD_peeknumber
3549  * Returns the value of the top number or environment variable on the stack
3550  * and leaves the item on the stack.
3551  */
3552 static int WCMD_peeknumber(VARSTACK **varstack) {
3553   int result = 0;
3554   VARSTACK *thisvar;
3555
3556   if (varstack) {
3557     thisvar = *varstack;
3558     if (!thisvar->isnum) {
3559       WCHAR tmpstr[MAXSTRING];
3560       if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3561         result = strtoulW(tmpstr,NULL,0);
3562       }
3563       WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3564     } else {
3565       result = thisvar->value;
3566     }
3567   }
3568   WINE_TRACE("Peeked number %d\n", result);
3569   return result;
3570 }
3571
3572 /****************************************************************************
3573  * WCMD_popnumber
3574  * Returns the value of the top number or environment variable on the stack
3575  * and removes the item from the stack.
3576  */
3577 static int WCMD_popnumber(VARSTACK **varstack) {
3578   int result = 0;
3579   VARSTACK *thisvar;
3580
3581   if (varstack) {
3582     thisvar = *varstack;
3583     result = WCMD_peeknumber(varstack);
3584     if (!thisvar->isnum) heap_free(thisvar->variable);
3585     *varstack = thisvar->next;
3586     heap_free(thisvar);
3587   }
3588   WINE_TRACE("Popped number %d\n", result);
3589   return result;
3590 }
3591
3592 /****************************************************************************
3593  * WCMD_pushoperator
3594  * Push an operator onto the supplied stack
3595  */
3596 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3597   OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3598   thisstack->precedence = precedence;
3599   thisstack->op = op;
3600   thisstack->next = *opstack;
3601   WINE_TRACE("Pushed operator %c\n", op);
3602   *opstack = thisstack;
3603 }
3604
3605 /****************************************************************************
3606  * WCMD_popoperator
3607  * Returns the operator from the top of the stack and removes the item from
3608  * the stack.
3609  */
3610 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3611   WCHAR result = 0;
3612   OPSTACK *thisop;
3613
3614   if (opstack) {
3615     thisop = *opstack;
3616     result = thisop->op;
3617     *opstack = thisop->next;
3618     heap_free(thisop);
3619   }
3620   WINE_TRACE("Popped operator %c\n", result);
3621   return result;
3622 }
3623
3624 /****************************************************************************
3625  * WCMD_reduce
3626  * Actions the top operator on the stack against the first and sometimes
3627  * second value on the variable stack, and pushes the result
3628  * Returns non-zero on error.
3629  */
3630 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3631   OPSTACK *thisop;
3632   int var1,var2;
3633   int rc = 0;
3634
3635   if (!*opstack || !*varstack) {
3636     WINE_TRACE("No operators for the reduce\n");
3637     return WCMD_NOOPERATOR;
3638   }
3639
3640   /* Remove the top operator */
3641   thisop = *opstack;
3642   *opstack = (*opstack)->next;
3643   WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop->op);
3644
3645   /* One variable operators */
3646   var1 = WCMD_popnumber(varstack);
3647   switch (thisop->op) {
3648   case '!': WCMD_pushnumber(NULL, !var1, varstack);
3649             break;
3650   case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3651             break;
3652   case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3653             break;
3654   case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3655             break;
3656   }
3657
3658   /* Two variable operators */
3659   if (!*varstack) {
3660     WINE_TRACE("No operands left for the reduce?\n");
3661     return WCMD_NOOPERAND;
3662   }
3663   switch (thisop->op) {
3664   case '!':
3665   case '~':
3666   case OP_POSITIVE:
3667   case OP_NEGATIVE:
3668             break; /* Handled above */
3669   case '*': var2 = WCMD_popnumber(varstack);
3670             WCMD_pushnumber(NULL, var2*var1, varstack);
3671             break;
3672   case '/': var2 = WCMD_popnumber(varstack);
3673             if (var1 == 0) return WCMD_DIVIDEBYZERO;
3674             WCMD_pushnumber(NULL, var2/var1, varstack);
3675             break;
3676   case '+': var2 = WCMD_popnumber(varstack);
3677             WCMD_pushnumber(NULL, var2+var1, varstack);
3678             break;
3679   case '-': var2 = WCMD_popnumber(varstack);
3680             WCMD_pushnumber(NULL, var2-var1, varstack);
3681             break;
3682   case '&': var2 = WCMD_popnumber(varstack);
3683             WCMD_pushnumber(NULL, var2&var1, varstack);
3684             break;
3685   case '%': var2 = WCMD_popnumber(varstack);
3686             if (var1 == 0) return WCMD_DIVIDEBYZERO;
3687             WCMD_pushnumber(NULL, var2%var1, varstack);
3688             break;
3689   case '^': var2 = WCMD_popnumber(varstack);
3690             WCMD_pushnumber(NULL, var2^var1, varstack);
3691             break;
3692   case '<': var2 = WCMD_popnumber(varstack);
3693             /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3694                which differs from the compiler (for example gcc) so being explicit. */
3695             if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3696               WCMD_pushnumber(NULL, 0, varstack);
3697             } else {
3698               WCMD_pushnumber(NULL, var2<<var1, varstack);
3699             }
3700             break;
3701   case '>': var2 = WCMD_popnumber(varstack);
3702             WCMD_pushnumber(NULL, var2>>var1, varstack);
3703             break;
3704   case '|': var2 = WCMD_popnumber(varstack);
3705             WCMD_pushnumber(NULL, var2|var1, varstack);
3706             break;
3707
3708   case OP_ASSSIGNMUL:
3709   case OP_ASSSIGNDIV:
3710   case OP_ASSSIGNMOD:
3711   case OP_ASSSIGNADD:
3712   case OP_ASSSIGNSUB:
3713   case OP_ASSSIGNAND:
3714   case OP_ASSSIGNNOT:
3715   case OP_ASSSIGNOR:
3716   case OP_ASSSIGNSHL:
3717   case OP_ASSSIGNSHR:
3718         {
3719           int i = 0;
3720
3721           /* The left of an equals must be one variable */
3722           if (!(*varstack) || (*varstack)->isnum) {
3723             return WCMD_NOOPERAND;
3724           }
3725
3726           /* Make the number stack grow by inserting the value of the variable */
3727           var2 = WCMD_peeknumber(varstack);
3728           WCMD_pushnumber(NULL, var2, varstack);
3729           WCMD_pushnumber(NULL, var1, varstack);
3730
3731           /* Make the operand stack grow by pushing the assign operator plus the
3732              operator to perform                                                 */
3733           while (calcassignments[i].op != ' ' &&
3734                  calcassignments[i].calculatedop != thisop->op) {
3735             i++;
3736           }
3737           if (calcassignments[i].calculatedop == ' ') {
3738             WINE_ERR("Unexpected operator %c\n", thisop->op);
3739             return WCMD_NOOPERATOR;
3740           }
3741           WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3742           WCMD_pushoperator(calcassignments[i].op,
3743                             WCMD_getprecedence(calcassignments[i].op), opstack);
3744           break;
3745         }
3746
3747   case '=':
3748         {
3749           WCHAR  intFormat[] = {'%','d','\0'};
3750           WCHAR  result[MAXSTRING];
3751
3752           /* Build the result, then push it onto the stack */
3753           sprintfW(result, intFormat, var1);
3754           WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3755                      wine_dbgstr_w(result));
3756           SetEnvironmentVariableW((*varstack)->variable, result);
3757           var2 = WCMD_popnumber(varstack);
3758           WCMD_pushnumber(NULL, var1, varstack);
3759           break;
3760         }
3761
3762   default:  WINE_ERR("Unrecognized operator %c\n", thisop->op);
3763   }
3764
3765   heap_free(thisop);
3766   return rc;
3767 }
3768
3769
3770 /****************************************************************************
3771  * WCMD_handleExpression
3772  * Handles an expression provided to set /a - If it finds brackets, it uses
3773  * recursion to process the parts in brackets.
3774  */
3775 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3776 {
3777   static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3778                                      '+','<','>','&','^','|','=',',','\0' };
3779   int       rc = 0;
3780   WCHAR    *pos;
3781   BOOL      lastwasnumber = FALSE;  /* FALSE makes a minus at the start of the expression easier to handle */
3782   OPSTACK  *opstackhead = NULL;
3783   VARSTACK *varstackhead = NULL;
3784   WCHAR     foundhalf = 0;
3785
3786   /* Initialize */
3787   WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3788   pos = *expr;
3789
3790   /* Iterate through until whole expression is processed */
3791   while (pos && *pos) {
3792     BOOL treatasnumber;
3793
3794     /* Skip whitespace to get to the next character to process*/
3795     while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3796     if (!*pos) goto exprreturn;
3797
3798     /* If we have found anything other than an operator then its a number/variable */
3799     if (strchrW(mathDelims, *pos) == NULL) {
3800       WCHAR *parmstart, *parm, *dupparm;
3801       WCHAR *nextpos;
3802
3803       /* Cannot have an expression with var/number twice, without an operator
3804          in-between, nor or number following a half constructed << or >> operator */
3805       if (lastwasnumber || foundhalf) {
3806         rc = WCMD_NOOPERATOR;
3807         goto exprerrorreturn;
3808       }
3809       lastwasnumber = TRUE;
3810
3811       if (isdigitW(*pos)) {
3812         /* For a number - just push it onto the stack */
3813         int num = strtoulW(pos, &nextpos, 0);
3814         WCMD_pushnumber(NULL, num, &varstackhead);
3815         pos = nextpos;
3816
3817         /* Verify the number was validly formed */
3818         if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3819           rc = WCMD_BADHEXOCT;
3820           goto exprerrorreturn;
3821         }
3822       } else {
3823
3824         /* For a variable - just push it onto the stack */
3825         parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3826         dupparm = heap_strdupW(parm);
3827         WCMD_pushnumber(dupparm, 0, &varstackhead);
3828         pos = parmstart + strlenW(dupparm);
3829       }
3830       continue;
3831     }
3832
3833     /* We have found an operator. Some operators are one character, some two, and the minus
3834        and plus signs need special processing as they can be either operators or just influence
3835        the parameter which follows them                                                         */
3836     if (foundhalf && (*pos != foundhalf)) {
3837       /* Badly constructed operator pair */
3838       rc = WCMD_NOOPERATOR;
3839       goto exprerrorreturn;
3840     }
3841
3842     treatasnumber = FALSE; /* We are processing an operand */
3843     switch (*pos) {
3844
3845     /* > and < are special as they are double character operators (and spaces can be between them!)
3846        If we see these for the first time, set a flag, and second time around we continue.
3847        Note these double character operators are stored as just one of the characters on the stack */
3848     case '>':
3849     case '<': if (!foundhalf) {
3850                 foundhalf = *pos;
3851                 pos++;
3852                 break;
3853               }
3854               /* We have found the rest, so clear up the knowledge of the half completed part and
3855                  drop through to normal operator processing                                       */
3856               foundhalf = 0;
3857               /* drop through */
3858
3859     case '=': if (*pos=='=') {
3860                 /* = is special cased as if the last was an operator then we may have e.g. += or
3861                    *= etc which we need to handle by replacing the operator that is on the stack
3862                    with a calculated assignment equivalent                                       */
3863                 if (!lastwasnumber && opstackhead) {
3864                   int i = 0;
3865                   while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3866                     i++;
3867                   }
3868                   if (calcassignments[i].op == ' ') {
3869                     rc = WCMD_NOOPERAND;
3870                     goto exprerrorreturn;
3871                   } else {
3872                     /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3873                        when the general operator handling happens further down.                   */
3874                     *pos = calcassignments[i].calculatedop;
3875                     WCMD_popoperator(&opstackhead);
3876                   }
3877                 }
3878               }
3879               /* Drop though */
3880
3881     /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3882        so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3883     case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3884               /* drop through */
3885     case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3886               /* drop through */
3887
3888     /* Normal operators - push onto stack unless precedence means we have to calculate it now */
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 '*': /* drop through */
3896     case '|':
3897                /* General code for handling most of the operators - look at the
3898                   precedence of the top item on the stack, and see if we need to
3899                   action the stack before we push something else onto it.        */
3900                {
3901                  int precedence = WCMD_getprecedence(*pos);
3902                  WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3903                             precedence, !opstackhead?-1:opstackhead->precedence);
3904
3905                  /* In general, for things with the same precedence, reduce immediately
3906                     except for assignments and unary operators which do not             */
3907                  while (!rc && opstackhead &&
3908                         ((opstackhead->precedence > precedence) ||
3909                          ((opstackhead->precedence == precedence) &&
3910                             (precedence != 1) && (precedence != 8)))) {
3911                    rc = WCMD_reduce(&opstackhead, &varstackhead);
3912                  }
3913                  if (rc) goto exprerrorreturn;
3914                  WCMD_pushoperator(*pos, precedence, &opstackhead);
3915                  pos++;
3916                  break;
3917                }
3918
3919     /* comma means start a new expression, ie calculate what we have */
3920     case ',':
3921                {
3922                  int prevresult = -1;
3923                  WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3924                  while (!rc && opstackhead) {
3925                    rc = WCMD_reduce(&opstackhead, &varstackhead);
3926                  }
3927                  if (rc) goto exprerrorreturn;
3928                  /* If we have anything other than one number left, error
3929                     otherwise throw the number away                      */
3930                  if (!varstackhead || varstackhead->next) {
3931                    rc = WCMD_NOOPERATOR;
3932                    goto exprerrorreturn;
3933                  }
3934                  prevresult = WCMD_popnumber(&varstackhead);
3935                  WINE_TRACE("Expression resolved to %d\n", prevresult);
3936                  heap_free(varstackhead);
3937                  varstackhead = NULL;
3938                  pos++;
3939                  break;
3940                }
3941
3942     /* Open bracket - use iteration to parse the inner expression, then continue */
3943     case '(' : {
3944                  int exprresult = 0;
3945                  pos++;
3946                  rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
3947                  if (rc) goto exprerrorreturn;
3948                  WCMD_pushnumber(NULL, exprresult, &varstackhead);
3949                  break;
3950                }
3951
3952     /* Close bracket - we have finished this depth, calculate and return */
3953     case ')' : {
3954                  pos++;
3955                  treatasnumber = TRUE; /* Things in brackets result in a number */
3956                  if (depth == 0) {
3957                    rc = WCMD_BADPAREN;
3958                    goto exprerrorreturn;
3959                  }
3960                  goto exprreturn;
3961                }
3962
3963     default:
3964         WINE_ERR("Unrecognized operator %c\n", *pos);
3965         pos++;
3966     }
3967     lastwasnumber = treatasnumber;
3968   }
3969
3970 exprreturn:
3971   *expr = pos;
3972
3973   /* We need to reduce until we have a single number (or variable) on the
3974      stack and set the return value to that                               */
3975   while (!rc && opstackhead) {
3976     rc = WCMD_reduce(&opstackhead, &varstackhead);
3977   }
3978   if (rc) goto exprerrorreturn;
3979
3980   /* If we have anything other than one number left, error
3981       otherwise throw the number away                      */
3982   if (!varstackhead || varstackhead->next) {
3983     rc = WCMD_NOOPERATOR;
3984     goto exprerrorreturn;
3985   }
3986
3987   /* Now get the number (and convert if its just a variable name) */
3988   *ret = WCMD_popnumber(&varstackhead);
3989
3990 exprerrorreturn:
3991   /* Free all remaining memory */
3992   while (opstackhead) WCMD_popoperator(&opstackhead);
3993   while (varstackhead) WCMD_popnumber(&varstackhead);
3994
3995   WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
3996   return rc;
3997 }
3998
3999 /****************************************************************************
4000  * WCMD_setshow_env
4001  *
4002  * Set/Show the environment variables
4003  */
4004
4005 void WCMD_setshow_env (WCHAR *s) {
4006
4007   LPVOID env;
4008   WCHAR *p;
4009   int status;
4010   static const WCHAR parmP[] = {'/','P','\0'};
4011   static const WCHAR parmA[] = {'/','A','\0'};
4012   WCHAR string[MAXSTRING];
4013
4014   if (param1[0] == 0x00 && quals[0] == 0x00) {
4015     env = GetEnvironmentStringsW();
4016     WCMD_setshow_sortenv( env, NULL );
4017     return;
4018   }
4019
4020   /* See if /P supplied, and if so echo the prompt, and read in a reply */
4021   if (CompareStringW(LOCALE_USER_DEFAULT,
4022                      NORM_IGNORECASE | SORT_STRINGSORT,
4023                      s, 2, parmP, -1) == CSTR_EQUAL) {
4024     DWORD count;
4025
4026     s += 2;
4027     while (*s && (*s==' ' || *s=='\t')) s++;
4028     if (*s=='\"')
4029         WCMD_strip_quotes(s);
4030
4031     /* If no parameter, or no '=' sign, return an error */
4032     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4033       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4034       return;
4035     }
4036
4037     /* Output the prompt */
4038     *p++ = '\0';
4039     if (strlenW(p) != 0) WCMD_output_asis(p);
4040
4041     /* Read the reply */
4042     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4043     if (count > 1) {
4044       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4045       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4046       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4047                  wine_dbgstr_w(string));
4048       status = SetEnvironmentVariableW(s, string);
4049     }
4050
4051   /* See if /A supplied, and if so calculate the results of all the expressions */
4052   } else if (CompareStringW(LOCALE_USER_DEFAULT,
4053                             NORM_IGNORECASE | SORT_STRINGSORT,
4054                             s, 2, parmA, -1) == CSTR_EQUAL) {
4055     /* /A supplied, so evaluate expressions and set variables appropriately */
4056     /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result  */
4057     /* of the final computation                                             */
4058     int result = 0;
4059     int rc = 0;
4060     WCHAR *thisexpr;
4061     WCHAR *src,*dst;
4062
4063     /* Remove all quotes before doing any calculations */
4064     thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4065     src = s+2;
4066     dst = thisexpr;
4067     while (*src) {
4068       if (*src != '"') *dst++ = *src;
4069       src++;
4070     }
4071     *dst = 0;
4072
4073     /* Now calculate the results of the expression */
4074     src = thisexpr;
4075     rc = WCMD_handleExpression(&src, &result, 0);
4076     heap_free(thisexpr);
4077
4078     /* If parsing failed, issue the error message */
4079     if (rc > 0) {
4080       WCMD_output_stderr(WCMD_LoadMessage(rc));
4081       return;
4082     }
4083
4084     /* If we have no context (interactive or cmd.exe /c) print the final result */
4085     if (!context) {
4086       static const WCHAR fmt[] = {'%','d','\0'};
4087       sprintfW(string, fmt, result);
4088       WCMD_output_asis(string);
4089     }
4090
4091   } else {
4092     DWORD gle;
4093
4094     if (*s=='\"')
4095         WCMD_strip_quotes(s);
4096     p = strchrW (s, '=');
4097     if (p == NULL) {
4098       env = GetEnvironmentStringsW();
4099       if (WCMD_setshow_sortenv( env, s ) == 0) {
4100         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4101         errorlevel = 1;
4102       }
4103       return;
4104     }
4105     *p++ = '\0';
4106
4107     if (strlenW(p) == 0) p = NULL;
4108     WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4109                wine_dbgstr_w(p));
4110     status = SetEnvironmentVariableW(s, p);
4111     gle = GetLastError();
4112     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4113       errorlevel = 1;
4114     } else if ((!status)) WCMD_print_error();
4115     else errorlevel = 0;
4116   }
4117 }
4118
4119 /****************************************************************************
4120  * WCMD_setshow_path
4121  *
4122  * Set/Show the path environment variable
4123  */
4124
4125 void WCMD_setshow_path (const WCHAR *args) {
4126
4127   WCHAR string[1024];
4128   DWORD status;
4129   static const WCHAR pathW[] = {'P','A','T','H','\0'};
4130   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4131
4132   if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4133     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4134     if (status != 0) {
4135       WCMD_output_asis ( pathEqW);
4136       WCMD_output_asis ( string);
4137       WCMD_output_asis ( newlineW);
4138     }
4139     else {
4140       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4141     }
4142   }
4143   else {
4144     if (*args == '=') args++; /* Skip leading '=' */
4145     status = SetEnvironmentVariableW(pathW, args);
4146     if (!status) WCMD_print_error();
4147   }
4148 }
4149
4150 /****************************************************************************
4151  * WCMD_setshow_prompt
4152  *
4153  * Set or show the command prompt.
4154  */
4155
4156 void WCMD_setshow_prompt (void) {
4157
4158   WCHAR *s;
4159   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4160
4161   if (strlenW(param1) == 0) {
4162     SetEnvironmentVariableW(promptW, NULL);
4163   }
4164   else {
4165     s = param1;
4166     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4167     if (strlenW(s) == 0) {
4168       SetEnvironmentVariableW(promptW, NULL);
4169     }
4170     else SetEnvironmentVariableW(promptW, s);
4171   }
4172 }
4173
4174 /****************************************************************************
4175  * WCMD_setshow_time
4176  *
4177  * Set/Show the system time
4178  * FIXME: Can't change time yet
4179  */
4180
4181 void WCMD_setshow_time (void) {
4182
4183   WCHAR curtime[64], buffer[64];
4184   DWORD count;
4185   SYSTEMTIME st;
4186   static const WCHAR parmT[] = {'/','T','\0'};
4187
4188   if (strlenW(param1) == 0) {
4189     GetLocalTime(&st);
4190     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4191                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4192       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4193       if (strstrW (quals, parmT) == NULL) {
4194         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4195         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4196         if (count > 2) {
4197           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4198         }
4199       }
4200     }
4201     else WCMD_print_error ();
4202   }
4203   else {
4204     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4205   }
4206 }
4207
4208 /****************************************************************************
4209  * WCMD_shift
4210  *
4211  * Shift batch parameters.
4212  * Optional /n says where to start shifting (n=0-8)
4213  */
4214
4215 void WCMD_shift (const WCHAR *args) {
4216   int start;
4217
4218   if (context != NULL) {
4219     WCHAR *pos = strchrW(args, '/');
4220     int   i;
4221
4222     if (pos == NULL) {
4223       start = 0;
4224     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4225       start = (*(pos+1) - '0');
4226     } else {
4227       SetLastError(ERROR_INVALID_PARAMETER);
4228       WCMD_print_error();
4229       return;
4230     }
4231
4232     WINE_TRACE("Shifting variables, starting at %d\n", start);
4233     for (i=start;i<=8;i++) {
4234       context -> shift_count[i] = context -> shift_count[i+1] + 1;
4235     }
4236     context -> shift_count[9] = context -> shift_count[9] + 1;
4237   }
4238
4239 }
4240
4241 /****************************************************************************
4242  * WCMD_start
4243  */
4244 void WCMD_start(const WCHAR *args)
4245 {
4246     static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4247                                  '\\','s','t','a','r','t','.','e','x','e',0};
4248     WCHAR file[MAX_PATH];
4249     WCHAR *cmdline;
4250     STARTUPINFOW st;
4251     PROCESS_INFORMATION pi;
4252
4253     GetWindowsDirectoryW( file, MAX_PATH );
4254     strcatW( file, exeW );
4255     cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
4256     strcpyW( cmdline, file );
4257     strcatW( cmdline, spaceW );
4258     strcatW( cmdline, args );
4259
4260     memset( &st, 0, sizeof(STARTUPINFOW) );
4261     st.cb = sizeof(STARTUPINFOW);
4262
4263     if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4264     {
4265         WaitForSingleObject( pi.hProcess, INFINITE );
4266         GetExitCodeProcess( pi.hProcess, &errorlevel );
4267         if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4268         CloseHandle(pi.hProcess);
4269         CloseHandle(pi.hThread);
4270     }
4271     else
4272     {
4273         SetLastError(ERROR_FILE_NOT_FOUND);
4274         WCMD_print_error ();
4275         errorlevel = 9009;
4276     }
4277     heap_free(cmdline);
4278 }
4279
4280 /****************************************************************************
4281  * WCMD_title
4282  *
4283  * Set the console title
4284  */
4285 void WCMD_title (const WCHAR *args) {
4286   SetConsoleTitleW(args);
4287 }
4288
4289 /****************************************************************************
4290  * WCMD_type
4291  *
4292  * Copy a file to standard output.
4293  */
4294
4295 void WCMD_type (WCHAR *args) {
4296
4297   int   argno         = 0;
4298   WCHAR *argN          = args;
4299   BOOL  writeHeaders  = FALSE;
4300
4301   if (param1[0] == 0x00) {
4302     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4303     return;
4304   }
4305
4306   if (param2[0] != 0x00) writeHeaders = TRUE;
4307
4308   /* Loop through all args */
4309   errorlevel = 0;
4310   while (argN) {
4311     WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4312
4313     HANDLE h;
4314     WCHAR buffer[512];
4315     DWORD count;
4316
4317     if (!argN) break;
4318
4319     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4320     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4321                 FILE_ATTRIBUTE_NORMAL, NULL);
4322     if (h == INVALID_HANDLE_VALUE) {
4323       WCMD_print_error ();
4324       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4325       errorlevel = 1;
4326     } else {
4327       if (writeHeaders) {
4328         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4329         WCMD_output(fmt, thisArg);
4330       }
4331       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4332         if (count == 0) break;  /* ReadFile reports success on EOF! */
4333         buffer[count] = 0;
4334         WCMD_output_asis (buffer);
4335       }
4336       CloseHandle (h);
4337     }
4338   }
4339 }
4340
4341 /****************************************************************************
4342  * WCMD_more
4343  *
4344  * Output either a file or stdin to screen in pages
4345  */
4346
4347 void WCMD_more (WCHAR *args) {
4348
4349   int   argno         = 0;
4350   WCHAR *argN         = args;
4351   WCHAR  moreStr[100];
4352   WCHAR  moreStrPage[100];
4353   WCHAR  buffer[512];
4354   DWORD count;
4355   static const WCHAR moreStart[] = {'-','-',' ','\0'};
4356   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
4357   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
4358                                     ')',' ','-','-','\n','\0'};
4359   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
4360
4361   /* Prefix the NLS more with '-- ', then load the text */
4362   errorlevel = 0;
4363   strcpyW(moreStr, moreStart);
4364   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4365               (sizeof(moreStr)/sizeof(WCHAR))-3);
4366
4367   if (param1[0] == 0x00) {
4368
4369     /* Wine implements pipes via temporary files, and hence stdin is
4370        effectively reading from the file. This means the prompts for
4371        more are satisfied by the next line from the input (file). To
4372        avoid this, ensure stdin is to the console                    */
4373     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
4374     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4375                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
4376                          FILE_ATTRIBUTE_NORMAL, 0);
4377     WINE_TRACE("No parms - working probably in pipe mode\n");
4378     SetStdHandle(STD_INPUT_HANDLE, hConIn);
4379
4380     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4381        once you get in this bit unless due to a pipe, its going to end badly...  */
4382     wsprintfW(moreStrPage, moreFmt, moreStr);
4383
4384     WCMD_enter_paged_mode(moreStrPage);
4385     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4386       if (count == 0) break;    /* ReadFile reports success on EOF! */
4387       buffer[count] = 0;
4388       WCMD_output_asis (buffer);
4389     }
4390     WCMD_leave_paged_mode();
4391
4392     /* Restore stdin to what it was */
4393     SetStdHandle(STD_INPUT_HANDLE, hstdin);
4394     CloseHandle(hConIn);
4395
4396     return;
4397   } else {
4398     BOOL needsPause = FALSE;
4399
4400     /* Loop through all args */
4401     WINE_TRACE("Parms supplied - working through each file\n");
4402     WCMD_enter_paged_mode(moreStrPage);
4403
4404     while (argN) {
4405       WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4406       HANDLE h;
4407
4408       if (!argN) break;
4409
4410       if (needsPause) {
4411
4412         /* Wait */
4413         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4414         WCMD_leave_paged_mode();
4415         WCMD_output_asis(moreStrPage);
4416         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4417         WCMD_enter_paged_mode(moreStrPage);
4418       }
4419
4420
4421       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4422       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4423                 FILE_ATTRIBUTE_NORMAL, NULL);
4424       if (h == INVALID_HANDLE_VALUE) {
4425         WCMD_print_error ();
4426         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4427         errorlevel = 1;
4428       } else {
4429         ULONG64 curPos  = 0;
4430         ULONG64 fileLen = 0;
4431         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
4432
4433         /* Get the file size */
4434         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4435         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4436
4437         needsPause = TRUE;
4438         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4439           if (count == 0) break;        /* ReadFile reports success on EOF! */
4440           buffer[count] = 0;
4441           curPos += count;
4442
4443           /* Update % count (would be used in WCMD_output_asis as prompt) */
4444           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4445
4446           WCMD_output_asis (buffer);
4447         }
4448         CloseHandle (h);
4449       }
4450     }
4451
4452     WCMD_leave_paged_mode();
4453   }
4454 }
4455
4456 /****************************************************************************
4457  * WCMD_verify
4458  *
4459  * Display verify flag.
4460  * FIXME: We don't actually do anything with the verify flag other than toggle
4461  * it...
4462  */
4463
4464 void WCMD_verify (const WCHAR *args) {
4465
4466   int count;
4467
4468   count = strlenW(args);
4469   if (count == 0) {
4470     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4471     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4472     return;
4473   }
4474   if (lstrcmpiW(args, onW) == 0) {
4475     verify_mode = TRUE;
4476     return;
4477   }
4478   else if (lstrcmpiW(args, offW) == 0) {
4479     verify_mode = FALSE;
4480     return;
4481   }
4482   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4483 }
4484
4485 /****************************************************************************
4486  * WCMD_version
4487  *
4488  * Display version info.
4489  */
4490
4491 void WCMD_version (void) {
4492
4493   WCMD_output_asis (version_string);
4494
4495 }
4496
4497 /****************************************************************************
4498  * WCMD_volume
4499  *
4500  * Display volume information (set_label = FALSE)
4501  * Additionally set volume label (set_label = TRUE)
4502  * Returns 1 on success, 0 otherwise
4503  */
4504
4505 int WCMD_volume(BOOL set_label, const WCHAR *path)
4506 {
4507   DWORD count, serial;
4508   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4509   BOOL status;
4510
4511   if (strlenW(path) == 0) {
4512     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4513     if (!status) {
4514       WCMD_print_error ();
4515       return 0;
4516     }
4517     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4518                                    &serial, NULL, NULL, NULL, 0);
4519   }
4520   else {
4521     static const WCHAR fmt[] = {'%','s','\\','\0'};
4522     if ((path[1] != ':') || (strlenW(path) != 2)) {
4523       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4524       return 0;
4525     }
4526     wsprintfW (curdir, fmt, path);
4527     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4528                                    &serial, NULL,
4529         NULL, NULL, 0);
4530   }
4531   if (!status) {
4532     WCMD_print_error ();
4533     return 0;
4534   }
4535   if (label[0] != '\0') {
4536     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4537         curdir[0], label);
4538   }
4539   else {
4540     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4541         curdir[0]);
4542   }
4543   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4544         HIWORD(serial), LOWORD(serial));
4545   if (set_label) {
4546     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4547     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4548     if (count > 1) {
4549       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
4550       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4551     }
4552     if (strlenW(path) != 0) {
4553       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4554     }
4555     else {
4556       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4557     }
4558   }
4559   return 1;
4560 }
4561
4562 /**************************************************************************
4563  * WCMD_exit
4564  *
4565  * Exit either the process, or just this batch program
4566  *
4567  */
4568
4569 void WCMD_exit (CMD_LIST **cmdList) {
4570
4571     static const WCHAR parmB[] = {'/','B','\0'};
4572     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4573
4574     if (context && lstrcmpiW(quals, parmB) == 0) {
4575         errorlevel = rc;
4576         context -> skip_rest = TRUE;
4577         *cmdList = NULL;
4578     } else {
4579         ExitProcess(rc);
4580     }
4581 }
4582
4583
4584 /*****************************************************************************
4585  * WCMD_assoc
4586  *
4587  *      Lists or sets file associations  (assoc = TRUE)
4588  *      Lists or sets file types         (assoc = FALSE)
4589  */
4590 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4591
4592     HKEY    key;
4593     DWORD   accessOptions = KEY_READ;
4594     WCHAR   *newValue;
4595     LONG    rc = ERROR_SUCCESS;
4596     WCHAR    keyValue[MAXSTRING];
4597     DWORD   valueLen = MAXSTRING;
4598     HKEY    readKey;
4599     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4600                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4601
4602     /* See if parameter includes '=' */
4603     errorlevel = 0;
4604     newValue = strchrW(args, '=');
4605     if (newValue) accessOptions |= KEY_WRITE;
4606
4607     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4608     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4609                       accessOptions, &key) != ERROR_SUCCESS) {
4610       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4611       return;
4612     }
4613
4614     /* If no parameters then list all associations */
4615     if (*args == 0x00) {
4616       int index = 0;
4617
4618       /* Enumerate all the keys */
4619       while (rc != ERROR_NO_MORE_ITEMS) {
4620         WCHAR  keyName[MAXSTRING];
4621         DWORD nameLen;
4622
4623         /* Find the next value */
4624         nameLen = MAXSTRING;
4625         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4626
4627         if (rc == ERROR_SUCCESS) {
4628
4629           /* Only interested in extension ones if assoc, or others
4630              if not assoc                                          */
4631           if ((keyName[0] == '.' && assoc) ||
4632               (!(keyName[0] == '.') && (!assoc)))
4633           {
4634             WCHAR subkey[MAXSTRING];
4635             strcpyW(subkey, keyName);
4636             if (!assoc) strcatW(subkey, shOpCmdW);
4637
4638             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4639
4640               valueLen = sizeof(keyValue)/sizeof(WCHAR);
4641               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4642               WCMD_output_asis(keyName);
4643               WCMD_output_asis(equalW);
4644               /* If no default value found, leave line empty after '=' */
4645               if (rc == ERROR_SUCCESS) {
4646                 WCMD_output_asis(keyValue);
4647               }
4648               WCMD_output_asis(newlineW);
4649               RegCloseKey(readKey);
4650             }
4651           }
4652         }
4653       }
4654
4655     } else {
4656
4657       /* Parameter supplied - if no '=' on command line, its a query */
4658       if (newValue == NULL) {
4659         WCHAR *space;
4660         WCHAR subkey[MAXSTRING];
4661
4662         /* Query terminates the parameter at the first space */
4663         strcpyW(keyValue, args);
4664         space = strchrW(keyValue, ' ');
4665         if (space) *space=0x00;
4666
4667         /* Set up key name */
4668         strcpyW(subkey, keyValue);
4669         if (!assoc) strcatW(subkey, shOpCmdW);
4670
4671         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4672
4673           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4674           WCMD_output_asis(args);
4675           WCMD_output_asis(equalW);
4676           /* If no default value found, leave line empty after '=' */
4677           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4678           WCMD_output_asis(newlineW);
4679           RegCloseKey(readKey);
4680
4681         } else {
4682           WCHAR  msgbuffer[MAXSTRING];
4683
4684           /* Load the translated 'File association not found' */
4685           if (assoc) {
4686             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4687           } else {
4688             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4689           }
4690           WCMD_output_stderr(msgbuffer, keyValue);
4691           errorlevel = 2;
4692         }
4693
4694       /* Not a query - its a set or clear of a value */
4695       } else {
4696
4697         WCHAR subkey[MAXSTRING];
4698
4699         /* Get pointer to new value */
4700         *newValue = 0x00;
4701         newValue++;
4702
4703         /* Set up key name */
4704         strcpyW(subkey, args);
4705         if (!assoc) strcatW(subkey, shOpCmdW);
4706
4707         /* If nothing after '=' then clear value - only valid for ASSOC */
4708         if (*newValue == 0x00) {
4709
4710           if (assoc) rc = RegDeleteKeyW(key, args);
4711           if (assoc && rc == ERROR_SUCCESS) {
4712             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4713
4714           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4715             WCMD_print_error();
4716             errorlevel = 2;
4717
4718           } else {
4719             WCHAR  msgbuffer[MAXSTRING];
4720
4721             /* Load the translated 'File association not found' */
4722             if (assoc) {
4723               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4724                           sizeof(msgbuffer)/sizeof(WCHAR));
4725             } else {
4726               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4727                           sizeof(msgbuffer)/sizeof(WCHAR));
4728             }
4729             WCMD_output_stderr(msgbuffer, keyValue);
4730             errorlevel = 2;
4731           }
4732
4733         /* It really is a set value = contents */
4734         } else {
4735           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4736                               accessOptions, NULL, &readKey, NULL);
4737           if (rc == ERROR_SUCCESS) {
4738             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4739                                 (LPBYTE)newValue,
4740                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
4741             RegCloseKey(readKey);
4742           }
4743
4744           if (rc != ERROR_SUCCESS) {
4745             WCMD_print_error();
4746             errorlevel = 2;
4747           } else {
4748             WCMD_output_asis(args);
4749             WCMD_output_asis(equalW);
4750             WCMD_output_asis(newValue);
4751             WCMD_output_asis(newlineW);
4752           }
4753         }
4754       }
4755     }
4756
4757     /* Clean up */
4758     RegCloseKey(key);
4759 }
4760
4761 /****************************************************************************
4762  * WCMD_color
4763  *
4764  * Colors the terminal screen.
4765  */
4766
4767 void WCMD_color (void) {
4768
4769   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4770   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4771
4772   if (param1[0] != 0x00 && strlenW(param1) > 2) {
4773     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4774     return;
4775   }
4776
4777   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4778   {
4779       COORD topLeft;
4780       DWORD screenSize;
4781       DWORD color = 0;
4782
4783       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4784
4785       topLeft.X = 0;
4786       topLeft.Y = 0;
4787
4788       /* Convert the color hex digits */
4789       if (param1[0] == 0x00) {
4790         color = defaultColor;
4791       } else {
4792         color = strtoulW(param1, NULL, 16);
4793       }
4794
4795       /* Fail if fg == bg color */
4796       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4797         errorlevel = 1;
4798         return;
4799       }
4800
4801       /* Set the current screen contents and ensure all future writes
4802          remain this color                                             */
4803       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4804       SetConsoleTextAttribute(hStdOut, color);
4805   }
4806 }