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