crypt32: Introduce function to encode an array of items as a set.
[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(WCHAR) + 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             WCHAR* flags = (WCHAR*)(ptr + sizeof(unsigned));
848             HANDLE* handles = (HANDLE*)(flags + num * sizeof(WCHAR));
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             if (assumeInternal || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1094             GetExitCodeProcess (pe.hProcess, &errorlevel);
1095             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1096         }
1097         CloseHandle(pe.hProcess);
1098         CloseHandle(pe.hThread);
1099         return;
1100       }
1101     }
1102   }
1103
1104   /* Not found anywhere - give up */
1105   SetLastError(ERROR_FILE_NOT_FOUND);
1106   WCMD_print_error ();
1107
1108   /* If a command fails to launch, it sets errorlevel 9009 - which
1109      does not seem to have any associated constant definition     */
1110   errorlevel = 9009;
1111   return;
1112
1113 }
1114
1115 /******************************************************************************
1116  * WCMD_show_prompt
1117  *
1118  *      Display the prompt on STDout
1119  *
1120  */
1121
1122 void WCMD_show_prompt (void) {
1123
1124   int status;
1125   WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1126   WCHAR *p, *q;
1127   DWORD len;
1128   static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1129
1130   len = GetEnvironmentVariable (envPrompt, prompt_string,
1131                                 sizeof(prompt_string)/sizeof(WCHAR));
1132   if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1133     const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1134     strcpyW (prompt_string, dfltPrompt);
1135   }
1136   p = prompt_string;
1137   q = out_string;
1138   *q = '\0';
1139   while (*p != '\0') {
1140     if (*p != '$') {
1141       *q++ = *p++;
1142       *q = '\0';
1143     }
1144     else {
1145       p++;
1146       switch (toupper(*p)) {
1147         case '$':
1148           *q++ = '$';
1149           break;
1150         case 'A':
1151           *q++ = '&';
1152           break;
1153         case 'B':
1154           *q++ = '|';
1155           break;
1156         case 'C':
1157           *q++ = '(';
1158           break;
1159         case 'D':
1160           GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1161           while (*q) q++;
1162           break;
1163         case 'E':
1164           *q++ = '\E';
1165           break;
1166         case 'F':
1167           *q++ = ')';
1168           break;
1169         case 'G':
1170           *q++ = '>';
1171           break;
1172         case 'H':
1173           *q++ = '\b';
1174           break;
1175         case 'L':
1176           *q++ = '<';
1177           break;
1178         case 'N':
1179           status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1180           if (status) {
1181             *q++ = curdir[0];
1182           }
1183           break;
1184         case 'P':
1185           status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1186           if (status) {
1187             strcatW (q, curdir);
1188             while (*q) q++;
1189           }
1190           break;
1191         case 'Q':
1192           *q++ = '=';
1193           break;
1194         case 'S':
1195           *q++ = ' ';
1196           break;
1197         case 'T':
1198           GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1199           while (*q) q++;
1200           break;
1201         case 'V':
1202           strcatW (q, version_string);
1203           while (*q) q++;
1204           break;
1205         case '_':
1206           *q++ = '\n';
1207           break;
1208         case '+':
1209           if (pushd_directories) {
1210             memset(q, '+', pushd_directories->u.stackdepth);
1211             q = q + pushd_directories->u.stackdepth;
1212           }
1213           break;
1214       }
1215       p++;
1216       *q = '\0';
1217     }
1218   }
1219   WCMD_output_asis (out_string);
1220 }
1221
1222 /****************************************************************************
1223  * WCMD_print_error
1224  *
1225  * Print the message for GetLastError
1226  */
1227
1228 void WCMD_print_error (void) {
1229   LPVOID lpMsgBuf;
1230   DWORD error_code;
1231   int status;
1232
1233   error_code = GetLastError ();
1234   status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1235                         NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1236   if (!status) {
1237     WINE_FIXME ("Cannot display message for error %d, status %d\n",
1238                         error_code, GetLastError());
1239     return;
1240   }
1241   WCMD_output_asis (lpMsgBuf);
1242   LocalFree ((HLOCAL)lpMsgBuf);
1243   WCMD_output_asis (newline);
1244   return;
1245 }
1246
1247 /*******************************************************************
1248  * WCMD_parse - parse a command into parameters and qualifiers.
1249  *
1250  *      On exit, all qualifiers are concatenated into q, the first string
1251  *      not beginning with "/" is in p1 and the
1252  *      second in p2. Any subsequent non-qualifier strings are lost.
1253  *      Parameters in quotes are handled.
1254  */
1255
1256 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1257
1258 int p = 0;
1259
1260   *q = *p1 = *p2 = '\0';
1261   while (TRUE) {
1262     switch (*s) {
1263       case '/':
1264         *q++ = *s++;
1265         while ((*s != '\0') && (*s != ' ') && *s != '/') {
1266           *q++ = toupperW (*s++);
1267         }
1268         *q = '\0';
1269         break;
1270       case ' ':
1271       case '\t':
1272         s++;
1273         break;
1274       case '"':
1275         s++;
1276         while ((*s != '\0') && (*s != '"')) {
1277           if (p == 0) *p1++ = *s++;
1278           else if (p == 1) *p2++ = *s++;
1279           else s++;
1280         }
1281         if (p == 0) *p1 = '\0';
1282         if (p == 1) *p2 = '\0';
1283         p++;
1284         if (*s == '"') s++;
1285         break;
1286       case '\0':
1287         return;
1288       default:
1289         while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1290           if (p == 0) *p1++ = *s++;
1291           else if (p == 1) *p2++ = *s++;
1292           else s++;
1293         }
1294         if (p == 0) *p1 = '\0';
1295         if (p == 1) *p2 = '\0';
1296         p++;
1297     }
1298   }
1299 }
1300
1301 /*******************************************************************
1302  * WCMD_output_asis_len - send output to current standard output
1303  *
1304  * Output a formatted unicode string. Ideally this will go to the console
1305  *  and hence required WriteConsoleW to output it, however if file i/o is
1306  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1307  */
1308 static void WCMD_output_asis_len(const WCHAR *message, int len) {
1309
1310     DWORD   nOut= 0;
1311     DWORD   res = 0;
1312
1313     /* If nothing to write, return (MORE does this sometimes) */
1314     if (!len) return;
1315
1316     /* Try to write as unicode assuming it is to a console */
1317     res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1318                         message, len, &nOut, NULL);
1319
1320     /* If writing to console fails, assume its file
1321        i/o so convert to OEM codepage and output                  */
1322     if (!res) {
1323       BOOL usedDefaultChar = FALSE;
1324       DWORD convertedChars;
1325
1326       if (!unicodePipes) {
1327         /*
1328          * Allocate buffer to use when writing to file. (Not freed, as one off)
1329          */
1330         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1331                                                   MAX_WRITECONSOLE_SIZE);
1332         if (!output_bufA) {
1333           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1334           return;
1335         }
1336
1337         /* Convert to OEM, then output */
1338         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1339                             len, output_bufA, MAX_WRITECONSOLE_SIZE,
1340                             "?", &usedDefaultChar);
1341         WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1342                   &nOut, FALSE);
1343       } else {
1344         WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message, len*sizeof(WCHAR),
1345                   &nOut, FALSE);
1346       }
1347     }
1348     return;
1349 }
1350
1351 /*******************************************************************
1352  * WCMD_output - send output to current standard output device.
1353  *
1354  */
1355
1356 void WCMD_output (const WCHAR *format, ...) {
1357
1358   va_list ap;
1359   WCHAR string[1024];
1360   int ret;
1361
1362   va_start(ap,format);
1363   ret = wvsprintf (string, format, ap);
1364   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1365        WINE_ERR("Output truncated in WCMD_output\n" );
1366        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1367        string[ret] = '\0';
1368   }
1369   va_end(ap);
1370   WCMD_output_asis_len(string, ret);
1371 }
1372
1373
1374 static int line_count;
1375 static int max_height;
1376 static int max_width;
1377 static BOOL paged_mode;
1378 static int numChars;
1379
1380 void WCMD_enter_paged_mode(const WCHAR *msg)
1381 {
1382   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1383
1384   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1385     max_height = consoleInfo.dwSize.Y;
1386     max_width  = consoleInfo.dwSize.X;
1387   } else {
1388     max_height = 25;
1389     max_width  = 80;
1390   }
1391   paged_mode = TRUE;
1392   line_count = 0;
1393   numChars   = 0;
1394   pagedMessage = (msg==NULL)? anykey : msg;
1395 }
1396
1397 void WCMD_leave_paged_mode(void)
1398 {
1399   paged_mode = FALSE;
1400   pagedMessage = NULL;
1401 }
1402
1403 /*******************************************************************
1404  * WCMD_output_asis - send output to current standard output device.
1405  *        without formatting eg. when message contains '%'
1406  */
1407
1408 void WCMD_output_asis (const WCHAR *message) {
1409   DWORD count;
1410   const WCHAR* ptr;
1411   WCHAR string[1024];
1412
1413   if (paged_mode) {
1414     do {
1415       ptr = message;
1416       while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1417         numChars++;
1418         ptr++;
1419       };
1420       if (*ptr == '\n') ptr++;
1421       WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message));
1422       if (ptr) {
1423         numChars = 0;
1424         if (++line_count >= max_height - 1) {
1425           line_count = 0;
1426           WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage));
1427           WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1428                          sizeof(string)/sizeof(WCHAR), &count, NULL);
1429         }
1430       }
1431     } while (((message = ptr) != NULL) && (*ptr));
1432   } else {
1433     WCMD_output_asis_len(message, lstrlen(message));
1434   }
1435 }
1436
1437
1438 /***************************************************************************
1439  * WCMD_strtrim_leading_spaces
1440  *
1441  *      Remove leading spaces from a string. Return a pointer to the first
1442  *      non-space character. Does not modify the input string
1443  */
1444
1445 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1446
1447   WCHAR *ptr;
1448
1449   ptr = string;
1450   while (*ptr == ' ') ptr++;
1451   return ptr;
1452 }
1453
1454 /*************************************************************************
1455  * WCMD_strtrim_trailing_spaces
1456  *
1457  *      Remove trailing spaces from a string. This routine modifies the input
1458  *      string by placing a null after the last non-space WCHARacter
1459  */
1460
1461 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1462
1463   WCHAR *ptr;
1464
1465   ptr = string + strlenW (string) - 1;
1466   while ((*ptr == ' ') && (ptr >= string)) {
1467     *ptr = '\0';
1468     ptr--;
1469   }
1470 }
1471
1472 /*************************************************************************
1473  * WCMD_opt_s_strip_quotes
1474  *
1475  *      Remove first and last quote WCHARacters, preserving all other text
1476  */
1477
1478 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1479   WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1480   while((*dest=*src) != '\0') {
1481       if (*src=='\"')
1482           lastq=dest;
1483       dest++, src++;
1484   }
1485   if (lastq) {
1486       dest=lastq++;
1487       while ((*dest++=*lastq++) != 0)
1488           ;
1489   }
1490 }
1491
1492 /*************************************************************************
1493  * WCMD_pipe
1494  *
1495  *      Handle pipes within a command - the DOS way using temporary files.
1496  */
1497
1498 void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) {
1499
1500   WCHAR *p;
1501   WCHAR *command = (*cmdEntry)->command;
1502   WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1503   static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1504   static const WCHAR redirIn[]  = {'%','s',' ','<',' ','%','s','\0'};
1505   static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1506   static const WCHAR cmdW[]     = {'C','M','D','\0'};
1507
1508
1509   GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1510   GetTempFileName (temp_path, cmdW, 0, temp_file);
1511   p = strchrW(command, '|');
1512   *p++ = '\0';
1513   wsprintf (temp_cmd, redirOut, command, temp_file);
1514   WCMD_execute (temp_cmd, var, val, cmdEntry);
1515   command = p;
1516   while ((p = strchrW(command, '|'))) {
1517     *p++ = '\0';
1518     GetTempFileName (temp_path, cmdW, 0, temp_file2);
1519     wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1520     WCMD_execute (temp_cmd, var, val, cmdEntry);
1521     DeleteFile (temp_file);
1522     strcpyW (temp_file, temp_file2);
1523     command = p;
1524   }
1525   wsprintf (temp_cmd, redirIn, command, temp_file);
1526   WCMD_execute (temp_cmd, var, val, cmdEntry);
1527   DeleteFile (temp_file);
1528 }
1529
1530 /*************************************************************************
1531  * WCMD_expand_envvar
1532  *
1533  *      Expands environment variables, allowing for WCHARacter substitution
1534  */
1535 static WCHAR *WCMD_expand_envvar(WCHAR *start) {
1536     WCHAR *endOfVar = NULL, *s;
1537     WCHAR *colonpos = NULL;
1538     WCHAR thisVar[MAXSTRING];
1539     WCHAR thisVarContents[MAXSTRING];
1540     WCHAR savedchar = 0x00;
1541     int len;
1542
1543     static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
1544     static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1545     static const WCHAR Date[]      = {'D','A','T','E','\0'};
1546     static const WCHAR DateP[]     = {'%','D','A','T','E','%','\0'};
1547     static const WCHAR Time[]      = {'T','I','M','E','\0'};
1548     static const WCHAR TimeP[]     = {'%','T','I','M','E','%','\0'};
1549     static const WCHAR Cd[]        = {'C','D','\0'};
1550     static const WCHAR CdP[]       = {'%','C','D','%','\0'};
1551     static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
1552     static const WCHAR RandomP[]   = {'%','R','A','N','D','O','M','%','\0'};
1553
1554     /* Find the end of the environment variable, and extract name */
1555     endOfVar = strchrW(start+1, '%');
1556     if (endOfVar == NULL) {
1557       /* In batch program, missing terminator for % and no following
1558          ':' just removes the '%'                                   */
1559       s = WCMD_strdupW(start + 1);
1560       strcpyW (start, s);
1561       free(s);
1562
1563       /* FIXME: Some other special conditions here depending on whether
1564          in batch, complex or not, and whether env var exists or not! */
1565       return start;
1566     }
1567     memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1568     thisVar[(endOfVar - start)+1] = 0x00;
1569     colonpos = strchrW(thisVar+1, ':');
1570
1571     /* If there's complex substitution, just need %var% for now
1572        to get the expanded data to play with                    */
1573     if (colonpos) {
1574         *colonpos = '%';
1575         savedchar = *(colonpos+1);
1576         *(colonpos+1) = 0x00;
1577     }
1578
1579     WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1580
1581     /* Expand to contents, if unchanged, return */
1582     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1583     /* override if existing env var called that name              */
1584     if ((CompareString (LOCALE_USER_DEFAULT,
1585                         NORM_IGNORECASE | SORT_STRINGSORT,
1586                         thisVar, 12, ErrorLvlP, -1) == 2) &&
1587                 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1588                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1589       static const WCHAR fmt[] = {'%','d','\0'};
1590       wsprintf(thisVarContents, fmt, errorlevel);
1591       len = strlenW(thisVarContents);
1592
1593     } else if ((CompareString (LOCALE_USER_DEFAULT,
1594                                NORM_IGNORECASE | SORT_STRINGSORT,
1595                                thisVar, 6, DateP, -1) == 2) &&
1596                 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1597                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1598
1599       GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1600                     NULL, thisVarContents, MAXSTRING);
1601       len = strlenW(thisVarContents);
1602
1603     } else if ((CompareString (LOCALE_USER_DEFAULT,
1604                                NORM_IGNORECASE | SORT_STRINGSORT,
1605                                thisVar, 6, TimeP, -1) == 2) &&
1606                 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1607                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1608       GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1609                         NULL, thisVarContents, MAXSTRING);
1610       len = strlenW(thisVarContents);
1611
1612     } else if ((CompareString (LOCALE_USER_DEFAULT,
1613                                NORM_IGNORECASE | SORT_STRINGSORT,
1614                                thisVar, 4, CdP, -1) == 2) &&
1615                 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1616                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1617       GetCurrentDirectory (MAXSTRING, thisVarContents);
1618       len = strlenW(thisVarContents);
1619
1620     } else if ((CompareString (LOCALE_USER_DEFAULT,
1621                                NORM_IGNORECASE | SORT_STRINGSORT,
1622                                thisVar, 8, RandomP, -1) == 2) &&
1623                 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1624                 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1625       static const WCHAR fmt[] = {'%','d','\0'};
1626       wsprintf(thisVarContents, fmt, rand() % 32768);
1627       len = strlenW(thisVarContents);
1628
1629     } else {
1630
1631       len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1632                                sizeof(thisVarContents)/sizeof(WCHAR));
1633     }
1634
1635     if (len == 0)
1636       return endOfVar+1;
1637
1638     /* In a batch program, unknown env vars are replaced with nothing,
1639          note syntax %garbage:1,3% results in anything after the ':'
1640          except the %
1641        From the command line, you just get back what you entered      */
1642     if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1643
1644       /* Restore the complex part after the compare */
1645       if (colonpos) {
1646         *colonpos = ':';
1647         *(colonpos+1) = savedchar;
1648       }
1649
1650       /* Command line - just ignore this */
1651       if (context == NULL) return endOfVar+1;
1652
1653       s = WCMD_strdupW(endOfVar + 1);
1654
1655       /* Batch - replace unknown env var with nothing */
1656       if (colonpos == NULL) {
1657         strcpyW (start, s);
1658
1659       } else {
1660         len = strlenW(thisVar);
1661         thisVar[len-1] = 0x00;
1662         /* If %:...% supplied, : is retained */
1663         if (colonpos == thisVar+1) {
1664           strcpyW (start, colonpos);
1665         } else {
1666           strcpyW (start, colonpos+1);
1667         }
1668         strcatW (start, s);
1669       }
1670       free (s);
1671       return start;
1672
1673     }
1674
1675     /* See if we need to do complex substitution (any ':'s), if not
1676        then our work here is done                                  */
1677     if (colonpos == NULL) {
1678       s = WCMD_strdupW(endOfVar + 1);
1679       strcpyW (start, thisVarContents);
1680       strcatW (start, s);
1681       free(s);
1682       return start;
1683     }
1684
1685     /* Restore complex bit */
1686     *colonpos = ':';
1687     *(colonpos+1) = savedchar;
1688
1689     /*
1690         Handle complex substitutions:
1691            xxx=yyy    (replace xxx with yyy)
1692            *xxx=yyy   (replace up to and including xxx with yyy)
1693            ~x         (from x WCHARs in)
1694            ~-x        (from x WCHARs from the end)
1695            ~x,y       (from x WCHARs in for y WCHARacters)
1696            ~x,-y      (from x WCHARs in until y WCHARacters from the end)
1697      */
1698
1699     /* ~ is substring manipulation */
1700     if (savedchar == '~') {
1701
1702       int   substrposition, substrlength = 0;
1703       WCHAR *commapos = strchrW(colonpos+2, ',');
1704       WCHAR *startCopy;
1705
1706       substrposition = atolW(colonpos+2);
1707       if (commapos) substrlength = atolW(commapos+1);
1708
1709       s = WCMD_strdupW(endOfVar + 1);
1710
1711       /* Check bounds */
1712       if (substrposition >= 0) {
1713         startCopy = &thisVarContents[min(substrposition, len)];
1714       } else {
1715         startCopy = &thisVarContents[max(0, len+substrposition-1)];
1716       }
1717
1718       if (commapos == NULL) {
1719         strcpyW (start, startCopy); /* Copy the lot */
1720       } else if (substrlength < 0) {
1721
1722         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1723         if (copybytes > len) copybytes = len;
1724         else if (copybytes < 0) copybytes = 0;
1725         memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1726         start[copybytes] = 0x00;
1727       } else {
1728         memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1729         start[substrlength] = 0x00;
1730       }
1731
1732       strcatW (start, s);
1733       free(s);
1734       return start;
1735
1736     /* search and replace manipulation */
1737     } else {
1738       WCHAR *equalspos = strstrW(colonpos, equalsW);
1739       WCHAR *replacewith = equalspos+1;
1740       WCHAR *found       = NULL;
1741       WCHAR *searchIn;
1742       WCHAR *searchFor;
1743
1744       s = WCMD_strdupW(endOfVar + 1);
1745       if (equalspos == NULL) return start+1;
1746
1747       /* Null terminate both strings */
1748       thisVar[strlenW(thisVar)-1] = 0x00;
1749       *equalspos = 0x00;
1750
1751       /* Since we need to be case insensitive, copy the 2 buffers */
1752       searchIn  = WCMD_strdupW(thisVarContents);
1753       CharUpperBuff(searchIn, strlenW(thisVarContents));
1754       searchFor = WCMD_strdupW(colonpos+1);
1755       CharUpperBuff(searchFor, strlenW(colonpos+1));
1756
1757
1758       /* Handle wildcard case */
1759       if (*(colonpos+1) == '*') {
1760         /* Search for string to replace */
1761         found = strstrW(searchIn, searchFor+1);
1762
1763         if (found) {
1764           /* Do replacement */
1765           strcpyW(start, replacewith);
1766           strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1767           strcatW(start, s);
1768           free(s);
1769         } else {
1770           /* Copy as it */
1771           strcpyW(start, thisVarContents);
1772           strcatW(start, s);
1773         }
1774
1775       } else {
1776         /* Loop replacing all instances */
1777         WCHAR *lastFound = searchIn;
1778         WCHAR *outputposn = start;
1779
1780         *start = 0x00;
1781         while ((found = strstrW(lastFound, searchFor))) {
1782             lstrcpynW(outputposn,
1783                     thisVarContents + (lastFound-searchIn),
1784                     (found - lastFound)+1);
1785             outputposn  = outputposn + (found - lastFound);
1786             strcatW(outputposn, replacewith);
1787             outputposn = outputposn + strlenW(replacewith);
1788             lastFound = found + strlenW(searchFor);
1789         }
1790         strcatW(outputposn,
1791                 thisVarContents + (lastFound-searchIn));
1792         strcatW(outputposn, s);
1793       }
1794       free(searchIn);
1795       free(searchFor);
1796       return start;
1797     }
1798     return start+1;
1799 }
1800
1801 /*************************************************************************
1802  * WCMD_LoadMessage
1803  *    Load a string from the resource file, handling any error
1804  *    Returns string retrieved from resource file
1805  */
1806 WCHAR *WCMD_LoadMessage(UINT id) {
1807     static WCHAR msg[2048];
1808     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1809
1810     if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1811        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1812        strcpyW(msg, failedMsg);
1813     }
1814     return msg;
1815 }
1816
1817 /*************************************************************************
1818  * WCMD_strdupW
1819  *    A wide version of strdup as its missing from unicode.h
1820  */
1821 WCHAR *WCMD_strdupW(WCHAR *input) {
1822    int len=strlenW(input)+1;
1823    /* Note: Use malloc not HeapAlloc to emulate strdup */
1824    WCHAR *result = malloc(len * sizeof(WCHAR));
1825    memcpy(result, input, len * sizeof(WCHAR));
1826    return result;
1827 }
1828
1829 /***************************************************************************
1830  * WCMD_Readfile
1831  *
1832  *      Read characters in from a console/file, returning result in Unicode
1833  *      with signature identical to ReadFile
1834  */
1835 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1836                           LPDWORD charsRead, const LPOVERLAPPED unused) {
1837
1838     BOOL   res;
1839
1840     /* Try to read from console as Unicode */
1841     res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1842
1843     /* If reading from console has failed we assume its file
1844        i/o so read in and convert from OEM codepage               */
1845     if (!res) {
1846
1847         DWORD numRead;
1848         /*
1849          * Allocate buffer to use when reading from file. Not freed
1850          */
1851         if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1852                                                   MAX_WRITECONSOLE_SIZE);
1853         if (!output_bufA) {
1854           WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1855           return 0;
1856         }
1857
1858         /* Read from file (assume OEM codepage) */
1859         res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1860
1861         /* Convert from OEM */
1862         *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1863                          intoBuf, maxChars);
1864
1865     }
1866     return res;
1867 }
1868
1869 /***************************************************************************
1870  * WCMD_DumpCommands
1871  *
1872  *      Domps out the parsed command line to ensure syntax is correct
1873  */
1874 void WCMD_DumpCommands(CMD_LIST *commands) {
1875     WCHAR buffer[MAXSTRING];
1876     CMD_LIST *thisCmd = commands;
1877     const WCHAR fmt[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1878                          '%','p',' ','%','s','\0'};
1879
1880     WINE_TRACE("Parsed line:\n");
1881     while (thisCmd != NULL) {
1882       sprintfW(buffer, fmt,
1883                thisCmd,
1884                thisCmd->isAmphersand?'Y':'N',
1885                thisCmd->bracketDepth,
1886                thisCmd->nextcommand,
1887                thisCmd->command);
1888       WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1889       thisCmd = thisCmd->nextcommand;
1890     }
1891 }
1892
1893 /***************************************************************************
1894  * WCMD_ReadAndParseLine
1895  *
1896  *   Either uses supplied input or
1897  *     Reads a file from the handle, and then...
1898  *   Parse the text buffer, spliting into separate commands
1899  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1900  *            following an &&
1901  *     - ( as the first character just ups the bracket depth
1902  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1903  *            adds a CMD_LIST structure with null command
1904  *     - Anything else gets put into the command string (including
1905  *            redirects)
1906  */
1907 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1908
1909     WCHAR    *curPos;
1910     BOOL      inQuotes = FALSE;
1911     WCHAR     curString[MAXSTRING];
1912     int       curLen   = 0;
1913     int       curDepth = 0;
1914     CMD_LIST *thisEntry = NULL;
1915     CMD_LIST *lastEntry = NULL;
1916     BOOL      isAmphersand = FALSE;
1917     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1918     const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1919     const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1920     const WCHAR ifCmd[]  = {'i','f',' ','\0'};
1921     const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1922     BOOL      inRem = FALSE;
1923     BOOL      inFor = FALSE;
1924     BOOL      inIn  = FALSE;
1925     BOOL      inIf  = FALSE;
1926     BOOL      inElse= FALSE;
1927     BOOL      onlyWhiteSpace = FALSE;
1928     BOOL      lastWasWhiteSpace = FALSE;
1929     BOOL      lastWasDo   = FALSE;
1930     BOOL      lastWasIn   = FALSE;
1931     BOOL      lastWasElse = FALSE;
1932
1933     /* Allocate working space for a command read from keyboard, file etc */
1934     if (!extraSpace)
1935       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1936
1937     /* If initial command read in, use that, otherwise get input from handle */
1938     if (optionalcmd != NULL) {
1939         strcpyW(extraSpace, optionalcmd);
1940     } else if (readFrom == INVALID_HANDLE_VALUE) {
1941         WINE_FIXME("No command nor handle supplied\n");
1942     } else {
1943         if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1944     }
1945     curPos = extraSpace;
1946
1947     /* Handle truncated input - issue warning */
1948     if (strlenW(extraSpace) == MAXSTRING -1) {
1949         WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1950         WCMD_output_asis(extraSpace);
1951         WCMD_output_asis(newline);
1952     }
1953
1954     /* Start with an empty string */
1955     curLen = 0;
1956
1957     /* Parse every character on the line being processed */
1958     while (*curPos != 0x00) {
1959
1960       WCHAR thisChar;
1961
1962       /* Debugging AID:
1963       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, curLen,
1964                  lastWasWhiteSpace, onlyWhiteSpace);
1965       */
1966
1967      /* Certain commands need special handling */
1968       if (curLen == 0) {
1969         const WCHAR forDO[]  = {'d','o',' ','\0'};
1970
1971         /* If command starts with 'rem', ignore any &&, ( etc */
1972         if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1973           curPos, 4, remCmd, -1) == 2) {
1974           inRem = TRUE;
1975
1976         /* If command starts with 'for', handle ('s mid line after IN or DO */
1977         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1978           curPos, 4, forCmd, -1) == 2) {
1979           inFor = TRUE;
1980
1981         /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1982            is only true in the command portion of the IF statement, but this
1983            should suffice for now
1984             FIXME: Silly syntax like "if 1(==1( (
1985                                         echo they equal
1986                                       )" will be parsed wrong */
1987         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1988           curPos, 3, ifCmd, -1) == 2) {
1989           inIf = TRUE;
1990
1991         } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1992           curPos, 5, ifElse, -1) == 2) {
1993           inElse = TRUE;
1994           lastWasElse = TRUE;
1995           onlyWhiteSpace = TRUE;
1996           memcpy(&curString[curLen], curPos, 5*sizeof(WCHAR));
1997           curLen+=5;
1998           curPos+=5;
1999           continue;
2000
2001         /* In a for loop, the DO command will follow a close bracket followed by
2002            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2003            is then 0, and all whitespace is skipped                                */
2004         } else if (inFor &&
2005                    (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2006                     curPos, 3, forDO, -1) == 2)) {
2007           WINE_TRACE("Found DO\n");
2008           lastWasDo = TRUE;
2009           onlyWhiteSpace = TRUE;
2010           memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2011           curLen+=3;
2012           curPos+=3;
2013           continue;
2014         }
2015       } else {
2016
2017         /* Special handling for the 'FOR' command */
2018         if (inFor && lastWasWhiteSpace) {
2019           const WCHAR forIN[] = {'i','n',' ','\0'};
2020
2021           WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2022
2023           if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2024               curPos, 3, forIN, -1) == 2) {
2025             WINE_TRACE("Found IN\n");
2026             lastWasIn = TRUE;
2027             onlyWhiteSpace = TRUE;
2028             memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2029             curLen+=3;
2030             curPos+=3;
2031             continue;
2032           }
2033         }
2034       }
2035
2036       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2037          so just use the default processing ie skip character specific
2038          matching below                                                    */
2039       if (!inRem) thisChar = *curPos;
2040       else        thisChar = 'X';  /* Character with no special processing */
2041
2042       lastWasWhiteSpace = FALSE; /* Will be reset below */
2043
2044       switch (thisChar) {
2045
2046       case '\t':/* drop through - ignore whitespace at the start of a command */
2047       case ' ': if (curLen > 0)
2048                   curString[curLen++] = *curPos;
2049
2050                 /* Remember just processed whitespace */
2051                 lastWasWhiteSpace = TRUE;
2052
2053                 break;
2054
2055       case '"': inQuotes = !inQuotes;
2056                 curString[curLen++] = *curPos;
2057                 break;
2058
2059       case '(': /* If a '(' is the first non whitespace in a command portion
2060                    ie start of line or just after &&, then we read until an
2061                    unquoted ) is found                                       */
2062                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2063                            ", for(%d, In:%d, Do:%d)"
2064                            ", if(%d, else:%d, lwe:%d)\n",
2065                            curLen, inQuotes,
2066                            onlyWhiteSpace,
2067                            inFor, lastWasIn, lastWasDo,
2068                            inIf, inElse, lastWasElse);
2069
2070                 /* Ignore open brackets inside the for set */
2071                 if (curLen == 0 && !inIn) {
2072                     WINE_TRACE("@@@4\n");
2073                   curDepth++;
2074
2075                 /* If in quotes, ignore brackets */
2076                 } else if (inQuotes) {
2077                     WINE_TRACE("@@@3\n");
2078                   curString[curLen++] = *curPos;
2079
2080                 /* In a FOR loop, an unquoted '(' may occur straight after
2081                       IN or DO
2082                    In an IF statement just handle it regardless as we don't
2083                       parse the operands
2084                    In an ELSE statement, only allow it straight away after
2085                       the ELSE and whitespace
2086                  */
2087                 } else if (inIf ||
2088                            (inElse && lastWasElse && onlyWhiteSpace) ||
2089                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2090
2091                   WINE_TRACE("@@@2\n");
2092                    /* If entering into an 'IN', set inIn */
2093                   if (inFor && lastWasIn && onlyWhiteSpace) {
2094                     WINE_TRACE("Inside an IN\n");
2095                     inIn = TRUE;
2096                   }
2097
2098                   /* Add the current command */
2099                   thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2100                   thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2101                                                  (curLen+1) * sizeof(WCHAR));
2102                   memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2103                   thisEntry->command[curLen] = 0x00;
2104                   curLen = 0;
2105                   thisEntry->nextcommand = NULL;
2106                   thisEntry->isAmphersand = isAmphersand;
2107                   thisEntry->bracketDepth = curDepth;
2108                   if (lastEntry) {
2109                     lastEntry->nextcommand = thisEntry;
2110                   } else {
2111                     *output = thisEntry;
2112                   }
2113                   lastEntry = thisEntry;
2114
2115                   curDepth++;
2116                 } else {
2117                   WINE_TRACE("@@@1\n");
2118                   curString[curLen++] = *curPos;
2119                 }
2120                 break;
2121
2122       case '&': if (!inQuotes && *(curPos+1) == '&') {
2123                   curPos++; /* Skip other & */
2124
2125                   /* Add an entry to the command list */
2126                   if (curLen > 0) {
2127                     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2128                     thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2129                                                    (curLen+1) * sizeof(WCHAR));
2130                     memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2131                     thisEntry->command[curLen] = 0x00;
2132                     curLen = 0;
2133                     thisEntry->nextcommand = NULL;
2134                     thisEntry->isAmphersand = isAmphersand;
2135                     thisEntry->bracketDepth = curDepth;
2136                     if (lastEntry) {
2137                       lastEntry->nextcommand = thisEntry;
2138                     } else {
2139                       *output = thisEntry;
2140                     }
2141                     lastEntry = thisEntry;
2142                   }
2143                   isAmphersand = TRUE;
2144                 } else {
2145                   curString[curLen++] = *curPos;
2146                 }
2147                 break;
2148
2149       case ')': if (!inQuotes && curDepth > 0) {
2150
2151                   /* Add the current command if there is one */
2152                   if (curLen) {
2153                     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2154                     thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2155                                                    (curLen+1) * sizeof(WCHAR));
2156                     memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2157                     thisEntry->command[curLen] = 0x00;
2158                     curLen = 0;
2159                     thisEntry->nextcommand = NULL;
2160                     thisEntry->isAmphersand = isAmphersand;
2161                     thisEntry->bracketDepth = curDepth;
2162                     if (lastEntry) {
2163                       lastEntry->nextcommand = thisEntry;
2164                     } else {
2165                       *output = thisEntry;
2166                     }
2167                     lastEntry = thisEntry;
2168                   }
2169
2170                   /* Add an empty entry to the command list */
2171                   thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2172                   thisEntry->command = NULL;
2173                   thisEntry->nextcommand = NULL;
2174                   thisEntry->isAmphersand = FALSE;
2175                   thisEntry->bracketDepth = curDepth;
2176                   curDepth--;
2177                   if (lastEntry) {
2178                     lastEntry->nextcommand = thisEntry;
2179                   } else {
2180                     *output = thisEntry;
2181                   }
2182                   lastEntry = thisEntry;
2183
2184                   /* Leave inIn if necessary */
2185                   if (inIn) inIn =  FALSE;
2186                 } else {
2187                   curString[curLen++] = *curPos;
2188                 }
2189                 break;
2190       default:
2191                 curString[curLen++] = *curPos;
2192       }
2193
2194       curPos++;
2195
2196       /* At various times we need to know if we have only skipped whitespace,
2197          so reset this variable and then it will remain true until a non
2198          whitespace is found                                               */
2199       if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2200
2201       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2202       if (!lastWasWhiteSpace) {
2203         lastWasIn = lastWasDo = FALSE;
2204       }
2205
2206       /* If we have reached the end, add this command into the list */
2207       if (*curPos == 0x00 && curLen > 0) {
2208
2209           /* Add an entry to the command list */
2210           thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2211           thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2212                                          (curLen+1) * sizeof(WCHAR));
2213           memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2214           thisEntry->command[curLen] = 0x00;
2215           curLen = 0;
2216           thisEntry->nextcommand = NULL;
2217           thisEntry->isAmphersand = isAmphersand;
2218           thisEntry->bracketDepth = curDepth;
2219           if (lastEntry) {
2220             lastEntry->nextcommand = thisEntry;
2221           } else {
2222             *output = thisEntry;
2223           }
2224           lastEntry = thisEntry;
2225       }
2226
2227       /* If we have reached the end of the string, see if bracketing outstanding */
2228       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2229         inRem = FALSE;
2230         isAmphersand = FALSE;
2231         inQuotes = FALSE;
2232         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2233
2234         /* Read more, skipping any blank lines */
2235         while (*extraSpace == 0x00) {
2236           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2237           if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2238         }
2239         curPos = extraSpace;
2240       }
2241     }
2242
2243     /* Dump out the parsed output */
2244     WCMD_DumpCommands(*output);
2245
2246     return extraSpace;
2247 }
2248
2249 /***************************************************************************
2250  * WCMD_process_commands
2251  *
2252  * Process all the commands read in so far
2253  */
2254 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2255                                 WCHAR *var, WCHAR *val) {
2256
2257     int bdepth = -1;
2258
2259     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2260
2261     /* Loop through the commands, processing them one by one */
2262     while (thisCmd) {
2263
2264       CMD_LIST *origCmd = thisCmd;
2265
2266       /* If processing one bracket only, and we find the end bracket
2267          entry (or less), return                                    */
2268       if (oneBracket && !thisCmd->command &&
2269           bdepth <= thisCmd->bracketDepth) {
2270         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2271                    thisCmd, thisCmd->nextcommand);
2272         return thisCmd->nextcommand;
2273       }
2274
2275       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2276          about them and it will be handled in there)
2277          Also, skip over any batch labels (eg. :fred)          */
2278       if (thisCmd->command && thisCmd->command[0] != ':') {
2279
2280         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2281
2282         if (strchrW(thisCmd->command,'|') != NULL) {
2283           WCMD_pipe (&thisCmd, var, val);
2284         } else {
2285           WCMD_execute (thisCmd->command, var, val, &thisCmd);
2286         }
2287       }
2288
2289       /* Step on unless the command itself already stepped on */
2290       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2291     }
2292     return NULL;
2293 }
2294
2295 /***************************************************************************
2296  * WCMD_free_commands
2297  *
2298  * Frees the storage held for a parsed command line
2299  * - This is not done in the process_commands, as eventually the current
2300  *   pointer will be modified within the commands, and hence a single free
2301  *   routine is simpler
2302  */
2303 void WCMD_free_commands(CMD_LIST *cmds) {
2304
2305     /* Loop through the commands, freeing them one by one */
2306     while (cmds) {
2307       CMD_LIST *thisCmd = cmds;
2308       cmds = cmds->nextcommand;
2309       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2310       HeapFree(GetProcessHeap(), 0, thisCmd);
2311     }
2312 }