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