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