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