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