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