cmd: Use DOS newlines, not UNIX.
[wine] / programs / cmd / wcmdmain.c
1 /*
2  * CMD - Wine-compatible command line interface.
3  *
4  * Copyright (C) 1999 - 2001 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /*
23  * FIXME:
24  * - Cannot handle parameters in quotes
25  * - Lots of functionality missing from builtins
26  */
27
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
33
34 const WCHAR inbuilt[][10] = {
35         {'A','T','T','R','I','B','\0'},
36         {'C','A','L','L','\0'},
37         {'C','D','\0'},
38         {'C','H','D','I','R','\0'},
39         {'C','L','S','\0'},
40         {'C','O','P','Y','\0'},
41         {'C','T','T','Y','\0'},
42         {'D','A','T','E','\0'},
43         {'D','E','L','\0'},
44         {'D','I','R','\0'},
45         {'E','C','H','O','\0'},
46         {'E','R','A','S','E','\0'},
47         {'F','O','R','\0'},
48         {'G','O','T','O','\0'},
49         {'H','E','L','P','\0'},
50         {'I','F','\0'},
51         {'L','A','B','E','L','\0'},
52         {'M','D','\0'},
53         {'M','K','D','I','R','\0'},
54         {'M','O','V','E','\0'},
55         {'P','A','T','H','\0'},
56         {'P','A','U','S','E','\0'},
57         {'P','R','O','M','P','T','\0'},
58         {'R','E','M','\0'},
59         {'R','E','N','\0'},
60         {'R','E','N','A','M','E','\0'},
61         {'R','D','\0'},
62         {'R','M','D','I','R','\0'},
63         {'S','E','T','\0'},
64         {'S','H','I','F','T','\0'},
65         {'T','I','M','E','\0'},
66         {'T','I','T','L','E','\0'},
67         {'T','Y','P','E','\0'},
68         {'V','E','R','I','F','Y','\0'},
69         {'V','E','R','\0'},
70         {'V','O','L','\0'},
71         {'E','N','D','L','O','C','A','L','\0'},
72         {'S','E','T','L','O','C','A','L','\0'},
73         {'P','U','S','H','D','\0'},
74         {'P','O','P','D','\0'},
75         {'A','S','S','O','C','\0'},
76         {'C','O','L','O','R','\0'},
77         {'F','T','Y','P','E','\0'},
78         {'M','O','R','E','\0'},
79         {'E','X','I','T','\0'}
80 };
81
82 HINSTANCE hinst;
83 DWORD errorlevel;
84 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
85 static int opt_c, opt_k, opt_s;
86 const WCHAR newline[] = {'\r','\n','\0'};
87 static const WCHAR equalsW[] = {'=','\0'};
88 static const WCHAR closeBW[] = {')','\0'};
89 WCHAR anykey[100];
90 WCHAR version_string[100];
91 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
92 BATCH_CONTEXT *context = NULL;
93 extern struct env_stack *pushd_directories;
94 static const WCHAR *pagedMessage = NULL;
95 static char  *output_bufA = NULL;
96 #define MAX_WRITECONSOLE_SIZE 65535
97 BOOL unicodePipes = FALSE;
98
99
100 /*******************************************************************
101  * WCMD_output_asis_len - send output to current standard output
102  *
103  * Output a formatted unicode string. Ideally this will go to the console
104  *  and hence required WriteConsoleW to output it, however if file i/o is
105  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
106  */
107 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
108
109     DWORD   nOut= 0;
110     DWORD   res = 0;
111
112     /* If nothing to write, return (MORE does this sometimes) */
113     if (!len) return;
114
115     /* Try to write as unicode assuming it is to a console */
116     res = WriteConsoleW(device, message, len, &nOut, NULL);
117
118     /* If writing to console fails, assume its file
119        i/o so convert to OEM codepage and output                  */
120     if (!res) {
121       BOOL usedDefaultChar = FALSE;
122       DWORD convertedChars;
123
124       if (!unicodePipes) {
125         /*
126          * Allocate buffer to use when writing to file. (Not freed, as one off)
127          */
128         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
129                                                   MAX_WRITECONSOLE_SIZE);
130         if (!output_bufA) {
131           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
132           return;
133         }
134
135         /* Convert to OEM, then output */
136         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
137                             len, output_bufA, MAX_WRITECONSOLE_SIZE,
138                             "?", &usedDefaultChar);
139         WriteFile(device, output_bufA, convertedChars,
140                   &nOut, FALSE);
141       } else {
142         WriteFile(device, message, len*sizeof(WCHAR),
143                   &nOut, FALSE);
144       }
145     }
146     return;
147 }
148
149 /*******************************************************************
150  * WCMD_output - send output to current standard output device.
151  *
152  */
153
154 void WCMD_output (const WCHAR *format, ...) {
155
156   va_list ap;
157   WCHAR string[1024];
158   int ret;
159
160   va_start(ap,format);
161   ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
162   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
163        WINE_ERR("Output truncated in WCMD_output\n" );
164        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
165        string[ret] = '\0';
166   }
167   va_end(ap);
168   WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
169 }
170
171
172 static int line_count;
173 static int max_height;
174 static int max_width;
175 static BOOL paged_mode;
176 static int numChars;
177
178 void WCMD_enter_paged_mode(const WCHAR *msg)
179 {
180   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
181
182   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
183     max_height = consoleInfo.dwSize.Y;
184     max_width  = consoleInfo.dwSize.X;
185   } else {
186     max_height = 25;
187     max_width  = 80;
188   }
189   paged_mode = TRUE;
190   line_count = 0;
191   numChars   = 0;
192   pagedMessage = (msg==NULL)? anykey : msg;
193 }
194
195 void WCMD_leave_paged_mode(void)
196 {
197   paged_mode = FALSE;
198   pagedMessage = NULL;
199 }
200
201 /***************************************************************************
202  * WCMD_Readfile
203  *
204  *      Read characters in from a console/file, returning result in Unicode
205  *      with signature identical to ReadFile
206  */
207 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
208                           LPDWORD charsRead, const LPOVERLAPPED unused) {
209
210     BOOL   res;
211
212     /* Try to read from console as Unicode */
213     res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
214
215     /* If reading from console has failed we assume its file
216        i/o so read in and convert from OEM codepage               */
217     if (!res) {
218
219         DWORD numRead;
220         /*
221          * Allocate buffer to use when reading from file. Not freed
222          */
223         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
224                                                   MAX_WRITECONSOLE_SIZE);
225         if (!output_bufA) {
226           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
227           return 0;
228         }
229
230         /* Read from file (assume OEM codepage) */
231         res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
232
233         /* Convert from OEM */
234         *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
235                          intoBuf, maxChars);
236
237     }
238     return res;
239 }
240
241 /*******************************************************************
242  * WCMD_output_asis - send output to current standard output device.
243  *        without formatting eg. when message contains '%'
244  */
245 void WCMD_output_asis (const WCHAR *message) {
246   DWORD count;
247   const WCHAR* ptr;
248   WCHAR string[1024];
249
250   if (paged_mode) {
251     do {
252       ptr = message;
253       while (*ptr && *ptr!='\n' && (numChars < max_width)) {
254         numChars++;
255         ptr++;
256       };
257       if (*ptr == '\n') ptr++;
258       WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
259                            GetStdHandle(STD_OUTPUT_HANDLE));
260       if (ptr) {
261         numChars = 0;
262         if (++line_count >= max_height - 1) {
263           line_count = 0;
264           WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
265                                GetStdHandle(STD_OUTPUT_HANDLE));
266           WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
267                          sizeof(string)/sizeof(WCHAR), &count, NULL);
268         }
269       }
270     } while (((message = ptr) != NULL) && (*ptr));
271   } else {
272     WCMD_output_asis_len(message, lstrlenW(message),
273                          GetStdHandle(STD_OUTPUT_HANDLE));
274   }
275 }
276
277 /****************************************************************************
278  * WCMD_print_error
279  *
280  * Print the message for GetLastError
281  */
282
283 void WCMD_print_error (void) {
284   LPVOID lpMsgBuf;
285   DWORD error_code;
286   int status;
287
288   error_code = GetLastError ();
289   status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
290                           NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
291   if (!status) {
292     WINE_FIXME ("Cannot display message for error %d, status %d\n",
293                         error_code, GetLastError());
294     return;
295   }
296
297   WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
298                        GetStdHandle(STD_ERROR_HANDLE));
299   LocalFree (lpMsgBuf);
300   WCMD_output_asis_len (newline, lstrlenW(newline),
301                         GetStdHandle(STD_ERROR_HANDLE));
302   return;
303 }
304
305 /******************************************************************************
306  * WCMD_show_prompt
307  *
308  *      Display the prompt on STDout
309  *
310  */
311
312 static void WCMD_show_prompt (void) {
313
314   int status;
315   WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
316   WCHAR *p, *q;
317   DWORD len;
318   static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
319
320   len = GetEnvironmentVariableW(envPrompt, prompt_string,
321                                 sizeof(prompt_string)/sizeof(WCHAR));
322   if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
323     const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
324     strcpyW (prompt_string, dfltPrompt);
325   }
326   p = prompt_string;
327   q = out_string;
328   *q = '\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           GetDateFormatW(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 = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
370           if (status) {
371             *q++ = curdir[0];
372           }
373           break;
374         case 'P':
375           status = GetCurrentDirectoryW(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           GetTimeFormatW(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 ((CompareStringW(LOCALE_USER_DEFAULT,
546                         NORM_IGNORECASE | SORT_STRINGSORT,
547                         thisVar, 12, ErrorLvlP, -1) == 2) &&
548                 (GetEnvironmentVariableW(ErrorLvl, thisVarContents, 1) == 0) &&
549                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
550       static const WCHAR fmt[] = {'%','d','\0'};
551       wsprintfW(thisVarContents, fmt, errorlevel);
552       len = strlenW(thisVarContents);
553
554     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
555                                NORM_IGNORECASE | SORT_STRINGSORT,
556                                thisVar, 6, DateP, -1) == 2) &&
557                 (GetEnvironmentVariableW(Date, thisVarContents, 1) == 0) &&
558                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
559
560       GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
561                     NULL, thisVarContents, MAXSTRING);
562       len = strlenW(thisVarContents);
563
564     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
565                                NORM_IGNORECASE | SORT_STRINGSORT,
566                                thisVar, 6, TimeP, -1) == 2) &&
567                 (GetEnvironmentVariableW(Time, thisVarContents, 1) == 0) &&
568                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
569       GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
570                         NULL, thisVarContents, MAXSTRING);
571       len = strlenW(thisVarContents);
572
573     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
574                                NORM_IGNORECASE | SORT_STRINGSORT,
575                                thisVar, 4, CdP, -1) == 2) &&
576                 (GetEnvironmentVariableW(Cd, thisVarContents, 1) == 0) &&
577                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
578       GetCurrentDirectoryW(MAXSTRING, thisVarContents);
579       len = strlenW(thisVarContents);
580
581     } else if ((CompareStringW(LOCALE_USER_DEFAULT,
582                                NORM_IGNORECASE | SORT_STRINGSORT,
583                                thisVar, 8, RandomP, -1) == 2) &&
584                 (GetEnvironmentVariableW(Random, thisVarContents, 1) == 0) &&
585                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
586       static const WCHAR fmt[] = {'%','d','\0'};
587       wsprintfW(thisVarContents, fmt, rand() % 32768);
588       len = strlenW(thisVarContents);
589
590     /* Look for a matching 'for' variable */
591     } else if (forVar &&
592                (CompareStringW(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 = ExpandEnvironmentStringsW(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       CharUpperBuffW(searchIn, strlenW(thisVarContents));
713       searchFor = WCMD_strdupW(colonpos+1);
714       CharUpperBuffW(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                (CompareStringW(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(STARTUPINFOW* st)
890 {
891     STARTUPINFOW 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(STARTUPINFOW);
896     GetStartupInfoW(&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 = GetEnvironmentVariableW(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     GetFullPathNameW(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 = GetEnvironmentVariableW(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     GetFullPathNameW(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 (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1060         found = TRUE;
1061       }
1062     }
1063
1064     /* 2. Any .* matches? */
1065     if (!found) {
1066       HANDLE          h;
1067       WIN32_FIND_DATAW finddata;
1068       static const WCHAR allFiles[] = {'.','*','\0'};
1069
1070       strcatW(thisDir,allFiles);
1071       h = FindFirstFileW(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 (GetFileAttributesW(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       STARTUPINFOW st;
1113       PROCESS_INFORMATION pe;
1114       SHFILEINFOW 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 = FindExecutableW (thisDir, NULL, temp);
1135         if ((INT_PTR)hinst < 32)
1136           console = 0;
1137         else
1138           console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1139
1140         ZeroMemory (&st, sizeof(STARTUPINFOW));
1141         st.cb = sizeof(STARTUPINFOW);
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 = CreateProcessW(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
1157         if (!status)
1158           break;
1159
1160         if (!assumeInternal && !console) errorlevel = 0;
1161         else
1162         {
1163             /* Always wait when called in a batch program context */
1164             if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1165             GetExitCodeProcess (pe.hProcess, &errorlevel);
1166             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1167         }
1168         CloseHandle(pe.hProcess);
1169         CloseHandle(pe.hThread);
1170         return;
1171       }
1172     }
1173   }
1174
1175   /* Not found anywhere - give up */
1176   SetLastError(ERROR_FILE_NOT_FOUND);
1177   WCMD_print_error ();
1178
1179   /* If a command fails to launch, it sets errorlevel 9009 - which
1180      does not seem to have any associated constant definition     */
1181   errorlevel = 9009;
1182   return;
1183
1184 }
1185
1186 /*****************************************************************************
1187  * Process one command. If the command is EXIT this routine does not return.
1188  * We will recurse through here executing batch files.
1189  */
1190 void WCMD_execute (WCHAR *command, WCHAR *redirects,
1191                    WCHAR *forVariable, WCHAR *forValue,
1192                    CMD_LIST **cmdList)
1193 {
1194     WCHAR *cmd, *p, *redir;
1195     int status, i;
1196     DWORD count, creationDisposition;
1197     HANDLE h;
1198     WCHAR *whichcmd;
1199     SECURITY_ATTRIBUTES sa;
1200     WCHAR *new_cmd = NULL;
1201     WCHAR *new_redir = NULL;
1202     HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1203                                 GetStdHandle (STD_OUTPUT_HANDLE),
1204                                 GetStdHandle (STD_ERROR_HANDLE)};
1205     DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
1206                                 STD_OUTPUT_HANDLE,
1207                                 STD_ERROR_HANDLE};
1208     BOOL piped = FALSE;
1209
1210     WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1211                wine_dbgstr_w(command), cmdList,
1212                wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1213
1214     /* If the next command is a pipe then we implement pipes by redirecting
1215        the output from this command to a temp file and input into the
1216        next command from that temp file.
1217        FIXME: Use of named pipes would make more sense here as currently this
1218        process has to finish before the next one can start but this requires
1219        a change to not wait for the first app to finish but rather the pipe  */
1220     if (cmdList && (*cmdList)->nextcommand &&
1221         (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1222
1223         WCHAR temp_path[MAX_PATH];
1224         static const WCHAR cmdW[]     = {'C','M','D','\0'};
1225
1226         /* Remember piping is in action */
1227         WINE_TRACE("Output needs to be piped\n");
1228         piped = TRUE;
1229
1230         /* Generate a unique temporary filename */
1231         GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1232         GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1233         WINE_TRACE("Using temporary file of %s\n",
1234                    wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1235     }
1236
1237     /* Move copy of the command onto the heap so it can be expanded */
1238     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1239     if (!new_cmd)
1240     {
1241         WINE_ERR("Could not allocate memory for new_cmd\n");
1242         return;
1243     }
1244     strcpyW(new_cmd, command);
1245
1246     /* Move copy of the redirects onto the heap so it can be expanded */
1247     new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1248     if (!new_redir)
1249     {
1250         WINE_ERR("Could not allocate memory for new_redir\n");
1251         HeapFree( GetProcessHeap(), 0, new_cmd );
1252         return;
1253     }
1254
1255     /* If piped output, send stdout to the pipe by appending >filename to redirects */
1256     if (piped) {
1257         static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1258         wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1259         WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1260     } else {
1261         strcpyW(new_redir, redirects);
1262     }
1263
1264     /* Expand variables in command line mode only (batch mode will
1265        be expanded as the line is read in, except for 'for' loops) */
1266     handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1267     handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1268     cmd = new_cmd;
1269
1270     /* Show prompt before batch line IF echo is on and in batch program */
1271     if (context && echo_mode && (cmd[0] != '@')) {
1272       WCMD_show_prompt();
1273       WCMD_output_asis ( cmd);
1274       WCMD_output_asis ( newline);
1275     }
1276
1277 /*
1278  *      Changing default drive has to be handled as a special case.
1279  */
1280
1281     if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1282       WCHAR envvar[5];
1283       WCHAR dir[MAX_PATH];
1284
1285       /* According to MSDN CreateProcess docs, special env vars record
1286          the current directory on each drive, in the form =C:
1287          so see if one specified, and if so go back to it             */
1288       strcpyW(envvar, equalsW);
1289       strcatW(envvar, cmd);
1290       if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1291         static const WCHAR fmt[] = {'%','s','\\','\0'};
1292         wsprintfW(cmd, fmt, cmd);
1293         WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1294       }
1295       WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1296       status = SetCurrentDirectoryW(cmd);
1297       if (!status) WCMD_print_error ();
1298       HeapFree( GetProcessHeap(), 0, cmd );
1299       HeapFree( GetProcessHeap(), 0, new_redir );
1300       return;
1301     }
1302
1303     sa.nLength = sizeof(sa);
1304     sa.lpSecurityDescriptor = NULL;
1305     sa.bInheritHandle = TRUE;
1306
1307 /*
1308  *      Redirect stdin, stdout and/or stderr if required.
1309  */
1310
1311     /* STDIN could come from a preceding pipe, so delete on close if it does */
1312     if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1313         WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1314         h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1315                   FILE_SHARE_READ, &sa, OPEN_EXISTING,
1316                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1317         if (h == INVALID_HANDLE_VALUE) {
1318           WCMD_print_error ();
1319           HeapFree( GetProcessHeap(), 0, cmd );
1320           HeapFree( GetProcessHeap(), 0, new_redir );
1321           return;
1322         }
1323         SetStdHandle (STD_INPUT_HANDLE, h);
1324
1325         /* No need to remember the temporary name any longer once opened */
1326         (*cmdList)->pipeFile[0] = 0x00;
1327
1328     /* Otherwise STDIN could come from a '<' redirect */
1329     } else if ((p = strchrW(new_redir,'<')) != NULL) {
1330       h = CreateFileW(WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
1331                 FILE_ATTRIBUTE_NORMAL, NULL);
1332       if (h == INVALID_HANDLE_VALUE) {
1333         WCMD_print_error ();
1334         HeapFree( GetProcessHeap(), 0, cmd );
1335         HeapFree( GetProcessHeap(), 0, new_redir );
1336         return;
1337       }
1338       SetStdHandle (STD_INPUT_HANDLE, h);
1339     }
1340
1341     /* Scan the whole command looking for > and 2> */
1342     redir = new_redir;
1343     while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1344       int handle = 0;
1345
1346       if (*(p-1)!='2') {
1347         handle = 1;
1348       } else {
1349         handle = 2;
1350       }
1351
1352       p++;
1353       if ('>' == *p) {
1354         creationDisposition = OPEN_ALWAYS;
1355         p++;
1356       }
1357       else {
1358         creationDisposition = CREATE_ALWAYS;
1359       }
1360
1361       /* Add support for 2>&1 */
1362       redir = p;
1363       if (*p == '&') {
1364         int idx = *(p+1) - '0';
1365
1366         if (DuplicateHandle(GetCurrentProcess(),
1367                         GetStdHandle(idx_stdhandles[idx]),
1368                         GetCurrentProcess(),
1369                         &h,
1370                         0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1371           WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1372         }
1373         WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1374
1375       } else {
1376         WCHAR *param = WCMD_parameter (p, 0, NULL);
1377         h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1378                         FILE_ATTRIBUTE_NORMAL, NULL);
1379         if (h == INVALID_HANDLE_VALUE) {
1380           WCMD_print_error ();
1381           HeapFree( GetProcessHeap(), 0, cmd );
1382           HeapFree( GetProcessHeap(), 0, new_redir );
1383           return;
1384         }
1385         if (SetFilePointer (h, 0, NULL, FILE_END) ==
1386               INVALID_SET_FILE_POINTER) {
1387           WCMD_print_error ();
1388         }
1389         WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1390       }
1391
1392       SetStdHandle (idx_stdhandles[handle], h);
1393     }
1394
1395 /*
1396  * Strip leading whitespaces, and a '@' if supplied
1397  */
1398     whichcmd = WCMD_strtrim_leading_spaces(cmd);
1399     WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1400     if (whichcmd[0] == '@') whichcmd++;
1401
1402 /*
1403  *      Check if the command entered is internal. If it is, pass the rest of the
1404  *      line down to the command. If not try to run a program.
1405  */
1406
1407     count = 0;
1408     while (IsCharAlphaNumericW(whichcmd[count])) {
1409       count++;
1410     }
1411     for (i=0; i<=WCMD_EXIT; i++) {
1412       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1413         whichcmd, count, inbuilt[i], -1) == 2) break;
1414     }
1415     p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
1416     WCMD_parse (p, quals, param1, param2);
1417     WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1418
1419     switch (i) {
1420
1421       case WCMD_ATTRIB:
1422         WCMD_setshow_attrib ();
1423         break;
1424       case WCMD_CALL:
1425         WCMD_call (p);
1426         break;
1427       case WCMD_CD:
1428       case WCMD_CHDIR:
1429         WCMD_setshow_default (p);
1430         break;
1431       case WCMD_CLS:
1432         WCMD_clear_screen ();
1433         break;
1434       case WCMD_COPY:
1435         WCMD_copy ();
1436         break;
1437       case WCMD_CTTY:
1438         WCMD_change_tty ();
1439         break;
1440       case WCMD_DATE:
1441         WCMD_setshow_date ();
1442         break;
1443       case WCMD_DEL:
1444       case WCMD_ERASE:
1445         WCMD_delete (p, TRUE);
1446         break;
1447       case WCMD_DIR:
1448         WCMD_directory (p);
1449         break;
1450       case WCMD_ECHO:
1451         WCMD_echo(&whichcmd[count]);
1452         break;
1453       case WCMD_FOR:
1454         WCMD_for (p, cmdList);
1455         break;
1456       case WCMD_GOTO:
1457         WCMD_goto (cmdList);
1458         break;
1459       case WCMD_HELP:
1460         WCMD_give_help (p);
1461         break;
1462       case WCMD_IF:
1463         WCMD_if (p, cmdList);
1464         break;
1465       case WCMD_LABEL:
1466         WCMD_volume (1, p);
1467         break;
1468       case WCMD_MD:
1469       case WCMD_MKDIR:
1470         WCMD_create_dir ();
1471         break;
1472       case WCMD_MOVE:
1473         WCMD_move ();
1474         break;
1475       case WCMD_PATH:
1476         WCMD_setshow_path (p);
1477         break;
1478       case WCMD_PAUSE:
1479         WCMD_pause ();
1480         break;
1481       case WCMD_PROMPT:
1482         WCMD_setshow_prompt ();
1483         break;
1484       case WCMD_REM:
1485         break;
1486       case WCMD_REN:
1487       case WCMD_RENAME:
1488         WCMD_rename ();
1489         break;
1490       case WCMD_RD:
1491       case WCMD_RMDIR:
1492         WCMD_remove_dir (p);
1493         break;
1494       case WCMD_SETLOCAL:
1495         WCMD_setlocal(p);
1496         break;
1497       case WCMD_ENDLOCAL:
1498         WCMD_endlocal();
1499         break;
1500       case WCMD_SET:
1501         WCMD_setshow_env (p);
1502         break;
1503       case WCMD_SHIFT:
1504         WCMD_shift (p);
1505         break;
1506       case WCMD_TIME:
1507         WCMD_setshow_time ();
1508         break;
1509       case WCMD_TITLE:
1510         if (strlenW(&whichcmd[count]) > 0)
1511           WCMD_title(&whichcmd[count+1]);
1512         break;
1513       case WCMD_TYPE:
1514         WCMD_type (p);
1515         break;
1516       case WCMD_VER:
1517         WCMD_version ();
1518         break;
1519       case WCMD_VERIFY:
1520         WCMD_verify (p);
1521         break;
1522       case WCMD_VOL:
1523         WCMD_volume (0, p);
1524         break;
1525       case WCMD_PUSHD:
1526         WCMD_pushd(p);
1527         break;
1528       case WCMD_POPD:
1529         WCMD_popd();
1530         break;
1531       case WCMD_ASSOC:
1532         WCMD_assoc(p, TRUE);
1533         break;
1534       case WCMD_COLOR:
1535         WCMD_color();
1536         break;
1537       case WCMD_FTYPE:
1538         WCMD_assoc(p, FALSE);
1539         break;
1540       case WCMD_MORE:
1541         WCMD_more(p);
1542         break;
1543       case WCMD_EXIT:
1544         WCMD_exit (cmdList);
1545         break;
1546       default:
1547         WCMD_run_program (whichcmd, 0);
1548     }
1549     HeapFree( GetProcessHeap(), 0, cmd );
1550     HeapFree( GetProcessHeap(), 0, new_redir );
1551
1552     /* Restore old handles */
1553     for (i=0; i<3; i++) {
1554       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1555         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1556         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1557       }
1558     }
1559 }
1560 /*************************************************************************
1561  * WCMD_LoadMessage
1562  *    Load a string from the resource file, handling any error
1563  *    Returns string retrieved from resource file
1564  */
1565 WCHAR *WCMD_LoadMessage(UINT id) {
1566     static WCHAR msg[2048];
1567     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1568
1569     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1570        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1571        strcpyW(msg, failedMsg);
1572     }
1573     return msg;
1574 }
1575
1576 /***************************************************************************
1577  * WCMD_DumpCommands
1578  *
1579  *      Dumps out the parsed command line to ensure syntax is correct
1580  */
1581 static void WCMD_DumpCommands(CMD_LIST *commands) {
1582     CMD_LIST *thisCmd = commands;
1583
1584     WINE_TRACE("Parsed line:\n");
1585     while (thisCmd != NULL) {
1586       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1587                thisCmd,
1588                thisCmd->prevDelim,
1589                thisCmd->bracketDepth,
1590                thisCmd->nextcommand,
1591                wine_dbgstr_w(thisCmd->command),
1592                wine_dbgstr_w(thisCmd->redirects));
1593       thisCmd = thisCmd->nextcommand;
1594     }
1595 }
1596
1597 /***************************************************************************
1598  * WCMD_addCommand
1599  *
1600  *   Adds a command to the current command list
1601  */
1602 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1603                      WCHAR *redirs,  int *redirLen,
1604                      WCHAR **copyTo, int **copyToLen,
1605                      CMD_DELIMITERS prevDelim, int curDepth,
1606                      CMD_LIST **lastEntry, CMD_LIST **output) {
1607
1608     CMD_LIST *thisEntry = NULL;
1609
1610     /* Allocate storage for command */
1611     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1612
1613     /* Copy in the command */
1614     if (command) {
1615         thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1616                                       (*commandLen+1) * sizeof(WCHAR));
1617         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1618         thisEntry->command[*commandLen] = 0x00;
1619
1620         /* Copy in the redirects */
1621         thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1622                                          (*redirLen+1) * sizeof(WCHAR));
1623         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1624         thisEntry->redirects[*redirLen] = 0x00;
1625         thisEntry->pipeFile[0] = 0x00;
1626
1627         /* Reset the lengths */
1628         *commandLen   = 0;
1629         *redirLen     = 0;
1630         *copyToLen    = commandLen;
1631         *copyTo       = command;
1632
1633     } else {
1634         thisEntry->command = NULL;
1635     }
1636
1637     /* Fill in other fields */
1638     thisEntry->nextcommand = NULL;
1639     thisEntry->prevDelim = prevDelim;
1640     thisEntry->bracketDepth = curDepth;
1641     if (*lastEntry) {
1642         (*lastEntry)->nextcommand = thisEntry;
1643     } else {
1644         *output = thisEntry;
1645     }
1646     *lastEntry = thisEntry;
1647 }
1648
1649
1650 /***************************************************************************
1651  * WCMD_IsEndQuote
1652  *
1653  *   Checks if the quote pointet to is the end-quote.
1654  *
1655  *   Quotes end if:
1656  *
1657  *   1) The current parameter ends at EOL or at the beginning
1658  *      of a redirection or pipe and not in a quote section.
1659  *
1660  *   2) If the next character is a space and not in a quote section.
1661  *
1662  *   Returns TRUE if this is an end quote, and FALSE if it is not.
1663  *
1664  */
1665 static BOOL WCMD_IsEndQuote(WCHAR *quote, int quoteIndex)
1666 {
1667     int quoteCount = quoteIndex;
1668     int i;
1669
1670     /* If we are not in a quoted section, then we are not an end-quote */
1671     if(quoteIndex == 0)
1672     {
1673         return FALSE;
1674     }
1675
1676     /* Check how many quotes are left for this parameter */
1677     for(i=0;quote[i];i++)
1678     {
1679         if(quote[i] == '"')
1680         {
1681             quoteCount++;
1682         }
1683
1684         /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1685         else if(((quoteCount % 2) == 0)
1686             && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1687         {
1688             break;
1689         }
1690     }
1691
1692     /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1693        be an end-quote */
1694     if(quoteIndex >= (quoteCount / 2))
1695     {
1696         return TRUE;
1697     }
1698
1699     /* No cigar */
1700     return FALSE;
1701 }
1702
1703 /***************************************************************************
1704  * WCMD_ReadAndParseLine
1705  *
1706  *   Either uses supplied input or
1707  *     Reads a file from the handle, and then...
1708  *   Parse the text buffer, spliting into separate commands
1709  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1710  *            following an &&
1711  *     - ( as the first character just ups the bracket depth
1712  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1713  *            adds a CMD_LIST structure with null command
1714  *     - Anything else gets put into the command string (including
1715  *            redirects)
1716  */
1717 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1718
1719     WCHAR    *curPos;
1720     int       inQuotes = 0;
1721     WCHAR     curString[MAXSTRING];
1722     int       curStringLen = 0;
1723     WCHAR     curRedirs[MAXSTRING];
1724     int       curRedirsLen = 0;
1725     WCHAR    *curCopyTo;
1726     int      *curLen;
1727     int       curDepth = 0;
1728     CMD_LIST *lastEntry = NULL;
1729     CMD_DELIMITERS prevDelim = CMD_NONE;
1730     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1731     const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1732     const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1733     const WCHAR ifCmd[]  = {'i','f',' ','\0'};
1734     const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1735     BOOL      inRem = FALSE;
1736     BOOL      inFor = FALSE;
1737     BOOL      inIn  = FALSE;
1738     BOOL      inIf  = FALSE;
1739     BOOL      inElse= FALSE;
1740     BOOL      onlyWhiteSpace = FALSE;
1741     BOOL      lastWasWhiteSpace = FALSE;
1742     BOOL      lastWasDo   = FALSE;
1743     BOOL      lastWasIn   = FALSE;
1744     BOOL      lastWasElse = FALSE;
1745     BOOL      lastWasRedirect = TRUE;
1746
1747     /* Allocate working space for a command read from keyboard, file etc */
1748     if (!extraSpace)
1749       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1750     if (!extraSpace)
1751     {
1752         WINE_ERR("Could not allocate memory for extraSpace\n");
1753         return NULL;
1754     }
1755
1756     /* If initial command read in, use that, otherwise get input from handle */
1757     if (optionalcmd != NULL) {
1758         strcpyW(extraSpace, optionalcmd);
1759     } else if (readFrom == INVALID_HANDLE_VALUE) {
1760         WINE_FIXME("No command nor handle supplied\n");
1761     } else {
1762         if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1763     }
1764     curPos = extraSpace;
1765
1766     /* Handle truncated input - issue warning */
1767     if (strlenW(extraSpace) == MAXSTRING -1) {
1768         WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1769         WCMD_output_asis(extraSpace);
1770         WCMD_output_asis(newline);
1771     }
1772
1773     /* Replace env vars if in a batch context */
1774     if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1775
1776     /* Start with an empty string, copying to the command string */
1777     curStringLen = 0;
1778     curRedirsLen = 0;
1779     curCopyTo    = curString;
1780     curLen       = &curStringLen;
1781     lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
1782
1783     /* Parse every character on the line being processed */
1784     while (*curPos != 0x00) {
1785
1786       WCHAR thisChar;
1787
1788       /* Debugging AID:
1789       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1790                  lastWasWhiteSpace, onlyWhiteSpace);
1791       */
1792
1793       /* Certain commands need special handling */
1794       if (curStringLen == 0 && curCopyTo == curString) {
1795         const WCHAR forDO[]  = {'d','o',' ','\0'};
1796
1797         /* If command starts with 'rem', ignore any &&, ( etc */
1798         if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1799           curPos, 4, remCmd, -1) == 2) {
1800           inRem = TRUE;
1801
1802         /* If command starts with 'for', handle ('s mid line after IN or DO */
1803         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1804           curPos, 4, forCmd, -1) == 2) {
1805           inFor = TRUE;
1806
1807         /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1808            is only true in the command portion of the IF statement, but this
1809            should suffice for now
1810             FIXME: Silly syntax like "if 1(==1( (
1811                                         echo they equal
1812                                       )" will be parsed wrong */
1813         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1814           curPos, 3, ifCmd, -1) == 2) {
1815           inIf = TRUE;
1816
1817         } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1818           curPos, 5, ifElse, -1) == 2) {
1819           inElse = TRUE;
1820           lastWasElse = TRUE;
1821           onlyWhiteSpace = TRUE;
1822           memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1823           (*curLen)+=5;
1824           curPos+=5;
1825           continue;
1826
1827         /* In a for loop, the DO command will follow a close bracket followed by
1828            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1829            is then 0, and all whitespace is skipped                                */
1830         } else if (inFor &&
1831                    (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1832                     curPos, 3, forDO, -1) == 2)) {
1833           WINE_TRACE("Found DO\n");
1834           lastWasDo = TRUE;
1835           onlyWhiteSpace = TRUE;
1836           memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1837           (*curLen)+=3;
1838           curPos+=3;
1839           continue;
1840         }
1841       } else if (curCopyTo == curString) {
1842
1843         /* Special handling for the 'FOR' command */
1844         if (inFor && lastWasWhiteSpace) {
1845           const WCHAR forIN[] = {'i','n',' ','\0'};
1846
1847           WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1848
1849           if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1850               curPos, 3, forIN, -1) == 2) {
1851             WINE_TRACE("Found IN\n");
1852             lastWasIn = TRUE;
1853             onlyWhiteSpace = TRUE;
1854             memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1855             (*curLen)+=3;
1856             curPos+=3;
1857             continue;
1858           }
1859         }
1860       }
1861
1862       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1863          so just use the default processing ie skip character specific
1864          matching below                                                    */
1865       if (!inRem) thisChar = *curPos;
1866       else        thisChar = 'X';  /* Character with no special processing */
1867
1868       lastWasWhiteSpace = FALSE; /* Will be reset below */
1869
1870       switch (thisChar) {
1871
1872       case '=': /* drop through - ignore token delimiters at the start of a command */
1873       case ',': /* drop through - ignore token delimiters at the start of a command */
1874       case '\t':/* drop through - ignore token delimiters at the start of a command */
1875       case ' ':
1876                 /* If a redirect in place, it ends here */
1877                 if (!inQuotes && !lastWasRedirect) {
1878
1879                   /* If finishing off a redirect, add a whitespace delimiter */
1880                   if (curCopyTo == curRedirs) {
1881                       curCopyTo[(*curLen)++] = ' ';
1882                   }
1883                   curCopyTo = curString;
1884                   curLen = &curStringLen;
1885                 }
1886                 if (*curLen > 0) {
1887                   curCopyTo[(*curLen)++] = *curPos;
1888                 }
1889
1890                 /* Remember just processed whitespace */
1891                 lastWasWhiteSpace = TRUE;
1892
1893                 break;
1894
1895       case '>': /* drop through - handle redirect chars the same */
1896       case '<':
1897                 /* Make a redirect start here */
1898                 if (!inQuotes) {
1899                   curCopyTo = curRedirs;
1900                   curLen = &curRedirsLen;
1901                   lastWasRedirect = TRUE;
1902                 }
1903
1904                 /* See if 1>, 2> etc, in which case we have some patching up
1905                    to do                                                     */
1906                 if (curPos != extraSpace &&
1907                     *(curPos-1)>='1' && *(curPos-1)<='9') {
1908
1909                     curStringLen--;
1910                     curString[curStringLen] = 0x00;
1911                     curCopyTo[(*curLen)++] = *(curPos-1);
1912                 }
1913
1914                 curCopyTo[(*curLen)++] = *curPos;
1915
1916                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1917                     do not process that ampersand as an AND operator */
1918                 if (thisChar == '>' && *(curPos+1) == '&') {
1919                     curCopyTo[(*curLen)++] = *(curPos+1);
1920                     curPos++;
1921                 }
1922                 break;
1923
1924       case '|': /* Pipe character only if not || */
1925                 if (!inQuotes) {
1926                   lastWasRedirect = FALSE;
1927
1928                   /* Add an entry to the command list */
1929                   if (curStringLen > 0) {
1930
1931                     /* Add the current command */
1932                     WCMD_addCommand(curString, &curStringLen,
1933                           curRedirs, &curRedirsLen,
1934                           &curCopyTo, &curLen,
1935                           prevDelim, curDepth,
1936                           &lastEntry, output);
1937
1938                   }
1939
1940                   if (*(curPos+1) == '|') {
1941                     curPos++; /* Skip other | */
1942                     prevDelim = CMD_ONFAILURE;
1943                   } else {
1944                     prevDelim = CMD_PIPE;
1945                   }
1946                 } else {
1947                   curCopyTo[(*curLen)++] = *curPos;
1948                 }
1949                 break;
1950
1951       case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1952                     inQuotes--;
1953                 } else {
1954                     inQuotes++; /* Quotes within quotes are fun! */
1955                 }
1956                 curCopyTo[(*curLen)++] = *curPos;
1957                 lastWasRedirect = FALSE;
1958                 break;
1959
1960       case '(': /* If a '(' is the first non whitespace in a command portion
1961                    ie start of line or just after &&, then we read until an
1962                    unquoted ) is found                                       */
1963                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1964                            ", for(%d, In:%d, Do:%d)"
1965                            ", if(%d, else:%d, lwe:%d)\n",
1966                            *curLen, inQuotes,
1967                            onlyWhiteSpace,
1968                            inFor, lastWasIn, lastWasDo,
1969                            inIf, inElse, lastWasElse);
1970                 lastWasRedirect = FALSE;
1971
1972                 /* Ignore open brackets inside the for set */
1973                 if (*curLen == 0 && !inIn) {
1974                   curDepth++;
1975
1976                 /* If in quotes, ignore brackets */
1977                 } else if (inQuotes) {
1978                   curCopyTo[(*curLen)++] = *curPos;
1979
1980                 /* In a FOR loop, an unquoted '(' may occur straight after
1981                       IN or DO
1982                    In an IF statement just handle it regardless as we don't
1983                       parse the operands
1984                    In an ELSE statement, only allow it straight away after
1985                       the ELSE and whitespace
1986                  */
1987                 } else if (inIf ||
1988                            (inElse && lastWasElse && onlyWhiteSpace) ||
1989                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
1990
1991                    /* If entering into an 'IN', set inIn */
1992                   if (inFor && lastWasIn && onlyWhiteSpace) {
1993                     WINE_TRACE("Inside an IN\n");
1994                     inIn = TRUE;
1995                   }
1996
1997                   /* Add the current command */
1998                   WCMD_addCommand(curString, &curStringLen,
1999                                   curRedirs, &curRedirsLen,
2000                                   &curCopyTo, &curLen,
2001                                   prevDelim, curDepth,
2002                                   &lastEntry, output);
2003
2004                   curDepth++;
2005                 } else {
2006                   curCopyTo[(*curLen)++] = *curPos;
2007                 }
2008                 break;
2009
2010       case '&': if (!inQuotes) {
2011                   lastWasRedirect = FALSE;
2012
2013                   /* Add an entry to the command list */
2014                   if (curStringLen > 0) {
2015
2016                     /* Add the current command */
2017                     WCMD_addCommand(curString, &curStringLen,
2018                           curRedirs, &curRedirsLen,
2019                           &curCopyTo, &curLen,
2020                           prevDelim, curDepth,
2021                           &lastEntry, output);
2022
2023                   }
2024
2025                   if (*(curPos+1) == '&') {
2026                     curPos++; /* Skip other & */
2027                     prevDelim = CMD_ONSUCCESS;
2028                   } else {
2029                     prevDelim = CMD_NONE;
2030                   }
2031                 } else {
2032                   curCopyTo[(*curLen)++] = *curPos;
2033                 }
2034                 break;
2035
2036       case ')': if (!inQuotes && curDepth > 0) {
2037                   lastWasRedirect = FALSE;
2038
2039                   /* Add the current command if there is one */
2040                   if (curStringLen) {
2041
2042                       /* Add the current command */
2043                       WCMD_addCommand(curString, &curStringLen,
2044                             curRedirs, &curRedirsLen,
2045                             &curCopyTo, &curLen,
2046                             prevDelim, curDepth,
2047                             &lastEntry, output);
2048                   }
2049
2050                   /* Add an empty entry to the command list */
2051                   prevDelim = CMD_NONE;
2052                   WCMD_addCommand(NULL, &curStringLen,
2053                         curRedirs, &curRedirsLen,
2054                         &curCopyTo, &curLen,
2055                         prevDelim, curDepth,
2056                         &lastEntry, output);
2057                   curDepth--;
2058
2059                   /* Leave inIn if necessary */
2060                   if (inIn) inIn =  FALSE;
2061                 } else {
2062                   curCopyTo[(*curLen)++] = *curPos;
2063                 }
2064                 break;
2065       default:
2066                 lastWasRedirect = FALSE;
2067                 curCopyTo[(*curLen)++] = *curPos;
2068       }
2069
2070       curPos++;
2071
2072       /* At various times we need to know if we have only skipped whitespace,
2073          so reset this variable and then it will remain true until a non
2074          whitespace is found                                               */
2075       if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2076
2077       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2078       if (!lastWasWhiteSpace) {
2079         lastWasIn = lastWasDo = FALSE;
2080       }
2081
2082       /* If we have reached the end, add this command into the list */
2083       if (*curPos == 0x00 && *curLen > 0) {
2084
2085           /* Add an entry to the command list */
2086           WCMD_addCommand(curString, &curStringLen,
2087                 curRedirs, &curRedirsLen,
2088                 &curCopyTo, &curLen,
2089                 prevDelim, curDepth,
2090                 &lastEntry, output);
2091       }
2092
2093       /* If we have reached the end of the string, see if bracketing outstanding */
2094       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2095         inRem = FALSE;
2096         prevDelim = CMD_NONE;
2097         inQuotes = 0;
2098         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2099
2100         /* Read more, skipping any blank lines */
2101         while (*extraSpace == 0x00) {
2102           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2103           if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2104         }
2105         curPos = extraSpace;
2106         if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2107       }
2108     }
2109
2110     /* Dump out the parsed output */
2111     WCMD_DumpCommands(*output);
2112
2113     return extraSpace;
2114 }
2115
2116 /***************************************************************************
2117  * WCMD_process_commands
2118  *
2119  * Process all the commands read in so far
2120  */
2121 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2122                                 WCHAR *var, WCHAR *val) {
2123
2124     int bdepth = -1;
2125
2126     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2127
2128     /* Loop through the commands, processing them one by one */
2129     while (thisCmd) {
2130
2131       CMD_LIST *origCmd = thisCmd;
2132
2133       /* If processing one bracket only, and we find the end bracket
2134          entry (or less), return                                    */
2135       if (oneBracket && !thisCmd->command &&
2136           bdepth <= thisCmd->bracketDepth) {
2137         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2138                    thisCmd, thisCmd->nextcommand);
2139         return thisCmd->nextcommand;
2140       }
2141
2142       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2143          about them and it will be handled in there)
2144          Also, skip over any batch labels (eg. :fred)          */
2145       if (thisCmd->command && thisCmd->command[0] != ':') {
2146         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2147         WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2148       }
2149
2150       /* Step on unless the command itself already stepped on */
2151       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2152     }
2153     return NULL;
2154 }
2155
2156 /***************************************************************************
2157  * WCMD_free_commands
2158  *
2159  * Frees the storage held for a parsed command line
2160  * - This is not done in the process_commands, as eventually the current
2161  *   pointer will be modified within the commands, and hence a single free
2162  *   routine is simpler
2163  */
2164 void WCMD_free_commands(CMD_LIST *cmds) {
2165
2166     /* Loop through the commands, freeing them one by one */
2167     while (cmds) {
2168       CMD_LIST *thisCmd = cmds;
2169       cmds = cmds->nextcommand;
2170       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2171       HeapFree(GetProcessHeap(), 0, thisCmd);
2172     }
2173 }
2174
2175
2176 /*****************************************************************************
2177  * Main entry point. This is a console application so we have a main() not a
2178  * winmain().
2179  */
2180
2181 int wmain (int argc, WCHAR *argvW[])
2182 {
2183   int     args;
2184   WCHAR  *cmd   = NULL;
2185   WCHAR string[1024];
2186   WCHAR envvar[4];
2187   HANDLE h;
2188   int opt_q;
2189   int opt_t = 0;
2190   static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2191                                    'b','a','t','\0'};
2192   char ansiVersion[100];
2193   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2194
2195   srand(time(NULL));
2196
2197   /* Pre initialize some messages */
2198   strcpy(ansiVersion, PACKAGE_VERSION);
2199   MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2200   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2201   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2202
2203   args  = argc;
2204   opt_c=opt_k=opt_q=opt_s=0;
2205   while (args > 0)
2206   {
2207       WCHAR c;
2208       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2209       if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2210           argvW++;
2211           args--;
2212           continue;
2213       }
2214
2215       c=(*argvW)[1];
2216       if (tolowerW(c)=='c') {
2217           opt_c=1;
2218       } else if (tolowerW(c)=='q') {
2219           opt_q=1;
2220       } else if (tolowerW(c)=='k') {
2221           opt_k=1;
2222       } else if (tolowerW(c)=='s') {
2223           opt_s=1;
2224       } else if (tolowerW(c)=='a') {
2225           unicodePipes=FALSE;
2226       } else if (tolowerW(c)=='u') {
2227           unicodePipes=TRUE;
2228       } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2229           opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2230       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2231           /* Ignored for compatibility with Windows */
2232       }
2233
2234       if ((*argvW)[2]==0) {
2235           argvW++;
2236           args--;
2237       }
2238       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2239       {
2240           *argvW+=2;
2241       }
2242
2243       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2244           break;
2245   }
2246
2247   if (opt_q) {
2248     const WCHAR eoff[] = {'O','F','F','\0'};
2249     WCMD_echo(eoff);
2250   }
2251
2252   if (opt_c || opt_k) {
2253       int     len,qcount;
2254       WCHAR** arg;
2255       int     argsLeft;
2256       WCHAR*  p;
2257
2258       /* opt_s left unflagged if the command starts with and contains exactly
2259        * one quoted string (exactly two quote characters). The quoted string
2260        * must be an executable name that has whitespace and must not have the
2261        * following characters: &<>()@^| */
2262
2263       /* Build the command to execute */
2264       len = 0;
2265       qcount = 0;
2266       argsLeft = args;
2267       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2268       {
2269           int has_space,bcount;
2270           WCHAR* a;
2271
2272           has_space=0;
2273           bcount=0;
2274           a=*arg;
2275           if( !*a ) has_space=1;
2276           while (*a!='\0') {
2277               if (*a=='\\') {
2278                   bcount++;
2279               } else {
2280                   if (*a==' ' || *a=='\t') {
2281                       has_space=1;
2282                   } else if (*a=='"') {
2283                       /* doubling of '\' preceding a '"',
2284                        * plus escaping of said '"'
2285                        */
2286                       len+=2*bcount+1;
2287                       qcount++;
2288                   }
2289                   bcount=0;
2290               }
2291               a++;
2292           }
2293           len+=(a-*arg) + 1; /* for the separating space */
2294           if (has_space)
2295           {
2296               len+=2; /* for the quotes */
2297               qcount+=2;
2298           }
2299       }
2300
2301       if (qcount!=2)
2302           opt_s=1;
2303
2304       /* check argvW[0] for a space and invalid characters */
2305       if (!opt_s) {
2306           opt_s=1;
2307           p=*argvW;
2308           while (*p!='\0') {
2309               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2310                   || *p=='@' || *p=='^' || *p=='|') {
2311                   opt_s=1;
2312                   break;
2313               }
2314               if (*p==' ')
2315                   opt_s=0;
2316               p++;
2317           }
2318       }
2319
2320       cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2321       if (!cmd)
2322           exit(1);
2323
2324       p = cmd;
2325       argsLeft = args;
2326       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2327       {
2328           int has_space,has_quote;
2329           WCHAR* a;
2330
2331           /* Check for quotes and spaces in this argument */
2332           has_space=has_quote=0;
2333           a=*arg;
2334           if( !*a ) has_space=1;
2335           while (*a!='\0') {
2336               if (*a==' ' || *a=='\t') {
2337                   has_space=1;
2338                   if (has_quote)
2339                       break;
2340               } else if (*a=='"') {
2341                   has_quote=1;
2342                   if (has_space)
2343                       break;
2344               }
2345               a++;
2346           }
2347
2348           /* Now transfer it to the command line */
2349           if (has_space)
2350               *p++='"';
2351           if (has_quote) {
2352               int bcount;
2353               WCHAR* a;
2354
2355               bcount=0;
2356               a=*arg;
2357               while (*a!='\0') {
2358                   if (*a=='\\') {
2359                       *p++=*a;
2360                       bcount++;
2361                   } else {
2362                       if (*a=='"') {
2363                           int i;
2364
2365                           /* Double all the '\\' preceding this '"', plus one */
2366                           for (i=0;i<=bcount;i++)
2367                               *p++='\\';
2368                           *p++='"';
2369                       } else {
2370                           *p++=*a;
2371                       }
2372                       bcount=0;
2373                   }
2374                   a++;
2375               }
2376           } else {
2377               strcpyW(p,*arg);
2378               p+=strlenW(*arg);
2379           }
2380           if (has_space)
2381               *p++='"';
2382           *p++=' ';
2383       }
2384       if (p > cmd)
2385           p--;  /* remove last space */
2386       *p = '\0';
2387
2388       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2389
2390       /* strip first and last quote characters if opt_s; check for invalid
2391        * executable is done later */
2392       if (opt_s && *cmd=='\"')
2393           WCMD_opt_s_strip_quotes(cmd);
2394   }
2395
2396   if (opt_c) {
2397       /* If we do a "wcmd /c command", we don't want to allocate a new
2398        * console since the command returns immediately. Rather, we use
2399        * the currently allocated input and output handles. This allows
2400        * us to pipe to and read from the command interpreter.
2401        */
2402
2403       /* Parse the command string, without reading any more input */
2404       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2405       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2406       WCMD_free_commands(toExecute);
2407       toExecute = NULL;
2408
2409       HeapFree(GetProcessHeap(), 0, cmd);
2410       return errorlevel;
2411   }
2412
2413   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2414                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2415   SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2416
2417   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2418   if (opt_t) {
2419       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2420           defaultColor = opt_t & 0xFF;
2421           param1[0] = 0x00;
2422           WCMD_color();
2423       }
2424   } else {
2425       /* Check HKCU\Software\Microsoft\Command Processor
2426          Then  HKLM\Software\Microsoft\Command Processor
2427            for defaultcolour value
2428            Note  Can be supplied as DWORD or REG_SZ
2429            Note2 When supplied as REG_SZ it's in decimal!!! */
2430       HKEY key;
2431       DWORD type;
2432       DWORD value=0, size=4;
2433       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2434                                       'M','i','c','r','o','s','o','f','t','\\',
2435                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2436       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2437
2438       if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2439                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2440           WCHAR  strvalue[4];
2441
2442           /* See if DWORD or REG_SZ */
2443           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2444                      NULL, NULL) == ERROR_SUCCESS) {
2445               if (type == REG_DWORD) {
2446                   size = sizeof(DWORD);
2447                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2448                                   (LPBYTE)&value, &size);
2449               } else if (type == REG_SZ) {
2450                   size = sizeof(strvalue)/sizeof(WCHAR);
2451                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2452                                   (LPBYTE)strvalue, &size);
2453                   value = strtoulW(strvalue, NULL, 10);
2454               }
2455           }
2456           RegCloseKey(key);
2457       }
2458
2459       if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2460                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2461           WCHAR  strvalue[4];
2462
2463           /* See if DWORD or REG_SZ */
2464           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2465                      NULL, NULL) == ERROR_SUCCESS) {
2466               if (type == REG_DWORD) {
2467                   size = sizeof(DWORD);
2468                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2469                                   (LPBYTE)&value, &size);
2470               } else if (type == REG_SZ) {
2471                   size = sizeof(strvalue)/sizeof(WCHAR);
2472                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2473                                   (LPBYTE)strvalue, &size);
2474                   value = strtoulW(strvalue, NULL, 10);
2475               }
2476           }
2477           RegCloseKey(key);
2478       }
2479
2480       /* If one found, set the screen to that colour */
2481       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2482           defaultColor = value & 0xFF;
2483           param1[0] = 0x00;
2484           WCMD_color();
2485       }
2486
2487   }
2488
2489   /* Save cwd into appropriate env var */
2490   GetCurrentDirectoryW(1024, string);
2491   if (IsCharAlphaW(string[0]) && string[1] == ':') {
2492     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2493     wsprintfW(envvar, fmt, string[0]);
2494     SetEnvironmentVariableW(envvar, string);
2495     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2496   }
2497
2498   if (opt_k) {
2499       /* Parse the command string, without reading any more input */
2500       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2501       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2502       WCMD_free_commands(toExecute);
2503       toExecute = NULL;
2504       HeapFree(GetProcessHeap(), 0, cmd);
2505   }
2506
2507 /*
2508  *      If there is an AUTOEXEC.BAT file, try to execute it.
2509  */
2510
2511   GetFullPathNameW (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2512   h = CreateFileW(string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2513   if (h != INVALID_HANDLE_VALUE) {
2514     CloseHandle (h);
2515 #if 0
2516     WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2517 #endif
2518   }
2519
2520 /*
2521  *      Loop forever getting commands and executing them.
2522  */
2523
2524   WCMD_version ();
2525   while (TRUE) {
2526
2527     /* Read until EOF (which for std input is never, but if redirect
2528        in place, may occur                                          */
2529     WCMD_show_prompt ();
2530     if (WCMD_ReadAndParseLine(NULL, &toExecute,
2531                               GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2532       break;
2533     WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2534     WCMD_free_commands(toExecute);
2535     toExecute = NULL;
2536   }
2537   return 0;
2538 }