cmd.exe: Correctly parse IF ELSE plus multipart/multiline.
[wine.git] / programs / cmd / wcmdmain.c
bloba9beb61a95acd81fc54321eae2813bf6e56537e5
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
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.
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.
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
22 * FIXME:
23 * - Cannot handle parameters in quotes
24 * - Lots of functionality missing from builtins
27 #include "config.h"
28 #include "wcmd.h"
29 #include "wine/debug.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
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'}
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;
98 static WCHAR *WCMD_expand_envvar(WCHAR *start);
100 /*****************************************************************************
101 * Main entry point. This is a console application so we have a main() not a
102 * winmain().
105 int wmain (int argc, WCHAR *argvW[])
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 */
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));
125 args = argc;
126 opt_c=opt_k=opt_q=opt_s=0;
127 while (args > 0)
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;
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 */
156 if ((*argvW)[2]==0) {
157 argvW++;
158 args--;
160 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
162 *argvW+=2;
165 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
166 break;
169 if (opt_q) {
170 const WCHAR eoff[] = {'O','F','F','\0'};
171 WCMD_echo(eoff);
174 if (opt_c || opt_k) {
175 int len,qcount;
176 WCHAR** arg;
177 int argsLeft;
178 WCHAR* p;
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: &<>()@^| */
185 /* Build the command to execute */
186 len = 0;
187 qcount = 0;
188 argsLeft = args;
189 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
191 int has_space,bcount;
192 WCHAR* a;
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 '"'
208 len+=2*bcount+1;
209 qcount++;
211 bcount=0;
213 a++;
215 len+=(a-*arg) + 1; /* for the separating space */
216 if (has_space)
218 len+=2; /* for the quotes */
219 qcount+=2;
223 if (qcount!=2)
224 opt_s=1;
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;
236 if (*p==' ')
237 opt_s=0;
238 p++;
242 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
243 if (!cmd)
244 exit(1);
246 p = cmd;
247 argsLeft = args;
248 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
250 int has_space,has_quote;
251 WCHAR* a;
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;
267 a++;
270 /* Now transfer it to the command line */
271 if (has_space)
272 *p++='"';
273 if (has_quote) {
274 int bcount;
275 WCHAR* a;
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;
287 /* Double all the '\\' preceding this '"', plus one */
288 for (i=0;i<=bcount;i++)
289 *p++='\\';
290 *p++='"';
291 } else {
292 *p++=*a;
294 bcount=0;
296 a++;
298 } else {
299 strcpyW(p,*arg);
300 p+=strlenW(*arg);
302 if (has_space)
303 *p++='"';
304 *p++=' ';
306 if (p > cmd)
307 p--; /* remove last space */
308 *p = '\0';
310 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
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);
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.
325 /* Parse the command string, without reading any more input */
326 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
327 WCMD_process_commands(toExecute);
328 WCMD_free_commands(toExecute);
329 toExecute = NULL;
331 HeapFree(GetProcessHeap(), 0, cmd);
332 return errorlevel;
335 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
336 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
337 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
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();
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'};
360 if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
361 0, KEY_READ, &key) == ERROR_SUCCESS) {
362 WCHAR strvalue[4];
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);
380 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
381 0, KEY_READ, &key) == ERROR_SUCCESS) {
382 WCHAR strvalue[4];
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);
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();
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);
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);
421 WCMD_free_commands(toExecute);
422 toExecute = NULL;
423 HeapFree(GetProcessHeap(), 0, cmd);
427 * If there is an AUTOEXEC.BAT file, try to execute it.
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
440 * Loop forever getting commands and executing them.
443 WCMD_version ();
444 while (TRUE) {
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);
453 WCMD_free_commands(toExecute);
454 toExecute = NULL;
456 return 0;
460 /*****************************************************************************
461 * Process one command. If the command is EXIT this routine does not return.
462 * We will recurse through here executing batch files.
466 void WCMD_process_command (WCHAR *command, CMD_LIST **cmdList)
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};
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);
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 */
496 p = new_cmd;
497 while ((p = strchrW(p, '%'))) {
498 i = *(p+1) - '0';
500 /* Replace %~ modifications if in batch program */
501 if (context && *(p+1) == '~') {
502 WCMD_HandleTildaModifiers(&p, NULL);
503 p++;
505 /* Replace use of %0...%9 if in batch program*/
506 } else if (context && (i >= 0) && (i <= 9)) {
507 s = WCMD_strdupW(p+2);
508 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
509 strcpyW (p, t);
510 strcatW (p, s);
511 free (s);
513 /* Replace use of %* if in batch program*/
514 } else if (context && *(p+1)=='*') {
515 WCHAR *startOfParms = NULL;
516 s = WCMD_strdupW(p+2);
517 t = WCMD_parameter (context -> command, 1, &startOfParms);
518 if (startOfParms != NULL) strcpyW (p, startOfParms);
519 else *p = 0x00;
520 strcatW (p, s);
521 free (s);
523 } else {
524 p = WCMD_expand_envvar(p);
527 cmd = new_cmd;
529 /* In a batch program, unknown variables are replace by nothing */
530 /* so remove any remaining %var% */
531 if (context) {
532 p = cmd;
533 while ((p = strchrW(p, '%'))) {
534 s = strchrW(p+1, '%');
535 if (!s) {
536 *p=0x00;
537 } else {
538 t = WCMD_strdupW(s+1);
539 strcpyW(p, t);
540 free(t);
544 /* Show prompt before batch line IF echo is on and in batch program */
545 if (echo_mode && (cmd[0] != '@')) {
546 WCMD_show_prompt();
547 WCMD_output_asis ( cmd);
548 WCMD_output_asis ( newline);
553 * Changing default drive has to be handled as a special case.
556 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
557 WCHAR envvar[5];
558 WCHAR dir[MAX_PATH];
560 /* According to MSDN CreateProcess docs, special env vars record
561 the current directory on each drive, in the form =C:
562 so see if one specified, and if so go back to it */
563 strcpyW(envvar, equalsW);
564 strcatW(envvar, cmd);
565 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
566 static const WCHAR fmt[] = {'%','s','\\','\0'};
567 wsprintf(cmd, fmt, cmd);
569 status = SetCurrentDirectory (cmd);
570 if (!status) WCMD_print_error ();
571 HeapFree( GetProcessHeap(), 0, cmd );
572 return;
575 sa.nLength = sizeof(sa);
576 sa.lpSecurityDescriptor = NULL;
577 sa.bInheritHandle = TRUE;
580 * Redirect stdin, stdout and/or stderr if required.
583 if ((p = strchrW(cmd,'<')) != NULL) {
584 if (first_redir == NULL) first_redir = p;
585 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
586 FILE_ATTRIBUTE_NORMAL, NULL);
587 if (h == INVALID_HANDLE_VALUE) {
588 WCMD_print_error ();
589 HeapFree( GetProcessHeap(), 0, cmd );
590 return;
592 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
593 SetStdHandle (STD_INPUT_HANDLE, h);
596 /* Scan the whole command looking for > and 2> */
597 redir = cmd;
598 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
599 int handle = 0;
601 if (*(p-1)!='2') {
602 if (first_redir == NULL) first_redir = p;
603 handle = 1;
604 } else {
605 if (first_redir == NULL) first_redir = (p-1);
606 handle = 2;
609 p++;
610 if ('>' == *p) {
611 creationDisposition = OPEN_ALWAYS;
612 p++;
614 else {
615 creationDisposition = CREATE_ALWAYS;
618 /* Add support for 2>&1 */
619 redir = p;
620 if (*p == '&') {
621 int idx = *(p+1) - '0';
623 if (DuplicateHandle(GetCurrentProcess(),
624 GetStdHandle(idx_stdhandles[idx]),
625 GetCurrentProcess(),
627 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
628 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
630 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
632 } else {
633 WCHAR *param = WCMD_parameter (p, 0, NULL);
634 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
635 FILE_ATTRIBUTE_NORMAL, NULL);
636 if (h == INVALID_HANDLE_VALUE) {
637 WCMD_print_error ();
638 HeapFree( GetProcessHeap(), 0, cmd );
639 return;
641 if (SetFilePointer (h, 0, NULL, FILE_END) ==
642 INVALID_SET_FILE_POINTER) {
643 WCMD_print_error ();
645 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
648 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
649 SetStdHandle (idx_stdhandles[handle], h);
652 /* Terminate the command string at <, or first 2> or > */
653 if (first_redir != NULL) *first_redir = '\0';
656 * Strip leading whitespaces, and a '@' if supplied
658 whichcmd = WCMD_strtrim_leading_spaces(cmd);
659 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
660 if (whichcmd[0] == '@') whichcmd++;
663 * Check if the command entered is internal. If it is, pass the rest of the
664 * line down to the command. If not try to run a program.
667 count = 0;
668 while (IsCharAlphaNumeric(whichcmd[count])) {
669 count++;
671 for (i=0; i<=WCMD_EXIT; i++) {
672 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
673 whichcmd, count, inbuilt[i], -1) == 2) break;
675 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
676 WCMD_parse (p, quals, param1, param2);
677 switch (i) {
679 case WCMD_ATTRIB:
680 WCMD_setshow_attrib ();
681 break;
682 case WCMD_CALL:
683 WCMD_call (p);
684 break;
685 case WCMD_CD:
686 case WCMD_CHDIR:
687 WCMD_setshow_default (p);
688 break;
689 case WCMD_CLS:
690 WCMD_clear_screen ();
691 break;
692 case WCMD_COPY:
693 WCMD_copy ();
694 break;
695 case WCMD_CTTY:
696 WCMD_change_tty ();
697 break;
698 case WCMD_DATE:
699 WCMD_setshow_date ();
700 break;
701 case WCMD_DEL:
702 case WCMD_ERASE:
703 WCMD_delete (p, TRUE);
704 break;
705 case WCMD_DIR:
706 WCMD_directory (p);
707 break;
708 case WCMD_ECHO:
709 WCMD_echo(&whichcmd[count]);
710 break;
711 case WCMD_FOR:
712 WCMD_for (p, cmdList);
713 break;
714 case WCMD_GOTO:
715 WCMD_goto (cmdList);
716 break;
717 case WCMD_HELP:
718 WCMD_give_help (p);
719 break;
720 case WCMD_IF:
721 WCMD_if (p, cmdList);
722 break;
723 case WCMD_LABEL:
724 WCMD_volume (1, p);
725 break;
726 case WCMD_MD:
727 case WCMD_MKDIR:
728 WCMD_create_dir ();
729 break;
730 case WCMD_MOVE:
731 WCMD_move ();
732 break;
733 case WCMD_PATH:
734 WCMD_setshow_path (p);
735 break;
736 case WCMD_PAUSE:
737 WCMD_pause ();
738 break;
739 case WCMD_PROMPT:
740 WCMD_setshow_prompt ();
741 break;
742 case WCMD_REM:
743 break;
744 case WCMD_REN:
745 case WCMD_RENAME:
746 WCMD_rename ();
747 break;
748 case WCMD_RD:
749 case WCMD_RMDIR:
750 WCMD_remove_dir (p);
751 break;
752 case WCMD_SETLOCAL:
753 WCMD_setlocal(p);
754 break;
755 case WCMD_ENDLOCAL:
756 WCMD_endlocal();
757 break;
758 case WCMD_SET:
759 WCMD_setshow_env (p);
760 break;
761 case WCMD_SHIFT:
762 WCMD_shift (p);
763 break;
764 case WCMD_TIME:
765 WCMD_setshow_time ();
766 break;
767 case WCMD_TITLE:
768 if (strlenW(&whichcmd[count]) > 0)
769 WCMD_title(&whichcmd[count+1]);
770 break;
771 case WCMD_TYPE:
772 WCMD_type (p);
773 break;
774 case WCMD_VER:
775 WCMD_version ();
776 break;
777 case WCMD_VERIFY:
778 WCMD_verify (p);
779 break;
780 case WCMD_VOL:
781 WCMD_volume (0, p);
782 break;
783 case WCMD_PUSHD:
784 WCMD_pushd(p);
785 break;
786 case WCMD_POPD:
787 WCMD_popd();
788 break;
789 case WCMD_ASSOC:
790 WCMD_assoc(p, TRUE);
791 break;
792 case WCMD_COLOR:
793 WCMD_color();
794 break;
795 case WCMD_FTYPE:
796 WCMD_assoc(p, FALSE);
797 break;
798 case WCMD_MORE:
799 WCMD_more(p);
800 break;
801 case WCMD_EXIT:
802 WCMD_exit (cmdList);
803 break;
804 default:
805 WCMD_run_program (whichcmd, 0);
807 HeapFree( GetProcessHeap(), 0, cmd );
809 /* Restore old handles */
810 for (i=0; i<3; i++) {
811 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
812 CloseHandle (GetStdHandle (idx_stdhandles[i]));
813 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
818 static void init_msvcrt_io_block(STARTUPINFO* st)
820 STARTUPINFO st_p;
821 /* fetch the parent MSVCRT info block if any, so that the child can use the
822 * same handles as its grand-father
824 st_p.cb = sizeof(STARTUPINFO);
825 GetStartupInfo(&st_p);
826 st->cbReserved2 = st_p.cbReserved2;
827 st->lpReserved2 = st_p.lpReserved2;
828 if (st_p.cbReserved2 && st_p.lpReserved2)
830 /* Override the entries for fd 0,1,2 if we happened
831 * to change those std handles (this depends on the way wcmd sets
832 * it's new input & output handles)
834 size_t sz = max(sizeof(unsigned) + (sizeof(WCHAR) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
835 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
836 if (ptr)
838 unsigned num = *(unsigned*)st_p.lpReserved2;
839 WCHAR* flags = (WCHAR*)(ptr + sizeof(unsigned));
840 HANDLE* handles = (HANDLE*)(flags + num * sizeof(WCHAR));
842 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
843 st->cbReserved2 = sz;
844 st->lpReserved2 = ptr;
846 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
847 if (num <= 0 || (flags[0] & WX_OPEN))
849 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
850 flags[0] |= WX_OPEN;
852 if (num <= 1 || (flags[1] & WX_OPEN))
854 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
855 flags[1] |= WX_OPEN;
857 if (num <= 2 || (flags[2] & WX_OPEN))
859 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
860 flags[2] |= WX_OPEN;
862 #undef WX_OPEN
867 /******************************************************************************
868 * WCMD_run_program
870 * Execute a command line as an external program. Must allow recursion.
872 * Precedence:
873 * Manual testing under windows shows PATHEXT plays a key part in this,
874 * and the search algorithm and precedence appears to be as follows.
876 * Search locations:
877 * If directory supplied on command, just use that directory
878 * If extension supplied on command, look for that explicit name first
879 * Otherwise, search in each directory on the path
880 * Precedence:
881 * If extension supplied on command, look for that explicit name first
882 * Then look for supplied name .* (even if extension supplied, so
883 * 'garbage.exe' will match 'garbage.exe.cmd')
884 * If any found, cycle through PATHEXT looking for name.exe one by one
885 * Launching
886 * Once a match has been found, it is launched - Code currently uses
887 * findexecutable to acheive this which is left untouched.
890 void WCMD_run_program (WCHAR *command, int called) {
892 WCHAR temp[MAX_PATH];
893 WCHAR pathtosearch[MAXSTRING];
894 WCHAR *pathposn;
895 WCHAR stemofsearch[MAX_PATH];
896 WCHAR *lastSlash;
897 WCHAR pathext[MAXSTRING];
898 BOOL extensionsupplied = FALSE;
899 BOOL launched = FALSE;
900 BOOL status;
901 BOOL assumeInternal = FALSE;
902 DWORD len;
903 static const WCHAR envPath[] = {'P','A','T','H','\0'};
904 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
905 static const WCHAR delims[] = {'/','\\',':','\0'};
907 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
908 if (!(*param1) && !(*param2))
909 return;
911 /* Calculate the search path and stem to search for */
912 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
913 static const WCHAR curDir[] = {'.',';','\0'};
914 strcpyW(pathtosearch, curDir);
915 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
916 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
917 static const WCHAR curDir[] = {'.','\0'};
918 strcpyW (pathtosearch, curDir);
920 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
921 strcpyW(stemofsearch, param1);
923 } else {
925 /* Convert eg. ..\fred to include a directory by removing file part */
926 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
927 lastSlash = strrchrW(pathtosearch, '\\');
928 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
929 if (lastSlash) *lastSlash = 0x00;
930 strcpyW(stemofsearch, lastSlash+1);
933 /* Now extract PATHEXT */
934 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
935 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
936 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
937 '.','c','o','m',';',
938 '.','c','m','d',';',
939 '.','e','x','e','\0'};
940 strcpyW (pathext, dfltPathExt);
943 /* Loop through the search path, dir by dir */
944 pathposn = pathtosearch;
945 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
946 wine_dbgstr_w(stemofsearch));
947 while (!launched && pathposn) {
949 WCHAR thisDir[MAX_PATH] = {'\0'};
950 WCHAR *pos = NULL;
951 BOOL found = FALSE;
952 const WCHAR slashW[] = {'\\','\0'};
954 /* Work on the first directory on the search path */
955 pos = strchrW(pathposn, ';');
956 if (pos) {
957 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
958 thisDir[(pos-pathposn)] = 0x00;
959 pathposn = pos+1;
961 } else {
962 strcpyW(thisDir, pathposn);
963 pathposn = NULL;
966 /* Since you can have eg. ..\.. on the path, need to expand
967 to full information */
968 strcpyW(temp, thisDir);
969 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
971 /* 1. If extension supplied, see if that file exists */
972 strcatW(thisDir, slashW);
973 strcatW(thisDir, stemofsearch);
974 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
976 /* 1. If extension supplied, see if that file exists */
977 if (extensionsupplied) {
978 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
979 found = TRUE;
983 /* 2. Any .* matches? */
984 if (!found) {
985 HANDLE h;
986 WIN32_FIND_DATA finddata;
987 static const WCHAR allFiles[] = {'.','*','\0'};
989 strcatW(thisDir,allFiles);
990 h = FindFirstFile(thisDir, &finddata);
991 FindClose(h);
992 if (h != INVALID_HANDLE_VALUE) {
994 WCHAR *thisExt = pathext;
996 /* 3. Yes - Try each path ext */
997 while (thisExt) {
998 WCHAR *nextExt = strchrW(thisExt, ';');
1000 if (nextExt) {
1001 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1002 pos[(nextExt-thisExt)] = 0x00;
1003 thisExt = nextExt+1;
1004 } else {
1005 strcpyW(pos, thisExt);
1006 thisExt = NULL;
1009 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1010 found = TRUE;
1011 thisExt = NULL;
1017 /* Internal programs won't be picked up by this search, so even
1018 though not found, try one last createprocess and wait for it
1019 to complete.
1020 Note: Ideally we could tell between a console app (wait) and a
1021 windows app, but the API's for it fail in this case */
1022 if (!found && pathposn == NULL) {
1023 WINE_TRACE("ASSUMING INTERNAL\n");
1024 assumeInternal = TRUE;
1025 } else {
1026 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1029 /* Once found, launch it */
1030 if (found || assumeInternal) {
1031 STARTUPINFO st;
1032 PROCESS_INFORMATION pe;
1033 SHFILEINFO psfi;
1034 DWORD console;
1035 HINSTANCE hinst;
1036 WCHAR *ext = strrchrW( thisDir, '.' );
1037 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1038 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1040 launched = TRUE;
1042 /* Special case BAT and CMD */
1043 if (ext && !strcmpiW(ext, batExt)) {
1044 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1045 return;
1046 } else if (ext && !strcmpiW(ext, cmdExt)) {
1047 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1048 return;
1049 } else {
1051 /* thisDir contains the file to be launched, but with what?
1052 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1053 hinst = FindExecutable (thisDir, NULL, temp);
1054 if ((INT_PTR)hinst < 32)
1055 console = 0;
1056 else
1057 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1059 ZeroMemory (&st, sizeof(STARTUPINFO));
1060 st.cb = sizeof(STARTUPINFO);
1061 init_msvcrt_io_block(&st);
1063 /* Launch the process and if a CUI wait on it to complete
1064 Note: Launching internal wine processes cannot specify a full path to exe */
1065 status = CreateProcess (assumeInternal?NULL : thisDir,
1066 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1067 if ((opt_c || opt_k) && !opt_s && !status
1068 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1069 /* strip first and last quote WCHARacters and try again */
1070 WCMD_opt_s_strip_quotes(command);
1071 opt_s=1;
1072 WCMD_run_program(command, called);
1073 return;
1075 if (!status) {
1076 WCMD_print_error ();
1077 /* If a command fails to launch, it sets errorlevel 9009 - which
1078 does not seem to have any associated constant definition */
1079 errorlevel = 9009;
1080 return;
1082 if (!assumeInternal && !console) errorlevel = 0;
1083 else
1085 if (assumeInternal || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1086 GetExitCodeProcess (pe.hProcess, &errorlevel);
1087 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1089 CloseHandle(pe.hProcess);
1090 CloseHandle(pe.hThread);
1091 return;
1096 /* Not found anywhere - give up */
1097 SetLastError(ERROR_FILE_NOT_FOUND);
1098 WCMD_print_error ();
1100 /* If a command fails to launch, it sets errorlevel 9009 - which
1101 does not seem to have any associated constant definition */
1102 errorlevel = 9009;
1103 return;
1107 /******************************************************************************
1108 * WCMD_show_prompt
1110 * Display the prompt on STDout
1114 void WCMD_show_prompt (void) {
1116 int status;
1117 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1118 WCHAR *p, *q;
1119 DWORD len;
1120 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1122 len = GetEnvironmentVariable (envPrompt, prompt_string,
1123 sizeof(prompt_string)/sizeof(WCHAR));
1124 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1125 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1126 strcpyW (prompt_string, dfltPrompt);
1128 p = prompt_string;
1129 q = out_string;
1130 *q = '\0';
1131 while (*p != '\0') {
1132 if (*p != '$') {
1133 *q++ = *p++;
1134 *q = '\0';
1136 else {
1137 p++;
1138 switch (toupper(*p)) {
1139 case '$':
1140 *q++ = '$';
1141 break;
1142 case 'A':
1143 *q++ = '&';
1144 break;
1145 case 'B':
1146 *q++ = '|';
1147 break;
1148 case 'C':
1149 *q++ = '(';
1150 break;
1151 case 'D':
1152 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1153 while (*q) q++;
1154 break;
1155 case 'E':
1156 *q++ = '\E';
1157 break;
1158 case 'F':
1159 *q++ = ')';
1160 break;
1161 case 'G':
1162 *q++ = '>';
1163 break;
1164 case 'H':
1165 *q++ = '\b';
1166 break;
1167 case 'L':
1168 *q++ = '<';
1169 break;
1170 case 'N':
1171 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1172 if (status) {
1173 *q++ = curdir[0];
1175 break;
1176 case 'P':
1177 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1178 if (status) {
1179 strcatW (q, curdir);
1180 while (*q) q++;
1182 break;
1183 case 'Q':
1184 *q++ = '=';
1185 break;
1186 case 'S':
1187 *q++ = ' ';
1188 break;
1189 case 'T':
1190 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1191 while (*q) q++;
1192 break;
1193 case 'V':
1194 strcatW (q, version_string);
1195 while (*q) q++;
1196 break;
1197 case '_':
1198 *q++ = '\n';
1199 break;
1200 case '+':
1201 if (pushd_directories) {
1202 memset(q, '+', pushd_directories->u.stackdepth);
1203 q = q + pushd_directories->u.stackdepth;
1205 break;
1207 p++;
1208 *q = '\0';
1211 WCMD_output_asis (out_string);
1214 /****************************************************************************
1215 * WCMD_print_error
1217 * Print the message for GetLastError
1220 void WCMD_print_error (void) {
1221 LPVOID lpMsgBuf;
1222 DWORD error_code;
1223 int status;
1225 error_code = GetLastError ();
1226 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1227 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1228 if (!status) {
1229 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1230 error_code, GetLastError());
1231 return;
1233 WCMD_output_asis (lpMsgBuf);
1234 LocalFree ((HLOCAL)lpMsgBuf);
1235 WCMD_output_asis (newline);
1236 return;
1239 /*******************************************************************
1240 * WCMD_parse - parse a command into parameters and qualifiers.
1242 * On exit, all qualifiers are concatenated into q, the first string
1243 * not beginning with "/" is in p1 and the
1244 * second in p2. Any subsequent non-qualifier strings are lost.
1245 * Parameters in quotes are handled.
1248 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1250 int p = 0;
1252 *q = *p1 = *p2 = '\0';
1253 while (TRUE) {
1254 switch (*s) {
1255 case '/':
1256 *q++ = *s++;
1257 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1258 *q++ = toupper (*s++);
1260 *q = '\0';
1261 break;
1262 case ' ':
1263 case '\t':
1264 s++;
1265 break;
1266 case '"':
1267 s++;
1268 while ((*s != '\0') && (*s != '"')) {
1269 if (p == 0) *p1++ = *s++;
1270 else if (p == 1) *p2++ = *s++;
1271 else s++;
1273 if (p == 0) *p1 = '\0';
1274 if (p == 1) *p2 = '\0';
1275 p++;
1276 if (*s == '"') s++;
1277 break;
1278 case '\0':
1279 return;
1280 default:
1281 while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1282 if (p == 0) *p1++ = *s++;
1283 else if (p == 1) *p2++ = *s++;
1284 else s++;
1286 if (p == 0) *p1 = '\0';
1287 if (p == 1) *p2 = '\0';
1288 p++;
1293 /*******************************************************************
1294 * WCMD_output_asis_len - send output to current standard output
1296 * Output a formatted unicode string. Ideally this will go to the console
1297 * and hence required WriteConsoleW to output it, however if file i/o is
1298 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1300 static void WCMD_output_asis_len(const WCHAR *message, int len) {
1302 DWORD nOut= 0;
1303 DWORD res = 0;
1305 /* If nothing to write, return (MORE does this sometimes) */
1306 if (!len) return;
1308 /* Try to write as unicode assuming it is to a console */
1309 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1310 message, len, &nOut, NULL);
1312 /* If writing to console fails, assume its file
1313 i/o so convert to OEM codepage and output */
1314 if (!res) {
1315 BOOL usedDefaultChar = FALSE;
1316 DWORD convertedChars;
1318 if (!unicodePipes) {
1320 * Allocate buffer to use when writing to file. (Not freed, as one off)
1322 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1323 MAX_WRITECONSOLE_SIZE);
1324 if (!output_bufA) {
1325 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1326 return;
1329 /* Convert to OEM, then output */
1330 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1331 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1332 "?", &usedDefaultChar);
1333 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1334 &nOut, FALSE);
1335 } else {
1336 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message, len*sizeof(WCHAR),
1337 &nOut, FALSE);
1340 return;
1343 /*******************************************************************
1344 * WCMD_output - send output to current standard output device.
1348 void WCMD_output (const WCHAR *format, ...) {
1350 va_list ap;
1351 WCHAR string[1024];
1352 int ret;
1354 va_start(ap,format);
1355 ret = wvsprintf (string, format, ap);
1356 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1357 WINE_ERR("Output truncated in WCMD_output\n" );
1358 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1359 string[ret] = '\0';
1361 va_end(ap);
1362 WCMD_output_asis_len(string, ret);
1366 static int line_count;
1367 static int max_height;
1368 static int max_width;
1369 static BOOL paged_mode;
1370 static int numChars;
1372 void WCMD_enter_paged_mode(const WCHAR *msg)
1374 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1376 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1377 max_height = consoleInfo.dwSize.Y;
1378 max_width = consoleInfo.dwSize.X;
1379 } else {
1380 max_height = 25;
1381 max_width = 80;
1383 paged_mode = TRUE;
1384 line_count = 0;
1385 numChars = 0;
1386 pagedMessage = (msg==NULL)? anykey : msg;
1389 void WCMD_leave_paged_mode(void)
1391 paged_mode = FALSE;
1392 pagedMessage = NULL;
1395 /*******************************************************************
1396 * WCMD_output_asis - send output to current standard output device.
1397 * without formatting eg. when message contains '%'
1400 void WCMD_output_asis (const WCHAR *message) {
1401 DWORD count;
1402 const WCHAR* ptr;
1403 WCHAR string[1024];
1405 if (paged_mode) {
1406 do {
1407 ptr = message;
1408 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1409 numChars++;
1410 ptr++;
1412 if (*ptr == '\n') ptr++;
1413 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message));
1414 if (ptr) {
1415 numChars = 0;
1416 if (++line_count >= max_height - 1) {
1417 line_count = 0;
1418 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage));
1419 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1420 sizeof(string)/sizeof(WCHAR), &count, NULL);
1423 } while (((message = ptr) != NULL) && (*ptr));
1424 } else {
1425 WCMD_output_asis_len(message, lstrlen(message));
1430 /***************************************************************************
1431 * WCMD_strtrim_leading_spaces
1433 * Remove leading spaces from a string. Return a pointer to the first
1434 * non-space character. Does not modify the input string
1437 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1439 WCHAR *ptr;
1441 ptr = string;
1442 while (*ptr == ' ') ptr++;
1443 return ptr;
1446 /*************************************************************************
1447 * WCMD_strtrim_trailing_spaces
1449 * Remove trailing spaces from a string. This routine modifies the input
1450 * string by placing a null after the last non-space WCHARacter
1453 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1455 WCHAR *ptr;
1457 ptr = string + strlenW (string) - 1;
1458 while ((*ptr == ' ') && (ptr >= string)) {
1459 *ptr = '\0';
1460 ptr--;
1464 /*************************************************************************
1465 * WCMD_opt_s_strip_quotes
1467 * Remove first and last quote WCHARacters, preserving all other text
1470 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1471 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1472 while((*dest=*src) != '\0') {
1473 if (*src=='\"')
1474 lastq=dest;
1475 dest++, src++;
1477 if (lastq) {
1478 dest=lastq++;
1479 while ((*dest++=*lastq++) != 0)
1484 /*************************************************************************
1485 * WCMD_pipe
1487 * Handle pipes within a command - the DOS way using temporary files.
1490 void WCMD_pipe (CMD_LIST **cmdEntry) {
1492 WCHAR *p;
1493 WCHAR *command = (*cmdEntry)->command;
1494 WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1495 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1496 static const WCHAR redirIn[] = {'%','s',' ','<',' ','%','s','\0'};
1497 static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1498 static const WCHAR cmdW[] = {'C','M','D','\0'};
1501 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1502 GetTempFileName (temp_path, cmdW, 0, temp_file);
1503 p = strchrW(command, '|');
1504 *p++ = '\0';
1505 wsprintf (temp_cmd, redirOut, command, temp_file);
1506 WCMD_process_command (temp_cmd, cmdEntry);
1507 command = p;
1508 while ((p = strchrW(command, '|'))) {
1509 *p++ = '\0';
1510 GetTempFileName (temp_path, cmdW, 0, temp_file2);
1511 wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1512 WCMD_process_command (temp_cmd, cmdEntry);
1513 DeleteFile (temp_file);
1514 strcpyW (temp_file, temp_file2);
1515 command = p;
1517 wsprintf (temp_cmd, redirIn, command, temp_file);
1518 WCMD_process_command (temp_cmd, cmdEntry);
1519 DeleteFile (temp_file);
1522 /*************************************************************************
1523 * WCMD_expand_envvar
1525 * Expands environment variables, allowing for WCHARacter substitution
1527 static WCHAR *WCMD_expand_envvar(WCHAR *start) {
1528 WCHAR *endOfVar = NULL, *s;
1529 WCHAR *colonpos = NULL;
1530 WCHAR thisVar[MAXSTRING];
1531 WCHAR thisVarContents[MAXSTRING];
1532 WCHAR savedchar = 0x00;
1533 int len;
1535 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1536 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1537 static const WCHAR Date[] = {'D','A','T','E','\0'};
1538 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1539 static const WCHAR Time[] = {'T','I','M','E','\0'};
1540 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1541 static const WCHAR Cd[] = {'C','D','\0'};
1542 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1543 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1544 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1546 /* Find the end of the environment variable, and extract name */
1547 endOfVar = strchrW(start+1, '%');
1548 if (endOfVar == NULL) {
1549 /* In batch program, missing terminator for % and no following
1550 ':' just removes the '%' */
1551 s = WCMD_strdupW(start + 1);
1552 strcpyW (start, s);
1553 free(s);
1555 /* FIXME: Some other special conditions here depending on whether
1556 in batch, complex or not, and whether env var exists or not! */
1557 return start;
1559 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1560 thisVar[(endOfVar - start)+1] = 0x00;
1561 colonpos = strchrW(thisVar+1, ':');
1563 /* If there's complex substitution, just need %var% for now
1564 to get the expanded data to play with */
1565 if (colonpos) {
1566 *colonpos = '%';
1567 savedchar = *(colonpos+1);
1568 *(colonpos+1) = 0x00;
1571 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1573 /* Expand to contents, if unchanged, return */
1574 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1575 /* override if existing env var called that name */
1576 if ((CompareString (LOCALE_USER_DEFAULT,
1577 NORM_IGNORECASE | SORT_STRINGSORT,
1578 thisVar, 12, ErrorLvlP, -1) == 2) &&
1579 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1580 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1581 static const WCHAR fmt[] = {'%','d','\0'};
1582 wsprintf(thisVarContents, fmt, errorlevel);
1583 len = strlenW(thisVarContents);
1585 } else if ((CompareString (LOCALE_USER_DEFAULT,
1586 NORM_IGNORECASE | SORT_STRINGSORT,
1587 thisVar, 6, DateP, -1) == 2) &&
1588 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1589 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1591 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1592 NULL, thisVarContents, MAXSTRING);
1593 len = strlenW(thisVarContents);
1595 } else if ((CompareString (LOCALE_USER_DEFAULT,
1596 NORM_IGNORECASE | SORT_STRINGSORT,
1597 thisVar, 6, TimeP, -1) == 2) &&
1598 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1599 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1600 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1601 NULL, thisVarContents, MAXSTRING);
1602 len = strlenW(thisVarContents);
1604 } else if ((CompareString (LOCALE_USER_DEFAULT,
1605 NORM_IGNORECASE | SORT_STRINGSORT,
1606 thisVar, 4, CdP, -1) == 2) &&
1607 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1608 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1609 GetCurrentDirectory (MAXSTRING, thisVarContents);
1610 len = strlenW(thisVarContents);
1612 } else if ((CompareString (LOCALE_USER_DEFAULT,
1613 NORM_IGNORECASE | SORT_STRINGSORT,
1614 thisVar, 8, RandomP, -1) == 2) &&
1615 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1616 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1617 static const WCHAR fmt[] = {'%','d','\0'};
1618 wsprintf(thisVarContents, fmt, rand() % 32768);
1619 len = strlenW(thisVarContents);
1621 } else {
1623 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1624 sizeof(thisVarContents)/sizeof(WCHAR));
1627 if (len == 0)
1628 return endOfVar+1;
1630 /* In a batch program, unknown env vars are replaced with nothing,
1631 note syntax %garbage:1,3% results in anything after the ':'
1632 except the %
1633 From the command line, you just get back what you entered */
1634 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1636 /* Restore the complex part after the compare */
1637 if (colonpos) {
1638 *colonpos = ':';
1639 *(colonpos+1) = savedchar;
1642 s = WCMD_strdupW(endOfVar + 1);
1644 /* Command line - just ignore this */
1645 if (context == NULL) return endOfVar+1;
1647 /* Batch - replace unknown env var with nothing */
1648 if (colonpos == NULL) {
1649 strcpyW (start, s);
1651 } else {
1652 len = strlenW(thisVar);
1653 thisVar[len-1] = 0x00;
1654 /* If %:...% supplied, : is retained */
1655 if (colonpos == thisVar+1) {
1656 strcpyW (start, colonpos);
1657 } else {
1658 strcpyW (start, colonpos+1);
1660 strcatW (start, s);
1662 free (s);
1663 return start;
1667 /* See if we need to do complex substitution (any ':'s), if not
1668 then our work here is done */
1669 if (colonpos == NULL) {
1670 s = WCMD_strdupW(endOfVar + 1);
1671 strcpyW (start, thisVarContents);
1672 strcatW (start, s);
1673 free(s);
1674 return start;
1677 /* Restore complex bit */
1678 *colonpos = ':';
1679 *(colonpos+1) = savedchar;
1682 Handle complex substitutions:
1683 xxx=yyy (replace xxx with yyy)
1684 *xxx=yyy (replace up to and including xxx with yyy)
1685 ~x (from x WCHARs in)
1686 ~-x (from x WCHARs from the end)
1687 ~x,y (from x WCHARs in for y WCHARacters)
1688 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1691 /* ~ is substring manipulation */
1692 if (savedchar == '~') {
1694 int substrposition, substrlength = 0;
1695 WCHAR *commapos = strchrW(colonpos+2, ',');
1696 WCHAR *startCopy;
1698 substrposition = atolW(colonpos+2);
1699 if (commapos) substrlength = atolW(commapos+1);
1701 s = WCMD_strdupW(endOfVar + 1);
1703 /* Check bounds */
1704 if (substrposition >= 0) {
1705 startCopy = &thisVarContents[min(substrposition, len)];
1706 } else {
1707 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1710 if (commapos == NULL) {
1711 strcpyW (start, startCopy); /* Copy the lot */
1712 } else if (substrlength < 0) {
1714 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1715 if (copybytes > len) copybytes = len;
1716 else if (copybytes < 0) copybytes = 0;
1717 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1718 start[copybytes] = 0x00;
1719 } else {
1720 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1721 start[substrlength] = 0x00;
1724 strcatW (start, s);
1725 free(s);
1726 return start;
1728 /* search and replace manipulation */
1729 } else {
1730 WCHAR *equalspos = strstrW(colonpos, equalsW);
1731 WCHAR *replacewith = equalspos+1;
1732 WCHAR *found = NULL;
1733 WCHAR *searchIn;
1734 WCHAR *searchFor;
1736 s = WCMD_strdupW(endOfVar + 1);
1737 if (equalspos == NULL) return start+1;
1739 /* Null terminate both strings */
1740 thisVar[strlenW(thisVar)-1] = 0x00;
1741 *equalspos = 0x00;
1743 /* Since we need to be case insensitive, copy the 2 buffers */
1744 searchIn = WCMD_strdupW(thisVarContents);
1745 CharUpperBuff(searchIn, strlenW(thisVarContents));
1746 searchFor = WCMD_strdupW(colonpos+1);
1747 CharUpperBuff(searchFor, strlenW(colonpos+1));
1750 /* Handle wildcard case */
1751 if (*(colonpos+1) == '*') {
1752 /* Search for string to replace */
1753 found = strstrW(searchIn, searchFor+1);
1755 if (found) {
1756 /* Do replacement */
1757 strcpyW(start, replacewith);
1758 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1759 strcatW(start, s);
1760 free(s);
1761 } else {
1762 /* Copy as it */
1763 strcpyW(start, thisVarContents);
1764 strcatW(start, s);
1767 } else {
1768 /* Loop replacing all instances */
1769 WCHAR *lastFound = searchIn;
1770 WCHAR *outputposn = start;
1772 *start = 0x00;
1773 while ((found = strstrW(lastFound, searchFor))) {
1774 lstrcpynW(outputposn,
1775 thisVarContents + (lastFound-searchIn),
1776 (found - lastFound)+1);
1777 outputposn = outputposn + (found - lastFound);
1778 strcatW(outputposn, replacewith);
1779 outputposn = outputposn + strlenW(replacewith);
1780 lastFound = found + strlenW(searchFor);
1782 strcatW(outputposn,
1783 thisVarContents + (lastFound-searchIn));
1784 strcatW(outputposn, s);
1786 free(searchIn);
1787 free(searchFor);
1788 return start;
1790 return start+1;
1793 /*************************************************************************
1794 * WCMD_LoadMessage
1795 * Load a string from the resource file, handling any error
1796 * Returns string retrieved from resource file
1798 WCHAR *WCMD_LoadMessage(UINT id) {
1799 static WCHAR msg[2048];
1800 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1802 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1803 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1804 strcpyW(msg, failedMsg);
1806 return msg;
1809 /*************************************************************************
1810 * WCMD_strdupW
1811 * A wide version of strdup as its missing from unicode.h
1813 WCHAR *WCMD_strdupW(WCHAR *input) {
1814 int len=strlenW(input)+1;
1815 /* Note: Use malloc not HeapAlloc to emulate strdup */
1816 WCHAR *result = malloc(len * sizeof(WCHAR));
1817 memcpy(result, input, len * sizeof(WCHAR));
1818 return result;
1821 /***************************************************************************
1822 * WCMD_Readfile
1824 * Read characters in from a console/file, returning result in Unicode
1825 * with signature identical to ReadFile
1827 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1828 LPDWORD charsRead, const LPOVERLAPPED unused) {
1830 BOOL res;
1832 /* Try to read from console as Unicode */
1833 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1835 /* If reading from console has failed we assume its file
1836 i/o so read in and convert from OEM codepage */
1837 if (!res) {
1839 DWORD numRead;
1841 * Allocate buffer to use when reading from file. Not freed
1843 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1844 MAX_WRITECONSOLE_SIZE);
1845 if (!output_bufA) {
1846 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1847 return 0;
1850 /* Read from file (assume OEM codepage) */
1851 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1853 /* Convert from OEM */
1854 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1855 intoBuf, maxChars);
1858 return res;
1861 /***************************************************************************
1862 * WCMD_DumpCommands
1864 * Domps out the parsed command line to ensure syntax is correct
1866 void WCMD_DumpCommands(CMD_LIST *commands) {
1867 WCHAR buffer[MAXSTRING];
1868 CMD_LIST *thisCmd = commands;
1869 const WCHAR fmt[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1870 '%','p',' ','%','s','\0'};
1872 WINE_TRACE("Parsed line:\n");
1873 while (thisCmd != NULL) {
1874 sprintfW(buffer, fmt,
1875 thisCmd,
1876 thisCmd->isAmphersand?'Y':'N',
1877 thisCmd->bracketDepth,
1878 thisCmd->nextcommand,
1879 thisCmd->command);
1880 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1881 thisCmd = thisCmd->nextcommand;
1885 /***************************************************************************
1886 * WCMD_ReadAndParseLine
1888 * Either uses supplied input or
1889 * Reads a file from the handle, and then...
1890 * Parse the text buffer, spliting into seperate commands
1891 * - unquoted && strings split 2 commands but the 2nd is flagged as
1892 * following an &&
1893 * - ( as the first character just ups the bracket depth
1894 * - unquoted ) when bracket depth > 0 terminates a bracket and
1895 * adds a CMD_LIST structure with null command
1896 * - Anything else gets put into the command string (including
1897 * redirects)
1899 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1901 WCHAR *curPos;
1902 BOOL inQuotes = FALSE;
1903 WCHAR curString[MAXSTRING];
1904 int curLen = 0;
1905 int curDepth = 0;
1906 CMD_LIST *thisEntry = NULL;
1907 CMD_LIST *lastEntry = NULL;
1908 BOOL isAmphersand = FALSE;
1909 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1910 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1911 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1912 const WCHAR ifCmd[] = {'i','f',' ','\0'};
1913 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1914 BOOL inRem = FALSE;
1915 BOOL inFor = FALSE;
1916 BOOL inIf = FALSE;
1917 BOOL inElse= FALSE;
1918 BOOL onlyWhiteSpace = FALSE;
1919 BOOL lastWasWhiteSpace = FALSE;
1920 BOOL lastWasDo = FALSE;
1921 BOOL lastWasIn = FALSE;
1922 BOOL lastWasElse = FALSE;
1924 /* Allocate working space for a command read from keyboard, file etc */
1925 if (!extraSpace)
1926 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1928 /* If initial command read in, use that, otherwise get input from handle */
1929 if (optionalcmd != NULL) {
1930 strcpyW(extraSpace, optionalcmd);
1931 } else if (readFrom == INVALID_HANDLE_VALUE) {
1932 WINE_FIXME("No command nor handle supplied\n");
1933 } else {
1934 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1936 curPos = extraSpace;
1938 /* Handle truncated input - issue warning */
1939 if (strlenW(extraSpace) == MAXSTRING -1) {
1940 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1941 WCMD_output_asis(extraSpace);
1942 WCMD_output_asis(newline);
1945 /* Start with an empty string */
1946 curLen = 0;
1948 /* Parse every character on the line being processed */
1949 while (*curPos != 0x00) {
1951 WCHAR thisChar;
1953 /* Debugging AID:
1954 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, curLen,
1955 lastWasWhiteSpace, onlyWhiteSpace);
1958 /* Certain commands need special handling */
1959 if (curLen == 0) {
1960 const WCHAR forDO[] = {'d','o',' ','\0'};
1962 /* If command starts with 'rem', ignore any &&, ( etc */
1963 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1964 curPos, 4, remCmd, -1) == 2) {
1965 inRem = TRUE;
1967 /* If command starts with 'for', handle ('s mid line after IN or DO */
1968 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1969 curPos, 4, forCmd, -1) == 2) {
1970 inFor = TRUE;
1972 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1973 is only true in the command portion of the IF statement, but this
1974 should suffice for now
1975 FIXME: Silly syntax like "if 1(==1( (
1976 echo they equal
1977 )" will be parsed wrong */
1978 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1979 curPos, 3, ifCmd, -1) == 2) {
1980 inIf = TRUE;
1982 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1983 curPos, 5, ifElse, -1) == 2) {
1984 inElse = TRUE;
1985 lastWasElse = TRUE;
1986 onlyWhiteSpace = TRUE;
1987 memcpy(&curString[curLen], curPos, 5*sizeof(WCHAR));
1988 curLen+=5;
1989 curPos+=5;
1990 continue;
1992 /* In a for loop, the DO command will follow a close bracket followed by
1993 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1994 is then 0, and all whitespace is skipped */
1995 } else if (inFor &&
1996 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1997 curPos, 3, forDO, -1) == 2)) {
1998 WINE_TRACE("Found DO\n");
1999 lastWasDo = TRUE;
2000 onlyWhiteSpace = TRUE;
2001 memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2002 curLen+=3;
2003 curPos+=3;
2004 continue;
2006 } else {
2008 /* Special handling for the 'FOR' command */
2009 if (inFor && lastWasWhiteSpace) {
2010 const WCHAR forIN[] = {'i','n',' ','\0'};
2012 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2014 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2015 curPos, 3, forIN, -1) == 2) {
2016 WINE_TRACE("Found IN\n");
2017 lastWasIn = TRUE;
2018 onlyWhiteSpace = TRUE;
2019 memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2020 curLen+=3;
2021 curPos+=3;
2022 continue;
2027 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2028 so just use the default processing ie skip character specific
2029 matching below */
2030 if (!inRem) thisChar = *curPos;
2031 else thisChar = 'X'; /* Character with no special processing */
2033 lastWasWhiteSpace = FALSE; /* Will be reset below */
2035 switch (thisChar) {
2037 case '\t':/* drop through - ignore whitespace at the start of a command */
2038 case ' ': if (curLen > 0)
2039 curString[curLen++] = *curPos;
2041 /* Remember just processed whitespace */
2042 lastWasWhiteSpace = TRUE;
2044 break;
2046 case '"': inQuotes = !inQuotes;
2047 break;
2049 case '(': /* If a '(' is the first non whitespace in a command portion
2050 ie start of line or just after &&, then we read until an
2051 unquoted ) is found */
2052 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2053 ", for(%d, In:%d, Do:%d)"
2054 ", if(%d, else:%d, lwe:%d)\n",
2055 curLen, inQuotes,
2056 onlyWhiteSpace,
2057 inFor, lastWasIn, lastWasDo,
2058 inIf, inElse, lastWasElse);
2059 if (curLen == 0) {
2060 curDepth++;
2062 /* If in quotes, ignore brackets */
2063 } else if (inQuotes) {
2064 curString[curLen++] = *curPos;
2066 /* In a FOR loop, an unquoted '(' may occur straight after
2067 IN or DO
2068 In an IF statement just handle it regardless as we don't
2069 parse the operands
2070 In an ELSE statement, only allow it straight away after
2071 the ELSE and whitespace
2073 } else if (inIf ||
2074 (inElse && lastWasElse && onlyWhiteSpace) ||
2075 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2077 /* Add the current command */
2078 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2079 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2080 (curLen+1) * sizeof(WCHAR));
2081 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2082 thisEntry->command[curLen] = 0x00;
2083 curLen = 0;
2084 thisEntry->nextcommand = NULL;
2085 thisEntry->isAmphersand = isAmphersand;
2086 thisEntry->bracketDepth = curDepth;
2087 if (lastEntry) {
2088 lastEntry->nextcommand = thisEntry;
2089 } else {
2090 *output = thisEntry;
2092 lastEntry = thisEntry;
2094 curDepth++;
2095 } else {
2096 curString[curLen++] = *curPos;
2098 break;
2100 case '&': if (!inQuotes && *(curPos+1) == '&') {
2101 curPos++; /* Skip other & */
2103 /* Add an entry to the command list */
2104 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2105 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2106 (curLen+1) * sizeof(WCHAR));
2107 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2108 thisEntry->command[curLen] = 0x00;
2109 curLen = 0;
2110 thisEntry->nextcommand = NULL;
2111 thisEntry->isAmphersand = isAmphersand;
2112 thisEntry->bracketDepth = curDepth;
2113 if (lastEntry) {
2114 lastEntry->nextcommand = thisEntry;
2115 } else {
2116 *output = thisEntry;
2118 lastEntry = thisEntry;
2119 isAmphersand = TRUE;
2120 } else {
2121 curString[curLen++] = *curPos;
2123 break;
2125 case ')': if (!inQuotes && curDepth > 0) {
2127 /* Add the current command if there is one */
2128 if (curLen) {
2129 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2130 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2131 (curLen+1) * sizeof(WCHAR));
2132 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2133 thisEntry->command[curLen] = 0x00;
2134 curLen = 0;
2135 thisEntry->nextcommand = NULL;
2136 thisEntry->isAmphersand = isAmphersand;
2137 thisEntry->bracketDepth = curDepth;
2138 if (lastEntry) {
2139 lastEntry->nextcommand = thisEntry;
2140 } else {
2141 *output = thisEntry;
2143 lastEntry = thisEntry;
2146 /* Add an empty entry to the command list */
2147 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2148 thisEntry->command = NULL;
2149 thisEntry->nextcommand = NULL;
2150 thisEntry->isAmphersand = FALSE;
2151 thisEntry->bracketDepth = curDepth;
2152 curDepth--;
2153 if (lastEntry) {
2154 lastEntry->nextcommand = thisEntry;
2155 } else {
2156 *output = thisEntry;
2158 lastEntry = thisEntry;
2159 } else {
2160 curString[curLen++] = *curPos;
2162 break;
2163 default:
2164 curString[curLen++] = *curPos;
2167 curPos++;
2169 /* At various times we need to know if we have only skipped whitespace,
2170 so reset this variable and then it will remain true until a non
2171 whitespace is found */
2172 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2174 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2175 if (!lastWasWhiteSpace) {
2176 lastWasIn = lastWasDo = FALSE;
2179 /* If we have reached the end, add this command into the list */
2180 if (*curPos == 0x00 && curLen > 0) {
2182 /* Add an entry to the command list */
2183 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2184 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2185 (curLen+1) * sizeof(WCHAR));
2186 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2187 thisEntry->command[curLen] = 0x00;
2188 curLen = 0;
2189 thisEntry->nextcommand = NULL;
2190 thisEntry->isAmphersand = isAmphersand;
2191 thisEntry->bracketDepth = curDepth;
2192 if (lastEntry) {
2193 lastEntry->nextcommand = thisEntry;
2194 } else {
2195 *output = thisEntry;
2197 lastEntry = thisEntry;
2200 /* If we have reached the end of the string, see if bracketing outstanding */
2201 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2202 inRem = FALSE;
2203 isAmphersand = FALSE;
2204 inQuotes = FALSE;
2205 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2207 /* Read more, skipping any blank lines */
2208 while (*extraSpace == 0x00) {
2209 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2210 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2212 curPos = extraSpace;
2216 /* Dump out the parsed output */
2217 WCMD_DumpCommands(*output);
2219 return extraSpace;
2222 /***************************************************************************
2223 * WCMD_process_commands
2225 * Process all the commands read in so far
2227 void WCMD_process_commands(CMD_LIST *thisCmd) {
2229 /* Loop through the commands, processing them one by one */
2230 while (thisCmd) {
2232 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2233 about them and it will be handled in there)
2234 Also, skip over any batch labels (eg. :fred) */
2235 if (thisCmd->command && thisCmd->command[0] != ':') {
2236 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2237 if (strchrW(thisCmd->command,'|') != NULL) {
2238 WCMD_pipe (&thisCmd);
2239 } else {
2240 WCMD_process_command (thisCmd->command, &thisCmd);
2243 if (thisCmd) thisCmd = thisCmd->nextcommand;
2247 /***************************************************************************
2248 * WCMD_free_commands
2250 * Frees the storage held for a parsed command line
2251 * - This is not done in the process_commands, as eventually the current
2252 * pointer will be modified within the commands, and hence a single free
2253 * routine is simpler
2255 void WCMD_free_commands(CMD_LIST *cmds) {
2257 /* Loop through the commands, freeing them one by one */
2258 while (cmds) {
2259 CMD_LIST *thisCmd = cmds;
2260 cmds = cmds->nextcommand;
2261 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2262 HeapFree(GetProcessHeap(), 0, thisCmd);