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