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