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