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