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