crypt32: Free the encoded msg (Coverity).
[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 occuring as the command is executed
801  * rather than at parse time, ie 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         if ((opt_c || opt_k) && !opt_s && !status
1217             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1218           /* strip first and last quote WCHARacters and try again */
1219           WCMD_strip_quotes(command);
1220           opt_s = TRUE;
1221           WCMD_run_program(command, called);
1222           return;
1223         }
1224
1225         if (!status)
1226           break;
1227
1228         if (!assumeInternal && !console) errorlevel = 0;
1229         else
1230         {
1231             /* Always wait when non-interactive (cmd /c or in batch program),
1232                or for console applications                                    */
1233             if (assumeInternal || !interactive || !HIWORD(console))
1234               WaitForSingleObject (pe.hProcess, INFINITE);
1235             GetExitCodeProcess (pe.hProcess, &errorlevel);
1236             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1237         }
1238         CloseHandle(pe.hProcess);
1239         CloseHandle(pe.hThread);
1240         return;
1241       }
1242     }
1243   }
1244
1245   /* Not found anywhere - were we called? */
1246   if (called) {
1247     CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
1248
1249     /* Parse the command string, without reading any more input */
1250     WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1251     WCMD_process_commands(toExecute, FALSE, called);
1252     WCMD_free_commands(toExecute);
1253     toExecute = NULL;
1254     return;
1255   }
1256
1257   /* Not found anywhere - give up */
1258   WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1259
1260   /* If a command fails to launch, it sets errorlevel 9009 - which
1261      does not seem to have any associated constant definition     */
1262   errorlevel = 9009;
1263   return;
1264
1265 }
1266
1267 /*****************************************************************************
1268  * Process one command. If the command is EXIT this routine does not return.
1269  * We will recurse through here executing batch files.
1270  * Note: If call is used to a non-existing program, we reparse the line and
1271  *       try to run it as an internal command. 'retrycall' represents whether
1272  *       we are attempting this retry.
1273  */
1274 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1275                    CMD_LIST **cmdList, BOOL retrycall)
1276 {
1277     WCHAR *cmd, *p, *redir;
1278     int status, i;
1279     DWORD count, creationDisposition;
1280     HANDLE h;
1281     WCHAR *whichcmd;
1282     SECURITY_ATTRIBUTES sa;
1283     WCHAR *new_cmd = NULL;
1284     WCHAR *new_redir = NULL;
1285     HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1286                                 GetStdHandle (STD_OUTPUT_HANDLE),
1287                                 GetStdHandle (STD_ERROR_HANDLE)};
1288     DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
1289                                 STD_OUTPUT_HANDLE,
1290                                 STD_ERROR_HANDLE};
1291     BOOL prev_echo_mode, piped = FALSE;
1292
1293     WINE_TRACE("command on entry:%s (%p)\n",
1294                wine_dbgstr_w(command), cmdList);
1295
1296     /* If the next command is a pipe then we implement pipes by redirecting
1297        the output from this command to a temp file and input into the
1298        next command from that temp file.
1299        FIXME: Use of named pipes would make more sense here as currently this
1300        process has to finish before the next one can start but this requires
1301        a change to not wait for the first app to finish but rather the pipe  */
1302     if (cmdList && (*cmdList)->nextcommand &&
1303         (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1304
1305         WCHAR temp_path[MAX_PATH];
1306         static const WCHAR cmdW[]     = {'C','M','D','\0'};
1307
1308         /* Remember piping is in action */
1309         WINE_TRACE("Output needs to be piped\n");
1310         piped = TRUE;
1311
1312         /* Generate a unique temporary filename */
1313         GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1314         GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1315         WINE_TRACE("Using temporary file of %s\n",
1316                    wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1317     }
1318
1319     /* Move copy of the command onto the heap so it can be expanded */
1320     new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR));
1321     strcpyW(new_cmd, command);
1322
1323     /* Move copy of the redirects onto the heap so it can be expanded */
1324     new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR));
1325
1326     /* If piped output, send stdout to the pipe by appending >filename to redirects */
1327     if (piped) {
1328         static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1329         wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1330         WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1331     } else {
1332         strcpyW(new_redir, redirects);
1333     }
1334
1335     /* Expand variables in command line mode only (batch mode will
1336        be expanded as the line is read in, except for 'for' loops) */
1337     handleExpansion(new_cmd, (context != NULL), delayedsubst);
1338     handleExpansion(new_redir, (context != NULL), delayedsubst);
1339     cmd = new_cmd;
1340
1341 /*
1342  *      Changing default drive has to be handled as a special case.
1343  */
1344
1345     if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1346       WCHAR envvar[5];
1347       WCHAR dir[MAX_PATH];
1348
1349       /* According to MSDN CreateProcess docs, special env vars record
1350          the current directory on each drive, in the form =C:
1351          so see if one specified, and if so go back to it             */
1352       strcpyW(envvar, equalW);
1353       strcatW(envvar, cmd);
1354       if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1355         static const WCHAR fmt[] = {'%','s','\\','\0'};
1356         wsprintfW(cmd, fmt, cmd);
1357         WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1358       }
1359       WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1360       status = SetCurrentDirectoryW(cmd);
1361       if (!status) WCMD_print_error ();
1362       heap_free(cmd );
1363       heap_free(new_redir);
1364       return;
1365     }
1366
1367     sa.nLength = sizeof(sa);
1368     sa.lpSecurityDescriptor = NULL;
1369     sa.bInheritHandle = TRUE;
1370
1371 /*
1372  *      Redirect stdin, stdout and/or stderr if required.
1373  */
1374
1375     /* STDIN could come from a preceding pipe, so delete on close if it does */
1376     if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1377         WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1378         h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1379                   FILE_SHARE_READ, &sa, OPEN_EXISTING,
1380                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1381         if (h == INVALID_HANDLE_VALUE) {
1382           WCMD_print_error ();
1383           heap_free(cmd);
1384           heap_free(new_redir);
1385           return;
1386         }
1387         SetStdHandle (STD_INPUT_HANDLE, h);
1388
1389         /* No need to remember the temporary name any longer once opened */
1390         (*cmdList)->pipeFile[0] = 0x00;
1391
1392     /* Otherwise STDIN could come from a '<' redirect */
1393     } else if ((p = strchrW(new_redir,'<')) != NULL) {
1394       h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1395                       &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1396       if (h == INVALID_HANDLE_VALUE) {
1397         WCMD_print_error ();
1398         heap_free(cmd);
1399         heap_free(new_redir);
1400         return;
1401       }
1402       SetStdHandle (STD_INPUT_HANDLE, h);
1403     }
1404
1405     /* Scan the whole command looking for > and 2> */
1406     redir = new_redir;
1407     while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1408       int handle = 0;
1409
1410       if (p > redir && (*(p-1)=='2'))
1411         handle = 2;
1412       else
1413         handle = 1;
1414
1415       p++;
1416       if ('>' == *p) {
1417         creationDisposition = OPEN_ALWAYS;
1418         p++;
1419       }
1420       else {
1421         creationDisposition = CREATE_ALWAYS;
1422       }
1423
1424       /* Add support for 2>&1 */
1425       redir = p;
1426       if (*p == '&') {
1427         int idx = *(p+1) - '0';
1428
1429         if (DuplicateHandle(GetCurrentProcess(),
1430                         GetStdHandle(idx_stdhandles[idx]),
1431                         GetCurrentProcess(),
1432                         &h,
1433                         0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1434           WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1435         }
1436         WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1437
1438       } else {
1439         WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
1440         h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1441                         FILE_ATTRIBUTE_NORMAL, NULL);
1442         if (h == INVALID_HANDLE_VALUE) {
1443           WCMD_print_error ();
1444           heap_free(cmd);
1445           heap_free(new_redir);
1446           return;
1447         }
1448         if (SetFilePointer (h, 0, NULL, FILE_END) ==
1449               INVALID_SET_FILE_POINTER) {
1450           WCMD_print_error ();
1451         }
1452         WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1453       }
1454
1455       SetStdHandle (idx_stdhandles[handle], h);
1456     }
1457
1458 /*
1459  * Strip leading whitespaces, and a '@' if supplied
1460  */
1461     whichcmd = WCMD_skip_leading_spaces(cmd);
1462     WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1463     if (whichcmd[0] == '@') whichcmd++;
1464
1465 /*
1466  *      Check if the command entered is internal. If it is, pass the rest of the
1467  *      line down to the command. If not try to run a program.
1468  */
1469
1470     count = 0;
1471     while (IsCharAlphaNumericW(whichcmd[count])) {
1472       count++;
1473     }
1474     for (i=0; i<=WCMD_EXIT; i++) {
1475       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1476         whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1477     }
1478     p = WCMD_skip_leading_spaces (&whichcmd[count]);
1479     WCMD_parse (p, quals, param1, param2);
1480     WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1481
1482     if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1483       /* this is a help request for a builtin program */
1484       i = WCMD_HELP;
1485       memcpy(p, whichcmd, count * sizeof(WCHAR));
1486       p[count] = '\0';
1487
1488     }
1489
1490     switch (i) {
1491
1492       case WCMD_CALL:
1493         WCMD_call (p);
1494         break;
1495       case WCMD_CD:
1496       case WCMD_CHDIR:
1497         WCMD_setshow_default (p);
1498         break;
1499       case WCMD_CLS:
1500         WCMD_clear_screen ();
1501         break;
1502       case WCMD_COPY:
1503         WCMD_copy (p);
1504         break;
1505       case WCMD_CTTY:
1506         WCMD_change_tty ();
1507         break;
1508       case WCMD_DATE:
1509         WCMD_setshow_date ();
1510         break;
1511       case WCMD_DEL:
1512       case WCMD_ERASE:
1513         WCMD_delete (p);
1514         break;
1515       case WCMD_DIR:
1516         WCMD_directory (p);
1517         break;
1518       case WCMD_ECHO:
1519         WCMD_echo(&whichcmd[count]);
1520         break;
1521       case WCMD_GOTO:
1522         WCMD_goto (cmdList);
1523         break;
1524       case WCMD_HELP:
1525         WCMD_give_help (p);
1526         break;
1527       case WCMD_LABEL:
1528         WCMD_volume (TRUE, p);
1529         break;
1530       case WCMD_MD:
1531       case WCMD_MKDIR:
1532         WCMD_create_dir (p);
1533         break;
1534       case WCMD_MOVE:
1535         WCMD_move ();
1536         break;
1537       case WCMD_PATH:
1538         WCMD_setshow_path (p);
1539         break;
1540       case WCMD_PAUSE:
1541         WCMD_pause ();
1542         break;
1543       case WCMD_PROMPT:
1544         WCMD_setshow_prompt ();
1545         break;
1546       case WCMD_REM:
1547         break;
1548       case WCMD_REN:
1549       case WCMD_RENAME:
1550         WCMD_rename ();
1551         break;
1552       case WCMD_RD:
1553       case WCMD_RMDIR:
1554         WCMD_remove_dir (p);
1555         break;
1556       case WCMD_SETLOCAL:
1557         WCMD_setlocal(p);
1558         break;
1559       case WCMD_ENDLOCAL:
1560         WCMD_endlocal();
1561         break;
1562       case WCMD_SET:
1563         WCMD_setshow_env (p);
1564         break;
1565       case WCMD_SHIFT:
1566         WCMD_shift (p);
1567         break;
1568       case WCMD_START:
1569         WCMD_start (p);
1570         break;
1571       case WCMD_TIME:
1572         WCMD_setshow_time ();
1573         break;
1574       case WCMD_TITLE:
1575         if (strlenW(&whichcmd[count]) > 0)
1576           WCMD_title(&whichcmd[count+1]);
1577         break;
1578       case WCMD_TYPE:
1579         WCMD_type (p);
1580         break;
1581       case WCMD_VER:
1582         WCMD_output_asis(newlineW);
1583         WCMD_version ();
1584         break;
1585       case WCMD_VERIFY:
1586         WCMD_verify (p);
1587         break;
1588       case WCMD_VOL:
1589         WCMD_volume (FALSE, p);
1590         break;
1591       case WCMD_PUSHD:
1592         WCMD_pushd(p);
1593         break;
1594       case WCMD_POPD:
1595         WCMD_popd();
1596         break;
1597       case WCMD_ASSOC:
1598         WCMD_assoc(p, TRUE);
1599         break;
1600       case WCMD_COLOR:
1601         WCMD_color();
1602         break;
1603       case WCMD_FTYPE:
1604         WCMD_assoc(p, FALSE);
1605         break;
1606       case WCMD_MORE:
1607         WCMD_more(p);
1608         break;
1609       case WCMD_CHOICE:
1610         WCMD_choice(p);
1611         break;
1612       case WCMD_EXIT:
1613         WCMD_exit (cmdList);
1614         break;
1615       case WCMD_FOR:
1616       case WCMD_IF:
1617         /* Very oddly, probably because of all the special parsing required for
1618            these two commands, neither for nor if are supported when called,
1619            ie call if 1==1... will fail.                                        */
1620         if (!retrycall) {
1621           if (i==WCMD_FOR) WCMD_for (p, cmdList);
1622           else if (i==WCMD_IF) WCMD_if (p, cmdList);
1623           break;
1624         }
1625         /* else: drop through */
1626       default:
1627         prev_echo_mode = echo_mode;
1628         WCMD_run_program (whichcmd, FALSE);
1629         echo_mode = prev_echo_mode;
1630     }
1631     heap_free(cmd);
1632     heap_free(new_redir);
1633
1634     /* Restore old handles */
1635     for (i=0; i<3; i++) {
1636       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1637         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1638         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1639       }
1640     }
1641 }
1642
1643 /*************************************************************************
1644  * WCMD_LoadMessage
1645  *    Load a string from the resource file, handling any error
1646  *    Returns string retrieved from resource file
1647  */
1648 WCHAR *WCMD_LoadMessage(UINT id) {
1649     static WCHAR msg[2048];
1650     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1651
1652     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1653        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1654        strcpyW(msg, failedMsg);
1655     }
1656     return msg;
1657 }
1658
1659 /***************************************************************************
1660  * WCMD_DumpCommands
1661  *
1662  *      Dumps out the parsed command line to ensure syntax is correct
1663  */
1664 static void WCMD_DumpCommands(CMD_LIST *commands) {
1665     CMD_LIST *thisCmd = commands;
1666
1667     WINE_TRACE("Parsed line:\n");
1668     while (thisCmd != NULL) {
1669       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1670                thisCmd,
1671                thisCmd->prevDelim,
1672                thisCmd->bracketDepth,
1673                thisCmd->nextcommand,
1674                wine_dbgstr_w(thisCmd->command),
1675                wine_dbgstr_w(thisCmd->redirects));
1676       thisCmd = thisCmd->nextcommand;
1677     }
1678 }
1679
1680 /***************************************************************************
1681  * WCMD_addCommand
1682  *
1683  *   Adds a command to the current command list
1684  */
1685 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1686                      WCHAR *redirs,  int *redirLen,
1687                      WCHAR **copyTo, int **copyToLen,
1688                      CMD_DELIMITERS prevDelim, int curDepth,
1689                      CMD_LIST **lastEntry, CMD_LIST **output) {
1690
1691     CMD_LIST *thisEntry = NULL;
1692
1693     /* Allocate storage for command */
1694     thisEntry = heap_alloc(sizeof(CMD_LIST));
1695
1696     /* Copy in the command */
1697     if (command) {
1698         thisEntry->command = heap_alloc((*commandLen+1) * sizeof(WCHAR));
1699         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1700         thisEntry->command[*commandLen] = 0x00;
1701
1702         /* Copy in the redirects */
1703         thisEntry->redirects = heap_alloc((*redirLen+1) * sizeof(WCHAR));
1704         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1705         thisEntry->redirects[*redirLen] = 0x00;
1706         thisEntry->pipeFile[0] = 0x00;
1707
1708         /* Reset the lengths */
1709         *commandLen   = 0;
1710         *redirLen     = 0;
1711         *copyToLen    = commandLen;
1712         *copyTo       = command;
1713
1714     } else {
1715         thisEntry->command = NULL;
1716         thisEntry->redirects = NULL;
1717         thisEntry->pipeFile[0] = 0x00;
1718     }
1719
1720     /* Fill in other fields */
1721     thisEntry->nextcommand = NULL;
1722     thisEntry->prevDelim = prevDelim;
1723     thisEntry->bracketDepth = curDepth;
1724     if (*lastEntry) {
1725         (*lastEntry)->nextcommand = thisEntry;
1726     } else {
1727         *output = thisEntry;
1728     }
1729     *lastEntry = thisEntry;
1730 }
1731
1732
1733 /***************************************************************************
1734  * WCMD_IsEndQuote
1735  *
1736  *   Checks if the quote pointed to is the end-quote.
1737  *
1738  *   Quotes end if:
1739  *
1740  *   1) The current parameter ends at EOL or at the beginning
1741  *      of a redirection or pipe and not in a quote section.
1742  *
1743  *   2) If the next character is a space and not in a quote section.
1744  *
1745  *   Returns TRUE if this is an end quote, and FALSE if it is not.
1746  *
1747  */
1748 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1749 {
1750     int quoteCount = quoteIndex;
1751     int i;
1752
1753     /* If we are not in a quoted section, then we are not an end-quote */
1754     if(quoteIndex == 0)
1755     {
1756         return FALSE;
1757     }
1758
1759     /* Check how many quotes are left for this parameter */
1760     for(i=0;quote[i];i++)
1761     {
1762         if(quote[i] == '"')
1763         {
1764             quoteCount++;
1765         }
1766
1767         /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1768         else if(((quoteCount % 2) == 0)
1769             && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1770         {
1771             break;
1772         }
1773     }
1774
1775     /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1776        be an end-quote */
1777     if(quoteIndex >= (quoteCount / 2))
1778     {
1779         return TRUE;
1780     }
1781
1782     /* No cigar */
1783     return FALSE;
1784 }
1785
1786 /***************************************************************************
1787  * WCMD_ReadAndParseLine
1788  *
1789  *   Either uses supplied input or
1790  *     Reads a file from the handle, and then...
1791  *   Parse the text buffer, splitting into separate commands
1792  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1793  *            following an &&
1794  *     - ( as the first character just ups the bracket depth
1795  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1796  *            adds a CMD_LIST structure with null command
1797  *     - Anything else gets put into the command string (including
1798  *            redirects)
1799  */
1800 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1801 {
1802     WCHAR    *curPos;
1803     int       inQuotes = 0;
1804     WCHAR     curString[MAXSTRING];
1805     int       curStringLen = 0;
1806     WCHAR     curRedirs[MAXSTRING];
1807     int       curRedirsLen = 0;
1808     WCHAR    *curCopyTo;
1809     int      *curLen;
1810     int       curDepth = 0;
1811     CMD_LIST *lastEntry = NULL;
1812     CMD_DELIMITERS prevDelim = CMD_NONE;
1813     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1814     static const WCHAR remCmd[] = {'r','e','m'};
1815     static const WCHAR forCmd[] = {'f','o','r'};
1816     static const WCHAR ifCmd[]  = {'i','f'};
1817     static const WCHAR ifElse[] = {'e','l','s','e'};
1818     BOOL      inRem = FALSE;
1819     BOOL      inFor = FALSE;
1820     BOOL      inIn  = FALSE;
1821     BOOL      inIf  = FALSE;
1822     BOOL      inElse= FALSE;
1823     BOOL      onlyWhiteSpace = FALSE;
1824     BOOL      lastWasWhiteSpace = FALSE;
1825     BOOL      lastWasDo   = FALSE;
1826     BOOL      lastWasIn   = FALSE;
1827     BOOL      lastWasElse = FALSE;
1828     BOOL      lastWasRedirect = TRUE;
1829     BOOL      lastWasCaret = FALSE;
1830
1831     /* Allocate working space for a command read from keyboard, file etc */
1832     if (!extraSpace)
1833         extraSpace = heap_alloc((MAXSTRING+1) * sizeof(WCHAR));
1834     if (!extraSpace)
1835     {
1836         WINE_ERR("Could not allocate memory for extraSpace\n");
1837         return NULL;
1838     }
1839
1840     /* If initial command read in, use that, otherwise get input from handle */
1841     if (optionalcmd != NULL) {
1842         strcpyW(extraSpace, optionalcmd);
1843     } else if (readFrom == INVALID_HANDLE_VALUE) {
1844         WINE_FIXME("No command nor handle supplied\n");
1845     } else {
1846         if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1847           return NULL;
1848     }
1849     curPos = extraSpace;
1850
1851     /* Handle truncated input - issue warning */
1852     if (strlenW(extraSpace) == MAXSTRING -1) {
1853         WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1854         WCMD_output_asis_stderr(extraSpace);
1855         WCMD_output_asis_stderr(newlineW);
1856     }
1857
1858     /* Replace env vars if in a batch context */
1859     if (context) handleExpansion(extraSpace, FALSE, FALSE);
1860
1861     /* Skip preceding whitespace */
1862     while (*curPos == ' ' || *curPos == '\t') curPos++;
1863
1864     /* Show prompt before batch line IF echo is on and in batch program */
1865     if (context && echo_mode && *curPos && (*curPos != '@')) {
1866       static const WCHAR echoDot[] = {'e','c','h','o','.'};
1867       static const WCHAR echoCol[] = {'e','c','h','o',':'};
1868       const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1869       DWORD curr_size = strlenW(curPos);
1870       DWORD min_len = (curr_size < len ? curr_size : len);
1871       WCMD_show_prompt();
1872       WCMD_output_asis(curPos);
1873       /* I don't know why Windows puts a space here but it does */
1874       /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1875       if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1876                          curPos, min_len, echoDot, len) != CSTR_EQUAL
1877           && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1878                          curPos, min_len, echoCol, len) != CSTR_EQUAL)
1879       {
1880           WCMD_output_asis(spaceW);
1881       }
1882       WCMD_output_asis(newlineW);
1883     }
1884
1885     /* Skip repeated 'no echo' characters */
1886     while (*curPos == '@') curPos++;
1887
1888     /* Start with an empty string, copying to the command string */
1889     curStringLen = 0;
1890     curRedirsLen = 0;
1891     curCopyTo    = curString;
1892     curLen       = &curStringLen;
1893     lastWasRedirect = FALSE;  /* Required e.g. for spaces between > and filename */
1894
1895     /* Parse every character on the line being processed */
1896     while (*curPos != 0x00) {
1897
1898       WCHAR thisChar;
1899
1900       /* Debugging AID:
1901       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1902                  lastWasWhiteSpace, onlyWhiteSpace);
1903       */
1904
1905       /* Prevent overflow caused by the caret escape char */
1906       if (*curLen >= MAXSTRING) {
1907         WINE_ERR("Overflow detected in command\n");
1908         return NULL;
1909       }
1910
1911       /* Certain commands need special handling */
1912       if (curStringLen == 0 && curCopyTo == curString) {
1913         static const WCHAR forDO[] = {'d','o'};
1914
1915         /* If command starts with 'rem ', ignore any &&, ( etc. */
1916         if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1917           inRem = TRUE;
1918
1919         } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1920           inFor = TRUE;
1921
1922         /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1923            is only true in the command portion of the IF statement, but this
1924            should suffice for now
1925             FIXME: Silly syntax like "if 1(==1( (
1926                                         echo they equal
1927                                       )" will be parsed wrong */
1928         } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1929           inIf = TRUE;
1930
1931         } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1932           const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1933           inElse = TRUE;
1934           lastWasElse = TRUE;
1935           onlyWhiteSpace = TRUE;
1936           memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1937           (*curLen)+=keyw_len;
1938           curPos+=keyw_len;
1939           continue;
1940
1941         /* In a for loop, the DO command will follow a close bracket followed by
1942            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1943            is then 0, and all whitespace is skipped                                */
1944         } else if (inFor &&
1945                    WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1946           const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1947           WINE_TRACE("Found 'DO '\n");
1948           lastWasDo = TRUE;
1949           onlyWhiteSpace = TRUE;
1950           memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1951           (*curLen)+=keyw_len;
1952           curPos+=keyw_len;
1953           continue;
1954         }
1955       } else if (curCopyTo == curString) {
1956
1957         /* Special handling for the 'FOR' command */
1958         if (inFor && lastWasWhiteSpace) {
1959           static const WCHAR forIN[] = {'i','n'};
1960
1961           WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1962
1963           if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1964             const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1965             WINE_TRACE("Found 'IN '\n");
1966             lastWasIn = TRUE;
1967             onlyWhiteSpace = TRUE;
1968             memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1969             (*curLen)+=keyw_len;
1970             curPos+=keyw_len;
1971             continue;
1972           }
1973         }
1974       }
1975
1976       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1977          so just use the default processing ie skip character specific
1978          matching below                                                    */
1979       if (!inRem) thisChar = *curPos;
1980       else        thisChar = 'X';  /* Character with no special processing */
1981
1982       lastWasWhiteSpace = FALSE; /* Will be reset below */
1983       lastWasCaret = FALSE;
1984
1985       switch (thisChar) {
1986
1987       case '=': /* drop through - ignore token delimiters at the start of a command */
1988       case ',': /* drop through - ignore token delimiters at the start of a command */
1989       case '\t':/* drop through - ignore token delimiters at the start of a command */
1990       case ' ':
1991                 /* If a redirect in place, it ends here */
1992                 if (!inQuotes && !lastWasRedirect) {
1993
1994                   /* If finishing off a redirect, add a whitespace delimiter */
1995                   if (curCopyTo == curRedirs) {
1996                       curCopyTo[(*curLen)++] = ' ';
1997                   }
1998                   curCopyTo = curString;
1999                   curLen = &curStringLen;
2000                 }
2001                 if (*curLen > 0) {
2002                   curCopyTo[(*curLen)++] = *curPos;
2003                 }
2004
2005                 /* Remember just processed whitespace */
2006                 lastWasWhiteSpace = TRUE;
2007
2008                 break;
2009
2010       case '>': /* drop through - handle redirect chars the same */
2011       case '<':
2012                 /* Make a redirect start here */
2013                 if (!inQuotes) {
2014                   curCopyTo = curRedirs;
2015                   curLen = &curRedirsLen;
2016                   lastWasRedirect = TRUE;
2017                 }
2018
2019                 /* See if 1>, 2> etc, in which case we have some patching up
2020                    to do (provided there's a preceding whitespace, and enough
2021                    chars read so far) */
2022                 if (curStringLen > 2
2023                         && (*(curPos-1)>='1') && (*(curPos-1)<='9')
2024                         && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2025                     curStringLen--;
2026                     curString[curStringLen] = 0x00;
2027                     curCopyTo[(*curLen)++] = *(curPos-1);
2028                 }
2029
2030                 curCopyTo[(*curLen)++] = *curPos;
2031
2032                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2033                     do not process that ampersand as an AND operator */
2034                 if (thisChar == '>' && *(curPos+1) == '&') {
2035                     curCopyTo[(*curLen)++] = *(curPos+1);
2036                     curPos++;
2037                 }
2038                 break;
2039
2040       case '|': /* Pipe character only if not || */
2041                 if (!inQuotes) {
2042                   lastWasRedirect = FALSE;
2043
2044                   /* Add an entry to the command list */
2045                   if (curStringLen > 0) {
2046
2047                     /* Add the current command */
2048                     WCMD_addCommand(curString, &curStringLen,
2049                           curRedirs, &curRedirsLen,
2050                           &curCopyTo, &curLen,
2051                           prevDelim, curDepth,
2052                           &lastEntry, output);
2053
2054                   }
2055
2056                   if (*(curPos+1) == '|') {
2057                     curPos++; /* Skip other | */
2058                     prevDelim = CMD_ONFAILURE;
2059                   } else {
2060                     prevDelim = CMD_PIPE;
2061                   }
2062                 } else {
2063                   curCopyTo[(*curLen)++] = *curPos;
2064                 }
2065                 break;
2066
2067       case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2068                     inQuotes--;
2069                 } else {
2070                     inQuotes++; /* Quotes within quotes are fun! */
2071                 }
2072                 curCopyTo[(*curLen)++] = *curPos;
2073                 lastWasRedirect = FALSE;
2074                 break;
2075
2076       case '(': /* If a '(' is the first non whitespace in a command portion
2077                    ie start of line or just after &&, then we read until an
2078                    unquoted ) is found                                       */
2079                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2080                            ", for(%d, In:%d, Do:%d)"
2081                            ", if(%d, else:%d, lwe:%d)\n",
2082                            *curLen, inQuotes,
2083                            onlyWhiteSpace,
2084                            inFor, lastWasIn, lastWasDo,
2085                            inIf, inElse, lastWasElse);
2086                 lastWasRedirect = FALSE;
2087
2088                 /* Ignore open brackets inside the for set */
2089                 if (*curLen == 0 && !inIn) {
2090                   curDepth++;
2091
2092                 /* If in quotes, ignore brackets */
2093                 } else if (inQuotes) {
2094                   curCopyTo[(*curLen)++] = *curPos;
2095
2096                 /* In a FOR loop, an unquoted '(' may occur straight after
2097                       IN or DO
2098                    In an IF statement just handle it regardless as we don't
2099                       parse the operands
2100                    In an ELSE statement, only allow it straight away after
2101                       the ELSE and whitespace
2102                  */
2103                 } else if (inIf ||
2104                            (inElse && lastWasElse && onlyWhiteSpace) ||
2105                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2106
2107                    /* If entering into an 'IN', set inIn */
2108                   if (inFor && lastWasIn && onlyWhiteSpace) {
2109                     WINE_TRACE("Inside an IN\n");
2110                     inIn = TRUE;
2111                   }
2112
2113                   /* Add the current command */
2114                   WCMD_addCommand(curString, &curStringLen,
2115                                   curRedirs, &curRedirsLen,
2116                                   &curCopyTo, &curLen,
2117                                   prevDelim, curDepth,
2118                                   &lastEntry, output);
2119
2120                   curDepth++;
2121                 } else {
2122                   curCopyTo[(*curLen)++] = *curPos;
2123                 }
2124                 break;
2125
2126       case '^': if (!inQuotes) {
2127                   /* If we reach the end of the input, we need to wait for more */
2128                   if (*(curPos+1) == 0x00) {
2129                     lastWasCaret = TRUE;
2130                     WINE_TRACE("Caret found at end of line\n");
2131                     break;
2132                   }
2133                   curPos++;
2134                 }
2135                 curCopyTo[(*curLen)++] = *curPos;
2136                 break;
2137
2138       case '&': if (!inQuotes) {
2139                   lastWasRedirect = FALSE;
2140
2141                   /* Add an entry to the command list */
2142                   if (curStringLen > 0) {
2143
2144                     /* Add the current command */
2145                     WCMD_addCommand(curString, &curStringLen,
2146                           curRedirs, &curRedirsLen,
2147                           &curCopyTo, &curLen,
2148                           prevDelim, curDepth,
2149                           &lastEntry, output);
2150
2151                   }
2152
2153                   if (*(curPos+1) == '&') {
2154                     curPos++; /* Skip other & */
2155                     prevDelim = CMD_ONSUCCESS;
2156                   } else {
2157                     prevDelim = CMD_NONE;
2158                   }
2159                 } else {
2160                   curCopyTo[(*curLen)++] = *curPos;
2161                 }
2162                 break;
2163
2164       case ')': if (!inQuotes && curDepth > 0) {
2165                   lastWasRedirect = FALSE;
2166
2167                   /* Add the current command if there is one */
2168                   if (curStringLen) {
2169
2170                       /* Add the current command */
2171                       WCMD_addCommand(curString, &curStringLen,
2172                             curRedirs, &curRedirsLen,
2173                             &curCopyTo, &curLen,
2174                             prevDelim, curDepth,
2175                             &lastEntry, output);
2176                   }
2177
2178                   /* Add an empty entry to the command list */
2179                   prevDelim = CMD_NONE;
2180                   WCMD_addCommand(NULL, &curStringLen,
2181                         curRedirs, &curRedirsLen,
2182                         &curCopyTo, &curLen,
2183                         prevDelim, curDepth,
2184                         &lastEntry, output);
2185                   curDepth--;
2186
2187                   /* Leave inIn if necessary */
2188                   if (inIn) inIn =  FALSE;
2189                 } else {
2190                   curCopyTo[(*curLen)++] = *curPos;
2191                 }
2192                 break;
2193       default:
2194                 lastWasRedirect = FALSE;
2195                 curCopyTo[(*curLen)++] = *curPos;
2196       }
2197
2198       curPos++;
2199
2200       /* At various times we need to know if we have only skipped whitespace,
2201          so reset this variable and then it will remain true until a non
2202          whitespace is found                                               */
2203       if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2204         onlyWhiteSpace = FALSE;
2205
2206       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2207       if (!lastWasWhiteSpace) {
2208         lastWasIn = lastWasDo = FALSE;
2209       }
2210
2211       /* If we have reached the end, add this command into the list
2212          Do not add command to list if escape char ^ was last */
2213       if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2214
2215           /* Add an entry to the command list */
2216           WCMD_addCommand(curString, &curStringLen,
2217                 curRedirs, &curRedirsLen,
2218                 &curCopyTo, &curLen,
2219                 prevDelim, curDepth,
2220                 &lastEntry, output);
2221       }
2222
2223       /* If we have reached the end of the string, see if bracketing or
2224          final caret is outstanding */
2225       if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2226           readFrom != INVALID_HANDLE_VALUE) {
2227         WCHAR *extraData;
2228
2229         WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2230         inRem = FALSE;
2231         prevDelim = CMD_NONE;
2232         inQuotes = 0;
2233         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2234         extraData = extraSpace;
2235
2236         /* Read more, skipping any blank lines */
2237         do {
2238           WINE_TRACE("Read more input\n");
2239           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2240           if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2241             break;
2242
2243           /* Edge case for carets - a completely blank line (i.e. was just
2244              CRLF) is oddly added as an LF but then more data is received (but
2245              only once more!) */
2246           if (lastWasCaret) {
2247             if (*extraSpace == 0x00) {
2248               WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2249               *extraData++ = '\r';
2250               *extraData = 0x00;
2251             } else break;
2252           }
2253
2254         } while (*extraData == 0x00);
2255         curPos = extraSpace;
2256         if (context) handleExpansion(extraSpace, FALSE, FALSE);
2257         /* Continue to echo commands IF echo is on and in batch program */
2258         if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2259           WCMD_output_asis(extraSpace);
2260           WCMD_output_asis(newlineW);
2261         }
2262       }
2263     }
2264
2265     /* Dump out the parsed output */
2266     WCMD_DumpCommands(*output);
2267
2268     return extraSpace;
2269 }
2270
2271 /***************************************************************************
2272  * WCMD_process_commands
2273  *
2274  * Process all the commands read in so far
2275  */
2276 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2277                                 BOOL retrycall) {
2278
2279     int bdepth = -1;
2280
2281     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2282
2283     /* Loop through the commands, processing them one by one */
2284     while (thisCmd) {
2285
2286       CMD_LIST *origCmd = thisCmd;
2287
2288       /* If processing one bracket only, and we find the end bracket
2289          entry (or less), return                                    */
2290       if (oneBracket && !thisCmd->command &&
2291           bdepth <= thisCmd->bracketDepth) {
2292         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2293                    thisCmd, thisCmd->nextcommand);
2294         return thisCmd->nextcommand;
2295       }
2296
2297       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2298          about them and it will be handled in there)
2299          Also, skip over any batch labels (eg. :fred)          */
2300       if (thisCmd->command && thisCmd->command[0] != ':') {
2301         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2302         WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2303       }
2304
2305       /* Step on unless the command itself already stepped on */
2306       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2307     }
2308     return NULL;
2309 }
2310
2311 /***************************************************************************
2312  * WCMD_free_commands
2313  *
2314  * Frees the storage held for a parsed command line
2315  * - This is not done in the process_commands, as eventually the current
2316  *   pointer will be modified within the commands, and hence a single free
2317  *   routine is simpler
2318  */
2319 void WCMD_free_commands(CMD_LIST *cmds) {
2320
2321     /* Loop through the commands, freeing them one by one */
2322     while (cmds) {
2323       CMD_LIST *thisCmd = cmds;
2324       cmds = cmds->nextcommand;
2325       heap_free(thisCmd->command);
2326       heap_free(thisCmd->redirects);
2327       heap_free(thisCmd);
2328     }
2329 }
2330
2331
2332 /*****************************************************************************
2333  * Main entry point. This is a console application so we have a main() not a
2334  * winmain().
2335  */
2336
2337 int wmain (int argc, WCHAR *argvW[])
2338 {
2339   int     args;
2340   WCHAR  *cmdLine = NULL;
2341   WCHAR  *cmd     = NULL;
2342   WCHAR  *argPos  = NULL;
2343   WCHAR string[1024];
2344   WCHAR envvar[4];
2345   BOOL opt_q;
2346   int opt_t = 0;
2347   static const WCHAR offW[] = {'O','F','F','\0'};
2348   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2349   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2350   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2351   OSVERSIONINFOW osv;
2352   char osver[50];
2353
2354   srand(time(NULL));
2355
2356   /* Get the windows version being emulated */
2357   osv.dwOSVersionInfoSize = sizeof(osv);
2358   GetVersionExW(&osv);
2359
2360   /* Pre initialize some messages */
2361   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2362   sprintf(osver, "%d.%d.%d (%s)", osv.dwMajorVersion, osv.dwMinorVersion,
2363           osv.dwBuildNumber, PACKAGE_VERSION);
2364   cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2365   strcpyW(version_string, cmd);
2366   LocalFree(cmd);
2367   cmd = NULL;
2368
2369   /* Can't use argc/argv as it will have stripped quotes from parameters
2370    * meaning cmd.exe /C echo "quoted string" is impossible
2371    */
2372   cmdLine = GetCommandLineW();
2373   WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2374   args = 1;                /* start at first arg, skipping cmd.exe itself */
2375
2376   opt_c = opt_k = opt_q = opt_s = FALSE;
2377   WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2378   while (argPos && argPos[0] != 0x00)
2379   {
2380       WCHAR c;
2381       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
2382       if (argPos[0]!='/' || argPos[1]=='\0') {
2383           args++;
2384           WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2385           continue;
2386       }
2387
2388       c=argPos[1];
2389       if (tolowerW(c)=='c') {
2390           opt_c = TRUE;
2391       } else if (tolowerW(c)=='q') {
2392           opt_q = TRUE;
2393       } else if (tolowerW(c)=='k') {
2394           opt_k = TRUE;
2395       } else if (tolowerW(c)=='s') {
2396           opt_s = TRUE;
2397       } else if (tolowerW(c)=='a') {
2398           unicodeOutput = FALSE;
2399       } else if (tolowerW(c)=='u') {
2400           unicodeOutput = TRUE;
2401       } else if (tolowerW(c)=='v' && argPos[2]==':') {
2402           delayedsubst = strncmpiW(&argPos[3], offW, 3);
2403           if (delayedsubst) WINE_TRACE("Delayed substitution is on\n");
2404       } else if (tolowerW(c)=='t' && argPos[2]==':') {
2405           opt_t=strtoulW(&argPos[3], NULL, 16);
2406       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2407           /* Ignored for compatibility with Windows */
2408       }
2409
2410       if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t' ||
2411           tolowerW(c)=='v') {
2412           args++;
2413           WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2414       }
2415       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2416       {
2417           /* Do not step to next parameter, instead carry on parsing this one */
2418           argPos+=2;
2419       }
2420
2421       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2422           break;
2423   }
2424
2425   if (opt_q) {
2426     WCMD_echo(offW);
2427   }
2428
2429   /* Until we start to read from the keyboard, stay as non-interactive */
2430   interactive = FALSE;
2431
2432   if (opt_c || opt_k) {
2433       int     len;
2434       WCHAR   *q1 = NULL,*q2 = NULL,*p;
2435
2436       /* Handle very edge case error scenario, "cmd.exe /c" ie when there are no
2437        * parameters after the /C or /K by pretending there was a single space     */
2438       if (argPos == NULL) argPos = (WCHAR *)spaceW;
2439
2440       /* Take a copy */
2441       cmd = heap_strdupW(argPos);
2442
2443       /* opt_s left unflagged if the command starts with and contains exactly
2444        * one quoted string (exactly two quote characters). The quoted string
2445        * must be an executable name that has whitespace and must not have the
2446        * following characters: &<>()@^| */
2447
2448       if (!opt_s) {
2449         /* 1. Confirm there is at least one quote */
2450         q1 = strchrW(argPos, '"');
2451         if (!q1) opt_s=1;
2452       }
2453
2454       if (!opt_s) {
2455           /* 2. Confirm there is a second quote */
2456           q2 = strchrW(q1+1, '"');
2457           if (!q2) opt_s=1;
2458       }
2459
2460       if (!opt_s) {
2461           /* 3. Ensure there are no more quotes */
2462           if (strchrW(q2+1, '"')) opt_s=1;
2463       }
2464
2465       /* check first parameter for a space and invalid characters. There must not be any
2466        * invalid characters, but there must be one or more whitespace                    */
2467       if (!opt_s) {
2468           opt_s = TRUE;
2469           p=q1;
2470           while (p!=q2) {
2471               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2472                   || *p=='@' || *p=='^' || *p=='|') {
2473                   opt_s = TRUE;
2474                   break;
2475               }
2476               if (*p==' ' || *p=='\t')
2477                   opt_s = FALSE;
2478               p++;
2479           }
2480       }
2481
2482       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2483
2484       /* Finally, we only stay in new mode IF the first parameter is quoted and
2485          is a valid executable, i.e. must exist, otherwise drop back to old mode  */
2486       if (!opt_s) {
2487         WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2488         WCHAR  pathext[MAXSTRING];
2489         BOOL found = FALSE;
2490
2491         /* Now extract PATHEXT */
2492         len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
2493         if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
2494           strcpyW (pathext, dfltPathExt);
2495         }
2496
2497         /* If the supplied parameter has any directory information, look there */
2498         WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2499         if (strchrW(thisArg, '\\') != NULL) {
2500
2501           GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
2502           WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2503           p = string + strlenW(string);
2504
2505           /* Does file exist with this name? */
2506           if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2507             WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2508             found = TRUE;
2509           } else {
2510             WCHAR *thisExt = pathext;
2511
2512             /* No - try with each of the PATHEXT extensions */
2513             while (!found && thisExt) {
2514               WCHAR *nextExt = strchrW(thisExt, ';');
2515
2516               if (nextExt) {
2517                 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2518                 p[(nextExt-thisExt)] = 0x00;
2519                 thisExt = nextExt+1;
2520               } else {
2521                 strcpyW(p, thisExt);
2522                 thisExt = NULL;
2523               }
2524
2525               /* Does file exist with this extension appended? */
2526               if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2527                 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2528                 found = TRUE;
2529               }
2530             }
2531           }
2532
2533         /* Otherwise we now need to look in the path to see if we can find it */
2534         } else {
2535           p = thisArg + strlenW(thisArg);
2536
2537           /* Does file exist with this name? */
2538           if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0)  {
2539             WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2540             found = TRUE;
2541           } else {
2542             WCHAR *thisExt = pathext;
2543
2544             /* No - try with each of the PATHEXT extensions */
2545             while (!found && thisExt) {
2546               WCHAR *nextExt = strchrW(thisExt, ';');
2547
2548               if (nextExt) {
2549                 *nextExt = 0;
2550                 nextExt = nextExt+1;
2551               } else {
2552                 nextExt = NULL;
2553               }
2554
2555               /* Does file exist with this extension? */
2556               if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0)  {
2557                 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2558                            wine_dbgstr_w(thisExt));
2559                 found = TRUE;
2560               }
2561               thisExt = nextExt;
2562             }
2563           }
2564         }
2565
2566         /* If not found, drop back to old behaviour */
2567         if (!found) {
2568           WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2569           opt_s = TRUE;
2570         }
2571
2572       }
2573
2574       /* strip first and last quote characters if opt_s; check for invalid
2575        * executable is done later */
2576       if (opt_s && *cmd=='\"')
2577           WCMD_strip_quotes(cmd);
2578   }
2579
2580   /* Save cwd into appropriate env var (Must be before the /c processing */
2581   GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
2582   if (IsCharAlphaW(string[0]) && string[1] == ':') {
2583     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2584     wsprintfW(envvar, fmt, string[0]);
2585     SetEnvironmentVariableW(envvar, string);
2586     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2587   }
2588
2589   if (opt_c) {
2590       /* If we do a "cmd /c command", we don't want to allocate a new
2591        * console since the command returns immediately. Rather, we use
2592        * the currently allocated input and output handles. This allows
2593        * us to pipe to and read from the command interpreter.
2594        */
2595
2596       /* Parse the command string, without reading any more input */
2597       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2598       WCMD_process_commands(toExecute, FALSE, FALSE);
2599       WCMD_free_commands(toExecute);
2600       toExecute = NULL;
2601
2602       heap_free(cmd);
2603       return errorlevel;
2604   }
2605
2606   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2607                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2608   SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2609
2610   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2611   if (opt_t) {
2612       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2613           defaultColor = opt_t & 0xFF;
2614           param1[0] = 0x00;
2615           WCMD_color();
2616       }
2617   } else {
2618       /* Check HKCU\Software\Microsoft\Command Processor
2619          Then  HKLM\Software\Microsoft\Command Processor
2620            for defaultcolour value
2621            Note  Can be supplied as DWORD or REG_SZ
2622            Note2 When supplied as REG_SZ it's in decimal!!! */
2623       HKEY key;
2624       DWORD type;
2625       DWORD value=0, size=4;
2626       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2627                                       'M','i','c','r','o','s','o','f','t','\\',
2628                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2629       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2630
2631       if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2632                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2633           WCHAR  strvalue[4];
2634
2635           /* See if DWORD or REG_SZ */
2636           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2637                      NULL, NULL) == ERROR_SUCCESS) {
2638               if (type == REG_DWORD) {
2639                   size = sizeof(DWORD);
2640                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2641                                   (LPBYTE)&value, &size);
2642               } else if (type == REG_SZ) {
2643                   size = sizeof(strvalue)/sizeof(WCHAR);
2644                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2645                                   (LPBYTE)strvalue, &size);
2646                   value = strtoulW(strvalue, NULL, 10);
2647               }
2648           }
2649           RegCloseKey(key);
2650       }
2651
2652       if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2653                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2654           WCHAR  strvalue[4];
2655
2656           /* See if DWORD or REG_SZ */
2657           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2658                      NULL, NULL) == ERROR_SUCCESS) {
2659               if (type == REG_DWORD) {
2660                   size = sizeof(DWORD);
2661                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2662                                   (LPBYTE)&value, &size);
2663               } else if (type == REG_SZ) {
2664                   size = sizeof(strvalue)/sizeof(WCHAR);
2665                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2666                                   (LPBYTE)strvalue, &size);
2667                   value = strtoulW(strvalue, NULL, 10);
2668               }
2669           }
2670           RegCloseKey(key);
2671       }
2672
2673       /* If one found, set the screen to that colour */
2674       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2675           defaultColor = value & 0xFF;
2676           param1[0] = 0x00;
2677           WCMD_color();
2678       }
2679
2680   }
2681
2682   if (opt_k) {
2683       /* Parse the command string, without reading any more input */
2684       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2685       WCMD_process_commands(toExecute, FALSE, FALSE);
2686       WCMD_free_commands(toExecute);
2687       toExecute = NULL;
2688       heap_free(cmd);
2689   }
2690
2691 /*
2692  *      Loop forever getting commands and executing them.
2693  */
2694
2695   SetEnvironmentVariableW(promptW, defaultpromptW);
2696   interactive = TRUE;
2697   if (!opt_k) WCMD_version ();
2698   while (TRUE) {
2699
2700     /* Read until EOF (which for std input is never, but if redirect
2701        in place, may occur                                          */
2702     if (echo_mode) WCMD_show_prompt();
2703     if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2704       break;
2705     WCMD_process_commands(toExecute, FALSE, FALSE);
2706     WCMD_free_commands(toExecute);
2707     toExecute = NULL;
2708   }
2709   return 0;
2710 }