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