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