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