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