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