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