shell32: Native RegisterClassEx requires cbSize to be set.
[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) == 2) 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((p[0] == '/') && (p[1] == '?')) {
1415
1416       /*this is a help request for a program*/
1417       i = WCMD_HELP;
1418       memcpy(p, whichcmd, count * sizeof(WCHAR));
1419       p[count] = '\0';
1420
1421     }
1422
1423     switch (i) {
1424
1425       case WCMD_ATTRIB:
1426         WCMD_setshow_attrib ();
1427         break;
1428       case WCMD_CALL:
1429         WCMD_call (p);
1430         break;
1431       case WCMD_CD:
1432       case WCMD_CHDIR:
1433         WCMD_setshow_default (p);
1434         break;
1435       case WCMD_CLS:
1436         WCMD_clear_screen ();
1437         break;
1438       case WCMD_COPY:
1439         WCMD_copy ();
1440         break;
1441       case WCMD_CTTY:
1442         WCMD_change_tty ();
1443         break;
1444       case WCMD_DATE:
1445         WCMD_setshow_date ();
1446         break;
1447       case WCMD_DEL:
1448       case WCMD_ERASE:
1449         WCMD_delete (p, TRUE);
1450         break;
1451       case WCMD_DIR:
1452         WCMD_directory (p);
1453         break;
1454       case WCMD_ECHO:
1455         WCMD_echo(&whichcmd[count]);
1456         break;
1457       case WCMD_FOR:
1458         WCMD_for (p, cmdList);
1459         break;
1460       case WCMD_GOTO:
1461         WCMD_goto (cmdList);
1462         break;
1463       case WCMD_HELP:
1464         WCMD_give_help (p);
1465         break;
1466       case WCMD_IF:
1467         WCMD_if (p, cmdList);
1468         break;
1469       case WCMD_LABEL:
1470         WCMD_volume (1, p);
1471         break;
1472       case WCMD_MD:
1473       case WCMD_MKDIR:
1474         WCMD_create_dir ();
1475         break;
1476       case WCMD_MOVE:
1477         WCMD_move ();
1478         break;
1479       case WCMD_PATH:
1480         WCMD_setshow_path (p);
1481         break;
1482       case WCMD_PAUSE:
1483         WCMD_pause ();
1484         break;
1485       case WCMD_PROMPT:
1486         WCMD_setshow_prompt ();
1487         break;
1488       case WCMD_REM:
1489         break;
1490       case WCMD_REN:
1491       case WCMD_RENAME:
1492         WCMD_rename ();
1493         break;
1494       case WCMD_RD:
1495       case WCMD_RMDIR:
1496         WCMD_remove_dir (p);
1497         break;
1498       case WCMD_SETLOCAL:
1499         WCMD_setlocal(p);
1500         break;
1501       case WCMD_ENDLOCAL:
1502         WCMD_endlocal();
1503         break;
1504       case WCMD_SET:
1505         WCMD_setshow_env (p);
1506         break;
1507       case WCMD_SHIFT:
1508         WCMD_shift (p);
1509         break;
1510       case WCMD_TIME:
1511         WCMD_setshow_time ();
1512         break;
1513       case WCMD_TITLE:
1514         if (strlenW(&whichcmd[count]) > 0)
1515           WCMD_title(&whichcmd[count+1]);
1516         break;
1517       case WCMD_TYPE:
1518         WCMD_type (p);
1519         break;
1520       case WCMD_VER:
1521         WCMD_version ();
1522         break;
1523       case WCMD_VERIFY:
1524         WCMD_verify (p);
1525         break;
1526       case WCMD_VOL:
1527         WCMD_volume (0, p);
1528         break;
1529       case WCMD_PUSHD:
1530         WCMD_pushd(p);
1531         break;
1532       case WCMD_POPD:
1533         WCMD_popd();
1534         break;
1535       case WCMD_ASSOC:
1536         WCMD_assoc(p, TRUE);
1537         break;
1538       case WCMD_COLOR:
1539         WCMD_color();
1540         break;
1541       case WCMD_FTYPE:
1542         WCMD_assoc(p, FALSE);
1543         break;
1544       case WCMD_MORE:
1545         WCMD_more(p);
1546         break;
1547       case WCMD_EXIT:
1548         WCMD_exit (cmdList);
1549         break;
1550       default:
1551         WCMD_run_program (whichcmd, 0);
1552     }
1553     HeapFree( GetProcessHeap(), 0, cmd );
1554     HeapFree( GetProcessHeap(), 0, new_redir );
1555
1556     /* Restore old handles */
1557     for (i=0; i<3; i++) {
1558       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1559         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1560         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1561       }
1562     }
1563 }
1564 /*************************************************************************
1565  * WCMD_LoadMessage
1566  *    Load a string from the resource file, handling any error
1567  *    Returns string retrieved from resource file
1568  */
1569 WCHAR *WCMD_LoadMessage(UINT id) {
1570     static WCHAR msg[2048];
1571     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1572
1573     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1574        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1575        strcpyW(msg, failedMsg);
1576     }
1577     return msg;
1578 }
1579
1580 /***************************************************************************
1581  * WCMD_DumpCommands
1582  *
1583  *      Dumps out the parsed command line to ensure syntax is correct
1584  */
1585 static void WCMD_DumpCommands(CMD_LIST *commands) {
1586     CMD_LIST *thisCmd = commands;
1587
1588     WINE_TRACE("Parsed line:\n");
1589     while (thisCmd != NULL) {
1590       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1591                thisCmd,
1592                thisCmd->prevDelim,
1593                thisCmd->bracketDepth,
1594                thisCmd->nextcommand,
1595                wine_dbgstr_w(thisCmd->command),
1596                wine_dbgstr_w(thisCmd->redirects));
1597       thisCmd = thisCmd->nextcommand;
1598     }
1599 }
1600
1601 /***************************************************************************
1602  * WCMD_addCommand
1603  *
1604  *   Adds a command to the current command list
1605  */
1606 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1607                      WCHAR *redirs,  int *redirLen,
1608                      WCHAR **copyTo, int **copyToLen,
1609                      CMD_DELIMITERS prevDelim, int curDepth,
1610                      CMD_LIST **lastEntry, CMD_LIST **output) {
1611
1612     CMD_LIST *thisEntry = NULL;
1613
1614     /* Allocate storage for command */
1615     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1616
1617     /* Copy in the command */
1618     if (command) {
1619         thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1620                                       (*commandLen+1) * sizeof(WCHAR));
1621         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1622         thisEntry->command[*commandLen] = 0x00;
1623
1624         /* Copy in the redirects */
1625         thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1626                                          (*redirLen+1) * sizeof(WCHAR));
1627         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1628         thisEntry->redirects[*redirLen] = 0x00;
1629         thisEntry->pipeFile[0] = 0x00;
1630
1631         /* Reset the lengths */
1632         *commandLen   = 0;
1633         *redirLen     = 0;
1634         *copyToLen    = commandLen;
1635         *copyTo       = command;
1636
1637     } else {
1638         thisEntry->command = NULL;
1639         thisEntry->redirects = NULL;
1640         thisEntry->pipeFile[0] = 0x00;
1641     }
1642
1643     /* Fill in other fields */
1644     thisEntry->nextcommand = NULL;
1645     thisEntry->prevDelim = prevDelim;
1646     thisEntry->bracketDepth = curDepth;
1647     if (*lastEntry) {
1648         (*lastEntry)->nextcommand = thisEntry;
1649     } else {
1650         *output = thisEntry;
1651     }
1652     *lastEntry = thisEntry;
1653 }
1654
1655
1656 /***************************************************************************
1657  * WCMD_IsEndQuote
1658  *
1659  *   Checks if the quote pointet to is the end-quote.
1660  *
1661  *   Quotes end if:
1662  *
1663  *   1) The current parameter ends at EOL or at the beginning
1664  *      of a redirection or pipe and not in a quote section.
1665  *
1666  *   2) If the next character is a space and not in a quote section.
1667  *
1668  *   Returns TRUE if this is an end quote, and FALSE if it is not.
1669  *
1670  */
1671 static BOOL WCMD_IsEndQuote(WCHAR *quote, int quoteIndex)
1672 {
1673     int quoteCount = quoteIndex;
1674     int i;
1675
1676     /* If we are not in a quoted section, then we are not an end-quote */
1677     if(quoteIndex == 0)
1678     {
1679         return FALSE;
1680     }
1681
1682     /* Check how many quotes are left for this parameter */
1683     for(i=0;quote[i];i++)
1684     {
1685         if(quote[i] == '"')
1686         {
1687             quoteCount++;
1688         }
1689
1690         /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1691         else if(((quoteCount % 2) == 0)
1692             && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1693         {
1694             break;
1695         }
1696     }
1697
1698     /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1699        be an end-quote */
1700     if(quoteIndex >= (quoteCount / 2))
1701     {
1702         return TRUE;
1703     }
1704
1705     /* No cigar */
1706     return FALSE;
1707 }
1708
1709 /***************************************************************************
1710  * WCMD_ReadAndParseLine
1711  *
1712  *   Either uses supplied input or
1713  *     Reads a file from the handle, and then...
1714  *   Parse the text buffer, spliting into separate commands
1715  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1716  *            following an &&
1717  *     - ( as the first character just ups the bracket depth
1718  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1719  *            adds a CMD_LIST structure with null command
1720  *     - Anything else gets put into the command string (including
1721  *            redirects)
1722  */
1723 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1724
1725     WCHAR    *curPos;
1726     int       inQuotes = 0;
1727     WCHAR     curString[MAXSTRING];
1728     int       curStringLen = 0;
1729     WCHAR     curRedirs[MAXSTRING];
1730     int       curRedirsLen = 0;
1731     WCHAR    *curCopyTo;
1732     int      *curLen;
1733     int       curDepth = 0;
1734     CMD_LIST *lastEntry = NULL;
1735     CMD_DELIMITERS prevDelim = CMD_NONE;
1736     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1737     const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1738     const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1739     const WCHAR ifCmd[]  = {'i','f',' ','\0'};
1740     const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1741     BOOL      inRem = FALSE;
1742     BOOL      inFor = FALSE;
1743     BOOL      inIn  = FALSE;
1744     BOOL      inIf  = FALSE;
1745     BOOL      inElse= FALSE;
1746     BOOL      onlyWhiteSpace = FALSE;
1747     BOOL      lastWasWhiteSpace = FALSE;
1748     BOOL      lastWasDo   = FALSE;
1749     BOOL      lastWasIn   = FALSE;
1750     BOOL      lastWasElse = FALSE;
1751     BOOL      lastWasRedirect = TRUE;
1752
1753     /* Allocate working space for a command read from keyboard, file etc */
1754     if (!extraSpace)
1755       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1756     if (!extraSpace)
1757     {
1758         WINE_ERR("Could not allocate memory for extraSpace\n");
1759         return NULL;
1760     }
1761
1762     /* If initial command read in, use that, otherwise get input from handle */
1763     if (optionalcmd != NULL) {
1764         strcpyW(extraSpace, optionalcmd);
1765     } else if (readFrom == INVALID_HANDLE_VALUE) {
1766         WINE_FIXME("No command nor handle supplied\n");
1767     } else {
1768         if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1769     }
1770     curPos = extraSpace;
1771
1772     /* Handle truncated input - issue warning */
1773     if (strlenW(extraSpace) == MAXSTRING -1) {
1774         WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1775         WCMD_output_asis(extraSpace);
1776         WCMD_output_asis(newline);
1777     }
1778
1779     /* Replace env vars if in a batch context */
1780     if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1781     /* Show prompt before batch line IF echo is on and in batch program */
1782     if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1783       WCMD_show_prompt();
1784       WCMD_output_asis(extraSpace);
1785       WCMD_output_asis(newline);
1786     }
1787
1788     /* Start with an empty string, copying to the command string */
1789     curStringLen = 0;
1790     curRedirsLen = 0;
1791     curCopyTo    = curString;
1792     curLen       = &curStringLen;
1793     lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
1794
1795     /* Parse every character on the line being processed */
1796     while (*curPos != 0x00) {
1797
1798       WCHAR thisChar;
1799
1800       /* Debugging AID:
1801       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1802                  lastWasWhiteSpace, onlyWhiteSpace);
1803       */
1804
1805       /* Certain commands need special handling */
1806       if (curStringLen == 0 && curCopyTo == curString) {
1807         const WCHAR forDO[]  = {'d','o',' ','\0'};
1808
1809         /* If command starts with 'rem', ignore any &&, ( etc */
1810         if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1811           curPos, 4, remCmd, -1) == 2) {
1812           inRem = TRUE;
1813
1814         /* If command starts with 'for', handle ('s mid line after IN or DO */
1815         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1816           curPos, 4, forCmd, -1) == 2) {
1817           inFor = TRUE;
1818
1819         /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1820            is only true in the command portion of the IF statement, but this
1821            should suffice for now
1822             FIXME: Silly syntax like "if 1(==1( (
1823                                         echo they equal
1824                                       )" will be parsed wrong */
1825         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1826           curPos, 3, ifCmd, -1) == 2) {
1827           inIf = TRUE;
1828
1829         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1830           curPos, 5, ifElse, -1) == 2) {
1831           inElse = TRUE;
1832           lastWasElse = TRUE;
1833           onlyWhiteSpace = TRUE;
1834           memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1835           (*curLen)+=5;
1836           curPos+=5;
1837           continue;
1838
1839         /* In a for loop, the DO command will follow a close bracket followed by
1840            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1841            is then 0, and all whitespace is skipped                                */
1842         } else if (inFor &&
1843                    (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1844                     curPos, 3, forDO, -1) == 2)) {
1845           WINE_TRACE("Found DO\n");
1846           lastWasDo = TRUE;
1847           onlyWhiteSpace = TRUE;
1848           memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1849           (*curLen)+=3;
1850           curPos+=3;
1851           continue;
1852         }
1853       } else if (curCopyTo == curString) {
1854
1855         /* Special handling for the 'FOR' command */
1856         if (inFor && lastWasWhiteSpace) {
1857           const WCHAR forIN[] = {'i','n',' ','\0'};
1858
1859           WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1860
1861           if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1862               curPos, 3, forIN, -1) == 2) {
1863             WINE_TRACE("Found IN\n");
1864             lastWasIn = TRUE;
1865             onlyWhiteSpace = TRUE;
1866             memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1867             (*curLen)+=3;
1868             curPos+=3;
1869             continue;
1870           }
1871         }
1872       }
1873
1874       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1875          so just use the default processing ie skip character specific
1876          matching below                                                    */
1877       if (!inRem) thisChar = *curPos;
1878       else        thisChar = 'X';  /* Character with no special processing */
1879
1880       lastWasWhiteSpace = FALSE; /* Will be reset below */
1881
1882       switch (thisChar) {
1883
1884       case '=': /* drop through - ignore token delimiters at the start of a command */
1885       case ',': /* drop through - ignore token delimiters at the start of a command */
1886       case '\t':/* drop through - ignore token delimiters at the start of a command */
1887       case ' ':
1888                 /* If a redirect in place, it ends here */
1889                 if (!inQuotes && !lastWasRedirect) {
1890
1891                   /* If finishing off a redirect, add a whitespace delimiter */
1892                   if (curCopyTo == curRedirs) {
1893                       curCopyTo[(*curLen)++] = ' ';
1894                   }
1895                   curCopyTo = curString;
1896                   curLen = &curStringLen;
1897                 }
1898                 if (*curLen > 0) {
1899                   curCopyTo[(*curLen)++] = *curPos;
1900                 }
1901
1902                 /* Remember just processed whitespace */
1903                 lastWasWhiteSpace = TRUE;
1904
1905                 break;
1906
1907       case '>': /* drop through - handle redirect chars the same */
1908       case '<':
1909                 /* Make a redirect start here */
1910                 if (!inQuotes) {
1911                   curCopyTo = curRedirs;
1912                   curLen = &curRedirsLen;
1913                   lastWasRedirect = TRUE;
1914                 }
1915
1916                 /* See if 1>, 2> etc, in which case we have some patching up
1917                    to do                                                     */
1918                 if (curPos != extraSpace &&
1919                     *(curPos-1)>='1' && *(curPos-1)<='9') {
1920
1921                     curStringLen--;
1922                     curString[curStringLen] = 0x00;
1923                     curCopyTo[(*curLen)++] = *(curPos-1);
1924                 }
1925
1926                 curCopyTo[(*curLen)++] = *curPos;
1927
1928                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1929                     do not process that ampersand as an AND operator */
1930                 if (thisChar == '>' && *(curPos+1) == '&') {
1931                     curCopyTo[(*curLen)++] = *(curPos+1);
1932                     curPos++;
1933                 }
1934                 break;
1935
1936       case '|': /* Pipe character only if not || */
1937                 if (!inQuotes) {
1938                   lastWasRedirect = FALSE;
1939
1940                   /* Add an entry to the command list */
1941                   if (curStringLen > 0) {
1942
1943                     /* Add the current command */
1944                     WCMD_addCommand(curString, &curStringLen,
1945                           curRedirs, &curRedirsLen,
1946                           &curCopyTo, &curLen,
1947                           prevDelim, curDepth,
1948                           &lastEntry, output);
1949
1950                   }
1951
1952                   if (*(curPos+1) == '|') {
1953                     curPos++; /* Skip other | */
1954                     prevDelim = CMD_ONFAILURE;
1955                   } else {
1956                     prevDelim = CMD_PIPE;
1957                   }
1958                 } else {
1959                   curCopyTo[(*curLen)++] = *curPos;
1960                 }
1961                 break;
1962
1963       case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1964                     inQuotes--;
1965                 } else {
1966                     inQuotes++; /* Quotes within quotes are fun! */
1967                 }
1968                 curCopyTo[(*curLen)++] = *curPos;
1969                 lastWasRedirect = FALSE;
1970                 break;
1971
1972       case '(': /* If a '(' is the first non whitespace in a command portion
1973                    ie start of line or just after &&, then we read until an
1974                    unquoted ) is found                                       */
1975                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1976                            ", for(%d, In:%d, Do:%d)"
1977                            ", if(%d, else:%d, lwe:%d)\n",
1978                            *curLen, inQuotes,
1979                            onlyWhiteSpace,
1980                            inFor, lastWasIn, lastWasDo,
1981                            inIf, inElse, lastWasElse);
1982                 lastWasRedirect = FALSE;
1983
1984                 /* Ignore open brackets inside the for set */
1985                 if (*curLen == 0 && !inIn) {
1986                   curDepth++;
1987
1988                 /* If in quotes, ignore brackets */
1989                 } else if (inQuotes) {
1990                   curCopyTo[(*curLen)++] = *curPos;
1991
1992                 /* In a FOR loop, an unquoted '(' may occur straight after
1993                       IN or DO
1994                    In an IF statement just handle it regardless as we don't
1995                       parse the operands
1996                    In an ELSE statement, only allow it straight away after
1997                       the ELSE and whitespace
1998                  */
1999                 } else if (inIf ||
2000                            (inElse && lastWasElse && onlyWhiteSpace) ||
2001                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2002
2003                    /* If entering into an 'IN', set inIn */
2004                   if (inFor && lastWasIn && onlyWhiteSpace) {
2005                     WINE_TRACE("Inside an IN\n");
2006                     inIn = TRUE;
2007                   }
2008
2009                   /* Add the current command */
2010                   WCMD_addCommand(curString, &curStringLen,
2011                                   curRedirs, &curRedirsLen,
2012                                   &curCopyTo, &curLen,
2013                                   prevDelim, curDepth,
2014                                   &lastEntry, output);
2015
2016                   curDepth++;
2017                 } else {
2018                   curCopyTo[(*curLen)++] = *curPos;
2019                 }
2020                 break;
2021
2022       case '&': if (!inQuotes) {
2023                   lastWasRedirect = FALSE;
2024
2025                   /* Add an entry to the command list */
2026                   if (curStringLen > 0) {
2027
2028                     /* Add the current command */
2029                     WCMD_addCommand(curString, &curStringLen,
2030                           curRedirs, &curRedirsLen,
2031                           &curCopyTo, &curLen,
2032                           prevDelim, curDepth,
2033                           &lastEntry, output);
2034
2035                   }
2036
2037                   if (*(curPos+1) == '&') {
2038                     curPos++; /* Skip other & */
2039                     prevDelim = CMD_ONSUCCESS;
2040                   } else {
2041                     prevDelim = CMD_NONE;
2042                   }
2043                 } else {
2044                   curCopyTo[(*curLen)++] = *curPos;
2045                 }
2046                 break;
2047
2048       case ')': if (!inQuotes && curDepth > 0) {
2049                   lastWasRedirect = FALSE;
2050
2051                   /* Add the current command if there is one */
2052                   if (curStringLen) {
2053
2054                       /* Add the current command */
2055                       WCMD_addCommand(curString, &curStringLen,
2056                             curRedirs, &curRedirsLen,
2057                             &curCopyTo, &curLen,
2058                             prevDelim, curDepth,
2059                             &lastEntry, output);
2060                   }
2061
2062                   /* Add an empty entry to the command list */
2063                   prevDelim = CMD_NONE;
2064                   WCMD_addCommand(NULL, &curStringLen,
2065                         curRedirs, &curRedirsLen,
2066                         &curCopyTo, &curLen,
2067                         prevDelim, curDepth,
2068                         &lastEntry, output);
2069                   curDepth--;
2070
2071                   /* Leave inIn if necessary */
2072                   if (inIn) inIn =  FALSE;
2073                 } else {
2074                   curCopyTo[(*curLen)++] = *curPos;
2075                 }
2076                 break;
2077       default:
2078                 lastWasRedirect = FALSE;
2079                 curCopyTo[(*curLen)++] = *curPos;
2080       }
2081
2082       curPos++;
2083
2084       /* At various times we need to know if we have only skipped whitespace,
2085          so reset this variable and then it will remain true until a non
2086          whitespace is found                                               */
2087       if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2088
2089       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2090       if (!lastWasWhiteSpace) {
2091         lastWasIn = lastWasDo = FALSE;
2092       }
2093
2094       /* If we have reached the end, add this command into the list */
2095       if (*curPos == 0x00 && *curLen > 0) {
2096
2097           /* Add an entry to the command list */
2098           WCMD_addCommand(curString, &curStringLen,
2099                 curRedirs, &curRedirsLen,
2100                 &curCopyTo, &curLen,
2101                 prevDelim, curDepth,
2102                 &lastEntry, output);
2103       }
2104
2105       /* If we have reached the end of the string, see if bracketing outstanding */
2106       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2107         inRem = FALSE;
2108         prevDelim = CMD_NONE;
2109         inQuotes = 0;
2110         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2111
2112         /* Read more, skipping any blank lines */
2113         while (*extraSpace == 0x00) {
2114           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2115           if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2116         }
2117         curPos = extraSpace;
2118         if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2119         /* Continue to echo commands IF echo is on and in batch program */
2120         if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2121           WCMD_output_asis(extraSpace);
2122           WCMD_output_asis(newline);
2123         }
2124       }
2125     }
2126
2127     /* Dump out the parsed output */
2128     WCMD_DumpCommands(*output);
2129
2130     return extraSpace;
2131 }
2132
2133 /***************************************************************************
2134  * WCMD_process_commands
2135  *
2136  * Process all the commands read in so far
2137  */
2138 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2139                                 WCHAR *var, WCHAR *val) {
2140
2141     int bdepth = -1;
2142
2143     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2144
2145     /* Loop through the commands, processing them one by one */
2146     while (thisCmd) {
2147
2148       CMD_LIST *origCmd = thisCmd;
2149
2150       /* If processing one bracket only, and we find the end bracket
2151          entry (or less), return                                    */
2152       if (oneBracket && !thisCmd->command &&
2153           bdepth <= thisCmd->bracketDepth) {
2154         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2155                    thisCmd, thisCmd->nextcommand);
2156         return thisCmd->nextcommand;
2157       }
2158
2159       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2160          about them and it will be handled in there)
2161          Also, skip over any batch labels (eg. :fred)          */
2162       if (thisCmd->command && thisCmd->command[0] != ':') {
2163         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2164         WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2165       }
2166
2167       /* Step on unless the command itself already stepped on */
2168       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2169     }
2170     return NULL;
2171 }
2172
2173 /***************************************************************************
2174  * WCMD_free_commands
2175  *
2176  * Frees the storage held for a parsed command line
2177  * - This is not done in the process_commands, as eventually the current
2178  *   pointer will be modified within the commands, and hence a single free
2179  *   routine is simpler
2180  */
2181 void WCMD_free_commands(CMD_LIST *cmds) {
2182
2183     /* Loop through the commands, freeing them one by one */
2184     while (cmds) {
2185       CMD_LIST *thisCmd = cmds;
2186       cmds = cmds->nextcommand;
2187       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2188       HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2189       HeapFree(GetProcessHeap(), 0, thisCmd);
2190     }
2191 }
2192
2193
2194 /*****************************************************************************
2195  * Main entry point. This is a console application so we have a main() not a
2196  * winmain().
2197  */
2198
2199 int wmain (int argc, WCHAR *argvW[])
2200 {
2201   int     args;
2202   WCHAR  *cmd   = NULL;
2203   WCHAR string[1024];
2204   WCHAR envvar[4];
2205   HANDLE h;
2206   int opt_q;
2207   int opt_t = 0;
2208   static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2209                                    'b','a','t','\0'};
2210   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2211   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2212   char ansiVersion[100];
2213   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2214
2215   srand(time(NULL));
2216
2217   /* Pre initialize some messages */
2218   strcpy(ansiVersion, PACKAGE_VERSION);
2219   MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2220   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2221   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2222
2223   args  = argc;
2224   opt_c=opt_k=opt_q=opt_s=0;
2225   while (args > 0)
2226   {
2227       WCHAR c;
2228       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2229       if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2230           argvW++;
2231           args--;
2232           continue;
2233       }
2234
2235       c=(*argvW)[1];
2236       if (tolowerW(c)=='c') {
2237           opt_c=1;
2238       } else if (tolowerW(c)=='q') {
2239           opt_q=1;
2240       } else if (tolowerW(c)=='k') {
2241           opt_k=1;
2242       } else if (tolowerW(c)=='s') {
2243           opt_s=1;
2244       } else if (tolowerW(c)=='a') {
2245           unicodePipes=FALSE;
2246       } else if (tolowerW(c)=='u') {
2247           unicodePipes=TRUE;
2248       } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2249           opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2250       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2251           /* Ignored for compatibility with Windows */
2252       }
2253
2254       if ((*argvW)[2]==0) {
2255           argvW++;
2256           args--;
2257       }
2258       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2259       {
2260           *argvW+=2;
2261       }
2262
2263       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2264           break;
2265   }
2266
2267   if (opt_q) {
2268     const WCHAR eoff[] = {'O','F','F','\0'};
2269     WCMD_echo(eoff);
2270   }
2271
2272   if (opt_c || opt_k) {
2273       int     len,qcount;
2274       WCHAR** arg;
2275       int     argsLeft;
2276       WCHAR*  p;
2277
2278       /* opt_s left unflagged if the command starts with and contains exactly
2279        * one quoted string (exactly two quote characters). The quoted string
2280        * must be an executable name that has whitespace and must not have the
2281        * following characters: &<>()@^| */
2282
2283       /* Build the command to execute */
2284       len = 0;
2285       qcount = 0;
2286       argsLeft = args;
2287       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2288       {
2289           int has_space,bcount;
2290           WCHAR* a;
2291
2292           has_space=0;
2293           bcount=0;
2294           a=*arg;
2295           if( !*a ) has_space=1;
2296           while (*a!='\0') {
2297               if (*a=='\\') {
2298                   bcount++;
2299               } else {
2300                   if (*a==' ' || *a=='\t') {
2301                       has_space=1;
2302                   } else if (*a=='"') {
2303                       /* doubling of '\' preceding a '"',
2304                        * plus escaping of said '"'
2305                        */
2306                       len+=2*bcount+1;
2307                       qcount++;
2308                   }
2309                   bcount=0;
2310               }
2311               a++;
2312           }
2313           len+=(a-*arg) + 1; /* for the separating space */
2314           if (has_space)
2315           {
2316               len+=2; /* for the quotes */
2317               qcount+=2;
2318           }
2319       }
2320
2321       if (qcount!=2)
2322           opt_s=1;
2323
2324       /* check argvW[0] for a space and invalid characters */
2325       if (!opt_s) {
2326           opt_s=1;
2327           p=*argvW;
2328           while (*p!='\0') {
2329               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2330                   || *p=='@' || *p=='^' || *p=='|') {
2331                   opt_s=1;
2332                   break;
2333               }
2334               if (*p==' ')
2335                   opt_s=0;
2336               p++;
2337           }
2338       }
2339
2340       cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2341       if (!cmd)
2342           exit(1);
2343
2344       p = cmd;
2345       argsLeft = args;
2346       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2347       {
2348           int has_space,has_quote;
2349           WCHAR* a;
2350
2351           /* Check for quotes and spaces in this argument */
2352           has_space=has_quote=0;
2353           a=*arg;
2354           if( !*a ) has_space=1;
2355           while (*a!='\0') {
2356               if (*a==' ' || *a=='\t') {
2357                   has_space=1;
2358                   if (has_quote)
2359                       break;
2360               } else if (*a=='"') {
2361                   has_quote=1;
2362                   if (has_space)
2363                       break;
2364               }
2365               a++;
2366           }
2367
2368           /* Now transfer it to the command line */
2369           if (has_space)
2370               *p++='"';
2371           if (has_quote) {
2372               int bcount;
2373               WCHAR* a;
2374
2375               bcount=0;
2376               a=*arg;
2377               while (*a!='\0') {
2378                   if (*a=='\\') {
2379                       *p++=*a;
2380                       bcount++;
2381                   } else {
2382                       if (*a=='"') {
2383                           int i;
2384
2385                           /* Double all the '\\' preceding this '"', plus one */
2386                           for (i=0;i<=bcount;i++)
2387                               *p++='\\';
2388                           *p++='"';
2389                       } else {
2390                           *p++=*a;
2391                       }
2392                       bcount=0;
2393                   }
2394                   a++;
2395               }
2396           } else {
2397               strcpyW(p,*arg);
2398               p+=strlenW(*arg);
2399           }
2400           if (has_space)
2401               *p++='"';
2402           *p++=' ';
2403       }
2404       if (p > cmd)
2405           p--;  /* remove last space */
2406       *p = '\0';
2407
2408       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2409
2410       /* strip first and last quote characters if opt_s; check for invalid
2411        * executable is done later */
2412       if (opt_s && *cmd=='\"')
2413           WCMD_opt_s_strip_quotes(cmd);
2414   }
2415
2416   if (opt_c) {
2417       /* If we do a "wcmd /c command", we don't want to allocate a new
2418        * console since the command returns immediately. Rather, we use
2419        * the currently allocated input and output handles. This allows
2420        * us to pipe to and read from the command interpreter.
2421        */
2422
2423       /* Parse the command string, without reading any more input */
2424       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2425       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2426       WCMD_free_commands(toExecute);
2427       toExecute = NULL;
2428
2429       HeapFree(GetProcessHeap(), 0, cmd);
2430       return errorlevel;
2431   }
2432
2433   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2434                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2435   SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2436
2437   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2438   if (opt_t) {
2439       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2440           defaultColor = opt_t & 0xFF;
2441           param1[0] = 0x00;
2442           WCMD_color();
2443       }
2444   } else {
2445       /* Check HKCU\Software\Microsoft\Command Processor
2446          Then  HKLM\Software\Microsoft\Command Processor
2447            for defaultcolour value
2448            Note  Can be supplied as DWORD or REG_SZ
2449            Note2 When supplied as REG_SZ it's in decimal!!! */
2450       HKEY key;
2451       DWORD type;
2452       DWORD value=0, size=4;
2453       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2454                                       'M','i','c','r','o','s','o','f','t','\\',
2455                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2456       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2457
2458       if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2459                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2460           WCHAR  strvalue[4];
2461
2462           /* See if DWORD or REG_SZ */
2463           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2464                      NULL, NULL) == ERROR_SUCCESS) {
2465               if (type == REG_DWORD) {
2466                   size = sizeof(DWORD);
2467                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2468                                   (LPBYTE)&value, &size);
2469               } else if (type == REG_SZ) {
2470                   size = sizeof(strvalue)/sizeof(WCHAR);
2471                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2472                                   (LPBYTE)strvalue, &size);
2473                   value = strtoulW(strvalue, NULL, 10);
2474               }
2475           }
2476           RegCloseKey(key);
2477       }
2478
2479       if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2480                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2481           WCHAR  strvalue[4];
2482
2483           /* See if DWORD or REG_SZ */
2484           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2485                      NULL, NULL) == ERROR_SUCCESS) {
2486               if (type == REG_DWORD) {
2487                   size = sizeof(DWORD);
2488                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2489                                   (LPBYTE)&value, &size);
2490               } else if (type == REG_SZ) {
2491                   size = sizeof(strvalue)/sizeof(WCHAR);
2492                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2493                                   (LPBYTE)strvalue, &size);
2494                   value = strtoulW(strvalue, NULL, 10);
2495               }
2496           }
2497           RegCloseKey(key);
2498       }
2499
2500       /* If one found, set the screen to that colour */
2501       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2502           defaultColor = value & 0xFF;
2503           param1[0] = 0x00;
2504           WCMD_color();
2505       }
2506
2507   }
2508
2509   /* Save cwd into appropriate env var */
2510   GetCurrentDirectoryW(1024, string);
2511   if (IsCharAlphaW(string[0]) && string[1] == ':') {
2512     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2513     wsprintfW(envvar, fmt, string[0]);
2514     SetEnvironmentVariableW(envvar, string);
2515     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2516   }
2517
2518   if (opt_k) {
2519       /* Parse the command string, without reading any more input */
2520       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2521       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2522       WCMD_free_commands(toExecute);
2523       toExecute = NULL;
2524       HeapFree(GetProcessHeap(), 0, cmd);
2525   }
2526
2527 /*
2528  *      If there is an AUTOEXEC.BAT file, try to execute it.
2529  */
2530
2531   GetFullPathNameW (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2532   h = CreateFileW(string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2533   if (h != INVALID_HANDLE_VALUE) {
2534     CloseHandle (h);
2535 #if 0
2536     WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2537 #endif
2538   }
2539
2540 /*
2541  *      Loop forever getting commands and executing them.
2542  */
2543
2544   SetEnvironmentVariableW(promptW, defaultpromptW);
2545   WCMD_version ();
2546   while (TRUE) {
2547
2548     /* Read until EOF (which for std input is never, but if redirect
2549        in place, may occur                                          */
2550     WCMD_show_prompt ();
2551     if (WCMD_ReadAndParseLine(NULL, &toExecute,
2552                               GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2553       break;
2554     WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2555     WCMD_free_commands(toExecute);
2556     toExecute = NULL;
2557   }
2558   return 0;
2559 }