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