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