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