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