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