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