wineconsole: Fix Italian translation.
[wine] / programs / cmd / wcmdmain.c
1 /*
2  * CMD - Wine-compatible command line interface.
3  *
4  * Copyright (C) 1999 - 2001 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  * - Cannot handle parameters in quotes
25  * - Lots of functionality missing from builtins
26  */
27
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
33
34 const WCHAR inbuilt[][10] = {
35         {'A','T','T','R','I','B','\0'},
36         {'C','A','L','L','\0'},
37         {'C','D','\0'},
38         {'C','H','D','I','R','\0'},
39         {'C','L','S','\0'},
40         {'C','O','P','Y','\0'},
41         {'C','T','T','Y','\0'},
42         {'D','A','T','E','\0'},
43         {'D','E','L','\0'},
44         {'D','I','R','\0'},
45         {'E','C','H','O','\0'},
46         {'E','R','A','S','E','\0'},
47         {'F','O','R','\0'},
48         {'G','O','T','O','\0'},
49         {'H','E','L','P','\0'},
50         {'I','F','\0'},
51         {'L','A','B','E','L','\0'},
52         {'M','D','\0'},
53         {'M','K','D','I','R','\0'},
54         {'M','O','V','E','\0'},
55         {'P','A','T','H','\0'},
56         {'P','A','U','S','E','\0'},
57         {'P','R','O','M','P','T','\0'},
58         {'R','E','M','\0'},
59         {'R','E','N','\0'},
60         {'R','E','N','A','M','E','\0'},
61         {'R','D','\0'},
62         {'R','M','D','I','R','\0'},
63         {'S','E','T','\0'},
64         {'S','H','I','F','T','\0'},
65         {'T','I','M','E','\0'},
66         {'T','I','T','L','E','\0'},
67         {'T','Y','P','E','\0'},
68         {'V','E','R','I','F','Y','\0'},
69         {'V','E','R','\0'},
70         {'V','O','L','\0'},
71         {'E','N','D','L','O','C','A','L','\0'},
72         {'S','E','T','L','O','C','A','L','\0'},
73         {'P','U','S','H','D','\0'},
74         {'P','O','P','D','\0'},
75         {'A','S','S','O','C','\0'},
76         {'C','O','L','O','R','\0'},
77         {'F','T','Y','P','E','\0'},
78         {'M','O','R','E','\0'},
79         {'E','X','I','T','\0'}
80 };
81
82 HINSTANCE hinst;
83 DWORD errorlevel;
84 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
85 static int opt_c, opt_k, opt_s;
86 const WCHAR newline[] = {'\r','\n','\0'};
87 static const WCHAR equalsW[] = {'=','\0'};
88 static const WCHAR closeBW[] = {')','\0'};
89 WCHAR anykey[100];
90 WCHAR version_string[100];
91 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
92 BATCH_CONTEXT *context = NULL;
93 extern struct env_stack *pushd_directories;
94 static const WCHAR *pagedMessage = NULL;
95 static char  *output_bufA = NULL;
96 #define MAX_WRITECONSOLE_SIZE 65535
97 BOOL unicodePipes = FALSE;
98
99
100 /*******************************************************************
101  * WCMD_output_asis_len - send output to current standard output
102  *
103  * Output a formatted unicode string. Ideally this will go to the console
104  *  and hence required WriteConsoleW to output it, however if file i/o is
105  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
106  */
107 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
108
109     DWORD   nOut= 0;
110     DWORD   res = 0;
111
112     /* If nothing to write, return (MORE does this sometimes) */
113     if (!len) return;
114
115     /* Try to write as unicode assuming it is to a console */
116     res = WriteConsoleW(device, message, len, &nOut, NULL);
117
118     /* If writing to console fails, assume its file
119        i/o so convert to OEM codepage and output                  */
120     if (!res) {
121       BOOL usedDefaultChar = FALSE;
122       DWORD convertedChars;
123
124       if (!unicodePipes) {
125         /*
126          * Allocate buffer to use when writing to file. (Not freed, as one off)
127          */
128         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
129                                                   MAX_WRITECONSOLE_SIZE);
130         if (!output_bufA) {
131           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
132           return;
133         }
134
135         /* Convert to OEM, then output */
136         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
137                             len, output_bufA, MAX_WRITECONSOLE_SIZE,
138                             "?", &usedDefaultChar);
139         WriteFile(device, output_bufA, convertedChars,
140                   &nOut, FALSE);
141       } else {
142         WriteFile(device, message, len*sizeof(WCHAR),
143                   &nOut, FALSE);
144       }
145     }
146     return;
147 }
148
149 /*******************************************************************
150  * WCMD_output - send output to current standard output device.
151  *
152  */
153
154 void WCMD_output (const WCHAR *format, ...) {
155
156   va_list ap;
157   WCHAR string[1024];
158   int ret;
159
160   va_start(ap,format);
161   ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
162   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
163        WINE_ERR("Output truncated in WCMD_output\n" );
164        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
165        string[ret] = '\0';
166   }
167   va_end(ap);
168   WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
169 }
170
171
172 static int line_count;
173 static int max_height;
174 static int max_width;
175 static BOOL paged_mode;
176 static int numChars;
177
178 void WCMD_enter_paged_mode(const WCHAR *msg)
179 {
180   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
181
182   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
183     max_height = consoleInfo.dwSize.Y;
184     max_width  = consoleInfo.dwSize.X;
185   } else {
186     max_height = 25;
187     max_width  = 80;
188   }
189   paged_mode = TRUE;
190   line_count = 0;
191   numChars   = 0;
192   pagedMessage = (msg==NULL)? anykey : msg;
193 }
194
195 void WCMD_leave_paged_mode(void)
196 {
197   paged_mode = FALSE;
198   pagedMessage = NULL;
199 }
200
201 /***************************************************************************
202  * WCMD_Readfile
203  *
204  *      Read characters in from a console/file, returning result in Unicode
205  *      with signature identical to ReadFile
206  */
207 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
208                           LPDWORD charsRead, const LPOVERLAPPED unused) {
209
210     BOOL   res;
211
212     /* Try to read from console as Unicode */
213     res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
214
215     /* If reading from console has failed we assume its file
216        i/o so read in and convert from OEM codepage               */
217     if (!res) {
218
219         DWORD numRead;
220         /*
221          * Allocate buffer to use when reading from file. Not freed
222          */
223         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
224                                                   MAX_WRITECONSOLE_SIZE);
225         if (!output_bufA) {
226           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
227           return 0;
228         }
229
230         /* Read from file (assume OEM codepage) */
231         res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
232
233         /* Convert from OEM */
234         *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
235                          intoBuf, maxChars);
236
237     }
238     return res;
239 }
240
241 /*******************************************************************
242  * WCMD_output_asis - send output to current standard output device.
243  *        without formatting eg. when message contains '%'
244  */
245 void WCMD_output_asis (const WCHAR *message) {
246   DWORD count;
247   const WCHAR* ptr;
248   WCHAR string[1024];
249
250   if (paged_mode) {
251     do {
252       ptr = message;
253       while (*ptr && *ptr!='\n' && (numChars < max_width)) {
254         numChars++;
255         ptr++;
256       };
257       if (*ptr == '\n') ptr++;
258       WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
259                            GetStdHandle(STD_OUTPUT_HANDLE));
260       if (ptr) {
261         numChars = 0;
262         if (++line_count >= max_height - 1) {
263           line_count = 0;
264           WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
265                                GetStdHandle(STD_OUTPUT_HANDLE));
266           WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
267                          sizeof(string)/sizeof(WCHAR), &count, NULL);
268         }
269       }
270     } while (((message = ptr) != NULL) && (*ptr));
271   } else {
272     WCMD_output_asis_len(message, lstrlenW(message),
273                          GetStdHandle(STD_OUTPUT_HANDLE));
274   }
275 }
276
277 /****************************************************************************
278  * WCMD_print_error
279  *
280  * Print the message for GetLastError
281  */
282
283 void WCMD_print_error (void) {
284   LPVOID lpMsgBuf;
285   DWORD error_code;
286   int status;
287
288   error_code = GetLastError ();
289   status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
290                           NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
291   if (!status) {
292     WINE_FIXME ("Cannot display message for error %d, status %d\n",
293                         error_code, GetLastError());
294     return;
295   }
296
297   WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
298                        GetStdHandle(STD_ERROR_HANDLE));
299   LocalFree (lpMsgBuf);
300   WCMD_output_asis_len (newline, lstrlenW(newline),
301                         GetStdHandle(STD_ERROR_HANDLE));
302   return;
303 }
304
305 /******************************************************************************
306  * WCMD_show_prompt
307  *
308  *      Display the prompt on STDout
309  *
310  */
311
312 static void WCMD_show_prompt (void) {
313
314   int status;
315   WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
316   WCHAR *p, *q;
317   DWORD len;
318   static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
319
320   len = GetEnvironmentVariableW(envPrompt, prompt_string,
321                                 sizeof(prompt_string)/sizeof(WCHAR));
322   if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
323     const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
324     strcpyW (prompt_string, dfltPrompt);
325   }
326   p = prompt_string;
327   q = out_string;
328   *q++ = '\r';
329   *q++ = '\n';
330   *q = '\0';
331   while (*p != '\0') {
332     if (*p != '$') {
333       *q++ = *p++;
334       *q = '\0';
335     }
336     else {
337       p++;
338       switch (toupper(*p)) {
339         case '$':
340           *q++ = '$';
341           break;
342         case 'A':
343           *q++ = '&';
344           break;
345         case 'B':
346           *q++ = '|';
347           break;
348         case 'C':
349           *q++ = '(';
350           break;
351         case 'D':
352           GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
353           while (*q) q++;
354           break;
355         case 'E':
356           *q++ = '\E';
357           break;
358         case 'F':
359           *q++ = ')';
360           break;
361         case 'G':
362           *q++ = '>';
363           break;
364         case 'H':
365           *q++ = '\b';
366           break;
367         case 'L':
368           *q++ = '<';
369           break;
370         case 'N':
371           status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
372           if (status) {
373             *q++ = curdir[0];
374           }
375           break;
376         case 'P':
377           status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
378           if (status) {
379             strcatW (q, curdir);
380             while (*q) q++;
381           }
382           break;
383         case 'Q':
384           *q++ = '=';
385           break;
386         case 'S':
387           *q++ = ' ';
388           break;
389         case 'T':
390           GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
391           while (*q) q++;
392           break;
393         case 'V':
394           strcatW (q, version_string);
395           while (*q) q++;
396           break;
397         case '_':
398           *q++ = '\n';
399           break;
400         case '+':
401           if (pushd_directories) {
402             memset(q, '+', pushd_directories->u.stackdepth);
403             q = q + pushd_directories->u.stackdepth;
404           }
405           break;
406       }
407       p++;
408       *q = '\0';
409     }
410   }
411   WCMD_output_asis (out_string);
412 }
413
414
415 /*************************************************************************
416  * WCMD_strdupW
417  *    A wide version of strdup as its missing from unicode.h
418  */
419 WCHAR *WCMD_strdupW(WCHAR *input) {
420    int len=strlenW(input)+1;
421    WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
422    memcpy(result, input, len * sizeof(WCHAR));
423    return result;
424 }
425
426 /*************************************************************************
427  * WCMD_strsubstW
428  *    Replaces a portion of a Unicode string with the specified string.
429  *    It's up to the caller to ensure there is enough space in the
430  *    destination buffer.
431  */
432 void WCMD_strsubstW(WCHAR* start, WCHAR* next, WCHAR* insert, int len) {
433
434    if (len < 0)
435       len=insert ? lstrlenW(insert) : 0;
436    if (start+len != next)
437        memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
438    if (insert)
439        memcpy(start, insert, len * sizeof(*insert));
440 }
441
442 /***************************************************************************
443  * WCMD_strtrim_leading_spaces
444  *
445  *      Remove leading spaces from a string. Return a pointer to the first
446  *      non-space character. Does not modify the input string
447  */
448 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
449
450   WCHAR *ptr;
451
452   ptr = string;
453   while (*ptr == ' ') ptr++;
454   return ptr;
455 }
456
457 /*************************************************************************
458  * WCMD_opt_s_strip_quotes
459  *
460  *      Remove first and last quote WCHARacters, preserving all other text
461  */
462 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
463   WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
464   while((*dest=*src) != '\0') {
465       if (*src=='\"')
466           lastq=dest;
467       dest++, src++;
468   }
469   if (lastq) {
470       dest=lastq++;
471       while ((*dest++=*lastq++) != 0)
472           ;
473   }
474 }
475
476
477 /*************************************************************************
478  * WCMD_expand_envvar
479  *
480  *      Expands environment variables, allowing for WCHARacter substitution
481  */
482 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
483     WCHAR *endOfVar = NULL, *s;
484     WCHAR *colonpos = NULL;
485     WCHAR thisVar[MAXSTRING];
486     WCHAR thisVarContents[MAXSTRING];
487     WCHAR savedchar = 0x00;
488     int len;
489
490     static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
491     static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
492     static const WCHAR Date[]      = {'D','A','T','E','\0'};
493     static const WCHAR DateP[]     = {'%','D','A','T','E','%','\0'};
494     static const WCHAR Time[]      = {'T','I','M','E','\0'};
495     static const WCHAR TimeP[]     = {'%','T','I','M','E','%','\0'};
496     static const WCHAR Cd[]        = {'C','D','\0'};
497     static const WCHAR CdP[]       = {'%','C','D','%','\0'};
498     static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
499     static const WCHAR RandomP[]   = {'%','R','A','N','D','O','M','%','\0'};
500     static const WCHAR Delims[]    = {'%',' ',':','\0'};
501
502     WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
503                wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
504
505     /* Find the end of the environment variable, and extract name */
506     endOfVar = strpbrkW(start+1, Delims);
507
508     if (endOfVar == NULL || *endOfVar==' ') {
509
510       /* In batch program, missing terminator for % and no following
511          ':' just removes the '%'                                   */
512       if (context) {
513         WCMD_strsubstW(start, start + 1, NULL, 0);
514         return start;
515       } else {
516
517         /* In command processing, just ignore it - allows command line
518            syntax like: for %i in (a.a) do echo %i                     */
519         return start+1;
520       }
521     }
522
523     /* If ':' found, process remaining up until '%' (or stop at ':' if
524        a missing '%' */
525     if (*endOfVar==':') {
526         WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
527         if (endOfVar2 != NULL) endOfVar = endOfVar2;
528     }
529
530     memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
531     thisVar[(endOfVar - start)+1] = 0x00;
532     colonpos = strchrW(thisVar+1, ':');
533
534     /* If there's complex substitution, just need %var% for now
535        to get the expanded data to play with                    */
536     if (colonpos) {
537         *colonpos = '%';
538         savedchar = *(colonpos+1);
539         *(colonpos+1) = 0x00;
540     }
541
542     WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
543
544     /* Expand to contents, if unchanged, return */
545     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
546     /* override if existing env var called that name              */
547     if ((CompareStringW(LOCALE_USER_DEFAULT,
548                         NORM_IGNORECASE | SORT_STRINGSORT,
549                         thisVar, 12, ErrorLvlP, -1) == 2) &&
550                 (GetEnvironmentVariableW(ErrorLvl, thisVarContents, 1) == 0) &&
551                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
552       static const WCHAR fmt[] = {'%','d','\0'};
553       wsprintfW(thisVarContents, fmt, errorlevel);
554       len = strlenW(thisVarContents);
555
556     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
557                                NORM_IGNORECASE | SORT_STRINGSORT,
558                                thisVar, 6, DateP, -1) == 2) &&
559                 (GetEnvironmentVariableW(Date, thisVarContents, 1) == 0) &&
560                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
561
562       GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
563                     NULL, thisVarContents, MAXSTRING);
564       len = strlenW(thisVarContents);
565
566     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
567                                NORM_IGNORECASE | SORT_STRINGSORT,
568                                thisVar, 6, TimeP, -1) == 2) &&
569                 (GetEnvironmentVariableW(Time, thisVarContents, 1) == 0) &&
570                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
571       GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
572                         NULL, thisVarContents, MAXSTRING);
573       len = strlenW(thisVarContents);
574
575     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
576                                NORM_IGNORECASE | SORT_STRINGSORT,
577                                thisVar, 4, CdP, -1) == 2) &&
578                 (GetEnvironmentVariableW(Cd, thisVarContents, 1) == 0) &&
579                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
580       GetCurrentDirectoryW(MAXSTRING, thisVarContents);
581       len = strlenW(thisVarContents);
582
583     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
584                                NORM_IGNORECASE | SORT_STRINGSORT,
585                                thisVar, 8, RandomP, -1) == 2) &&
586                 (GetEnvironmentVariableW(Random, thisVarContents, 1) == 0) &&
587                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
588       static const WCHAR fmt[] = {'%','d','\0'};
589       wsprintfW(thisVarContents, fmt, rand() % 32768);
590       len = strlenW(thisVarContents);
591
592     /* Look for a matching 'for' variable */
593     } else if (forVar &&
594                (CompareStringW(LOCALE_USER_DEFAULT,
595                                SORT_STRINGSORT,
596                                thisVar,
597                                (colonpos - thisVar) - 1,
598                                forVar, -1) == 2)) {
599       strcpyW(thisVarContents, forVal);
600       len = strlenW(thisVarContents);
601
602     } else {
603
604       len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
605                                sizeof(thisVarContents)/sizeof(WCHAR));
606     }
607
608     if (len == 0)
609       return endOfVar+1;
610
611     /* In a batch program, unknown env vars are replaced with nothing,
612          note syntax %garbage:1,3% results in anything after the ':'
613          except the %
614        From the command line, you just get back what you entered      */
615     if (lstrcmpiW(thisVar, thisVarContents) == 0) {
616
617       /* Restore the complex part after the compare */
618       if (colonpos) {
619         *colonpos = ':';
620         *(colonpos+1) = savedchar;
621       }
622
623       /* Command line - just ignore this */
624       if (context == NULL) return endOfVar+1;
625
626
627       /* Batch - replace unknown env var with nothing */
628       if (colonpos == NULL) {
629         WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
630       } else {
631         len = strlenW(thisVar);
632         thisVar[len-1] = 0x00;
633         /* If %:...% supplied, : is retained */
634         if (colonpos == thisVar+1) {
635           WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
636         } else {
637           WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
638         }
639       }
640       return start;
641
642     }
643
644     /* See if we need to do complex substitution (any ':'s), if not
645        then our work here is done                                  */
646     if (colonpos == NULL) {
647       WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
648       return start;
649     }
650
651     /* Restore complex bit */
652     *colonpos = ':';
653     *(colonpos+1) = savedchar;
654
655     /*
656         Handle complex substitutions:
657            xxx=yyy    (replace xxx with yyy)
658            *xxx=yyy   (replace up to and including xxx with yyy)
659            ~x         (from x WCHARs in)
660            ~-x        (from x WCHARs from the end)
661            ~x,y       (from x WCHARs in for y WCHARacters)
662            ~x,-y      (from x WCHARs in until y WCHARacters from the end)
663      */
664
665     /* ~ is substring manipulation */
666     if (savedchar == '~') {
667
668       int   substrposition, substrlength = 0;
669       WCHAR *commapos = strchrW(colonpos+2, ',');
670       WCHAR *startCopy;
671
672       substrposition = atolW(colonpos+2);
673       if (commapos) substrlength = atolW(commapos+1);
674
675       /* Check bounds */
676       if (substrposition >= 0) {
677         startCopy = &thisVarContents[min(substrposition, len)];
678       } else {
679         startCopy = &thisVarContents[max(0, len+substrposition-1)];
680       }
681
682       if (commapos == NULL) {
683         /* Copy the lot */
684         WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
685       } else if (substrlength < 0) {
686
687         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
688         if (copybytes > len) copybytes = len;
689         else if (copybytes < 0) copybytes = 0;
690         WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
691       } else {
692         WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
693       }
694
695       return start;
696
697     /* search and replace manipulation */
698     } else {
699       WCHAR *equalspos = strstrW(colonpos, equalsW);
700       WCHAR *replacewith = equalspos+1;
701       WCHAR *found       = NULL;
702       WCHAR *searchIn;
703       WCHAR *searchFor;
704
705       if (equalspos == NULL) return start+1;
706       s = WCMD_strdupW(endOfVar + 1);
707
708       /* Null terminate both strings */
709       thisVar[strlenW(thisVar)-1] = 0x00;
710       *equalspos = 0x00;
711
712       /* Since we need to be case insensitive, copy the 2 buffers */
713       searchIn  = WCMD_strdupW(thisVarContents);
714       CharUpperBuffW(searchIn, strlenW(thisVarContents));
715       searchFor = WCMD_strdupW(colonpos+1);
716       CharUpperBuffW(searchFor, strlenW(colonpos+1));
717
718       /* Handle wildcard case */
719       if (*(colonpos+1) == '*') {
720         /* Search for string to replace */
721         found = strstrW(searchIn, searchFor+1);
722
723         if (found) {
724           /* Do replacement */
725           strcpyW(start, replacewith);
726           strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
727           strcatW(start, s);
728         } else {
729           /* Copy as is */
730           strcpyW(start, thisVarContents);
731           strcatW(start, s);
732         }
733
734       } else {
735         /* Loop replacing all instances */
736         WCHAR *lastFound = searchIn;
737         WCHAR *outputposn = start;
738
739         *start = 0x00;
740         while ((found = strstrW(lastFound, searchFor))) {
741             lstrcpynW(outputposn,
742                     thisVarContents + (lastFound-searchIn),
743                     (found - lastFound)+1);
744             outputposn  = outputposn + (found - lastFound);
745             strcatW(outputposn, replacewith);
746             outputposn = outputposn + strlenW(replacewith);
747             lastFound = found + strlenW(searchFor);
748         }
749         strcatW(outputposn,
750                 thisVarContents + (lastFound-searchIn));
751         strcatW(outputposn, s);
752       }
753       HeapFree(GetProcessHeap(), 0, s);
754       HeapFree(GetProcessHeap(), 0, searchIn);
755       HeapFree(GetProcessHeap(), 0, searchFor);
756       return start;
757     }
758     return start+1;
759 }
760
761 /*****************************************************************************
762  * Expand the command. Native expands lines from batch programs as they are
763  * read in and not again, except for 'for' variable substitution.
764  * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
765  */
766 static void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
767
768   /* For commands in a context (batch program):                  */
769   /*   Expand environment variables in a batch file %{0-9} first */
770   /*     including support for any ~ modifiers                   */
771   /* Additionally:                                               */
772   /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
773   /*     names allowing environment variable overrides           */
774   /* NOTE: To support the %PATH:xxx% syntax, also perform        */
775   /*   manual expansion of environment variables here            */
776
777   WCHAR *p = cmd;
778   WCHAR *t;
779   int   i;
780
781   while ((p = strchrW(p, '%'))) {
782
783     WINE_TRACE("Translate command:%s %d (at: %s)\n",
784                    wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
785     i = *(p+1) - '0';
786
787     /* Don't touch %% unless its in Batch */
788     if (!justFors && *(p+1) == '%') {
789       if (context) {
790         WCMD_strsubstW(p, p+1, NULL, 0);
791       }
792       p+=1;
793
794     /* Replace %~ modifications if in batch program */
795     } else if (*(p+1) == '~') {
796       WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
797       p++;
798
799     /* Replace use of %0...%9 if in batch program*/
800     } else if (!justFors && context && (i >= 0) && (i <= 9)) {
801       t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
802       WCMD_strsubstW(p, p+2, t, -1);
803
804     /* Replace use of %* if in batch program*/
805     } else if (!justFors && context && *(p+1)=='*') {
806       WCHAR *startOfParms = NULL;
807       t = WCMD_parameter (context -> command, 1, &startOfParms);
808       if (startOfParms != NULL)
809         WCMD_strsubstW(p, p+2, startOfParms, -1);
810       else
811         WCMD_strsubstW(p, p+2, NULL, 0);
812
813     } else if (forVariable &&
814                (CompareStringW(LOCALE_USER_DEFAULT,
815                                SORT_STRINGSORT,
816                                p,
817                                strlenW(forVariable),
818                                forVariable, -1) == 2)) {
819       WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
820
821     } else if (!justFors) {
822       p = WCMD_expand_envvar(p, forVariable, forValue);
823
824     /* In a FOR loop, see if this is the variable to replace */
825     } else { /* Ignore %'s on second pass of batch program */
826       p++;
827     }
828   }
829
830   return;
831 }
832
833
834 /*******************************************************************
835  * WCMD_parse - parse a command into parameters and qualifiers.
836  *
837  *      On exit, all qualifiers are concatenated into q, the first string
838  *      not beginning with "/" is in p1 and the
839  *      second in p2. Any subsequent non-qualifier strings are lost.
840  *      Parameters in quotes are handled.
841  */
842 static void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
843 {
844   int p = 0;
845
846   *q = *p1 = *p2 = '\0';
847   while (TRUE) {
848     switch (*s) {
849       case '/':
850         *q++ = *s++;
851         while ((*s != '\0') && (*s != ' ') && *s != '/') {
852           *q++ = toupperW (*s++);
853         }
854         *q = '\0';
855         break;
856       case ' ':
857       case '\t':
858         s++;
859         break;
860       case '"':
861         s++;
862         while ((*s != '\0') && (*s != '"')) {
863           if (p == 0) *p1++ = *s++;
864           else if (p == 1) *p2++ = *s++;
865           else s++;
866         }
867         if (p == 0) *p1 = '\0';
868         if (p == 1) *p2 = '\0';
869         p++;
870         if (*s == '"') s++;
871         break;
872       case '\0':
873         return;
874       default:
875         while ((*s != '\0') && (*s != ' ') && (*s != '\t')
876                && (*s != '=')  && (*s != ',') ) {
877           if (p == 0) *p1++ = *s++;
878           else if (p == 1) *p2++ = *s++;
879           else s++;
880         }
881         /* Skip concurrent parms */
882         while ((*s == ' ') || (*s == '\t') || (*s == '=')  || (*s == ',') ) s++;
883
884         if (p == 0) *p1 = '\0';
885         if (p == 1) *p2 = '\0';
886         p++;
887     }
888   }
889 }
890
891 static void init_msvcrt_io_block(STARTUPINFOW* st)
892 {
893     STARTUPINFOW st_p;
894     /* fetch the parent MSVCRT info block if any, so that the child can use the
895      * same handles as its grand-father
896      */
897     st_p.cb = sizeof(STARTUPINFOW);
898     GetStartupInfoW(&st_p);
899     st->cbReserved2 = st_p.cbReserved2;
900     st->lpReserved2 = st_p.lpReserved2;
901     if (st_p.cbReserved2 && st_p.lpReserved2)
902     {
903         /* Override the entries for fd 0,1,2 if we happened
904          * to change those std handles (this depends on the way wcmd sets
905          * it's new input & output handles)
906          */
907         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
908         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
909         if (ptr)
910         {
911             unsigned num = *(unsigned*)st_p.lpReserved2;
912             char* flags = (char*)(ptr + sizeof(unsigned));
913             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
914
915             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
916             st->cbReserved2 = sz;
917             st->lpReserved2 = ptr;
918
919 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
920             if (num <= 0 || (flags[0] & WX_OPEN))
921             {
922                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
923                 flags[0] |= WX_OPEN;
924             }
925             if (num <= 1 || (flags[1] & WX_OPEN))
926             {
927                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
928                 flags[1] |= WX_OPEN;
929             }
930             if (num <= 2 || (flags[2] & WX_OPEN))
931             {
932                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
933                 flags[2] |= WX_OPEN;
934             }
935 #undef WX_OPEN
936         }
937     }
938 }
939
940 /******************************************************************************
941  * WCMD_run_program
942  *
943  *      Execute a command line as an external program. Must allow recursion.
944  *
945  *      Precedence:
946  *        Manual testing under windows shows PATHEXT plays a key part in this,
947  *      and the search algorithm and precedence appears to be as follows.
948  *
949  *      Search locations:
950  *        If directory supplied on command, just use that directory
951  *        If extension supplied on command, look for that explicit name first
952  *        Otherwise, search in each directory on the path
953  *      Precedence:
954  *        If extension supplied on command, look for that explicit name first
955  *        Then look for supplied name .* (even if extension supplied, so
956  *          'garbage.exe' will match 'garbage.exe.cmd')
957  *        If any found, cycle through PATHEXT looking for name.exe one by one
958  *      Launching
959  *        Once a match has been found, it is launched - Code currently uses
960  *          findexecutable to achieve this which is left untouched.
961  */
962
963 void WCMD_run_program (WCHAR *command, int called) {
964
965   WCHAR  temp[MAX_PATH];
966   WCHAR  pathtosearch[MAXSTRING];
967   WCHAR *pathposn;
968   WCHAR  stemofsearch[MAX_PATH];    /* maximum allowed executable name is
969                                        MAX_PATH, including null character */
970   WCHAR *lastSlash;
971   WCHAR  pathext[MAXSTRING];
972   BOOL  extensionsupplied = FALSE;
973   BOOL  launched = FALSE;
974   BOOL  status;
975   BOOL  assumeInternal = FALSE;
976   DWORD len;
977   static const WCHAR envPath[] = {'P','A','T','H','\0'};
978   static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
979   static const WCHAR delims[] = {'/','\\',':','\0'};
980
981   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
982   if (!(*param1) && !(*param2))
983     return;
984
985   /* Calculate the search path and stem to search for */
986   if (strpbrkW (param1, delims) == NULL) {  /* No explicit path given, search path */
987     static const WCHAR curDir[] = {'.',';','\0'};
988     strcpyW(pathtosearch, curDir);
989     len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
990     if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
991       static const WCHAR curDir[] = {'.','\0'};
992       strcpyW (pathtosearch, curDir);
993     }
994     if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
995     if (strlenW(param1) >= MAX_PATH)
996     {
997         WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
998         return;
999     }
1000
1001     strcpyW(stemofsearch, param1);
1002
1003   } else {
1004
1005     /* Convert eg. ..\fred to include a directory by removing file part */
1006     GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1007     lastSlash = strrchrW(pathtosearch, '\\');
1008     if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1009     strcpyW(stemofsearch, lastSlash+1);
1010
1011     /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1012        c:\windows\a.bat syntax                                                 */
1013     if (lastSlash) *(lastSlash + 1) = 0x00;
1014   }
1015
1016   /* Now extract PATHEXT */
1017   len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1018   if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1019     static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1020                                         '.','c','o','m',';',
1021                                         '.','c','m','d',';',
1022                                         '.','e','x','e','\0'};
1023     strcpyW (pathext, dfltPathExt);
1024   }
1025
1026   /* Loop through the search path, dir by dir */
1027   pathposn = pathtosearch;
1028   WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1029              wine_dbgstr_w(stemofsearch));
1030   while (!launched && pathposn) {
1031
1032     WCHAR  thisDir[MAX_PATH] = {'\0'};
1033     WCHAR *pos               = NULL;
1034     BOOL  found             = FALSE;
1035     const WCHAR slashW[] = {'\\','\0'};
1036
1037     /* Work on the first directory on the search path */
1038     pos = strchrW(pathposn, ';');
1039     if (pos) {
1040       memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1041       thisDir[(pos-pathposn)] = 0x00;
1042       pathposn = pos+1;
1043
1044     } else {
1045       strcpyW(thisDir, pathposn);
1046       pathposn = NULL;
1047     }
1048
1049     /* Since you can have eg. ..\.. on the path, need to expand
1050        to full information                                      */
1051     strcpyW(temp, thisDir);
1052     GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1053
1054     /* 1. If extension supplied, see if that file exists */
1055     strcatW(thisDir, slashW);
1056     strcatW(thisDir, stemofsearch);
1057     pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1058
1059     /* 1. If extension supplied, see if that file exists */
1060     if (extensionsupplied) {
1061       if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1062         found = TRUE;
1063       }
1064     }
1065
1066     /* 2. Any .* matches? */
1067     if (!found) {
1068       HANDLE          h;
1069       WIN32_FIND_DATAW finddata;
1070       static const WCHAR allFiles[] = {'.','*','\0'};
1071
1072       strcatW(thisDir,allFiles);
1073       h = FindFirstFileW(thisDir, &finddata);
1074       FindClose(h);
1075       if (h != INVALID_HANDLE_VALUE) {
1076
1077         WCHAR *thisExt = pathext;
1078
1079         /* 3. Yes - Try each path ext */
1080         while (thisExt) {
1081           WCHAR *nextExt = strchrW(thisExt, ';');
1082
1083           if (nextExt) {
1084             memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1085             pos[(nextExt-thisExt)] = 0x00;
1086             thisExt = nextExt+1;
1087           } else {
1088             strcpyW(pos, thisExt);
1089             thisExt = NULL;
1090           }
1091
1092           if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1093             found = TRUE;
1094             thisExt = NULL;
1095           }
1096         }
1097       }
1098     }
1099
1100    /* Internal programs won't be picked up by this search, so even
1101       though not found, try one last createprocess and wait for it
1102       to complete.
1103       Note: Ideally we could tell between a console app (wait) and a
1104       windows app, but the API's for it fail in this case           */
1105     if (!found && pathposn == NULL) {
1106         WINE_TRACE("ASSUMING INTERNAL\n");
1107         assumeInternal = TRUE;
1108     } else {
1109         WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1110     }
1111
1112     /* Once found, launch it */
1113     if (found || assumeInternal) {
1114       STARTUPINFOW st;
1115       PROCESS_INFORMATION pe;
1116       SHFILEINFOW psfi;
1117       DWORD console;
1118       HINSTANCE hinst;
1119       WCHAR *ext = strrchrW( thisDir, '.' );
1120       static const WCHAR batExt[] = {'.','b','a','t','\0'};
1121       static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1122
1123       launched = TRUE;
1124
1125       /* Special case BAT and CMD */
1126       if (ext && !strcmpiW(ext, batExt)) {
1127         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1128         return;
1129       } else if (ext && !strcmpiW(ext, cmdExt)) {
1130         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1131         return;
1132       } else {
1133
1134         /* thisDir contains the file to be launched, but with what?
1135            eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1136         hinst = FindExecutableW (thisDir, NULL, temp);
1137         if ((INT_PTR)hinst < 32)
1138           console = 0;
1139         else
1140           console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1141
1142         ZeroMemory (&st, sizeof(STARTUPINFOW));
1143         st.cb = sizeof(STARTUPINFOW);
1144         init_msvcrt_io_block(&st);
1145
1146         /* Launch the process and if a CUI wait on it to complete
1147            Note: Launching internal wine processes cannot specify a full path to exe */
1148         status = CreateProcessW(assumeInternal?NULL : thisDir,
1149                                 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1150         if ((opt_c || opt_k) && !opt_s && !status
1151             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1152           /* strip first and last quote WCHARacters and try again */
1153           WCMD_opt_s_strip_quotes(command);
1154           opt_s=1;
1155           WCMD_run_program(command, called);
1156           return;
1157         }
1158
1159         if (!status)
1160           break;
1161
1162         if (!assumeInternal && !console) errorlevel = 0;
1163         else
1164         {
1165             /* Always wait when called in a batch program context */
1166             if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1167             GetExitCodeProcess (pe.hProcess, &errorlevel);
1168             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1169         }
1170         CloseHandle(pe.hProcess);
1171         CloseHandle(pe.hThread);
1172         return;
1173       }
1174     }
1175   }
1176
1177   /* Not found anywhere - give up */
1178   SetLastError(ERROR_FILE_NOT_FOUND);
1179   WCMD_print_error ();
1180
1181   /* If a command fails to launch, it sets errorlevel 9009 - which
1182      does not seem to have any associated constant definition     */
1183   errorlevel = 9009;
1184   return;
1185
1186 }
1187
1188 /*****************************************************************************
1189  * Process one command. If the command is EXIT this routine does not return.
1190  * We will recurse through here executing batch files.
1191  */
1192 void WCMD_execute (WCHAR *command, WCHAR *redirects,
1193                    WCHAR *forVariable, WCHAR *forValue,
1194                    CMD_LIST **cmdList)
1195 {
1196     WCHAR *cmd, *p, *redir;
1197     int status, i;
1198     DWORD count, creationDisposition;
1199     HANDLE h;
1200     WCHAR *whichcmd;
1201     SECURITY_ATTRIBUTES sa;
1202     WCHAR *new_cmd = NULL;
1203     WCHAR *new_redir = NULL;
1204     HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1205                                 GetStdHandle (STD_OUTPUT_HANDLE),
1206                                 GetStdHandle (STD_ERROR_HANDLE)};
1207     DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
1208                                 STD_OUTPUT_HANDLE,
1209                                 STD_ERROR_HANDLE};
1210     BOOL piped = FALSE;
1211
1212     WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1213                wine_dbgstr_w(command), cmdList,
1214                wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1215
1216     /* If the next command is a pipe then we implement pipes by redirecting
1217        the output from this command to a temp file and input into the
1218        next command from that temp file.
1219        FIXME: Use of named pipes would make more sense here as currently this
1220        process has to finish before the next one can start but this requires
1221        a change to not wait for the first app to finish but rather the pipe  */
1222     if (cmdList && (*cmdList)->nextcommand &&
1223         (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1224
1225         WCHAR temp_path[MAX_PATH];
1226         static const WCHAR cmdW[]     = {'C','M','D','\0'};
1227
1228         /* Remember piping is in action */
1229         WINE_TRACE("Output needs to be piped\n");
1230         piped = TRUE;
1231
1232         /* Generate a unique temporary filename */
1233         GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1234         GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1235         WINE_TRACE("Using temporary file of %s\n",
1236                    wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1237     }
1238
1239     /* Move copy of the command onto the heap so it can be expanded */
1240     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1241     if (!new_cmd)
1242     {
1243         WINE_ERR("Could not allocate memory for new_cmd\n");
1244         return;
1245     }
1246     strcpyW(new_cmd, command);
1247
1248     /* Move copy of the redirects onto the heap so it can be expanded */
1249     new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1250     if (!new_redir)
1251     {
1252         WINE_ERR("Could not allocate memory for new_redir\n");
1253         HeapFree( GetProcessHeap(), 0, new_cmd );
1254         return;
1255     }
1256
1257     /* If piped output, send stdout to the pipe by appending >filename to redirects */
1258     if (piped) {
1259         static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1260         wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1261         WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1262     } else {
1263         strcpyW(new_redir, redirects);
1264     }
1265
1266     /* Expand variables in command line mode only (batch mode will
1267        be expanded as the line is read in, except for 'for' loops) */
1268     handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1269     handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1270     cmd = new_cmd;
1271
1272 /*
1273  *      Changing default drive has to be handled as a special case.
1274  */
1275
1276     if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1277       WCHAR envvar[5];
1278       WCHAR dir[MAX_PATH];
1279
1280       /* According to MSDN CreateProcess docs, special env vars record
1281          the current directory on each drive, in the form =C:
1282          so see if one specified, and if so go back to it             */
1283       strcpyW(envvar, equalsW);
1284       strcatW(envvar, cmd);
1285       if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1286         static const WCHAR fmt[] = {'%','s','\\','\0'};
1287         wsprintfW(cmd, fmt, cmd);
1288         WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1289       }
1290       WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1291       status = SetCurrentDirectoryW(cmd);
1292       if (!status) WCMD_print_error ();
1293       HeapFree( GetProcessHeap(), 0, cmd );
1294       HeapFree( GetProcessHeap(), 0, new_redir );
1295       return;
1296     }
1297
1298     sa.nLength = sizeof(sa);
1299     sa.lpSecurityDescriptor = NULL;
1300     sa.bInheritHandle = TRUE;
1301
1302 /*
1303  *      Redirect stdin, stdout and/or stderr if required.
1304  */
1305
1306     /* STDIN could come from a preceding pipe, so delete on close if it does */
1307     if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1308         WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1309         h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1310                   FILE_SHARE_READ, &sa, OPEN_EXISTING,
1311                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1312         if (h == INVALID_HANDLE_VALUE) {
1313           WCMD_print_error ();
1314           HeapFree( GetProcessHeap(), 0, cmd );
1315           HeapFree( GetProcessHeap(), 0, new_redir );
1316           return;
1317         }
1318         SetStdHandle (STD_INPUT_HANDLE, h);
1319
1320         /* No need to remember the temporary name any longer once opened */
1321         (*cmdList)->pipeFile[0] = 0x00;
1322
1323     /* Otherwise STDIN could come from a '<' redirect */
1324     } else if ((p = strchrW(new_redir,'<')) != NULL) {
1325       h = CreateFileW(WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
1326                 FILE_ATTRIBUTE_NORMAL, NULL);
1327       if (h == INVALID_HANDLE_VALUE) {
1328         WCMD_print_error ();
1329         HeapFree( GetProcessHeap(), 0, cmd );
1330         HeapFree( GetProcessHeap(), 0, new_redir );
1331         return;
1332       }
1333       SetStdHandle (STD_INPUT_HANDLE, h);
1334     }
1335
1336     /* Scan the whole command looking for > and 2> */
1337     redir = new_redir;
1338     while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1339       int handle = 0;
1340
1341       if (*(p-1)!='2') {
1342         handle = 1;
1343       } else {
1344         handle = 2;
1345       }
1346
1347       p++;
1348       if ('>' == *p) {
1349         creationDisposition = OPEN_ALWAYS;
1350         p++;
1351       }
1352       else {
1353         creationDisposition = CREATE_ALWAYS;
1354       }
1355
1356       /* Add support for 2>&1 */
1357       redir = p;
1358       if (*p == '&') {
1359         int idx = *(p+1) - '0';
1360
1361         if (DuplicateHandle(GetCurrentProcess(),
1362                         GetStdHandle(idx_stdhandles[idx]),
1363                         GetCurrentProcess(),
1364                         &h,
1365                         0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1366           WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1367         }
1368         WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1369
1370       } else {
1371         WCHAR *param = WCMD_parameter (p, 0, NULL);
1372         h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1373                         FILE_ATTRIBUTE_NORMAL, NULL);
1374         if (h == INVALID_HANDLE_VALUE) {
1375           WCMD_print_error ();
1376           HeapFree( GetProcessHeap(), 0, cmd );
1377           HeapFree( GetProcessHeap(), 0, new_redir );
1378           return;
1379         }
1380         if (SetFilePointer (h, 0, NULL, FILE_END) ==
1381               INVALID_SET_FILE_POINTER) {
1382           WCMD_print_error ();
1383         }
1384         WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1385       }
1386
1387       SetStdHandle (idx_stdhandles[handle], h);
1388     }
1389
1390 /*
1391  * Strip leading whitespaces, and a '@' if supplied
1392  */
1393     whichcmd = WCMD_strtrim_leading_spaces(cmd);
1394     WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1395     if (whichcmd[0] == '@') whichcmd++;
1396
1397 /*
1398  *      Check if the command entered is internal. If it is, pass the rest of the
1399  *      line down to the command. If not try to run a program.
1400  */
1401
1402     count = 0;
1403     while (IsCharAlphaNumericW(whichcmd[count])) {
1404       count++;
1405     }
1406     for (i=0; i<=WCMD_EXIT; i++) {
1407       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1408         whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1409     }
1410     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
1411     WCMD_parse (p, quals, param1, param2);
1412     WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1413
1414     if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1415       /* this is a help request for a builtin program */
1416       i = WCMD_HELP;
1417       memcpy(p, whichcmd, count * sizeof(WCHAR));
1418       p[count] = '\0';
1419
1420     }
1421
1422     switch (i) {
1423
1424       case WCMD_ATTRIB:
1425         WCMD_setshow_attrib ();
1426         break;
1427       case WCMD_CALL:
1428         WCMD_call (p);
1429         break;
1430       case WCMD_CD:
1431       case WCMD_CHDIR:
1432         WCMD_setshow_default (p);
1433         break;
1434       case WCMD_CLS:
1435         WCMD_clear_screen ();
1436         break;
1437       case WCMD_COPY:
1438         WCMD_copy ();
1439         break;
1440       case WCMD_CTTY:
1441         WCMD_change_tty ();
1442         break;
1443       case WCMD_DATE:
1444         WCMD_setshow_date ();
1445         break;
1446       case WCMD_DEL:
1447       case WCMD_ERASE:
1448         WCMD_delete (p, TRUE);
1449         break;
1450       case WCMD_DIR:
1451         WCMD_directory (p);
1452         break;
1453       case WCMD_ECHO:
1454         WCMD_echo(&whichcmd[count]);
1455         break;
1456       case WCMD_FOR:
1457         WCMD_for (p, cmdList);
1458         break;
1459       case WCMD_GOTO:
1460         WCMD_goto (cmdList);
1461         break;
1462       case WCMD_HELP:
1463         WCMD_give_help (p);
1464         break;
1465       case WCMD_IF:
1466         WCMD_if (p, cmdList);
1467         break;
1468       case WCMD_LABEL:
1469         WCMD_volume (1, p);
1470         break;
1471       case WCMD_MD:
1472       case WCMD_MKDIR:
1473         WCMD_create_dir ();
1474         break;
1475       case WCMD_MOVE:
1476         WCMD_move ();
1477         break;
1478       case WCMD_PATH:
1479         WCMD_setshow_path (p);
1480         break;
1481       case WCMD_PAUSE:
1482         WCMD_pause ();
1483         break;
1484       case WCMD_PROMPT:
1485         WCMD_setshow_prompt ();
1486         break;
1487       case WCMD_REM:
1488         break;
1489       case WCMD_REN:
1490       case WCMD_RENAME:
1491         WCMD_rename ();
1492         break;
1493       case WCMD_RD:
1494       case WCMD_RMDIR:
1495         WCMD_remove_dir (p);
1496         break;
1497       case WCMD_SETLOCAL:
1498         WCMD_setlocal(p);
1499         break;
1500       case WCMD_ENDLOCAL:
1501         WCMD_endlocal();
1502         break;
1503       case WCMD_SET:
1504         WCMD_setshow_env (p);
1505         break;
1506       case WCMD_SHIFT:
1507         WCMD_shift (p);
1508         break;
1509       case WCMD_TIME:
1510         WCMD_setshow_time ();
1511         break;
1512       case WCMD_TITLE:
1513         if (strlenW(&whichcmd[count]) > 0)
1514           WCMD_title(&whichcmd[count+1]);
1515         break;
1516       case WCMD_TYPE:
1517         WCMD_type (p);
1518         break;
1519       case WCMD_VER:
1520         WCMD_version ();
1521         break;
1522       case WCMD_VERIFY:
1523         WCMD_verify (p);
1524         break;
1525       case WCMD_VOL:
1526         WCMD_volume (0, p);
1527         break;
1528       case WCMD_PUSHD:
1529         WCMD_pushd(p);
1530         break;
1531       case WCMD_POPD:
1532         WCMD_popd();
1533         break;
1534       case WCMD_ASSOC:
1535         WCMD_assoc(p, TRUE);
1536         break;
1537       case WCMD_COLOR:
1538         WCMD_color();
1539         break;
1540       case WCMD_FTYPE:
1541         WCMD_assoc(p, FALSE);
1542         break;
1543       case WCMD_MORE:
1544         WCMD_more(p);
1545         break;
1546       case WCMD_EXIT:
1547         WCMD_exit (cmdList);
1548         break;
1549       default:
1550         WCMD_run_program (whichcmd, 0);
1551     }
1552     HeapFree( GetProcessHeap(), 0, cmd );
1553     HeapFree( GetProcessHeap(), 0, new_redir );
1554
1555     /* Restore old handles */
1556     for (i=0; i<3; i++) {
1557       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1558         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1559         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1560       }
1561     }
1562 }
1563 /*************************************************************************
1564  * WCMD_LoadMessage
1565  *    Load a string from the resource file, handling any error
1566  *    Returns string retrieved from resource file
1567  */
1568 WCHAR *WCMD_LoadMessage(UINT id) {
1569     static WCHAR msg[2048];
1570     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1571
1572     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1573        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1574        strcpyW(msg, failedMsg);
1575     }
1576     return msg;
1577 }
1578
1579 /***************************************************************************
1580  * WCMD_DumpCommands
1581  *
1582  *      Dumps out the parsed command line to ensure syntax is correct
1583  */
1584 static void WCMD_DumpCommands(CMD_LIST *commands) {
1585     CMD_LIST *thisCmd = commands;
1586
1587     WINE_TRACE("Parsed line:\n");
1588     while (thisCmd != NULL) {
1589       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1590                thisCmd,
1591                thisCmd->prevDelim,
1592                thisCmd->bracketDepth,
1593                thisCmd->nextcommand,
1594                wine_dbgstr_w(thisCmd->command),
1595                wine_dbgstr_w(thisCmd->redirects));
1596       thisCmd = thisCmd->nextcommand;
1597     }
1598 }
1599
1600 /***************************************************************************
1601  * WCMD_addCommand
1602  *
1603  *   Adds a command to the current command list
1604  */
1605 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1606                      WCHAR *redirs,  int *redirLen,
1607                      WCHAR **copyTo, int **copyToLen,
1608                      CMD_DELIMITERS prevDelim, int curDepth,
1609                      CMD_LIST **lastEntry, CMD_LIST **output) {
1610
1611     CMD_LIST *thisEntry = NULL;
1612
1613     /* Allocate storage for command */
1614     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1615
1616     /* Copy in the command */
1617     if (command) {
1618         thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1619                                       (*commandLen+1) * sizeof(WCHAR));
1620         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1621         thisEntry->command[*commandLen] = 0x00;
1622
1623         /* Copy in the redirects */
1624         thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1625                                          (*redirLen+1) * sizeof(WCHAR));
1626         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1627         thisEntry->redirects[*redirLen] = 0x00;
1628         thisEntry->pipeFile[0] = 0x00;
1629
1630         /* Reset the lengths */
1631         *commandLen   = 0;
1632         *redirLen     = 0;
1633         *copyToLen    = commandLen;
1634         *copyTo       = command;
1635
1636     } else {
1637         thisEntry->command = NULL;
1638         thisEntry->redirects = NULL;
1639         thisEntry->pipeFile[0] = 0x00;
1640     }
1641
1642     /* Fill in other fields */
1643     thisEntry->nextcommand = NULL;
1644     thisEntry->prevDelim = prevDelim;
1645     thisEntry->bracketDepth = curDepth;
1646     if (*lastEntry) {
1647         (*lastEntry)->nextcommand = thisEntry;
1648     } else {
1649         *output = thisEntry;
1650     }
1651     *lastEntry = thisEntry;
1652 }
1653
1654
1655 /***************************************************************************
1656  * WCMD_IsEndQuote
1657  *
1658  *   Checks if the quote pointet to is the end-quote.
1659  *
1660  *   Quotes end if:
1661  *
1662  *   1) The current parameter ends at EOL or at the beginning
1663  *      of a redirection or pipe and not in a quote section.
1664  *
1665  *   2) If the next character is a space and not in a quote section.
1666  *
1667  *   Returns TRUE if this is an end quote, and FALSE if it is not.
1668  *
1669  */
1670 static BOOL WCMD_IsEndQuote(WCHAR *quote, int quoteIndex)
1671 {
1672     int quoteCount = quoteIndex;
1673     int i;
1674
1675     /* If we are not in a quoted section, then we are not an end-quote */
1676     if(quoteIndex == 0)
1677     {
1678         return FALSE;
1679     }
1680
1681     /* Check how many quotes are left for this parameter */
1682     for(i=0;quote[i];i++)
1683     {
1684         if(quote[i] == '"')
1685         {
1686             quoteCount++;
1687         }
1688
1689         /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1690         else if(((quoteCount % 2) == 0)
1691             && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1692         {
1693             break;
1694         }
1695     }
1696
1697     /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1698        be an end-quote */
1699     if(quoteIndex >= (quoteCount / 2))
1700     {
1701         return TRUE;
1702     }
1703
1704     /* No cigar */
1705     return FALSE;
1706 }
1707
1708 /***************************************************************************
1709  * WCMD_ReadAndParseLine
1710  *
1711  *   Either uses supplied input or
1712  *     Reads a file from the handle, and then...
1713  *   Parse the text buffer, spliting into separate commands
1714  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1715  *            following an &&
1716  *     - ( as the first character just ups the bracket depth
1717  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1718  *            adds a CMD_LIST structure with null command
1719  *     - Anything else gets put into the command string (including
1720  *            redirects)
1721  */
1722 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1723
1724     WCHAR    *curPos;
1725     int       inQuotes = 0;
1726     WCHAR     curString[MAXSTRING];
1727     int       curStringLen = 0;
1728     WCHAR     curRedirs[MAXSTRING];
1729     int       curRedirsLen = 0;
1730     WCHAR    *curCopyTo;
1731     int      *curLen;
1732     int       curDepth = 0;
1733     CMD_LIST *lastEntry = NULL;
1734     CMD_DELIMITERS prevDelim = CMD_NONE;
1735     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1736     const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1737     const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1738     const WCHAR ifCmd[]  = {'i','f',' ','\0'};
1739     const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1740     BOOL      inRem = FALSE;
1741     BOOL      inFor = FALSE;
1742     BOOL      inIn  = FALSE;
1743     BOOL      inIf  = FALSE;
1744     BOOL      inElse= FALSE;
1745     BOOL      onlyWhiteSpace = FALSE;
1746     BOOL      lastWasWhiteSpace = FALSE;
1747     BOOL      lastWasDo   = FALSE;
1748     BOOL      lastWasIn   = FALSE;
1749     BOOL      lastWasElse = FALSE;
1750     BOOL      lastWasRedirect = TRUE;
1751
1752     /* Allocate working space for a command read from keyboard, file etc */
1753     if (!extraSpace)
1754       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1755     if (!extraSpace)
1756     {
1757         WINE_ERR("Could not allocate memory for extraSpace\n");
1758         return NULL;
1759     }
1760
1761     /* If initial command read in, use that, otherwise get input from handle */
1762     if (optionalcmd != NULL) {
1763         strcpyW(extraSpace, optionalcmd);
1764     } else if (readFrom == INVALID_HANDLE_VALUE) {
1765         WINE_FIXME("No command nor handle supplied\n");
1766     } else {
1767         if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1768     }
1769     curPos = extraSpace;
1770
1771     /* Handle truncated input - issue warning */
1772     if (strlenW(extraSpace) == MAXSTRING -1) {
1773         WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1774         WCMD_output_asis(extraSpace);
1775         WCMD_output_asis(newline);
1776     }
1777
1778     /* Replace env vars if in a batch context */
1779     if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1780     /* Show prompt before batch line IF echo is on and in batch program */
1781     if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1782       WCMD_show_prompt();
1783       WCMD_output_asis(extraSpace);
1784       WCMD_output_asis(newline);
1785     }
1786
1787     /* Start with an empty string, copying to the command string */
1788     curStringLen = 0;
1789     curRedirsLen = 0;
1790     curCopyTo    = curString;
1791     curLen       = &curStringLen;
1792     lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
1793
1794     /* Parse every character on the line being processed */
1795     while (*curPos != 0x00) {
1796
1797       WCHAR thisChar;
1798
1799       /* Debugging AID:
1800       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1801                  lastWasWhiteSpace, onlyWhiteSpace);
1802       */
1803
1804       /* Certain commands need special handling */
1805       if (curStringLen == 0 && curCopyTo == curString) {
1806         const WCHAR forDO[]  = {'d','o',' ','\0'};
1807
1808         /* If command starts with 'rem', ignore any &&, ( etc */
1809         if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1810           curPos, 4, remCmd, -1) == 2) {
1811           inRem = TRUE;
1812
1813         /* If command starts with 'for', handle ('s mid line after IN or DO */
1814         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1815           curPos, 4, forCmd, -1) == 2) {
1816           inFor = TRUE;
1817
1818         /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1819            is only true in the command portion of the IF statement, but this
1820            should suffice for now
1821             FIXME: Silly syntax like "if 1(==1( (
1822                                         echo they equal
1823                                       )" will be parsed wrong */
1824         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1825           curPos, 3, ifCmd, -1) == 2) {
1826           inIf = TRUE;
1827
1828         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1829           curPos, 5, ifElse, -1) == 2) {
1830           inElse = TRUE;
1831           lastWasElse = TRUE;
1832           onlyWhiteSpace = TRUE;
1833           memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1834           (*curLen)+=5;
1835           curPos+=5;
1836           continue;
1837
1838         /* In a for loop, the DO command will follow a close bracket followed by
1839            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1840            is then 0, and all whitespace is skipped                                */
1841         } else if (inFor &&
1842                    (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1843                     curPos, 3, forDO, -1) == 2)) {
1844           WINE_TRACE("Found DO\n");
1845           lastWasDo = TRUE;
1846           onlyWhiteSpace = TRUE;
1847           memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1848           (*curLen)+=3;
1849           curPos+=3;
1850           continue;
1851         }
1852       } else if (curCopyTo == curString) {
1853
1854         /* Special handling for the 'FOR' command */
1855         if (inFor && lastWasWhiteSpace) {
1856           const WCHAR forIN[] = {'i','n',' ','\0'};
1857
1858           WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1859
1860           if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1861               curPos, 3, forIN, -1) == 2) {
1862             WINE_TRACE("Found IN\n");
1863             lastWasIn = TRUE;
1864             onlyWhiteSpace = TRUE;
1865             memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1866             (*curLen)+=3;
1867             curPos+=3;
1868             continue;
1869           }
1870         }
1871       }
1872
1873       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1874          so just use the default processing ie skip character specific
1875          matching below                                                    */
1876       if (!inRem) thisChar = *curPos;
1877       else        thisChar = 'X';  /* Character with no special processing */
1878
1879       lastWasWhiteSpace = FALSE; /* Will be reset below */
1880
1881       switch (thisChar) {
1882
1883       case '=': /* drop through - ignore token delimiters at the start of a command */
1884       case ',': /* drop through - ignore token delimiters at the start of a command */
1885       case '\t':/* drop through - ignore token delimiters at the start of a command */
1886       case ' ':
1887                 /* If a redirect in place, it ends here */
1888                 if (!inQuotes && !lastWasRedirect) {
1889
1890                   /* If finishing off a redirect, add a whitespace delimiter */
1891                   if (curCopyTo == curRedirs) {
1892                       curCopyTo[(*curLen)++] = ' ';
1893                   }
1894                   curCopyTo = curString;
1895                   curLen = &curStringLen;
1896                 }
1897                 if (*curLen > 0) {
1898                   curCopyTo[(*curLen)++] = *curPos;
1899                 }
1900
1901                 /* Remember just processed whitespace */
1902                 lastWasWhiteSpace = TRUE;
1903
1904                 break;
1905
1906       case '>': /* drop through - handle redirect chars the same */
1907       case '<':
1908                 /* Make a redirect start here */
1909                 if (!inQuotes) {
1910                   curCopyTo = curRedirs;
1911                   curLen = &curRedirsLen;
1912                   lastWasRedirect = TRUE;
1913                 }
1914
1915                 /* See if 1>, 2> etc, in which case we have some patching up
1916                    to do                                                     */
1917                 if (curPos != extraSpace &&
1918                     *(curPos-1)>='1' && *(curPos-1)<='9') {
1919
1920                     curStringLen--;
1921                     curString[curStringLen] = 0x00;
1922                     curCopyTo[(*curLen)++] = *(curPos-1);
1923                 }
1924
1925                 curCopyTo[(*curLen)++] = *curPos;
1926
1927                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1928                     do not process that ampersand as an AND operator */
1929                 if (thisChar == '>' && *(curPos+1) == '&') {
1930                     curCopyTo[(*curLen)++] = *(curPos+1);
1931                     curPos++;
1932                 }
1933                 break;
1934
1935       case '|': /* Pipe character only if not || */
1936                 if (!inQuotes) {
1937                   lastWasRedirect = FALSE;
1938
1939                   /* Add an entry to the command list */
1940                   if (curStringLen > 0) {
1941
1942                     /* Add the current command */
1943                     WCMD_addCommand(curString, &curStringLen,
1944                           curRedirs, &curRedirsLen,
1945                           &curCopyTo, &curLen,
1946                           prevDelim, curDepth,
1947                           &lastEntry, output);
1948
1949                   }
1950
1951                   if (*(curPos+1) == '|') {
1952                     curPos++; /* Skip other | */
1953                     prevDelim = CMD_ONFAILURE;
1954                   } else {
1955                     prevDelim = CMD_PIPE;
1956                   }
1957                 } else {
1958                   curCopyTo[(*curLen)++] = *curPos;
1959                 }
1960                 break;
1961
1962       case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1963                     inQuotes--;
1964                 } else {
1965                     inQuotes++; /* Quotes within quotes are fun! */
1966                 }
1967                 curCopyTo[(*curLen)++] = *curPos;
1968                 lastWasRedirect = FALSE;
1969                 break;
1970
1971       case '(': /* If a '(' is the first non whitespace in a command portion
1972                    ie start of line or just after &&, then we read until an
1973                    unquoted ) is found                                       */
1974                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1975                            ", for(%d, In:%d, Do:%d)"
1976                            ", if(%d, else:%d, lwe:%d)\n",
1977                            *curLen, inQuotes,
1978                            onlyWhiteSpace,
1979                            inFor, lastWasIn, lastWasDo,
1980                            inIf, inElse, lastWasElse);
1981                 lastWasRedirect = FALSE;
1982
1983                 /* Ignore open brackets inside the for set */
1984                 if (*curLen == 0 && !inIn) {
1985                   curDepth++;
1986
1987                 /* If in quotes, ignore brackets */
1988                 } else if (inQuotes) {
1989                   curCopyTo[(*curLen)++] = *curPos;
1990
1991                 /* In a FOR loop, an unquoted '(' may occur straight after
1992                       IN or DO
1993                    In an IF statement just handle it regardless as we don't
1994                       parse the operands
1995                    In an ELSE statement, only allow it straight away after
1996                       the ELSE and whitespace
1997                  */
1998                 } else if (inIf ||
1999                            (inElse && lastWasElse && onlyWhiteSpace) ||
2000                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2001
2002                    /* If entering into an 'IN', set inIn */
2003                   if (inFor && lastWasIn && onlyWhiteSpace) {
2004                     WINE_TRACE("Inside an IN\n");
2005                     inIn = TRUE;
2006                   }
2007
2008                   /* Add the current command */
2009                   WCMD_addCommand(curString, &curStringLen,
2010                                   curRedirs, &curRedirsLen,
2011                                   &curCopyTo, &curLen,
2012                                   prevDelim, curDepth,
2013                                   &lastEntry, output);
2014
2015                   curDepth++;
2016                 } else {
2017                   curCopyTo[(*curLen)++] = *curPos;
2018                 }
2019                 break;
2020
2021       case '&': if (!inQuotes) {
2022                   lastWasRedirect = FALSE;
2023
2024                   /* Add an entry to the command list */
2025                   if (curStringLen > 0) {
2026
2027                     /* Add the current command */
2028                     WCMD_addCommand(curString, &curStringLen,
2029                           curRedirs, &curRedirsLen,
2030                           &curCopyTo, &curLen,
2031                           prevDelim, curDepth,
2032                           &lastEntry, output);
2033
2034                   }
2035
2036                   if (*(curPos+1) == '&') {
2037                     curPos++; /* Skip other & */
2038                     prevDelim = CMD_ONSUCCESS;
2039                   } else {
2040                     prevDelim = CMD_NONE;
2041                   }
2042                 } else {
2043                   curCopyTo[(*curLen)++] = *curPos;
2044                 }
2045                 break;
2046
2047       case ')': if (!inQuotes && curDepth > 0) {
2048                   lastWasRedirect = FALSE;
2049
2050                   /* Add the current command if there is one */
2051                   if (curStringLen) {
2052
2053                       /* Add the current command */
2054                       WCMD_addCommand(curString, &curStringLen,
2055                             curRedirs, &curRedirsLen,
2056                             &curCopyTo, &curLen,
2057                             prevDelim, curDepth,
2058                             &lastEntry, output);
2059                   }
2060
2061                   /* Add an empty entry to the command list */
2062                   prevDelim = CMD_NONE;
2063                   WCMD_addCommand(NULL, &curStringLen,
2064                         curRedirs, &curRedirsLen,
2065                         &curCopyTo, &curLen,
2066                         prevDelim, curDepth,
2067                         &lastEntry, output);
2068                   curDepth--;
2069
2070                   /* Leave inIn if necessary */
2071                   if (inIn) inIn =  FALSE;
2072                 } else {
2073                   curCopyTo[(*curLen)++] = *curPos;
2074                 }
2075                 break;
2076       default:
2077                 lastWasRedirect = FALSE;
2078                 curCopyTo[(*curLen)++] = *curPos;
2079       }
2080
2081       curPos++;
2082
2083       /* At various times we need to know if we have only skipped whitespace,
2084          so reset this variable and then it will remain true until a non
2085          whitespace is found                                               */
2086       if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2087
2088       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2089       if (!lastWasWhiteSpace) {
2090         lastWasIn = lastWasDo = FALSE;
2091       }
2092
2093       /* If we have reached the end, add this command into the list */
2094       if (*curPos == 0x00 && *curLen > 0) {
2095
2096           /* Add an entry to the command list */
2097           WCMD_addCommand(curString, &curStringLen,
2098                 curRedirs, &curRedirsLen,
2099                 &curCopyTo, &curLen,
2100                 prevDelim, curDepth,
2101                 &lastEntry, output);
2102       }
2103
2104       /* If we have reached the end of the string, see if bracketing outstanding */
2105       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2106         inRem = FALSE;
2107         prevDelim = CMD_NONE;
2108         inQuotes = 0;
2109         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2110
2111         /* Read more, skipping any blank lines */
2112         while (*extraSpace == 0x00) {
2113           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2114           if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2115         }
2116         curPos = extraSpace;
2117         if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2118         /* Continue to echo commands IF echo is on and in batch program */
2119         if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2120           WCMD_output_asis(extraSpace);
2121           WCMD_output_asis(newline);
2122         }
2123       }
2124     }
2125
2126     /* Dump out the parsed output */
2127     WCMD_DumpCommands(*output);
2128
2129     return extraSpace;
2130 }
2131
2132 /***************************************************************************
2133  * WCMD_process_commands
2134  *
2135  * Process all the commands read in so far
2136  */
2137 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2138                                 WCHAR *var, WCHAR *val) {
2139
2140     int bdepth = -1;
2141
2142     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2143
2144     /* Loop through the commands, processing them one by one */
2145     while (thisCmd) {
2146
2147       CMD_LIST *origCmd = thisCmd;
2148
2149       /* If processing one bracket only, and we find the end bracket
2150          entry (or less), return                                    */
2151       if (oneBracket && !thisCmd->command &&
2152           bdepth <= thisCmd->bracketDepth) {
2153         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2154                    thisCmd, thisCmd->nextcommand);
2155         return thisCmd->nextcommand;
2156       }
2157
2158       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2159          about them and it will be handled in there)
2160          Also, skip over any batch labels (eg. :fred)          */
2161       if (thisCmd->command && thisCmd->command[0] != ':') {
2162         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2163         WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2164       }
2165
2166       /* Step on unless the command itself already stepped on */
2167       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2168     }
2169     return NULL;
2170 }
2171
2172 /***************************************************************************
2173  * WCMD_free_commands
2174  *
2175  * Frees the storage held for a parsed command line
2176  * - This is not done in the process_commands, as eventually the current
2177  *   pointer will be modified within the commands, and hence a single free
2178  *   routine is simpler
2179  */
2180 void WCMD_free_commands(CMD_LIST *cmds) {
2181
2182     /* Loop through the commands, freeing them one by one */
2183     while (cmds) {
2184       CMD_LIST *thisCmd = cmds;
2185       cmds = cmds->nextcommand;
2186       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2187       HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2188       HeapFree(GetProcessHeap(), 0, thisCmd);
2189     }
2190 }
2191
2192
2193 /*****************************************************************************
2194  * Main entry point. This is a console application so we have a main() not a
2195  * winmain().
2196  */
2197
2198 int wmain (int argc, WCHAR *argvW[])
2199 {
2200   int     args;
2201   WCHAR  *cmd   = NULL;
2202   WCHAR string[1024];
2203   WCHAR envvar[4];
2204   HANDLE h;
2205   int opt_q;
2206   int opt_t = 0;
2207   static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2208                                    'b','a','t','\0'};
2209   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2210   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2211   char ansiVersion[100];
2212   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2213
2214   srand(time(NULL));
2215
2216   /* Pre initialize some messages */
2217   strcpy(ansiVersion, PACKAGE_VERSION);
2218   MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2219   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2220   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2221
2222   args  = argc;
2223   opt_c=opt_k=opt_q=opt_s=0;
2224   while (args > 0)
2225   {
2226       WCHAR c;
2227       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2228       if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2229           argvW++;
2230           args--;
2231           continue;
2232       }
2233
2234       c=(*argvW)[1];
2235       if (tolowerW(c)=='c') {
2236           opt_c=1;
2237       } else if (tolowerW(c)=='q') {
2238           opt_q=1;
2239       } else if (tolowerW(c)=='k') {
2240           opt_k=1;
2241       } else if (tolowerW(c)=='s') {
2242           opt_s=1;
2243       } else if (tolowerW(c)=='a') {
2244           unicodePipes=FALSE;
2245       } else if (tolowerW(c)=='u') {
2246           unicodePipes=TRUE;
2247       } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2248           opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2249       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2250           /* Ignored for compatibility with Windows */
2251       }
2252
2253       if ((*argvW)[2]==0) {
2254           argvW++;
2255           args--;
2256       }
2257       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2258       {
2259           *argvW+=2;
2260       }
2261
2262       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2263           break;
2264   }
2265
2266   if (opt_q) {
2267     const WCHAR eoff[] = {'O','F','F','\0'};
2268     WCMD_echo(eoff);
2269   }
2270
2271   if (opt_c || opt_k) {
2272       int     len,qcount;
2273       WCHAR** arg;
2274       int     argsLeft;
2275       WCHAR*  p;
2276
2277       /* opt_s left unflagged if the command starts with and contains exactly
2278        * one quoted string (exactly two quote characters). The quoted string
2279        * must be an executable name that has whitespace and must not have the
2280        * following characters: &<>()@^| */
2281
2282       /* Build the command to execute */
2283       len = 0;
2284       qcount = 0;
2285       argsLeft = args;
2286       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2287       {
2288           int has_space,bcount;
2289           WCHAR* a;
2290
2291           has_space=0;
2292           bcount=0;
2293           a=*arg;
2294           if( !*a ) has_space=1;
2295           while (*a!='\0') {
2296               if (*a=='\\') {
2297                   bcount++;
2298               } else {
2299                   if (*a==' ' || *a=='\t') {
2300                       has_space=1;
2301                   } else if (*a=='"') {
2302                       /* doubling of '\' preceding a '"',
2303                        * plus escaping of said '"'
2304                        */
2305                       len+=2*bcount+1;
2306                       qcount++;
2307                   }
2308                   bcount=0;
2309               }
2310               a++;
2311           }
2312           len+=(a-*arg) + 1; /* for the separating space */
2313           if (has_space)
2314           {
2315               len+=2; /* for the quotes */
2316               qcount+=2;
2317           }
2318       }
2319
2320       if (qcount!=2)
2321           opt_s=1;
2322
2323       /* check argvW[0] for a space and invalid characters */
2324       if (!opt_s) {
2325           opt_s=1;
2326           p=*argvW;
2327           while (*p!='\0') {
2328               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2329                   || *p=='@' || *p=='^' || *p=='|') {
2330                   opt_s=1;
2331                   break;
2332               }
2333               if (*p==' ')
2334                   opt_s=0;
2335               p++;
2336           }
2337       }
2338
2339       cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2340       if (!cmd)
2341           exit(1);
2342
2343       p = cmd;
2344       argsLeft = args;
2345       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2346       {
2347           int has_space,has_quote;
2348           WCHAR* a;
2349
2350           /* Check for quotes and spaces in this argument */
2351           has_space=has_quote=0;
2352           a=*arg;
2353           if( !*a ) has_space=1;
2354           while (*a!='\0') {
2355               if (*a==' ' || *a=='\t') {
2356                   has_space=1;
2357                   if (has_quote)
2358                       break;
2359               } else if (*a=='"') {
2360                   has_quote=1;
2361                   if (has_space)
2362                       break;
2363               }
2364               a++;
2365           }
2366
2367           /* Now transfer it to the command line */
2368           if (has_space)
2369               *p++='"';
2370           if (has_quote) {
2371               int bcount;
2372               WCHAR* a;
2373
2374               bcount=0;
2375               a=*arg;
2376               while (*a!='\0') {
2377                   if (*a=='\\') {
2378                       *p++=*a;
2379                       bcount++;
2380                   } else {
2381                       if (*a=='"') {
2382                           int i;
2383
2384                           /* Double all the '\\' preceding this '"', plus one */
2385                           for (i=0;i<=bcount;i++)
2386                               *p++='\\';
2387                           *p++='"';
2388                       } else {
2389                           *p++=*a;
2390                       }
2391                       bcount=0;
2392                   }
2393                   a++;
2394               }
2395           } else {
2396               strcpyW(p,*arg);
2397               p+=strlenW(*arg);
2398           }
2399           if (has_space)
2400               *p++='"';
2401           *p++=' ';
2402       }
2403       if (p > cmd)
2404           p--;  /* remove last space */
2405       *p = '\0';
2406
2407       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2408
2409       /* strip first and last quote characters if opt_s; check for invalid
2410        * executable is done later */
2411       if (opt_s && *cmd=='\"')
2412           WCMD_opt_s_strip_quotes(cmd);
2413   }
2414
2415   if (opt_c) {
2416       /* If we do a "wcmd /c command", we don't want to allocate a new
2417        * console since the command returns immediately. Rather, we use
2418        * the currently allocated input and output handles. This allows
2419        * us to pipe to and read from the command interpreter.
2420        */
2421
2422       /* Parse the command string, without reading any more input */
2423       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2424       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2425       WCMD_free_commands(toExecute);
2426       toExecute = NULL;
2427
2428       HeapFree(GetProcessHeap(), 0, cmd);
2429       return errorlevel;
2430   }
2431
2432   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2433                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2434   SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2435
2436   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2437   if (opt_t) {
2438       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2439           defaultColor = opt_t & 0xFF;
2440           param1[0] = 0x00;
2441           WCMD_color();
2442       }
2443   } else {
2444       /* Check HKCU\Software\Microsoft\Command Processor
2445          Then  HKLM\Software\Microsoft\Command Processor
2446            for defaultcolour value
2447            Note  Can be supplied as DWORD or REG_SZ
2448            Note2 When supplied as REG_SZ it's in decimal!!! */
2449       HKEY key;
2450       DWORD type;
2451       DWORD value=0, size=4;
2452       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2453                                       'M','i','c','r','o','s','o','f','t','\\',
2454                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2455       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2456
2457       if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2458                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2459           WCHAR  strvalue[4];
2460
2461           /* See if DWORD or REG_SZ */
2462           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2463                      NULL, NULL) == ERROR_SUCCESS) {
2464               if (type == REG_DWORD) {
2465                   size = sizeof(DWORD);
2466                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2467                                   (LPBYTE)&value, &size);
2468               } else if (type == REG_SZ) {
2469                   size = sizeof(strvalue)/sizeof(WCHAR);
2470                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2471                                   (LPBYTE)strvalue, &size);
2472                   value = strtoulW(strvalue, NULL, 10);
2473               }
2474           }
2475           RegCloseKey(key);
2476       }
2477
2478       if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2479                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2480           WCHAR  strvalue[4];
2481
2482           /* See if DWORD or REG_SZ */
2483           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2484                      NULL, NULL) == ERROR_SUCCESS) {
2485               if (type == REG_DWORD) {
2486                   size = sizeof(DWORD);
2487                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2488                                   (LPBYTE)&value, &size);
2489               } else if (type == REG_SZ) {
2490                   size = sizeof(strvalue)/sizeof(WCHAR);
2491                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2492                                   (LPBYTE)strvalue, &size);
2493                   value = strtoulW(strvalue, NULL, 10);
2494               }
2495           }
2496           RegCloseKey(key);
2497       }
2498
2499       /* If one found, set the screen to that colour */
2500       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2501           defaultColor = value & 0xFF;
2502           param1[0] = 0x00;
2503           WCMD_color();
2504       }
2505
2506   }
2507
2508   /* Save cwd into appropriate env var */
2509   GetCurrentDirectoryW(1024, string);
2510   if (IsCharAlphaW(string[0]) && string[1] == ':') {
2511     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2512     wsprintfW(envvar, fmt, string[0]);
2513     SetEnvironmentVariableW(envvar, string);
2514     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2515   }
2516
2517   if (opt_k) {
2518       /* Parse the command string, without reading any more input */
2519       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2520       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2521       WCMD_free_commands(toExecute);
2522       toExecute = NULL;
2523       HeapFree(GetProcessHeap(), 0, cmd);
2524   }
2525
2526 /*
2527  *      If there is an AUTOEXEC.BAT file, try to execute it.
2528  */
2529
2530   GetFullPathNameW (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2531   h = CreateFileW(string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2532   if (h != INVALID_HANDLE_VALUE) {
2533     CloseHandle (h);
2534 #if 0
2535     WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2536 #endif
2537   }
2538
2539 /*
2540  *      Loop forever getting commands and executing them.
2541  */
2542
2543   SetEnvironmentVariableW(promptW, defaultpromptW);
2544   WCMD_version ();
2545   while (TRUE) {
2546
2547     /* Read until EOF (which for std input is never, but if redirect
2548        in place, may occur                                          */
2549     WCMD_show_prompt ();
2550     if (WCMD_ReadAndParseLine(NULL, &toExecute,
2551                               GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2552       break;
2553     WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2554     WCMD_free_commands(toExecute);
2555     toExecute = NULL;
2556   }
2557   return 0;
2558 }