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