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