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