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
));
606 WINE_ERR("Could not allocate memory for new_cmd\n");
609 strcpyW(new_cmd
, command
);
611 /* Move copy of the redirects onto the heap so it can be expanded */
612 new_redir
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
615 WINE_ERR("Could not allocate memory for new_redir\n");
616 HeapFree( GetProcessHeap(), 0, new_cmd
);
620 /* If piped output, send stdout to the pipe by appending >filename to redirects */
622 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
623 wsprintf (new_redir
, redirOut
, redirects
, (*cmdList
)->nextcommand
->pipeFile
);
624 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
626 strcpyW(new_redir
, redirects
);
629 /* Expand variables in command line mode only (batch mode will
630 be expanded as the line is read in, except for 'for' loops) */
631 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
632 handleExpansion(new_redir
, (context
!= NULL
), forVariable
, forValue
);
635 /* Show prompt before batch line IF echo is on and in batch program */
636 if (context
&& echo_mode
&& (cmd
[0] != '@')) {
638 WCMD_output_asis ( cmd
);
639 WCMD_output_asis ( newline
);
643 * Changing default drive has to be handled as a special case.
646 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
650 /* According to MSDN CreateProcess docs, special env vars record
651 the current directory on each drive, in the form =C:
652 so see if one specified, and if so go back to it */
653 strcpyW(envvar
, equalsW
);
654 strcatW(envvar
, cmd
);
655 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
656 static const WCHAR fmt
[] = {'%','s','\\','\0'};
657 wsprintf(cmd
, fmt
, cmd
);
658 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
660 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
661 status
= SetCurrentDirectory (cmd
);
662 if (!status
) WCMD_print_error ();
663 HeapFree( GetProcessHeap(), 0, cmd
);
664 HeapFree( GetProcessHeap(), 0, new_redir
);
668 sa
.nLength
= sizeof(sa
);
669 sa
.lpSecurityDescriptor
= NULL
;
670 sa
.bInheritHandle
= TRUE
;
673 * Redirect stdin, stdout and/or stderr if required.
676 /* STDIN could come from a preceding pipe, so delete on close if it does */
677 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
678 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
679 h
= CreateFile ((*cmdList
)->pipeFile
, GENERIC_READ
,
680 FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
681 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
682 if (h
== INVALID_HANDLE_VALUE
) {
684 HeapFree( GetProcessHeap(), 0, cmd
);
685 HeapFree( GetProcessHeap(), 0, new_redir
);
688 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
689 SetStdHandle (STD_INPUT_HANDLE
, h
);
691 /* No need to remember the temporary name any longer once opened */
692 (*cmdList
)->pipeFile
[0] = 0x00;
694 /* Otherwise STDIN could come from a '<' redirect */
695 } else if ((p
= strchrW(new_redir
,'<')) != NULL
) {
696 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
697 FILE_ATTRIBUTE_NORMAL
, NULL
);
698 if (h
== INVALID_HANDLE_VALUE
) {
700 HeapFree( GetProcessHeap(), 0, cmd
);
701 HeapFree( GetProcessHeap(), 0, new_redir
);
704 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
705 SetStdHandle (STD_INPUT_HANDLE
, h
);
708 /* Scan the whole command looking for > and 2> */
710 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
721 creationDisposition
= OPEN_ALWAYS
;
725 creationDisposition
= CREATE_ALWAYS
;
728 /* Add support for 2>&1 */
731 int idx
= *(p
+1) - '0';
733 if (DuplicateHandle(GetCurrentProcess(),
734 GetStdHandle(idx_stdhandles
[idx
]),
737 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
738 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
740 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
743 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
744 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
745 FILE_ATTRIBUTE_NORMAL
, NULL
);
746 if (h
== INVALID_HANDLE_VALUE
) {
748 HeapFree( GetProcessHeap(), 0, cmd
);
749 HeapFree( GetProcessHeap(), 0, new_redir
);
752 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
753 INVALID_SET_FILE_POINTER
) {
756 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
759 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
760 SetStdHandle (idx_stdhandles
[handle
], h
);
764 * Strip leading whitespaces, and a '@' if supplied
766 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
767 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
768 if (whichcmd
[0] == '@') whichcmd
++;
771 * Check if the command entered is internal. If it is, pass the rest of the
772 * line down to the command. If not try to run a program.
776 while (IsCharAlphaNumeric(whichcmd
[count
])) {
779 for (i
=0; i
<=WCMD_EXIT
; i
++) {
780 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
781 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
783 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
784 WCMD_parse (p
, quals
, param1
, param2
);
785 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
790 WCMD_setshow_attrib ();
797 WCMD_setshow_default (p
);
800 WCMD_clear_screen ();
809 WCMD_setshow_date ();
813 WCMD_delete (p
, TRUE
);
819 WCMD_echo(&whichcmd
[count
]);
822 WCMD_for (p
, cmdList
);
831 WCMD_if (p
, cmdList
);
844 WCMD_setshow_path (p
);
850 WCMD_setshow_prompt ();
869 WCMD_setshow_env (p
);
875 WCMD_setshow_time ();
878 if (strlenW(&whichcmd
[count
]) > 0)
879 WCMD_title(&whichcmd
[count
+1]);
906 WCMD_assoc(p
, FALSE
);
915 WCMD_run_program (whichcmd
, 0);
917 HeapFree( GetProcessHeap(), 0, cmd
);
918 HeapFree( GetProcessHeap(), 0, new_redir
);
920 /* Restore old handles */
921 for (i
=0; i
<3; i
++) {
922 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
923 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
924 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
929 static void init_msvcrt_io_block(STARTUPINFO
* st
)
932 /* fetch the parent MSVCRT info block if any, so that the child can use the
933 * same handles as its grand-father
935 st_p
.cb
= sizeof(STARTUPINFO
);
936 GetStartupInfo(&st_p
);
937 st
->cbReserved2
= st_p
.cbReserved2
;
938 st
->lpReserved2
= st_p
.lpReserved2
;
939 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
941 /* Override the entries for fd 0,1,2 if we happened
942 * to change those std handles (this depends on the way wcmd sets
943 * it's new input & output handles)
945 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
946 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
949 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
950 char* flags
= (char*)(ptr
+ sizeof(unsigned));
951 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
953 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
954 st
->cbReserved2
= sz
;
955 st
->lpReserved2
= ptr
;
957 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
958 if (num
<= 0 || (flags
[0] & WX_OPEN
))
960 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
963 if (num
<= 1 || (flags
[1] & WX_OPEN
))
965 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
968 if (num
<= 2 || (flags
[2] & WX_OPEN
))
970 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
978 /******************************************************************************
981 * Execute a command line as an external program. Must allow recursion.
984 * Manual testing under windows shows PATHEXT plays a key part in this,
985 * and the search algorithm and precedence appears to be as follows.
988 * If directory supplied on command, just use that directory
989 * If extension supplied on command, look for that explicit name first
990 * Otherwise, search in each directory on the path
992 * If extension supplied on command, look for that explicit name first
993 * Then look for supplied name .* (even if extension supplied, so
994 * 'garbage.exe' will match 'garbage.exe.cmd')
995 * If any found, cycle through PATHEXT looking for name.exe one by one
997 * Once a match has been found, it is launched - Code currently uses
998 * findexecutable to achieve this which is left untouched.
1001 void WCMD_run_program (WCHAR
*command
, int called
) {
1003 WCHAR temp
[MAX_PATH
];
1004 WCHAR pathtosearch
[MAXSTRING
];
1006 WCHAR stemofsearch
[MAX_PATH
];
1008 WCHAR pathext
[MAXSTRING
];
1009 BOOL extensionsupplied
= FALSE
;
1010 BOOL launched
= FALSE
;
1012 BOOL assumeInternal
= FALSE
;
1014 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
1015 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
1016 static const WCHAR delims
[] = {'/','\\',':','\0'};
1018 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
1019 if (!(*param1
) && !(*param2
))
1022 /* Calculate the search path and stem to search for */
1023 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
1024 static const WCHAR curDir
[] = {'.',';','\0'};
1025 strcpyW(pathtosearch
, curDir
);
1026 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
1027 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
1028 static const WCHAR curDir
[] = {'.','\0'};
1029 strcpyW (pathtosearch
, curDir
);
1031 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
1032 strcpyW(stemofsearch
, param1
);
1036 /* Convert eg. ..\fred to include a directory by removing file part */
1037 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
1038 lastSlash
= strrchrW(pathtosearch
, '\\');
1039 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1040 strcpyW(stemofsearch
, lastSlash
+1);
1042 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1043 c:\windows\a.bat syntax */
1044 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1047 /* Now extract PATHEXT */
1048 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
1049 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
1050 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
1051 '.','c','o','m',';',
1052 '.','c','m','d',';',
1053 '.','e','x','e','\0'};
1054 strcpyW (pathext
, dfltPathExt
);
1057 /* Loop through the search path, dir by dir */
1058 pathposn
= pathtosearch
;
1059 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1060 wine_dbgstr_w(stemofsearch
));
1061 while (!launched
&& pathposn
) {
1063 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1066 const WCHAR slashW
[] = {'\\','\0'};
1068 /* Work on the first directory on the search path */
1069 pos
= strchrW(pathposn
, ';');
1071 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1072 thisDir
[(pos
-pathposn
)] = 0x00;
1076 strcpyW(thisDir
, pathposn
);
1080 /* Since you can have eg. ..\.. on the path, need to expand
1081 to full information */
1082 strcpyW(temp
, thisDir
);
1083 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
1085 /* 1. If extension supplied, see if that file exists */
1086 strcatW(thisDir
, slashW
);
1087 strcatW(thisDir
, stemofsearch
);
1088 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1090 /* 1. If extension supplied, see if that file exists */
1091 if (extensionsupplied
) {
1092 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1097 /* 2. Any .* matches? */
1100 WIN32_FIND_DATA finddata
;
1101 static const WCHAR allFiles
[] = {'.','*','\0'};
1103 strcatW(thisDir
,allFiles
);
1104 h
= FindFirstFile(thisDir
, &finddata
);
1106 if (h
!= INVALID_HANDLE_VALUE
) {
1108 WCHAR
*thisExt
= pathext
;
1110 /* 3. Yes - Try each path ext */
1112 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1115 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1116 pos
[(nextExt
-thisExt
)] = 0x00;
1117 thisExt
= nextExt
+1;
1119 strcpyW(pos
, thisExt
);
1123 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1131 /* Internal programs won't be picked up by this search, so even
1132 though not found, try one last createprocess and wait for it
1134 Note: Ideally we could tell between a console app (wait) and a
1135 windows app, but the API's for it fail in this case */
1136 if (!found
&& pathposn
== NULL
) {
1137 WINE_TRACE("ASSUMING INTERNAL\n");
1138 assumeInternal
= TRUE
;
1140 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1143 /* Once found, launch it */
1144 if (found
|| assumeInternal
) {
1146 PROCESS_INFORMATION pe
;
1150 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1151 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1152 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1156 /* Special case BAT and CMD */
1157 if (ext
&& !strcmpiW(ext
, batExt
)) {
1158 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1160 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1161 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1165 /* thisDir contains the file to be launched, but with what?
1166 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1167 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1168 if ((INT_PTR
)hinst
< 32)
1171 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1173 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1174 st
.cb
= sizeof(STARTUPINFO
);
1175 init_msvcrt_io_block(&st
);
1177 /* Launch the process and if a CUI wait on it to complete
1178 Note: Launching internal wine processes cannot specify a full path to exe */
1179 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1180 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1181 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1182 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1183 /* strip first and last quote WCHARacters and try again */
1184 WCMD_opt_s_strip_quotes(command
);
1186 WCMD_run_program(command
, called
);
1190 WCMD_print_error ();
1191 /* If a command fails to launch, it sets errorlevel 9009 - which
1192 does not seem to have any associated constant definition */
1196 if (!assumeInternal
&& !console
) errorlevel
= 0;
1199 /* Always wait when called in a batch program context */
1200 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1201 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1202 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1204 CloseHandle(pe
.hProcess
);
1205 CloseHandle(pe
.hThread
);
1211 /* Not found anywhere - give up */
1212 SetLastError(ERROR_FILE_NOT_FOUND
);
1213 WCMD_print_error ();
1215 /* If a command fails to launch, it sets errorlevel 9009 - which
1216 does not seem to have any associated constant definition */
1222 /******************************************************************************
1225 * Display the prompt on STDout
1229 void WCMD_show_prompt (void) {
1232 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1235 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1237 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1238 sizeof(prompt_string
)/sizeof(WCHAR
));
1239 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1240 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1241 strcpyW (prompt_string
, dfltPrompt
);
1246 while (*p
!= '\0') {
1253 switch (toupper(*p
)) {
1267 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1286 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1292 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1294 strcatW (q
, curdir
);
1305 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1309 strcatW (q
, version_string
);
1316 if (pushd_directories
) {
1317 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1318 q
= q
+ pushd_directories
->u
.stackdepth
;
1326 WCMD_output_asis (out_string
);
1329 /****************************************************************************
1332 * Print the message for GetLastError
1335 void WCMD_print_error (void) {
1340 error_code
= GetLastError ();
1341 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1342 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1344 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1345 error_code
, GetLastError());
1349 WCMD_output_asis_len(lpMsgBuf
, lstrlen(lpMsgBuf
),
1350 GetStdHandle(STD_ERROR_HANDLE
));
1351 LocalFree ((HLOCAL
)lpMsgBuf
);
1352 WCMD_output_asis_len (newline
, lstrlen(newline
),
1353 GetStdHandle(STD_ERROR_HANDLE
));
1357 /*******************************************************************
1358 * WCMD_parse - parse a command into parameters and qualifiers.
1360 * On exit, all qualifiers are concatenated into q, the first string
1361 * not beginning with "/" is in p1 and the
1362 * second in p2. Any subsequent non-qualifier strings are lost.
1363 * Parameters in quotes are handled.
1366 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1370 *q
= *p1
= *p2
= '\0';
1375 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1376 *q
++ = toupperW (*s
++);
1386 while ((*s
!= '\0') && (*s
!= '"')) {
1387 if (p
== 0) *p1
++ = *s
++;
1388 else if (p
== 1) *p2
++ = *s
++;
1391 if (p
== 0) *p1
= '\0';
1392 if (p
== 1) *p2
= '\0';
1399 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
1400 && (*s
!= '=') && (*s
!= ',') ) {
1401 if (p
== 0) *p1
++ = *s
++;
1402 else if (p
== 1) *p2
++ = *s
++;
1405 /* Skip concurrent parms */
1406 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
1408 if (p
== 0) *p1
= '\0';
1409 if (p
== 1) *p2
= '\0';
1415 /*******************************************************************
1416 * WCMD_output_asis_len - send output to current standard output
1418 * Output a formatted unicode string. Ideally this will go to the console
1419 * and hence required WriteConsoleW to output it, however if file i/o is
1420 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1422 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
1427 /* If nothing to write, return (MORE does this sometimes) */
1430 /* Try to write as unicode assuming it is to a console */
1431 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
1433 /* If writing to console fails, assume its file
1434 i/o so convert to OEM codepage and output */
1436 BOOL usedDefaultChar
= FALSE
;
1437 DWORD convertedChars
;
1439 if (!unicodePipes
) {
1441 * Allocate buffer to use when writing to file. (Not freed, as one off)
1443 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1444 MAX_WRITECONSOLE_SIZE
);
1446 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1450 /* Convert to OEM, then output */
1451 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1452 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1453 "?", &usedDefaultChar
);
1454 WriteFile(device
, output_bufA
, convertedChars
,
1457 WriteFile(device
, message
, len
*sizeof(WCHAR
),
1464 /*******************************************************************
1465 * WCMD_output - send output to current standard output device.
1469 void WCMD_output (const WCHAR
*format
, ...) {
1475 va_start(ap
,format
);
1476 ret
= wvsprintf (string
, format
, ap
);
1477 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1478 WINE_ERR("Output truncated in WCMD_output\n" );
1479 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1483 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
1487 static int line_count
;
1488 static int max_height
;
1489 static int max_width
;
1490 static BOOL paged_mode
;
1491 static int numChars
;
1493 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1495 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1497 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1498 max_height
= consoleInfo
.dwSize
.Y
;
1499 max_width
= consoleInfo
.dwSize
.X
;
1507 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1510 void WCMD_leave_paged_mode(void)
1513 pagedMessage
= NULL
;
1516 /*******************************************************************
1517 * WCMD_output_asis - send output to current standard output device.
1518 * without formatting eg. when message contains '%'
1521 void WCMD_output_asis (const WCHAR
*message
) {
1529 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1533 if (*ptr
== '\n') ptr
++;
1534 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
),
1535 GetStdHandle(STD_OUTPUT_HANDLE
));
1538 if (++line_count
>= max_height
- 1) {
1540 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
),
1541 GetStdHandle(STD_OUTPUT_HANDLE
));
1542 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1543 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1546 } while (((message
= ptr
) != NULL
) && (*ptr
));
1548 WCMD_output_asis_len(message
, lstrlen(message
),
1549 GetStdHandle(STD_OUTPUT_HANDLE
));
1554 /***************************************************************************
1555 * WCMD_strtrim_leading_spaces
1557 * Remove leading spaces from a string. Return a pointer to the first
1558 * non-space character. Does not modify the input string
1561 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1566 while (*ptr
== ' ') ptr
++;
1570 /*************************************************************************
1571 * WCMD_strtrim_trailing_spaces
1573 * Remove trailing spaces from a string. This routine modifies the input
1574 * string by placing a null after the last non-space WCHARacter
1577 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1581 ptr
= string
+ strlenW (string
) - 1;
1582 while ((*ptr
== ' ') && (ptr
>= string
)) {
1588 /*************************************************************************
1589 * WCMD_opt_s_strip_quotes
1591 * Remove first and last quote WCHARacters, preserving all other text
1594 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1595 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1596 while((*dest
=*src
) != '\0') {
1603 while ((*dest
++=*lastq
++) != 0)
1608 /*************************************************************************
1609 * WCMD_expand_envvar
1611 * Expands environment variables, allowing for WCHARacter substitution
1613 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forVar
, WCHAR
*forVal
) {
1614 WCHAR
*endOfVar
= NULL
, *s
;
1615 WCHAR
*colonpos
= NULL
;
1616 WCHAR thisVar
[MAXSTRING
];
1617 WCHAR thisVarContents
[MAXSTRING
];
1618 WCHAR savedchar
= 0x00;
1621 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1622 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1623 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1624 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1625 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1626 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1627 static const WCHAR Cd
[] = {'C','D','\0'};
1628 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1629 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1630 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1631 static const WCHAR Delims
[] = {'%',' ',':','\0'};
1633 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
1634 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
1636 /* Find the end of the environment variable, and extract name */
1637 endOfVar
= strpbrkW(start
+1, Delims
);
1639 if (endOfVar
== NULL
|| *endOfVar
==' ') {
1641 /* In batch program, missing terminator for % and no following
1642 ':' just removes the '%' */
1644 s
= WCMD_strdupW(start
+ 1);
1650 /* In command processing, just ignore it - allows command line
1651 syntax like: for %i in (a.a) do echo %i */
1656 /* If ':' found, process remaining up until '%' (or stop at ':' if
1658 if (*endOfVar
==':') {
1659 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
1660 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
1663 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1664 thisVar
[(endOfVar
- start
)+1] = 0x00;
1665 colonpos
= strchrW(thisVar
+1, ':');
1667 /* If there's complex substitution, just need %var% for now
1668 to get the expanded data to play with */
1671 savedchar
= *(colonpos
+1);
1672 *(colonpos
+1) = 0x00;
1675 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1677 /* Expand to contents, if unchanged, return */
1678 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1679 /* override if existing env var called that name */
1680 if ((CompareString (LOCALE_USER_DEFAULT
,
1681 NORM_IGNORECASE
| SORT_STRINGSORT
,
1682 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1683 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1684 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1685 static const WCHAR fmt
[] = {'%','d','\0'};
1686 wsprintf(thisVarContents
, fmt
, errorlevel
);
1687 len
= strlenW(thisVarContents
);
1689 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1690 NORM_IGNORECASE
| SORT_STRINGSORT
,
1691 thisVar
, 6, DateP
, -1) == 2) &&
1692 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1693 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1695 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1696 NULL
, thisVarContents
, MAXSTRING
);
1697 len
= strlenW(thisVarContents
);
1699 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1700 NORM_IGNORECASE
| SORT_STRINGSORT
,
1701 thisVar
, 6, TimeP
, -1) == 2) &&
1702 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1703 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1704 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1705 NULL
, thisVarContents
, MAXSTRING
);
1706 len
= strlenW(thisVarContents
);
1708 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1709 NORM_IGNORECASE
| SORT_STRINGSORT
,
1710 thisVar
, 4, CdP
, -1) == 2) &&
1711 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1712 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1713 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1714 len
= strlenW(thisVarContents
);
1716 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1717 NORM_IGNORECASE
| SORT_STRINGSORT
,
1718 thisVar
, 8, RandomP
, -1) == 2) &&
1719 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1720 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1721 static const WCHAR fmt
[] = {'%','d','\0'};
1722 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1723 len
= strlenW(thisVarContents
);
1725 /* Look for a matching 'for' variable */
1726 } else if (forVar
&&
1727 (CompareString (LOCALE_USER_DEFAULT
,
1730 (colonpos
- thisVar
) - 1,
1731 forVar
, -1) == 2)) {
1732 strcpyW(thisVarContents
, forVal
);
1733 len
= strlenW(thisVarContents
);
1737 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1738 sizeof(thisVarContents
)/sizeof(WCHAR
));
1744 /* In a batch program, unknown env vars are replaced with nothing,
1745 note syntax %garbage:1,3% results in anything after the ':'
1747 From the command line, you just get back what you entered */
1748 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1750 /* Restore the complex part after the compare */
1753 *(colonpos
+1) = savedchar
;
1756 /* Command line - just ignore this */
1757 if (context
== NULL
) return endOfVar
+1;
1759 s
= WCMD_strdupW(endOfVar
+ 1);
1761 /* Batch - replace unknown env var with nothing */
1762 if (colonpos
== NULL
) {
1766 len
= strlenW(thisVar
);
1767 thisVar
[len
-1] = 0x00;
1768 /* If %:...% supplied, : is retained */
1769 if (colonpos
== thisVar
+1) {
1770 strcpyW (start
, colonpos
);
1772 strcpyW (start
, colonpos
+1);
1781 /* See if we need to do complex substitution (any ':'s), if not
1782 then our work here is done */
1783 if (colonpos
== NULL
) {
1784 s
= WCMD_strdupW(endOfVar
+ 1);
1785 strcpyW (start
, thisVarContents
);
1791 /* Restore complex bit */
1793 *(colonpos
+1) = savedchar
;
1796 Handle complex substitutions:
1797 xxx=yyy (replace xxx with yyy)
1798 *xxx=yyy (replace up to and including xxx with yyy)
1799 ~x (from x WCHARs in)
1800 ~-x (from x WCHARs from the end)
1801 ~x,y (from x WCHARs in for y WCHARacters)
1802 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1805 /* ~ is substring manipulation */
1806 if (savedchar
== '~') {
1808 int substrposition
, substrlength
= 0;
1809 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1812 substrposition
= atolW(colonpos
+2);
1813 if (commapos
) substrlength
= atolW(commapos
+1);
1815 s
= WCMD_strdupW(endOfVar
+ 1);
1818 if (substrposition
>= 0) {
1819 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1821 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1824 if (commapos
== NULL
) {
1825 strcpyW (start
, startCopy
); /* Copy the lot */
1826 } else if (substrlength
< 0) {
1828 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1829 if (copybytes
> len
) copybytes
= len
;
1830 else if (copybytes
< 0) copybytes
= 0;
1831 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1832 start
[copybytes
] = 0x00;
1834 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1835 start
[substrlength
] = 0x00;
1842 /* search and replace manipulation */
1844 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1845 WCHAR
*replacewith
= equalspos
+1;
1846 WCHAR
*found
= NULL
;
1850 s
= WCMD_strdupW(endOfVar
+ 1);
1851 if (equalspos
== NULL
) return start
+1;
1853 /* Null terminate both strings */
1854 thisVar
[strlenW(thisVar
)-1] = 0x00;
1857 /* Since we need to be case insensitive, copy the 2 buffers */
1858 searchIn
= WCMD_strdupW(thisVarContents
);
1859 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1860 searchFor
= WCMD_strdupW(colonpos
+1);
1861 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1864 /* Handle wildcard case */
1865 if (*(colonpos
+1) == '*') {
1866 /* Search for string to replace */
1867 found
= strstrW(searchIn
, searchFor
+1);
1870 /* Do replacement */
1871 strcpyW(start
, replacewith
);
1872 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1877 strcpyW(start
, thisVarContents
);
1882 /* Loop replacing all instances */
1883 WCHAR
*lastFound
= searchIn
;
1884 WCHAR
*outputposn
= start
;
1887 while ((found
= strstrW(lastFound
, searchFor
))) {
1888 lstrcpynW(outputposn
,
1889 thisVarContents
+ (lastFound
-searchIn
),
1890 (found
- lastFound
)+1);
1891 outputposn
= outputposn
+ (found
- lastFound
);
1892 strcatW(outputposn
, replacewith
);
1893 outputposn
= outputposn
+ strlenW(replacewith
);
1894 lastFound
= found
+ strlenW(searchFor
);
1897 thisVarContents
+ (lastFound
-searchIn
));
1898 strcatW(outputposn
, s
);
1907 /*************************************************************************
1909 * Load a string from the resource file, handling any error
1910 * Returns string retrieved from resource file
1912 WCHAR
*WCMD_LoadMessage(UINT id
) {
1913 static WCHAR msg
[2048];
1914 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1916 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1917 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1918 strcpyW(msg
, failedMsg
);
1923 /*************************************************************************
1925 * A wide version of strdup as its missing from unicode.h
1927 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1928 int len
=strlenW(input
)+1;
1929 /* Note: Use malloc not HeapAlloc to emulate strdup */
1930 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1931 memcpy(result
, input
, len
* sizeof(WCHAR
));
1935 /***************************************************************************
1938 * Read characters in from a console/file, returning result in Unicode
1939 * with signature identical to ReadFile
1941 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1942 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1946 /* Try to read from console as Unicode */
1947 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1949 /* If reading from console has failed we assume its file
1950 i/o so read in and convert from OEM codepage */
1955 * Allocate buffer to use when reading from file. Not freed
1957 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1958 MAX_WRITECONSOLE_SIZE
);
1960 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1964 /* Read from file (assume OEM codepage) */
1965 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1967 /* Convert from OEM */
1968 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
1975 /***************************************************************************
1978 * Domps out the parsed command line to ensure syntax is correct
1980 void WCMD_DumpCommands(CMD_LIST
*commands
) {
1981 WCHAR buffer
[MAXSTRING
];
1982 CMD_LIST
*thisCmd
= commands
;
1983 const WCHAR fmt
[] = {'%','p',' ','%','d',' ','%','2','.','2','d',' ',
1984 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1987 WINE_TRACE("Parsed line:\n");
1988 while (thisCmd
!= NULL
) {
1989 sprintfW(buffer
, fmt
,
1992 thisCmd
->bracketDepth
,
1993 thisCmd
->nextcommand
,
1995 thisCmd
->redirects
);
1996 WINE_TRACE("%s\n", wine_dbgstr_w(buffer
));
1997 thisCmd
= thisCmd
->nextcommand
;
2001 /***************************************************************************
2004 * Adds a command to the current command list
2006 void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
2007 WCHAR
*redirs
, int *redirLen
,
2008 WCHAR
**copyTo
, int **copyToLen
,
2009 CMD_DELIMITERS prevDelim
, int curDepth
,
2010 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
2012 CMD_LIST
*thisEntry
= NULL
;
2014 /* Allocate storage for command */
2015 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2017 /* Copy in the command */
2019 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2020 (*commandLen
+1) * sizeof(WCHAR
));
2021 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
2022 thisEntry
->command
[*commandLen
] = 0x00;
2024 /* Copy in the redirects */
2025 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
2026 (*redirLen
+1) * sizeof(WCHAR
));
2027 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
2028 thisEntry
->redirects
[*redirLen
] = 0x00;
2029 thisEntry
->pipeFile
[0] = 0x00;
2031 /* Reset the lengths */
2034 *copyToLen
= commandLen
;
2038 thisEntry
->command
= NULL
;
2041 /* Fill in other fields */
2042 thisEntry
->nextcommand
= NULL
;
2043 thisEntry
->prevDelim
= prevDelim
;
2044 thisEntry
->bracketDepth
= curDepth
;
2046 (*lastEntry
)->nextcommand
= thisEntry
;
2048 *output
= thisEntry
;
2050 *lastEntry
= thisEntry
;
2053 /***************************************************************************
2054 * WCMD_ReadAndParseLine
2056 * Either uses supplied input or
2057 * Reads a file from the handle, and then...
2058 * Parse the text buffer, spliting into separate commands
2059 * - unquoted && strings split 2 commands but the 2nd is flagged as
2061 * - ( as the first character just ups the bracket depth
2062 * - unquoted ) when bracket depth > 0 terminates a bracket and
2063 * adds a CMD_LIST structure with null command
2064 * - Anything else gets put into the command string (including
2067 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
2070 BOOL inQuotes
= FALSE
;
2071 WCHAR curString
[MAXSTRING
];
2072 int curStringLen
= 0;
2073 WCHAR curRedirs
[MAXSTRING
];
2074 int curRedirsLen
= 0;
2078 CMD_LIST
*lastEntry
= NULL
;
2079 CMD_DELIMITERS prevDelim
= CMD_NONE
;
2080 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
2081 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
2082 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
2083 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
2084 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
2090 BOOL onlyWhiteSpace
= FALSE
;
2091 BOOL lastWasWhiteSpace
= FALSE
;
2092 BOOL lastWasDo
= FALSE
;
2093 BOOL lastWasIn
= FALSE
;
2094 BOOL lastWasElse
= FALSE
;
2095 BOOL lastWasRedirect
= TRUE
;
2097 /* Allocate working space for a command read from keyboard, file etc */
2099 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
2102 WINE_ERR("Could not allocate memory for extraSpace\n");
2106 /* If initial command read in, use that, otherwise get input from handle */
2107 if (optionalcmd
!= NULL
) {
2108 strcpyW(extraSpace
, optionalcmd
);
2109 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
2110 WINE_FIXME("No command nor handle supplied\n");
2112 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
2114 curPos
= extraSpace
;
2116 /* Handle truncated input - issue warning */
2117 if (strlenW(extraSpace
) == MAXSTRING
-1) {
2118 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
2119 WCMD_output_asis(extraSpace
);
2120 WCMD_output_asis(newline
);
2123 /* Replace env vars if in a batch context */
2124 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2126 /* Start with an empty string, copying to the command string */
2129 curCopyTo
= curString
;
2130 curLen
= &curStringLen
;
2131 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
2133 /* Parse every character on the line being processed */
2134 while (*curPos
!= 0x00) {
2139 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2140 lastWasWhiteSpace, onlyWhiteSpace);
2143 /* Certain commands need special handling */
2144 if (curStringLen
== 0 && curCopyTo
== curString
) {
2145 const WCHAR forDO
[] = {'d','o',' ','\0'};
2147 /* If command starts with 'rem', ignore any &&, ( etc */
2148 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2149 curPos
, 4, remCmd
, -1) == 2) {
2152 /* If command starts with 'for', handle ('s mid line after IN or DO */
2153 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2154 curPos
, 4, forCmd
, -1) == 2) {
2157 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2158 is only true in the command portion of the IF statement, but this
2159 should suffice for now
2160 FIXME: Silly syntax like "if 1(==1( (
2162 )" will be parsed wrong */
2163 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2164 curPos
, 3, ifCmd
, -1) == 2) {
2167 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2168 curPos
, 5, ifElse
, -1) == 2) {
2171 onlyWhiteSpace
= TRUE
;
2172 memcpy(&curCopyTo
[*curLen
], curPos
, 5*sizeof(WCHAR
));
2177 /* In a for loop, the DO command will follow a close bracket followed by
2178 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2179 is then 0, and all whitespace is skipped */
2181 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2182 curPos
, 3, forDO
, -1) == 2)) {
2183 WINE_TRACE("Found DO\n");
2185 onlyWhiteSpace
= TRUE
;
2186 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2191 } else if (curCopyTo
== curString
) {
2193 /* Special handling for the 'FOR' command */
2194 if (inFor
&& lastWasWhiteSpace
) {
2195 const WCHAR forIN
[] = {'i','n',' ','\0'};
2197 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2199 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2200 curPos
, 3, forIN
, -1) == 2) {
2201 WINE_TRACE("Found IN\n");
2203 onlyWhiteSpace
= TRUE
;
2204 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2212 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2213 so just use the default processing ie skip character specific
2215 if (!inRem
) thisChar
= *curPos
;
2216 else thisChar
= 'X'; /* Character with no special processing */
2218 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2222 case '=': /* drop through - ignore token delimiters at the start of a command */
2223 case ',': /* drop through - ignore token delimiters at the start of a command */
2224 case '\t':/* drop through - ignore token delimiters at the start of a command */
2226 /* If a redirect in place, it ends here */
2227 if (!inQuotes
&& !lastWasRedirect
) {
2229 /* If finishing off a redirect, add a whitespace delimiter */
2230 if (curCopyTo
== curRedirs
) {
2231 curCopyTo
[(*curLen
)++] = ' ';
2233 curCopyTo
= curString
;
2234 curLen
= &curStringLen
;
2237 curCopyTo
[(*curLen
)++] = *curPos
;
2240 /* Remember just processed whitespace */
2241 lastWasWhiteSpace
= TRUE
;
2245 case '>': /* drop through - handle redirect chars the same */
2247 /* Make a redirect start here */
2249 curCopyTo
= curRedirs
;
2250 curLen
= &curRedirsLen
;
2251 lastWasRedirect
= TRUE
;
2254 /* See if 1>, 2> etc, in which case we have some patching up
2256 if (curPos
!= extraSpace
&&
2257 *(curPos
-1)>='1' && *(curPos
-1)<='9') {
2260 curString
[curStringLen
] = 0x00;
2261 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2264 curCopyTo
[(*curLen
)++] = *curPos
;
2267 case '|': /* Pipe character only if not || */
2269 lastWasRedirect
= FALSE
;
2271 /* Add an entry to the command list */
2272 if (curStringLen
> 0) {
2274 /* Add the current command */
2275 WCMD_addCommand(curString
, &curStringLen
,
2276 curRedirs
, &curRedirsLen
,
2277 &curCopyTo
, &curLen
,
2278 prevDelim
, curDepth
,
2279 &lastEntry
, output
);
2283 if (*(curPos
+1) == '|') {
2284 curPos
++; /* Skip other | */
2285 prevDelim
= CMD_ONFAILURE
;
2287 prevDelim
= CMD_PIPE
;
2290 curCopyTo
[(*curLen
)++] = *curPos
;
2294 case '"': inQuotes
= !inQuotes
;
2295 curCopyTo
[(*curLen
)++] = *curPos
;
2296 lastWasRedirect
= FALSE
;
2299 case '(': /* If a '(' is the first non whitespace in a command portion
2300 ie start of line or just after &&, then we read until an
2301 unquoted ) is found */
2302 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2303 ", for(%d, In:%d, Do:%d)"
2304 ", if(%d, else:%d, lwe:%d)\n",
2307 inFor
, lastWasIn
, lastWasDo
,
2308 inIf
, inElse
, lastWasElse
);
2309 lastWasRedirect
= FALSE
;
2311 /* Ignore open brackets inside the for set */
2312 if (*curLen
== 0 && !inIn
) {
2315 /* If in quotes, ignore brackets */
2316 } else if (inQuotes
) {
2317 curCopyTo
[(*curLen
)++] = *curPos
;
2319 /* In a FOR loop, an unquoted '(' may occur straight after
2321 In an IF statement just handle it regardless as we don't
2323 In an ELSE statement, only allow it straight away after
2324 the ELSE and whitespace
2327 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2328 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2330 /* If entering into an 'IN', set inIn */
2331 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2332 WINE_TRACE("Inside an IN\n");
2336 /* Add the current command */
2337 WCMD_addCommand(curString
, &curStringLen
,
2338 curRedirs
, &curRedirsLen
,
2339 &curCopyTo
, &curLen
,
2340 prevDelim
, curDepth
,
2341 &lastEntry
, output
);
2345 curCopyTo
[(*curLen
)++] = *curPos
;
2349 case '&': if (!inQuotes
) {
2350 lastWasRedirect
= FALSE
;
2352 /* Add an entry to the command list */
2353 if (curStringLen
> 0) {
2355 /* Add the current command */
2356 WCMD_addCommand(curString
, &curStringLen
,
2357 curRedirs
, &curRedirsLen
,
2358 &curCopyTo
, &curLen
,
2359 prevDelim
, curDepth
,
2360 &lastEntry
, output
);
2364 if (*(curPos
+1) == '&') {
2365 curPos
++; /* Skip other & */
2366 prevDelim
= CMD_ONSUCCESS
;
2368 prevDelim
= CMD_NONE
;
2371 curCopyTo
[(*curLen
)++] = *curPos
;
2375 case ')': if (!inQuotes
&& curDepth
> 0) {
2376 lastWasRedirect
= FALSE
;
2378 /* Add the current command if there is one */
2381 /* Add the current command */
2382 WCMD_addCommand(curString
, &curStringLen
,
2383 curRedirs
, &curRedirsLen
,
2384 &curCopyTo
, &curLen
,
2385 prevDelim
, curDepth
,
2386 &lastEntry
, output
);
2389 /* Add an empty entry to the command list */
2390 prevDelim
= CMD_NONE
;
2391 WCMD_addCommand(NULL
, &curStringLen
,
2392 curRedirs
, &curRedirsLen
,
2393 &curCopyTo
, &curLen
,
2394 prevDelim
, curDepth
,
2395 &lastEntry
, output
);
2398 /* Leave inIn if necessary */
2399 if (inIn
) inIn
= FALSE
;
2401 curCopyTo
[(*curLen
)++] = *curPos
;
2405 lastWasRedirect
= FALSE
;
2406 curCopyTo
[(*curLen
)++] = *curPos
;
2411 /* At various times we need to know if we have only skipped whitespace,
2412 so reset this variable and then it will remain true until a non
2413 whitespace is found */
2414 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2416 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2417 if (!lastWasWhiteSpace
) {
2418 lastWasIn
= lastWasDo
= FALSE
;
2421 /* If we have reached the end, add this command into the list */
2422 if (*curPos
== 0x00 && *curLen
> 0) {
2424 /* Add an entry to the command list */
2425 WCMD_addCommand(curString
, &curStringLen
,
2426 curRedirs
, &curRedirsLen
,
2427 &curCopyTo
, &curLen
,
2428 prevDelim
, curDepth
,
2429 &lastEntry
, output
);
2432 /* If we have reached the end of the string, see if bracketing outstanding */
2433 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2435 prevDelim
= CMD_NONE
;
2437 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2439 /* Read more, skipping any blank lines */
2440 while (*extraSpace
== 0x00) {
2441 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2442 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2444 curPos
= extraSpace
;
2445 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2449 /* Dump out the parsed output */
2450 WCMD_DumpCommands(*output
);
2455 /***************************************************************************
2456 * WCMD_process_commands
2458 * Process all the commands read in so far
2460 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2461 WCHAR
*var
, WCHAR
*val
) {
2465 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2467 /* Loop through the commands, processing them one by one */
2470 CMD_LIST
*origCmd
= thisCmd
;
2472 /* If processing one bracket only, and we find the end bracket
2473 entry (or less), return */
2474 if (oneBracket
&& !thisCmd
->command
&&
2475 bdepth
<= thisCmd
->bracketDepth
) {
2476 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2477 thisCmd
, thisCmd
->nextcommand
);
2478 return thisCmd
->nextcommand
;
2481 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2482 about them and it will be handled in there)
2483 Also, skip over any batch labels (eg. :fred) */
2484 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2485 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2486 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2489 /* Step on unless the command itself already stepped on */
2490 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2495 /***************************************************************************
2496 * WCMD_free_commands
2498 * Frees the storage held for a parsed command line
2499 * - This is not done in the process_commands, as eventually the current
2500 * pointer will be modified within the commands, and hence a single free
2501 * routine is simpler
2503 void WCMD_free_commands(CMD_LIST
*cmds
) {
2505 /* Loop through the commands, freeing them one by one */
2507 CMD_LIST
*thisCmd
= cmds
;
2508 cmds
= cmds
->nextcommand
;
2509 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2510 HeapFree(GetProcessHeap(), 0, thisCmd
);