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