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