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