2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
31 #include "wine/debug.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
35 const WCHAR inbuilt
[][10] = {
36 {'A','T','T','R','I','B','\0'},
37 {'C','A','L','L','\0'},
39 {'C','H','D','I','R','\0'},
41 {'C','O','P','Y','\0'},
42 {'C','T','T','Y','\0'},
43 {'D','A','T','E','\0'},
46 {'E','C','H','O','\0'},
47 {'E','R','A','S','E','\0'},
49 {'G','O','T','O','\0'},
50 {'H','E','L','P','\0'},
52 {'L','A','B','E','L','\0'},
54 {'M','K','D','I','R','\0'},
55 {'M','O','V','E','\0'},
56 {'P','A','T','H','\0'},
57 {'P','A','U','S','E','\0'},
58 {'P','R','O','M','P','T','\0'},
61 {'R','E','N','A','M','E','\0'},
63 {'R','M','D','I','R','\0'},
65 {'S','H','I','F','T','\0'},
66 {'T','I','M','E','\0'},
67 {'T','I','T','L','E','\0'},
68 {'T','Y','P','E','\0'},
69 {'V','E','R','I','F','Y','\0'},
72 {'E','N','D','L','O','C','A','L','\0'},
73 {'S','E','T','L','O','C','A','L','\0'},
74 {'P','U','S','H','D','\0'},
75 {'P','O','P','D','\0'},
76 {'A','S','S','O','C','\0'},
77 {'C','O','L','O','R','\0'},
78 {'F','T','Y','P','E','\0'},
79 {'M','O','R','E','\0'},
80 {'E','X','I','T','\0'}
85 int echo_mode
= 1, verify_mode
= 0, defaultColor
= 7;
86 static int opt_c
, opt_k
, opt_s
;
87 const WCHAR newline
[] = {'\n','\0'};
88 static const WCHAR equalsW
[] = {'=','\0'};
89 static const WCHAR closeBW
[] = {')','\0'};
91 WCHAR version_string
[100];
92 WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
93 BATCH_CONTEXT
*context
= NULL
;
94 extern struct env_stack
*pushd_directories
;
95 static const WCHAR
*pagedMessage
= NULL
;
96 static char *output_bufA
= NULL
;
97 #define MAX_WRITECONSOLE_SIZE 65535
98 BOOL unicodePipes
= FALSE
;
100 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forvar
, WCHAR
*forVal
);
101 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
);
103 /*****************************************************************************
104 * Main entry point. This is a console application so we have a main() not a
108 int wmain (int argc
, WCHAR
*argvW
[])
117 static const WCHAR autoexec
[] = {'\\','a','u','t','o','e','x','e','c','.',
119 char ansiVersion
[100];
120 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
124 /* Pre initialize some messages */
125 strcpy(ansiVersion
, PACKAGE_VERSION
);
126 MultiByteToWideChar(CP_ACP
, 0, ansiVersion
, -1, string
, 1024);
127 wsprintf(version_string
, WCMD_LoadMessage(WCMD_VERSION
), string
);
128 strcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
131 opt_c
=opt_k
=opt_q
=opt_s
=0;
135 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW
));
136 if ((*argvW
)[0]!='/' || (*argvW
)[1]=='\0') {
143 if (tolowerW(c
)=='c') {
145 } else if (tolowerW(c
)=='q') {
147 } else if (tolowerW(c
)=='k') {
149 } else if (tolowerW(c
)=='s') {
151 } else if (tolowerW(c
)=='a') {
153 } else if (tolowerW(c
)=='u') {
155 } else if (tolowerW(c
)=='t' && (*argvW
)[2]==':') {
156 opt_t
=strtoulW(&(*argvW
)[3], NULL
, 16);
157 } else if (tolowerW(c
)=='x' || tolowerW(c
)=='y') {
158 /* Ignored for compatibility with Windows */
161 if ((*argvW
)[2]==0) {
165 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
170 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
175 const WCHAR eoff
[] = {'O','F','F','\0'};
179 if (opt_c
|| opt_k
) {
185 /* opt_s left unflagged if the command starts with and contains exactly
186 * one quoted string (exactly two quote characters). The quoted string
187 * must be an executable name that has whitespace and must not have the
188 * following characters: &<>()@^| */
190 /* Build the command to execute */
194 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
196 int has_space
,bcount
;
202 if( !*a
) has_space
=1;
207 if (*a
==' ' || *a
=='\t') {
209 } else if (*a
=='"') {
210 /* doubling of '\' preceding a '"',
211 * plus escaping of said '"'
220 len
+=(a
-*arg
) + 1; /* for the separating space */
223 len
+=2; /* for the quotes */
231 /* check argvW[0] for a space and invalid characters */
236 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
237 || *p
=='@' || *p
=='^' || *p
=='|') {
247 cmd
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
253 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
255 int has_space
,has_quote
;
258 /* Check for quotes and spaces in this argument */
259 has_space
=has_quote
=0;
261 if( !*a
) has_space
=1;
263 if (*a
==' ' || *a
=='\t') {
267 } else if (*a
=='"') {
275 /* Now transfer it to the command line */
292 /* Double all the '\\' preceding this '"', plus one */
293 for (i
=0;i
<=bcount
;i
++)
312 p
--; /* remove last space */
315 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
317 /* strip first and last quote characters if opt_s; check for invalid
318 * executable is done later */
319 if (opt_s
&& *cmd
=='\"')
320 WCMD_opt_s_strip_quotes(cmd
);
324 /* If we do a "wcmd /c command", we don't want to allocate a new
325 * console since the command returns immediately. Rather, we use
326 * the currently allocated input and output handles. This allows
327 * us to pipe to and read from the command interpreter.
330 /* Parse the command string, without reading any more input */
331 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
332 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
333 WCMD_free_commands(toExecute
);
336 HeapFree(GetProcessHeap(), 0, cmd
);
340 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
341 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
342 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE
));
344 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
346 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
347 defaultColor
= opt_t
& 0xFF;
352 /* Check HKCU\Software\Microsoft\Command Processor
353 Then HKLM\Software\Microsoft\Command Processor
354 for defaultcolour value
355 Note Can be supplied as DWORD or REG_SZ
356 Note2 When supplied as REG_SZ it's in decimal!!! */
359 DWORD value
=0, size
=4;
360 static const WCHAR regKeyW
[] = {'S','o','f','t','w','a','r','e','\\',
361 'M','i','c','r','o','s','o','f','t','\\',
362 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
363 static const WCHAR dfltColorW
[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
365 if (RegOpenKeyEx(HKEY_CURRENT_USER
, regKeyW
,
366 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
369 /* See if DWORD or REG_SZ */
370 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
371 NULL
, NULL
) == ERROR_SUCCESS
) {
372 if (type
== REG_DWORD
) {
373 size
= sizeof(DWORD
);
374 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
375 (LPBYTE
)&value
, &size
);
376 } else if (type
== REG_SZ
) {
377 size
= sizeof(strvalue
)/sizeof(WCHAR
);
378 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
379 (LPBYTE
)strvalue
, &size
);
380 value
= strtoulW(strvalue
, NULL
, 10);
386 if (value
== 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE
, regKeyW
,
387 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
390 /* See if DWORD or REG_SZ */
391 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
392 NULL
, NULL
) == ERROR_SUCCESS
) {
393 if (type
== REG_DWORD
) {
394 size
= sizeof(DWORD
);
395 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
396 (LPBYTE
)&value
, &size
);
397 } else if (type
== REG_SZ
) {
398 size
= sizeof(strvalue
)/sizeof(WCHAR
);
399 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
400 (LPBYTE
)strvalue
, &size
);
401 value
= strtoulW(strvalue
, NULL
, 10);
407 /* If one found, set the screen to that colour */
408 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
409 defaultColor
= value
& 0xFF;
416 /* Save cwd into appropriate env var */
417 GetCurrentDirectory(1024, string
);
418 if (IsCharAlpha(string
[0]) && string
[1] == ':') {
419 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
420 wsprintf(envvar
, fmt
, string
[0]);
421 SetEnvironmentVariable(envvar
, string
);
422 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
426 /* Parse the command string, without reading any more input */
427 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
428 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
429 WCMD_free_commands(toExecute
);
431 HeapFree(GetProcessHeap(), 0, cmd
);
435 * If there is an AUTOEXEC.BAT file, try to execute it.
438 GetFullPathName (autoexec
, sizeof(string
)/sizeof(WCHAR
), string
, NULL
);
439 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
440 if (h
!= INVALID_HANDLE_VALUE
) {
443 WCMD_batch (autoexec
, autoexec
, 0, NULL
, INVALID_HANDLE_VALUE
);
448 * Loop forever getting commands and executing them.
454 /* Read until EOF (which for std input is never, but if redirect
455 in place, may occur */
457 if (WCMD_ReadAndParseLine(NULL
, &toExecute
,
458 GetStdHandle(STD_INPUT_HANDLE
)) == NULL
)
460 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
461 WCMD_free_commands(toExecute
);
467 /*****************************************************************************
468 * Expand the command. Native expands lines from batch programs as they are
469 * read in and not again, except for 'for' variable substitution.
470 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
472 void handleExpansion(WCHAR
*cmd
, BOOL justFors
, WCHAR
*forVariable
, WCHAR
*forValue
) {
474 /* For commands in a context (batch program): */
475 /* Expand environment variables in a batch file %{0-9} first */
476 /* including support for any ~ modifiers */
478 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
479 /* names allowing environment variable overrides */
480 /* NOTE: To support the %PATH:xxx% syntax, also perform */
481 /* manual expansion of environment variables here */
487 while ((p
= strchrW(p
, '%'))) {
489 WINE_TRACE("Translate command:%s %d (at: %s)\n",
490 wine_dbgstr_w(cmd
), justFors
, wine_dbgstr_w(p
));
493 /* Don't touch %% unless its in Batch */
494 if (!justFors
&& *(p
+1) == '%') {
496 s
= WCMD_strdupW(p
+1);
502 /* Replace %~ modifications if in batch program */
503 } else if (*(p
+1) == '~') {
504 WCMD_HandleTildaModifiers(&p
, forVariable
, forValue
, justFors
);
507 /* Replace use of %0...%9 if in batch program*/
508 } else if (!justFors
&& context
&& (i
>= 0) && (i
<= 9)) {
509 s
= WCMD_strdupW(p
+2);
510 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
515 /* Replace use of %* if in batch program*/
516 } else if (!justFors
&& context
&& *(p
+1)=='*') {
517 WCHAR
*startOfParms
= NULL
;
518 s
= WCMD_strdupW(p
+2);
519 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
520 if (startOfParms
!= NULL
) strcpyW (p
, startOfParms
);
525 } else if (forVariable
&&
526 (CompareString (LOCALE_USER_DEFAULT
,
529 strlenW(forVariable
),
530 forVariable
, -1) == 2)) {
531 s
= WCMD_strdupW(p
+ strlenW(forVariable
));
532 strcpyW(p
, forValue
);
536 } else if (!justFors
) {
537 p
= WCMD_expand_envvar(p
, forVariable
, forValue
);
539 /* In a FOR loop, see if this is the variable to replace */
540 } else { /* Ignore %'s on second pass of batch program */
549 /*****************************************************************************
550 * Process one command. If the command is EXIT this routine does not return.
551 * We will recurse through here executing batch files.
555 void WCMD_execute (WCHAR
*command
, WCHAR
*redirects
,
556 WCHAR
*forVariable
, WCHAR
*forValue
,
559 WCHAR
*cmd
, *p
, *redir
;
561 DWORD count
, creationDisposition
;
564 SECURITY_ATTRIBUTES sa
;
565 WCHAR
*new_cmd
= NULL
;
566 WCHAR
*new_redir
= NULL
;
567 HANDLE old_stdhandles
[3] = {INVALID_HANDLE_VALUE
,
568 INVALID_HANDLE_VALUE
,
569 INVALID_HANDLE_VALUE
};
570 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
575 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
576 wine_dbgstr_w(command
), cmdList
,
577 wine_dbgstr_w(forVariable
), wine_dbgstr_w(forValue
));
579 /* If the next command is a pipe then we implement pipes by redirecting
580 the output from this command to a temp file and input into the
581 next command from that temp file.
582 FIXME: Use of named pipes would make more sense here as currently this
583 process has to finish before the next one can start but this requires
584 a change to not wait for the first app to finish but rather the pipe */
585 if (cmdList
&& (*cmdList
)->nextcommand
&&
586 (*cmdList
)->nextcommand
->prevDelim
== CMD_PIPE
) {
588 WCHAR temp_path
[MAX_PATH
];
589 static const WCHAR cmdW
[] = {'C','M','D','\0'};
591 /* Remember piping is in action */
592 WINE_TRACE("Output needs to be piped\n");
595 /* Generate a unique temporary filename */
596 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
597 GetTempFileName (temp_path
, cmdW
, 0, (*cmdList
)->nextcommand
->pipeFile
);
598 WINE_TRACE("Using temporary file of %s\n",
599 wine_dbgstr_w((*cmdList
)->nextcommand
->pipeFile
));
602 /* Move copy of the command onto the heap so it can be expanded */
603 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
604 strcpyW(new_cmd
, command
);
606 /* Move copy of the redirects onto the heap so it can be expanded */
607 new_redir
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
609 /* If piped output, send stdout to the pipe by appending >filename to redirects */
611 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
612 wsprintf (new_redir
, redirOut
, redirects
, (*cmdList
)->nextcommand
->pipeFile
);
613 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
615 strcpyW(new_redir
, redirects
);
618 /* Expand variables in command line mode only (batch mode will
619 be expanded as the line is read in, except for 'for' loops) */
620 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
621 handleExpansion(new_redir
, (context
!= NULL
), forVariable
, forValue
);
624 /* Show prompt before batch line IF echo is on and in batch program */
625 if (context
&& echo_mode
&& (cmd
[0] != '@')) {
627 WCMD_output_asis ( cmd
);
628 WCMD_output_asis ( newline
);
632 * Changing default drive has to be handled as a special case.
635 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
639 /* According to MSDN CreateProcess docs, special env vars record
640 the current directory on each drive, in the form =C:
641 so see if one specified, and if so go back to it */
642 strcpyW(envvar
, equalsW
);
643 strcatW(envvar
, cmd
);
644 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
645 static const WCHAR fmt
[] = {'%','s','\\','\0'};
646 wsprintf(cmd
, fmt
, cmd
);
647 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
649 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
650 status
= SetCurrentDirectory (cmd
);
651 if (!status
) WCMD_print_error ();
652 HeapFree( GetProcessHeap(), 0, cmd
);
653 HeapFree( GetProcessHeap(), 0, new_redir
);
657 sa
.nLength
= sizeof(sa
);
658 sa
.lpSecurityDescriptor
= NULL
;
659 sa
.bInheritHandle
= TRUE
;
662 * Redirect stdin, stdout and/or stderr if required.
665 /* STDIN could come from a preceding pipe, so delete on close if it does */
666 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
667 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
668 h
= CreateFile ((*cmdList
)->pipeFile
, GENERIC_READ
,
669 FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
670 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
671 if (h
== INVALID_HANDLE_VALUE
) {
673 HeapFree( GetProcessHeap(), 0, cmd
);
674 HeapFree( GetProcessHeap(), 0, new_redir
);
677 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
678 SetStdHandle (STD_INPUT_HANDLE
, h
);
680 /* No need to remember the temporary name any longer once opened */
681 (*cmdList
)->pipeFile
[0] = 0x00;
683 /* Otherwise STDIN could come from a '<' redirect */
684 } else if ((p
= strchrW(new_redir
,'<')) != NULL
) {
685 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
686 FILE_ATTRIBUTE_NORMAL
, NULL
);
687 if (h
== INVALID_HANDLE_VALUE
) {
689 HeapFree( GetProcessHeap(), 0, cmd
);
690 HeapFree( GetProcessHeap(), 0, new_redir
);
693 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
694 SetStdHandle (STD_INPUT_HANDLE
, h
);
697 /* Scan the whole command looking for > and 2> */
699 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
710 creationDisposition
= OPEN_ALWAYS
;
714 creationDisposition
= CREATE_ALWAYS
;
717 /* Add support for 2>&1 */
720 int idx
= *(p
+1) - '0';
722 if (DuplicateHandle(GetCurrentProcess(),
723 GetStdHandle(idx_stdhandles
[idx
]),
726 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
727 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
729 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
732 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
733 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
734 FILE_ATTRIBUTE_NORMAL
, NULL
);
735 if (h
== INVALID_HANDLE_VALUE
) {
737 HeapFree( GetProcessHeap(), 0, cmd
);
738 HeapFree( GetProcessHeap(), 0, new_redir
);
741 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
742 INVALID_SET_FILE_POINTER
) {
745 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
748 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
749 SetStdHandle (idx_stdhandles
[handle
], h
);
753 * Strip leading whitespaces, and a '@' if supplied
755 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
756 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
757 if (whichcmd
[0] == '@') whichcmd
++;
760 * Check if the command entered is internal. If it is, pass the rest of the
761 * line down to the command. If not try to run a program.
765 while (IsCharAlphaNumeric(whichcmd
[count
])) {
768 for (i
=0; i
<=WCMD_EXIT
; i
++) {
769 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
770 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
772 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
773 WCMD_parse (p
, quals
, param1
, param2
);
774 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
779 WCMD_setshow_attrib ();
786 WCMD_setshow_default (p
);
789 WCMD_clear_screen ();
798 WCMD_setshow_date ();
802 WCMD_delete (p
, TRUE
);
808 WCMD_echo(&whichcmd
[count
]);
811 WCMD_for (p
, cmdList
);
820 WCMD_if (p
, cmdList
);
833 WCMD_setshow_path (p
);
839 WCMD_setshow_prompt ();
858 WCMD_setshow_env (p
);
864 WCMD_setshow_time ();
867 if (strlenW(&whichcmd
[count
]) > 0)
868 WCMD_title(&whichcmd
[count
+1]);
895 WCMD_assoc(p
, FALSE
);
904 WCMD_run_program (whichcmd
, 0);
906 HeapFree( GetProcessHeap(), 0, cmd
);
907 HeapFree( GetProcessHeap(), 0, new_redir
);
909 /* Restore old handles */
910 for (i
=0; i
<3; i
++) {
911 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
912 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
913 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
918 static void init_msvcrt_io_block(STARTUPINFO
* st
)
921 /* fetch the parent MSVCRT info block if any, so that the child can use the
922 * same handles as its grand-father
924 st_p
.cb
= sizeof(STARTUPINFO
);
925 GetStartupInfo(&st_p
);
926 st
->cbReserved2
= st_p
.cbReserved2
;
927 st
->lpReserved2
= st_p
.lpReserved2
;
928 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
930 /* Override the entries for fd 0,1,2 if we happened
931 * to change those std handles (this depends on the way wcmd sets
932 * it's new input & output handles)
934 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
935 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
938 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
939 char* flags
= (char*)(ptr
+ sizeof(unsigned));
940 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
942 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
943 st
->cbReserved2
= sz
;
944 st
->lpReserved2
= ptr
;
946 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
947 if (num
<= 0 || (flags
[0] & WX_OPEN
))
949 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
952 if (num
<= 1 || (flags
[1] & WX_OPEN
))
954 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
957 if (num
<= 2 || (flags
[2] & WX_OPEN
))
959 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
967 /******************************************************************************
970 * Execute a command line as an external program. Must allow recursion.
973 * Manual testing under windows shows PATHEXT plays a key part in this,
974 * and the search algorithm and precedence appears to be as follows.
977 * If directory supplied on command, just use that directory
978 * If extension supplied on command, look for that explicit name first
979 * Otherwise, search in each directory on the path
981 * If extension supplied on command, look for that explicit name first
982 * Then look for supplied name .* (even if extension supplied, so
983 * 'garbage.exe' will match 'garbage.exe.cmd')
984 * If any found, cycle through PATHEXT looking for name.exe one by one
986 * Once a match has been found, it is launched - Code currently uses
987 * findexecutable to achieve this which is left untouched.
990 void WCMD_run_program (WCHAR
*command
, int called
) {
992 WCHAR temp
[MAX_PATH
];
993 WCHAR pathtosearch
[MAXSTRING
];
995 WCHAR stemofsearch
[MAX_PATH
];
997 WCHAR pathext
[MAXSTRING
];
998 BOOL extensionsupplied
= FALSE
;
999 BOOL launched
= FALSE
;
1001 BOOL assumeInternal
= FALSE
;
1003 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
1004 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
1005 static const WCHAR delims
[] = {'/','\\',':','\0'};
1007 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
1008 if (!(*param1
) && !(*param2
))
1011 /* Calculate the search path and stem to search for */
1012 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
1013 static const WCHAR curDir
[] = {'.',';','\0'};
1014 strcpyW(pathtosearch
, curDir
);
1015 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
1016 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
1017 static const WCHAR curDir
[] = {'.','\0'};
1018 strcpyW (pathtosearch
, curDir
);
1020 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
1021 strcpyW(stemofsearch
, param1
);
1025 /* Convert eg. ..\fred to include a directory by removing file part */
1026 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
1027 lastSlash
= strrchrW(pathtosearch
, '\\');
1028 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1029 strcpyW(stemofsearch
, lastSlash
+1);
1031 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1032 c:\windows\a.bat syntax */
1033 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1036 /* Now extract PATHEXT */
1037 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
1038 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
1039 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
1040 '.','c','o','m',';',
1041 '.','c','m','d',';',
1042 '.','e','x','e','\0'};
1043 strcpyW (pathext
, dfltPathExt
);
1046 /* Loop through the search path, dir by dir */
1047 pathposn
= pathtosearch
;
1048 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1049 wine_dbgstr_w(stemofsearch
));
1050 while (!launched
&& pathposn
) {
1052 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1055 const WCHAR slashW
[] = {'\\','\0'};
1057 /* Work on the first directory on the search path */
1058 pos
= strchrW(pathposn
, ';');
1060 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1061 thisDir
[(pos
-pathposn
)] = 0x00;
1065 strcpyW(thisDir
, pathposn
);
1069 /* Since you can have eg. ..\.. on the path, need to expand
1070 to full information */
1071 strcpyW(temp
, thisDir
);
1072 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
1074 /* 1. If extension supplied, see if that file exists */
1075 strcatW(thisDir
, slashW
);
1076 strcatW(thisDir
, stemofsearch
);
1077 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1079 /* 1. If extension supplied, see if that file exists */
1080 if (extensionsupplied
) {
1081 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1086 /* 2. Any .* matches? */
1089 WIN32_FIND_DATA finddata
;
1090 static const WCHAR allFiles
[] = {'.','*','\0'};
1092 strcatW(thisDir
,allFiles
);
1093 h
= FindFirstFile(thisDir
, &finddata
);
1095 if (h
!= INVALID_HANDLE_VALUE
) {
1097 WCHAR
*thisExt
= pathext
;
1099 /* 3. Yes - Try each path ext */
1101 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1104 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1105 pos
[(nextExt
-thisExt
)] = 0x00;
1106 thisExt
= nextExt
+1;
1108 strcpyW(pos
, thisExt
);
1112 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1120 /* Internal programs won't be picked up by this search, so even
1121 though not found, try one last createprocess and wait for it
1123 Note: Ideally we could tell between a console app (wait) and a
1124 windows app, but the API's for it fail in this case */
1125 if (!found
&& pathposn
== NULL
) {
1126 WINE_TRACE("ASSUMING INTERNAL\n");
1127 assumeInternal
= TRUE
;
1129 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1132 /* Once found, launch it */
1133 if (found
|| assumeInternal
) {
1135 PROCESS_INFORMATION pe
;
1139 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1140 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1141 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1145 /* Special case BAT and CMD */
1146 if (ext
&& !strcmpiW(ext
, batExt
)) {
1147 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1149 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1150 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1154 /* thisDir contains the file to be launched, but with what?
1155 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1156 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1157 if ((INT_PTR
)hinst
< 32)
1160 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1162 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1163 st
.cb
= sizeof(STARTUPINFO
);
1164 init_msvcrt_io_block(&st
);
1166 /* Launch the process and if a CUI wait on it to complete
1167 Note: Launching internal wine processes cannot specify a full path to exe */
1168 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1169 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1170 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1171 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1172 /* strip first and last quote WCHARacters and try again */
1173 WCMD_opt_s_strip_quotes(command
);
1175 WCMD_run_program(command
, called
);
1179 WCMD_print_error ();
1180 /* If a command fails to launch, it sets errorlevel 9009 - which
1181 does not seem to have any associated constant definition */
1185 if (!assumeInternal
&& !console
) errorlevel
= 0;
1188 /* Always wait when called in a batch program context */
1189 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1190 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1191 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1193 CloseHandle(pe
.hProcess
);
1194 CloseHandle(pe
.hThread
);
1200 /* Not found anywhere - give up */
1201 SetLastError(ERROR_FILE_NOT_FOUND
);
1202 WCMD_print_error ();
1204 /* If a command fails to launch, it sets errorlevel 9009 - which
1205 does not seem to have any associated constant definition */
1211 /******************************************************************************
1214 * Display the prompt on STDout
1218 void WCMD_show_prompt (void) {
1221 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1224 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1226 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1227 sizeof(prompt_string
)/sizeof(WCHAR
));
1228 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1229 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1230 strcpyW (prompt_string
, dfltPrompt
);
1235 while (*p
!= '\0') {
1242 switch (toupper(*p
)) {
1256 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1275 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1281 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1283 strcatW (q
, curdir
);
1294 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1298 strcatW (q
, version_string
);
1305 if (pushd_directories
) {
1306 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1307 q
= q
+ pushd_directories
->u
.stackdepth
;
1315 WCMD_output_asis (out_string
);
1318 /****************************************************************************
1321 * Print the message for GetLastError
1324 void WCMD_print_error (void) {
1329 error_code
= GetLastError ();
1330 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1331 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1333 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1334 error_code
, GetLastError());
1338 WCMD_output_asis_len(lpMsgBuf
, lstrlen(lpMsgBuf
),
1339 GetStdHandle(STD_ERROR_HANDLE
));
1340 LocalFree ((HLOCAL
)lpMsgBuf
);
1341 WCMD_output_asis_len (newline
, lstrlen(newline
),
1342 GetStdHandle(STD_ERROR_HANDLE
));
1346 /*******************************************************************
1347 * WCMD_parse - parse a command into parameters and qualifiers.
1349 * On exit, all qualifiers are concatenated into q, the first string
1350 * not beginning with "/" is in p1 and the
1351 * second in p2. Any subsequent non-qualifier strings are lost.
1352 * Parameters in quotes are handled.
1355 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1359 *q
= *p1
= *p2
= '\0';
1364 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1365 *q
++ = toupperW (*s
++);
1375 while ((*s
!= '\0') && (*s
!= '"')) {
1376 if (p
== 0) *p1
++ = *s
++;
1377 else if (p
== 1) *p2
++ = *s
++;
1380 if (p
== 0) *p1
= '\0';
1381 if (p
== 1) *p2
= '\0';
1388 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
1389 && (*s
!= '=') && (*s
!= ',') ) {
1390 if (p
== 0) *p1
++ = *s
++;
1391 else if (p
== 1) *p2
++ = *s
++;
1394 /* Skip concurrent parms */
1395 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
1397 if (p
== 0) *p1
= '\0';
1398 if (p
== 1) *p2
= '\0';
1404 /*******************************************************************
1405 * WCMD_output_asis_len - send output to current standard output
1407 * Output a formatted unicode string. Ideally this will go to the console
1408 * and hence required WriteConsoleW to output it, however if file i/o is
1409 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1411 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
1416 /* If nothing to write, return (MORE does this sometimes) */
1419 /* Try to write as unicode assuming it is to a console */
1420 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
1422 /* If writing to console fails, assume its file
1423 i/o so convert to OEM codepage and output */
1425 BOOL usedDefaultChar
= FALSE
;
1426 DWORD convertedChars
;
1428 if (!unicodePipes
) {
1430 * Allocate buffer to use when writing to file. (Not freed, as one off)
1432 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1433 MAX_WRITECONSOLE_SIZE
);
1435 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1439 /* Convert to OEM, then output */
1440 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1441 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1442 "?", &usedDefaultChar
);
1443 WriteFile(device
, output_bufA
, convertedChars
,
1446 WriteFile(device
, message
, len
*sizeof(WCHAR
),
1453 /*******************************************************************
1454 * WCMD_output - send output to current standard output device.
1458 void WCMD_output (const WCHAR
*format
, ...) {
1464 va_start(ap
,format
);
1465 ret
= wvsprintf (string
, format
, ap
);
1466 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1467 WINE_ERR("Output truncated in WCMD_output\n" );
1468 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1472 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
1476 static int line_count
;
1477 static int max_height
;
1478 static int max_width
;
1479 static BOOL paged_mode
;
1480 static int numChars
;
1482 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1484 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1486 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1487 max_height
= consoleInfo
.dwSize
.Y
;
1488 max_width
= consoleInfo
.dwSize
.X
;
1496 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1499 void WCMD_leave_paged_mode(void)
1502 pagedMessage
= NULL
;
1505 /*******************************************************************
1506 * WCMD_output_asis - send output to current standard output device.
1507 * without formatting eg. when message contains '%'
1510 void WCMD_output_asis (const WCHAR
*message
) {
1518 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1522 if (*ptr
== '\n') ptr
++;
1523 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
),
1524 GetStdHandle(STD_OUTPUT_HANDLE
));
1527 if (++line_count
>= max_height
- 1) {
1529 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
),
1530 GetStdHandle(STD_OUTPUT_HANDLE
));
1531 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1532 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1535 } while (((message
= ptr
) != NULL
) && (*ptr
));
1537 WCMD_output_asis_len(message
, lstrlen(message
),
1538 GetStdHandle(STD_OUTPUT_HANDLE
));
1543 /***************************************************************************
1544 * WCMD_strtrim_leading_spaces
1546 * Remove leading spaces from a string. Return a pointer to the first
1547 * non-space character. Does not modify the input string
1550 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1555 while (*ptr
== ' ') ptr
++;
1559 /*************************************************************************
1560 * WCMD_strtrim_trailing_spaces
1562 * Remove trailing spaces from a string. This routine modifies the input
1563 * string by placing a null after the last non-space WCHARacter
1566 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1570 ptr
= string
+ strlenW (string
) - 1;
1571 while ((*ptr
== ' ') && (ptr
>= string
)) {
1577 /*************************************************************************
1578 * WCMD_opt_s_strip_quotes
1580 * Remove first and last quote WCHARacters, preserving all other text
1583 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1584 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1585 while((*dest
=*src
) != '\0') {
1592 while ((*dest
++=*lastq
++) != 0)
1597 /*************************************************************************
1598 * WCMD_expand_envvar
1600 * Expands environment variables, allowing for WCHARacter substitution
1602 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forVar
, WCHAR
*forVal
) {
1603 WCHAR
*endOfVar
= NULL
, *s
;
1604 WCHAR
*colonpos
= NULL
;
1605 WCHAR thisVar
[MAXSTRING
];
1606 WCHAR thisVarContents
[MAXSTRING
];
1607 WCHAR savedchar
= 0x00;
1610 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1611 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1612 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1613 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1614 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1615 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1616 static const WCHAR Cd
[] = {'C','D','\0'};
1617 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1618 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1619 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1620 static const WCHAR Delims
[] = {'%',' ',':','\0'};
1622 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
1623 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
1625 /* Find the end of the environment variable, and extract name */
1626 endOfVar
= strpbrkW(start
+1, Delims
);
1628 if (endOfVar
== NULL
|| *endOfVar
==' ') {
1630 /* In batch program, missing terminator for % and no following
1631 ':' just removes the '%' */
1633 s
= WCMD_strdupW(start
+ 1);
1639 /* In command processing, just ignore it - allows command line
1640 syntax like: for %i in (a.a) do echo %i */
1645 /* If ':' found, process remaining up until '%' (or stop at ':' if
1647 if (*endOfVar
==':') {
1648 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
1649 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
1652 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1653 thisVar
[(endOfVar
- start
)+1] = 0x00;
1654 colonpos
= strchrW(thisVar
+1, ':');
1656 /* If there's complex substitution, just need %var% for now
1657 to get the expanded data to play with */
1660 savedchar
= *(colonpos
+1);
1661 *(colonpos
+1) = 0x00;
1664 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1666 /* Expand to contents, if unchanged, return */
1667 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1668 /* override if existing env var called that name */
1669 if ((CompareString (LOCALE_USER_DEFAULT
,
1670 NORM_IGNORECASE
| SORT_STRINGSORT
,
1671 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1672 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1673 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1674 static const WCHAR fmt
[] = {'%','d','\0'};
1675 wsprintf(thisVarContents
, fmt
, errorlevel
);
1676 len
= strlenW(thisVarContents
);
1678 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1679 NORM_IGNORECASE
| SORT_STRINGSORT
,
1680 thisVar
, 6, DateP
, -1) == 2) &&
1681 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1682 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1684 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1685 NULL
, thisVarContents
, MAXSTRING
);
1686 len
= strlenW(thisVarContents
);
1688 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1689 NORM_IGNORECASE
| SORT_STRINGSORT
,
1690 thisVar
, 6, TimeP
, -1) == 2) &&
1691 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1692 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1693 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1694 NULL
, thisVarContents
, MAXSTRING
);
1695 len
= strlenW(thisVarContents
);
1697 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1698 NORM_IGNORECASE
| SORT_STRINGSORT
,
1699 thisVar
, 4, CdP
, -1) == 2) &&
1700 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1701 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1702 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1703 len
= strlenW(thisVarContents
);
1705 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1706 NORM_IGNORECASE
| SORT_STRINGSORT
,
1707 thisVar
, 8, RandomP
, -1) == 2) &&
1708 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1709 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1710 static const WCHAR fmt
[] = {'%','d','\0'};
1711 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1712 len
= strlenW(thisVarContents
);
1714 /* Look for a matching 'for' variable */
1715 } else if (forVar
&&
1716 (CompareString (LOCALE_USER_DEFAULT
,
1719 (colonpos
- thisVar
) - 1,
1720 forVar
, -1) == 2)) {
1721 strcpyW(thisVarContents
, forVal
);
1722 len
= strlenW(thisVarContents
);
1726 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1727 sizeof(thisVarContents
)/sizeof(WCHAR
));
1733 /* In a batch program, unknown env vars are replaced with nothing,
1734 note syntax %garbage:1,3% results in anything after the ':'
1736 From the command line, you just get back what you entered */
1737 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1739 /* Restore the complex part after the compare */
1742 *(colonpos
+1) = savedchar
;
1745 /* Command line - just ignore this */
1746 if (context
== NULL
) return endOfVar
+1;
1748 s
= WCMD_strdupW(endOfVar
+ 1);
1750 /* Batch - replace unknown env var with nothing */
1751 if (colonpos
== NULL
) {
1755 len
= strlenW(thisVar
);
1756 thisVar
[len
-1] = 0x00;
1757 /* If %:...% supplied, : is retained */
1758 if (colonpos
== thisVar
+1) {
1759 strcpyW (start
, colonpos
);
1761 strcpyW (start
, colonpos
+1);
1770 /* See if we need to do complex substitution (any ':'s), if not
1771 then our work here is done */
1772 if (colonpos
== NULL
) {
1773 s
= WCMD_strdupW(endOfVar
+ 1);
1774 strcpyW (start
, thisVarContents
);
1780 /* Restore complex bit */
1782 *(colonpos
+1) = savedchar
;
1785 Handle complex substitutions:
1786 xxx=yyy (replace xxx with yyy)
1787 *xxx=yyy (replace up to and including xxx with yyy)
1788 ~x (from x WCHARs in)
1789 ~-x (from x WCHARs from the end)
1790 ~x,y (from x WCHARs in for y WCHARacters)
1791 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1794 /* ~ is substring manipulation */
1795 if (savedchar
== '~') {
1797 int substrposition
, substrlength
= 0;
1798 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1801 substrposition
= atolW(colonpos
+2);
1802 if (commapos
) substrlength
= atolW(commapos
+1);
1804 s
= WCMD_strdupW(endOfVar
+ 1);
1807 if (substrposition
>= 0) {
1808 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1810 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1813 if (commapos
== NULL
) {
1814 strcpyW (start
, startCopy
); /* Copy the lot */
1815 } else if (substrlength
< 0) {
1817 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1818 if (copybytes
> len
) copybytes
= len
;
1819 else if (copybytes
< 0) copybytes
= 0;
1820 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1821 start
[copybytes
] = 0x00;
1823 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1824 start
[substrlength
] = 0x00;
1831 /* search and replace manipulation */
1833 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1834 WCHAR
*replacewith
= equalspos
+1;
1835 WCHAR
*found
= NULL
;
1839 s
= WCMD_strdupW(endOfVar
+ 1);
1840 if (equalspos
== NULL
) return start
+1;
1842 /* Null terminate both strings */
1843 thisVar
[strlenW(thisVar
)-1] = 0x00;
1846 /* Since we need to be case insensitive, copy the 2 buffers */
1847 searchIn
= WCMD_strdupW(thisVarContents
);
1848 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1849 searchFor
= WCMD_strdupW(colonpos
+1);
1850 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1853 /* Handle wildcard case */
1854 if (*(colonpos
+1) == '*') {
1855 /* Search for string to replace */
1856 found
= strstrW(searchIn
, searchFor
+1);
1859 /* Do replacement */
1860 strcpyW(start
, replacewith
);
1861 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1866 strcpyW(start
, thisVarContents
);
1871 /* Loop replacing all instances */
1872 WCHAR
*lastFound
= searchIn
;
1873 WCHAR
*outputposn
= start
;
1876 while ((found
= strstrW(lastFound
, searchFor
))) {
1877 lstrcpynW(outputposn
,
1878 thisVarContents
+ (lastFound
-searchIn
),
1879 (found
- lastFound
)+1);
1880 outputposn
= outputposn
+ (found
- lastFound
);
1881 strcatW(outputposn
, replacewith
);
1882 outputposn
= outputposn
+ strlenW(replacewith
);
1883 lastFound
= found
+ strlenW(searchFor
);
1886 thisVarContents
+ (lastFound
-searchIn
));
1887 strcatW(outputposn
, s
);
1896 /*************************************************************************
1898 * Load a string from the resource file, handling any error
1899 * Returns string retrieved from resource file
1901 WCHAR
*WCMD_LoadMessage(UINT id
) {
1902 static WCHAR msg
[2048];
1903 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1905 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1906 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1907 strcpyW(msg
, failedMsg
);
1912 /*************************************************************************
1914 * A wide version of strdup as its missing from unicode.h
1916 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1917 int len
=strlenW(input
)+1;
1918 /* Note: Use malloc not HeapAlloc to emulate strdup */
1919 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1920 memcpy(result
, input
, len
* sizeof(WCHAR
));
1924 /***************************************************************************
1927 * Read characters in from a console/file, returning result in Unicode
1928 * with signature identical to ReadFile
1930 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1931 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1935 /* Try to read from console as Unicode */
1936 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1938 /* If reading from console has failed we assume its file
1939 i/o so read in and convert from OEM codepage */
1944 * Allocate buffer to use when reading from file. Not freed
1946 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1947 MAX_WRITECONSOLE_SIZE
);
1949 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1953 /* Read from file (assume OEM codepage) */
1954 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1956 /* Convert from OEM */
1957 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
1964 /***************************************************************************
1967 * Domps out the parsed command line to ensure syntax is correct
1969 void WCMD_DumpCommands(CMD_LIST
*commands
) {
1970 WCHAR buffer
[MAXSTRING
];
1971 CMD_LIST
*thisCmd
= commands
;
1972 const WCHAR fmt
[] = {'%','p',' ','%','d',' ','%','2','.','2','d',' ',
1973 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1976 WINE_TRACE("Parsed line:\n");
1977 while (thisCmd
!= NULL
) {
1978 sprintfW(buffer
, fmt
,
1981 thisCmd
->bracketDepth
,
1982 thisCmd
->nextcommand
,
1984 thisCmd
->redirects
);
1985 WINE_TRACE("%s\n", wine_dbgstr_w(buffer
));
1986 thisCmd
= thisCmd
->nextcommand
;
1990 /***************************************************************************
1993 * Adds a command to the current command list
1995 void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1996 WCHAR
*redirs
, int *redirLen
,
1997 WCHAR
**copyTo
, int **copyToLen
,
1998 CMD_DELIMITERS prevDelim
, int curDepth
,
1999 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
2001 CMD_LIST
*thisEntry
= NULL
;
2003 /* Allocate storage for command */
2004 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2006 /* Copy in the command */
2008 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2009 (*commandLen
+1) * sizeof(WCHAR
));
2010 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
2011 thisEntry
->command
[*commandLen
] = 0x00;
2013 /* Copy in the redirects */
2014 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
2015 (*redirLen
+1) * sizeof(WCHAR
));
2016 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
2017 thisEntry
->redirects
[*redirLen
] = 0x00;
2018 thisEntry
->pipeFile
[0] = 0x00;
2020 /* Reset the lengths */
2023 *copyToLen
= commandLen
;
2027 thisEntry
->command
= NULL
;
2030 /* Fill in other fields */
2031 thisEntry
->nextcommand
= NULL
;
2032 thisEntry
->prevDelim
= prevDelim
;
2033 thisEntry
->bracketDepth
= curDepth
;
2035 (*lastEntry
)->nextcommand
= thisEntry
;
2037 *output
= thisEntry
;
2039 *lastEntry
= thisEntry
;
2042 /***************************************************************************
2043 * WCMD_ReadAndParseLine
2045 * Either uses supplied input or
2046 * Reads a file from the handle, and then...
2047 * Parse the text buffer, spliting into separate commands
2048 * - unquoted && strings split 2 commands but the 2nd is flagged as
2050 * - ( as the first character just ups the bracket depth
2051 * - unquoted ) when bracket depth > 0 terminates a bracket and
2052 * adds a CMD_LIST structure with null command
2053 * - Anything else gets put into the command string (including
2056 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
2059 BOOL inQuotes
= FALSE
;
2060 WCHAR curString
[MAXSTRING
];
2061 int curStringLen
= 0;
2062 WCHAR curRedirs
[MAXSTRING
];
2063 int curRedirsLen
= 0;
2067 CMD_LIST
*lastEntry
= NULL
;
2068 CMD_DELIMITERS prevDelim
= CMD_NONE
;
2069 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
2070 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
2071 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
2072 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
2073 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
2079 BOOL onlyWhiteSpace
= FALSE
;
2080 BOOL lastWasWhiteSpace
= FALSE
;
2081 BOOL lastWasDo
= FALSE
;
2082 BOOL lastWasIn
= FALSE
;
2083 BOOL lastWasElse
= FALSE
;
2084 BOOL lastWasRedirect
= TRUE
;
2086 /* Allocate working space for a command read from keyboard, file etc */
2088 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
2090 /* If initial command read in, use that, otherwise get input from handle */
2091 if (optionalcmd
!= NULL
) {
2092 strcpyW(extraSpace
, optionalcmd
);
2093 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
2094 WINE_FIXME("No command nor handle supplied\n");
2096 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
2098 curPos
= extraSpace
;
2100 /* Handle truncated input - issue warning */
2101 if (strlenW(extraSpace
) == MAXSTRING
-1) {
2102 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
2103 WCMD_output_asis(extraSpace
);
2104 WCMD_output_asis(newline
);
2107 /* Replace env vars if in a batch context */
2108 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2110 /* Start with an empty string, copying to the command string */
2113 curCopyTo
= curString
;
2114 curLen
= &curStringLen
;
2115 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
2117 /* Parse every character on the line being processed */
2118 while (*curPos
!= 0x00) {
2123 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2124 lastWasWhiteSpace, onlyWhiteSpace);
2127 /* Certain commands need special handling */
2128 if (curStringLen
== 0 && curCopyTo
== curString
) {
2129 const WCHAR forDO
[] = {'d','o',' ','\0'};
2131 /* If command starts with 'rem', ignore any &&, ( etc */
2132 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2133 curPos
, 4, remCmd
, -1) == 2) {
2136 /* If command starts with 'for', handle ('s mid line after IN or DO */
2137 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2138 curPos
, 4, forCmd
, -1) == 2) {
2141 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2142 is only true in the command portion of the IF statement, but this
2143 should suffice for now
2144 FIXME: Silly syntax like "if 1(==1( (
2146 )" will be parsed wrong */
2147 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2148 curPos
, 3, ifCmd
, -1) == 2) {
2151 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2152 curPos
, 5, ifElse
, -1) == 2) {
2155 onlyWhiteSpace
= TRUE
;
2156 memcpy(&curCopyTo
[*curLen
], curPos
, 5*sizeof(WCHAR
));
2161 /* In a for loop, the DO command will follow a close bracket followed by
2162 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2163 is then 0, and all whitespace is skipped */
2165 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2166 curPos
, 3, forDO
, -1) == 2)) {
2167 WINE_TRACE("Found DO\n");
2169 onlyWhiteSpace
= TRUE
;
2170 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2175 } else if (curCopyTo
== curString
) {
2177 /* Special handling for the 'FOR' command */
2178 if (inFor
&& lastWasWhiteSpace
) {
2179 const WCHAR forIN
[] = {'i','n',' ','\0'};
2181 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2183 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2184 curPos
, 3, forIN
, -1) == 2) {
2185 WINE_TRACE("Found IN\n");
2187 onlyWhiteSpace
= TRUE
;
2188 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2196 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2197 so just use the default processing ie skip character specific
2199 if (!inRem
) thisChar
= *curPos
;
2200 else thisChar
= 'X'; /* Character with no special processing */
2202 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2206 case '=': /* drop through - ignore token delimiters at the start of a command */
2207 case ',': /* drop through - ignore token delimiters at the start of a command */
2208 case '\t':/* drop through - ignore token delimiters at the start of a command */
2210 /* If a redirect in place, it ends here */
2211 if (!inQuotes
&& !lastWasRedirect
) {
2213 /* If finishing off a redirect, add a whitespace delimiter */
2214 if (curCopyTo
== curRedirs
) {
2215 curCopyTo
[(*curLen
)++] = ' ';
2217 curCopyTo
= curString
;
2218 curLen
= &curStringLen
;
2221 curCopyTo
[(*curLen
)++] = *curPos
;
2224 /* Remember just processed whitespace */
2225 lastWasWhiteSpace
= TRUE
;
2229 case '>': /* drop through - handle redirect chars the same */
2231 /* Make a redirect start here */
2233 curCopyTo
= curRedirs
;
2234 curLen
= &curRedirsLen
;
2235 lastWasRedirect
= TRUE
;
2238 /* See if 1>, 2> etc, in which case we have some patching up
2240 if (curPos
!= extraSpace
&&
2241 *(curPos
-1)>='1' && *(curPos
-1)<='9') {
2244 curString
[curStringLen
] = 0x00;
2245 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2248 curCopyTo
[(*curLen
)++] = *curPos
;
2251 case '|': /* Pipe character only if not || */
2253 lastWasRedirect
= FALSE
;
2255 /* Add an entry to the command list */
2256 if (curStringLen
> 0) {
2258 /* Add the current command */
2259 WCMD_addCommand(curString
, &curStringLen
,
2260 curRedirs
, &curRedirsLen
,
2261 &curCopyTo
, &curLen
,
2262 prevDelim
, curDepth
,
2263 &lastEntry
, output
);
2267 if (*(curPos
+1) == '|') {
2268 curPos
++; /* Skip other | */
2269 prevDelim
= CMD_ONFAILURE
;
2271 prevDelim
= CMD_PIPE
;
2274 curCopyTo
[(*curLen
)++] = *curPos
;
2278 case '"': inQuotes
= !inQuotes
;
2279 curCopyTo
[(*curLen
)++] = *curPos
;
2280 lastWasRedirect
= FALSE
;
2283 case '(': /* If a '(' is the first non whitespace in a command portion
2284 ie start of line or just after &&, then we read until an
2285 unquoted ) is found */
2286 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2287 ", for(%d, In:%d, Do:%d)"
2288 ", if(%d, else:%d, lwe:%d)\n",
2291 inFor
, lastWasIn
, lastWasDo
,
2292 inIf
, inElse
, lastWasElse
);
2293 lastWasRedirect
= FALSE
;
2295 /* Ignore open brackets inside the for set */
2296 if (*curLen
== 0 && !inIn
) {
2299 /* If in quotes, ignore brackets */
2300 } else if (inQuotes
) {
2301 curCopyTo
[(*curLen
)++] = *curPos
;
2303 /* In a FOR loop, an unquoted '(' may occur straight after
2305 In an IF statement just handle it regardless as we don't
2307 In an ELSE statement, only allow it straight away after
2308 the ELSE and whitespace
2311 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2312 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2314 /* If entering into an 'IN', set inIn */
2315 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2316 WINE_TRACE("Inside an IN\n");
2320 /* Add the current command */
2321 WCMD_addCommand(curString
, &curStringLen
,
2322 curRedirs
, &curRedirsLen
,
2323 &curCopyTo
, &curLen
,
2324 prevDelim
, curDepth
,
2325 &lastEntry
, output
);
2329 curCopyTo
[(*curLen
)++] = *curPos
;
2333 case '&': if (!inQuotes
) {
2334 lastWasRedirect
= FALSE
;
2336 /* Add an entry to the command list */
2337 if (curStringLen
> 0) {
2339 /* Add the current command */
2340 WCMD_addCommand(curString
, &curStringLen
,
2341 curRedirs
, &curRedirsLen
,
2342 &curCopyTo
, &curLen
,
2343 prevDelim
, curDepth
,
2344 &lastEntry
, output
);
2348 if (*(curPos
+1) == '&') {
2349 curPos
++; /* Skip other & */
2350 prevDelim
= CMD_ONSUCCESS
;
2352 prevDelim
= CMD_NONE
;
2355 curCopyTo
[(*curLen
)++] = *curPos
;
2359 case ')': if (!inQuotes
&& curDepth
> 0) {
2360 lastWasRedirect
= FALSE
;
2362 /* Add the current command if there is one */
2365 /* Add the current command */
2366 WCMD_addCommand(curString
, &curStringLen
,
2367 curRedirs
, &curRedirsLen
,
2368 &curCopyTo
, &curLen
,
2369 prevDelim
, curDepth
,
2370 &lastEntry
, output
);
2373 /* Add an empty entry to the command list */
2374 prevDelim
= CMD_NONE
;
2375 WCMD_addCommand(NULL
, &curStringLen
,
2376 curRedirs
, &curRedirsLen
,
2377 &curCopyTo
, &curLen
,
2378 prevDelim
, curDepth
,
2379 &lastEntry
, output
);
2382 /* Leave inIn if necessary */
2383 if (inIn
) inIn
= FALSE
;
2385 curCopyTo
[(*curLen
)++] = *curPos
;
2389 lastWasRedirect
= FALSE
;
2390 curCopyTo
[(*curLen
)++] = *curPos
;
2395 /* At various times we need to know if we have only skipped whitespace,
2396 so reset this variable and then it will remain true until a non
2397 whitespace is found */
2398 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2400 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2401 if (!lastWasWhiteSpace
) {
2402 lastWasIn
= lastWasDo
= FALSE
;
2405 /* If we have reached the end, add this command into the list */
2406 if (*curPos
== 0x00 && *curLen
> 0) {
2408 /* Add an entry to the command list */
2409 WCMD_addCommand(curString
, &curStringLen
,
2410 curRedirs
, &curRedirsLen
,
2411 &curCopyTo
, &curLen
,
2412 prevDelim
, curDepth
,
2413 &lastEntry
, output
);
2416 /* If we have reached the end of the string, see if bracketing outstanding */
2417 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2419 prevDelim
= CMD_NONE
;
2421 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2423 /* Read more, skipping any blank lines */
2424 while (*extraSpace
== 0x00) {
2425 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2426 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2428 curPos
= extraSpace
;
2429 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2433 /* Dump out the parsed output */
2434 WCMD_DumpCommands(*output
);
2439 /***************************************************************************
2440 * WCMD_process_commands
2442 * Process all the commands read in so far
2444 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2445 WCHAR
*var
, WCHAR
*val
) {
2449 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2451 /* Loop through the commands, processing them one by one */
2454 CMD_LIST
*origCmd
= thisCmd
;
2456 /* If processing one bracket only, and we find the end bracket
2457 entry (or less), return */
2458 if (oneBracket
&& !thisCmd
->command
&&
2459 bdepth
<= thisCmd
->bracketDepth
) {
2460 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2461 thisCmd
, thisCmd
->nextcommand
);
2462 return thisCmd
->nextcommand
;
2465 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2466 about them and it will be handled in there)
2467 Also, skip over any batch labels (eg. :fred) */
2468 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2469 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2470 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2473 /* Step on unless the command itself already stepped on */
2474 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2479 /***************************************************************************
2480 * WCMD_free_commands
2482 * Frees the storage held for a parsed command line
2483 * - This is not done in the process_commands, as eventually the current
2484 * pointer will be modified within the commands, and hence a single free
2485 * routine is simpler
2487 void WCMD_free_commands(CMD_LIST
*cmds
) {
2489 /* Loop through the commands, freeing them one by one */
2491 CMD_LIST
*thisCmd
= cmds
;
2492 cmds
= cmds
->nextcommand
;
2493 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2494 HeapFree(GetProcessHeap(), 0, thisCmd
);