new 818051de2c8769029049ce3d36c6b856f47496c9
[wine/hacks.git] / programs / cmd / wcmdmain.c
blob4cd8416fed697cb7cdd14e559a091be24f3b3024
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, FALSE, NULL, NULL);
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, FALSE, NULL, NULL);
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, FALSE, NULL, NULL);
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 /* Don't touch %% */
501 if (*(p+1) == '%') {
502 p+=2;
504 /* Replace %~ modifications if in batch program */
505 } else if (context && *(p+1) == '~') {
506 WCMD_HandleTildaModifiers(&p, NULL);
507 p++;
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);
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);
527 } else {
528 p = WCMD_expand_envvar(p);
531 cmd = new_cmd;
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);
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);
561 * Changing default drive has to be handled as a special case.
564 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
565 WCHAR envvar[5];
566 WCHAR dir[MAX_PATH];
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);
577 status = SetCurrentDirectory (cmd);
578 if (!status) WCMD_print_error ();
579 HeapFree( GetProcessHeap(), 0, cmd );
580 return;
583 sa.nLength = sizeof(sa);
584 sa.lpSecurityDescriptor = NULL;
585 sa.bInheritHandle = TRUE;
588 * Redirect stdin, stdout and/or stderr if required.
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;
600 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
601 SetStdHandle (STD_INPUT_HANDLE, h);
604 /* Scan the whole command looking for > and 2> */
605 redir = cmd;
606 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
607 int handle = 0;
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;
617 p++;
618 if ('>' == *p) {
619 creationDisposition = OPEN_ALWAYS;
620 p++;
622 else {
623 creationDisposition = CREATE_ALWAYS;
626 /* Add support for 2>&1 */
627 redir = p;
628 if (*p == '&') {
629 int idx = *(p+1) - '0';
631 if (DuplicateHandle(GetCurrentProcess(),
632 GetStdHandle(idx_stdhandles[idx]),
633 GetCurrentProcess(),
635 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
636 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
638 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
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;
649 if (SetFilePointer (h, 0, NULL, FILE_END) ==
650 INVALID_SET_FILE_POINTER) {
651 WCMD_print_error ();
653 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
656 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
657 SetStdHandle (idx_stdhandles[handle], h);
660 /* Terminate the command string at <, or first 2> or > */
661 if (first_redir != NULL) *first_redir = '\0';
664 * Strip leading whitespaces, and a '@' if supplied
666 whichcmd = WCMD_strtrim_leading_spaces(cmd);
667 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
668 if (whichcmd[0] == '@') whichcmd++;
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.
675 count = 0;
676 while (IsCharAlphaNumeric(whichcmd[count])) {
677 count++;
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;
683 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
684 WCMD_parse (p, quals, param1, param2);
685 switch (i) {
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);
815 HeapFree( GetProcessHeap(), 0, cmd );
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]);
826 static void init_msvcrt_io_block(STARTUPINFO* st)
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
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)
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)
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)
846 unsigned num = *(unsigned*)st_p.lpReserved2;
847 WCHAR* flags = (WCHAR*)(ptr + sizeof(unsigned));
848 HANDLE* handles = (HANDLE*)(flags + num * sizeof(WCHAR));
850 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
851 st->cbReserved2 = sz;
852 st->lpReserved2 = ptr;
854 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
855 if (num <= 0 || (flags[0] & WX_OPEN))
857 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
858 flags[0] |= WX_OPEN;
860 if (num <= 1 || (flags[1] & WX_OPEN))
862 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
863 flags[1] |= WX_OPEN;
865 if (num <= 2 || (flags[2] & WX_OPEN))
867 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
868 flags[2] |= WX_OPEN;
870 #undef WX_OPEN
875 /******************************************************************************
876 * WCMD_run_program
878 * Execute a command line as an external program. Must allow recursion.
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.
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.
898 void WCMD_run_program (WCHAR *command, int called) {
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'};
915 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
916 if (!(*param1) && !(*param2))
917 return;
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);
928 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
929 strcpyW(stemofsearch, param1);
931 } else {
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);
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);
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) {
957 WCHAR thisDir[MAX_PATH] = {'\0'};
958 WCHAR *pos = NULL;
959 BOOL found = FALSE;
960 const WCHAR slashW[] = {'\\','\0'};
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;
969 } else {
970 strcpyW(thisDir, pathposn);
971 pathposn = NULL;
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);
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 */
984 /* 1. If extension supplied, see if that file exists */
985 if (extensionsupplied) {
986 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
987 found = TRUE;
991 /* 2. Any .* matches? */
992 if (!found) {
993 HANDLE h;
994 WIN32_FIND_DATA finddata;
995 static const WCHAR allFiles[] = {'.','*','\0'};
997 strcatW(thisDir,allFiles);
998 h = FindFirstFile(thisDir, &finddata);
999 FindClose(h);
1000 if (h != INVALID_HANDLE_VALUE) {
1002 WCHAR *thisExt = pathext;
1004 /* 3. Yes - Try each path ext */
1005 while (thisExt) {
1006 WCHAR *nextExt = strchrW(thisExt, ';');
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;
1017 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1018 found = TRUE;
1019 thisExt = NULL;
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));
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'};
1048 launched = TRUE;
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 {
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);
1067 ZeroMemory (&st, sizeof(STARTUPINFO));
1068 st.cb = sizeof(STARTUPINFO);
1069 init_msvcrt_io_block(&st);
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;
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;
1090 if (!assumeInternal && !console) errorlevel = 0;
1091 else
1093 if (assumeInternal || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1094 GetExitCodeProcess (pe.hProcess, &errorlevel);
1095 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1097 CloseHandle(pe.hProcess);
1098 CloseHandle(pe.hThread);
1099 return;
1104 /* Not found anywhere - give up */
1105 SetLastError(ERROR_FILE_NOT_FOUND);
1106 WCMD_print_error ();
1108 /* If a command fails to launch, it sets errorlevel 9009 - which
1109 does not seem to have any associated constant definition */
1110 errorlevel = 9009;
1111 return;
1115 /******************************************************************************
1116 * WCMD_show_prompt
1118 * Display the prompt on STDout
1122 void WCMD_show_prompt (void) {
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'};
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);
1136 p = prompt_string;
1137 q = out_string;
1138 *q = '\0';
1139 while (*p != '\0') {
1140 if (*p != '$') {
1141 *q++ = *p++;
1142 *q = '\0';
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];
1183 break;
1184 case 'P':
1185 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1186 if (status) {
1187 strcatW (q, curdir);
1188 while (*q) q++;
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;
1213 break;
1215 p++;
1216 *q = '\0';
1219 WCMD_output_asis (out_string);
1222 /****************************************************************************
1223 * WCMD_print_error
1225 * Print the message for GetLastError
1228 void WCMD_print_error (void) {
1229 LPVOID lpMsgBuf;
1230 DWORD error_code;
1231 int status;
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;
1241 WCMD_output_asis (lpMsgBuf);
1242 LocalFree ((HLOCAL)lpMsgBuf);
1243 WCMD_output_asis (newline);
1244 return;
1247 /*******************************************************************
1248 * WCMD_parse - parse a command into parameters and qualifiers.
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.
1256 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1258 int p = 0;
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++);
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++;
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++;
1294 if (p == 0) *p1 = '\0';
1295 if (p == 1) *p2 = '\0';
1296 p++;
1301 /*******************************************************************
1302 * WCMD_output_asis_len - send output to current standard output
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
1308 static void WCMD_output_asis_len(const WCHAR *message, int len) {
1310 DWORD nOut= 0;
1311 DWORD res = 0;
1313 /* If nothing to write, return (MORE does this sometimes) */
1314 if (!len) return;
1316 /* Try to write as unicode assuming it is to a console */
1317 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1318 message, len, &nOut, NULL);
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;
1326 if (!unicodePipes) {
1328 * Allocate buffer to use when writing to file. (Not freed, as one off)
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;
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);
1348 return;
1351 /*******************************************************************
1352 * WCMD_output - send output to current standard output device.
1356 void WCMD_output (const WCHAR *format, ...) {
1358 va_list ap;
1359 WCHAR string[1024];
1360 int ret;
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';
1369 va_end(ap);
1370 WCMD_output_asis_len(string, ret);
1374 static int line_count;
1375 static int max_height;
1376 static int max_width;
1377 static BOOL paged_mode;
1378 static int numChars;
1380 void WCMD_enter_paged_mode(const WCHAR *msg)
1382 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
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;
1391 paged_mode = TRUE;
1392 line_count = 0;
1393 numChars = 0;
1394 pagedMessage = (msg==NULL)? anykey : msg;
1397 void WCMD_leave_paged_mode(void)
1399 paged_mode = FALSE;
1400 pagedMessage = NULL;
1403 /*******************************************************************
1404 * WCMD_output_asis - send output to current standard output device.
1405 * without formatting eg. when message contains '%'
1408 void WCMD_output_asis (const WCHAR *message) {
1409 DWORD count;
1410 const WCHAR* ptr;
1411 WCHAR string[1024];
1413 if (paged_mode) {
1414 do {
1415 ptr = message;
1416 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1417 numChars++;
1418 ptr++;
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);
1431 } while (((message = ptr) != NULL) && (*ptr));
1432 } else {
1433 WCMD_output_asis_len(message, lstrlen(message));
1438 /***************************************************************************
1439 * WCMD_strtrim_leading_spaces
1441 * Remove leading spaces from a string. Return a pointer to the first
1442 * non-space character. Does not modify the input string
1445 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1447 WCHAR *ptr;
1449 ptr = string;
1450 while (*ptr == ' ') ptr++;
1451 return ptr;
1454 /*************************************************************************
1455 * WCMD_strtrim_trailing_spaces
1457 * Remove trailing spaces from a string. This routine modifies the input
1458 * string by placing a null after the last non-space WCHARacter
1461 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1463 WCHAR *ptr;
1465 ptr = string + strlenW (string) - 1;
1466 while ((*ptr == ' ') && (ptr >= string)) {
1467 *ptr = '\0';
1468 ptr--;
1472 /*************************************************************************
1473 * WCMD_opt_s_strip_quotes
1475 * Remove first and last quote WCHARacters, preserving all other text
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++;
1485 if (lastq) {
1486 dest=lastq++;
1487 while ((*dest++=*lastq++) != 0)
1492 /*************************************************************************
1493 * WCMD_pipe
1495 * Handle pipes within a command - the DOS way using temporary files.
1498 void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) {
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'};
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;
1525 wsprintf (temp_cmd, redirIn, command, temp_file);
1526 WCMD_execute (temp_cmd, var, val, cmdEntry);
1527 DeleteFile (temp_file);
1530 /*************************************************************************
1531 * WCMD_expand_envvar
1533 * Expands environment variables, allowing for WCHARacter substitution
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;
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'};
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);
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;
1567 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1568 thisVar[(endOfVar - start)+1] = 0x00;
1569 colonpos = strchrW(thisVar+1, ':');
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;
1579 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
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);
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)) {
1599 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1600 NULL, thisVarContents, MAXSTRING);
1601 len = strlenW(thisVarContents);
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);
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);
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);
1629 } else {
1631 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1632 sizeof(thisVarContents)/sizeof(WCHAR));
1635 if (len == 0)
1636 return endOfVar+1;
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) {
1644 /* Restore the complex part after the compare */
1645 if (colonpos) {
1646 *colonpos = ':';
1647 *(colonpos+1) = savedchar;
1650 /* Command line - just ignore this */
1651 if (context == NULL) return endOfVar+1;
1653 s = WCMD_strdupW(endOfVar + 1);
1655 /* Batch - replace unknown env var with nothing */
1656 if (colonpos == NULL) {
1657 strcpyW (start, s);
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);
1668 strcatW (start, s);
1670 free (s);
1671 return start;
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;
1685 /* Restore complex bit */
1686 *colonpos = ':';
1687 *(colonpos+1) = savedchar;
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)
1699 /* ~ is substring manipulation */
1700 if (savedchar == '~') {
1702 int substrposition, substrlength = 0;
1703 WCHAR *commapos = strchrW(colonpos+2, ',');
1704 WCHAR *startCopy;
1706 substrposition = atolW(colonpos+2);
1707 if (commapos) substrlength = atolW(commapos+1);
1709 s = WCMD_strdupW(endOfVar + 1);
1711 /* Check bounds */
1712 if (substrposition >= 0) {
1713 startCopy = &thisVarContents[min(substrposition, len)];
1714 } else {
1715 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1718 if (commapos == NULL) {
1719 strcpyW (start, startCopy); /* Copy the lot */
1720 } else if (substrlength < 0) {
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;
1732 strcatW (start, s);
1733 free(s);
1734 return start;
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;
1744 s = WCMD_strdupW(endOfVar + 1);
1745 if (equalspos == NULL) return start+1;
1747 /* Null terminate both strings */
1748 thisVar[strlenW(thisVar)-1] = 0x00;
1749 *equalspos = 0x00;
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));
1758 /* Handle wildcard case */
1759 if (*(colonpos+1) == '*') {
1760 /* Search for string to replace */
1761 found = strstrW(searchIn, searchFor+1);
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);
1775 } else {
1776 /* Loop replacing all instances */
1777 WCHAR *lastFound = searchIn;
1778 WCHAR *outputposn = start;
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);
1790 strcatW(outputposn,
1791 thisVarContents + (lastFound-searchIn));
1792 strcatW(outputposn, s);
1794 free(searchIn);
1795 free(searchFor);
1796 return start;
1798 return start+1;
1801 /*************************************************************************
1802 * WCMD_LoadMessage
1803 * Load a string from the resource file, handling any error
1804 * Returns string retrieved from resource file
1806 WCHAR *WCMD_LoadMessage(UINT id) {
1807 static WCHAR msg[2048];
1808 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1810 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1811 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1812 strcpyW(msg, failedMsg);
1814 return msg;
1817 /*************************************************************************
1818 * WCMD_strdupW
1819 * A wide version of strdup as its missing from unicode.h
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;
1829 /***************************************************************************
1830 * WCMD_Readfile
1832 * Read characters in from a console/file, returning result in Unicode
1833 * with signature identical to ReadFile
1835 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1836 LPDWORD charsRead, const LPOVERLAPPED unused) {
1838 BOOL res;
1840 /* Try to read from console as Unicode */
1841 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
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) {
1847 DWORD numRead;
1849 * Allocate buffer to use when reading from file. Not freed
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;
1858 /* Read from file (assume OEM codepage) */
1859 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1861 /* Convert from OEM */
1862 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1863 intoBuf, maxChars);
1866 return res;
1869 /***************************************************************************
1870 * WCMD_DumpCommands
1872 * Domps out the parsed command line to ensure syntax is correct
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'};
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;
1893 /***************************************************************************
1894 * WCMD_ReadAndParseLine
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)
1907 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
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;
1933 /* Allocate working space for a command read from keyboard, file etc */
1934 if (!extraSpace)
1935 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
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;
1945 curPos = extraSpace;
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);
1954 /* Start with an empty string */
1955 curLen = 0;
1957 /* Parse every character on the line being processed */
1958 while (*curPos != 0x00) {
1960 WCHAR thisChar;
1962 /* Debugging AID:
1963 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, curLen,
1964 lastWasWhiteSpace, onlyWhiteSpace);
1967 /* Certain commands need special handling */
1968 if (curLen == 0) {
1969 const WCHAR forDO[] = {'d','o',' ','\0'};
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;
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;
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;
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;
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;
2015 } else {
2017 /* Special handling for the 'FOR' command */
2018 if (inFor && lastWasWhiteSpace) {
2019 const WCHAR forIN[] = {'i','n',' ','\0'};
2021 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
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;
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 */
2042 lastWasWhiteSpace = FALSE; /* Will be reset below */
2044 switch (thisChar) {
2046 case '\t':/* drop through - ignore whitespace at the start of a command */
2047 case ' ': if (curLen > 0)
2048 curString[curLen++] = *curPos;
2050 /* Remember just processed whitespace */
2051 lastWasWhiteSpace = TRUE;
2053 break;
2055 case '"': inQuotes = !inQuotes;
2056 curString[curLen++] = *curPos;
2057 break;
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);
2070 /* Ignore open brackets inside the for set */
2071 if (curLen == 0 && !inIn) {
2072 WINE_TRACE("@@@4\n");
2073 curDepth++;
2075 /* If in quotes, ignore brackets */
2076 } else if (inQuotes) {
2077 WINE_TRACE("@@@3\n");
2078 curString[curLen++] = *curPos;
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
2087 } else if (inIf ||
2088 (inElse && lastWasElse && onlyWhiteSpace) ||
2089 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
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;
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;
2113 lastEntry = thisEntry;
2115 curDepth++;
2116 } else {
2117 WINE_TRACE("@@@1\n");
2118 curString[curLen++] = *curPos;
2120 break;
2122 case '&': if (!inQuotes && *(curPos+1) == '&') {
2123 curPos++; /* Skip other & */
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;
2141 lastEntry = thisEntry;
2143 isAmphersand = TRUE;
2144 } else {
2145 curString[curLen++] = *curPos;
2147 break;
2149 case ')': if (!inQuotes && curDepth > 0) {
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;
2167 lastEntry = thisEntry;
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;
2182 lastEntry = thisEntry;
2184 /* Leave inIn if necessary */
2185 if (inIn) inIn = FALSE;
2186 } else {
2187 curString[curLen++] = *curPos;
2189 break;
2190 default:
2191 curString[curLen++] = *curPos;
2194 curPos++;
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;
2201 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2202 if (!lastWasWhiteSpace) {
2203 lastWasIn = lastWasDo = FALSE;
2206 /* If we have reached the end, add this command into the list */
2207 if (*curPos == 0x00 && curLen > 0) {
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;
2224 lastEntry = thisEntry;
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));
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;
2239 curPos = extraSpace;
2243 /* Dump out the parsed output */
2244 WCMD_DumpCommands(*output);
2246 return extraSpace;
2249 /***************************************************************************
2250 * WCMD_process_commands
2252 * Process all the commands read in so far
2254 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2255 WCHAR *var, WCHAR *val) {
2257 int bdepth = -1;
2259 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2261 /* Loop through the commands, processing them one by one */
2262 while (thisCmd) {
2264 CMD_LIST *origCmd = thisCmd;
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;
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] != ':') {
2280 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2282 if (strchrW(thisCmd->command,'|') != NULL) {
2283 WCMD_pipe (&thisCmd, var, val);
2284 } else {
2285 WCMD_execute (thisCmd->command, var, val, &thisCmd);
2289 /* Step on unless the command itself already stepped on */
2290 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2292 return NULL;
2295 /***************************************************************************
2296 * WCMD_free_commands
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
2303 void WCMD_free_commands(CMD_LIST *cmds) {
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);