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