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