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