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
);
425 /* Parse the command string, without reading any more input */
426 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
427 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
428 WCMD_free_commands(toExecute
);
430 HeapFree(GetProcessHeap(), 0, cmd
);
434 * If there is an AUTOEXEC.BAT file, try to execute it.
437 GetFullPathName (autoexec
, sizeof(string
)/sizeof(WCHAR
), string
, NULL
);
438 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
439 if (h
!= INVALID_HANDLE_VALUE
) {
442 WCMD_batch (autoexec
, autoexec
, 0, NULL
, INVALID_HANDLE_VALUE
);
447 * Loop forever getting commands and executing them.
453 /* Read until EOF (which for std input is never, but if redirect
454 in place, may occur */
456 if (WCMD_ReadAndParseLine(NULL
, &toExecute
,
457 GetStdHandle(STD_INPUT_HANDLE
)) == NULL
)
459 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
460 WCMD_free_commands(toExecute
);
466 /*****************************************************************************
467 * Expand the command. Native expands lines from batch programs as they are
468 * read in and not again, except for 'for' variable substitution.
469 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
471 void handleExpansion(WCHAR
*cmd
, BOOL justFors
, WCHAR
*forVariable
, WCHAR
*forValue
) {
473 /* For commands in a context (batch program): */
474 /* Expand environment variables in a batch file %{0-9} first */
475 /* including support for any ~ modifiers */
477 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
478 /* names allowing environment variable overrides */
479 /* NOTE: To support the %PATH:xxx% syntax, also perform */
480 /* manual expansion of environment variables here */
486 while ((p
= strchrW(p
, '%'))) {
488 WINE_TRACE("Translate command:%s %d (at: %s)\n",
489 wine_dbgstr_w(cmd
), justFors
, wine_dbgstr_w(p
));
492 /* Don't touch %% unless its in Batch */
493 if (!justFors
&& *(p
+1) == '%') {
495 s
= WCMD_strdupW(p
+1);
501 /* Replace %~ modifications if in batch program */
502 } else if (*(p
+1) == '~') {
503 WCMD_HandleTildaModifiers(&p
, forVariable
, forValue
, justFors
);
506 /* Replace use of %0...%9 if in batch program*/
507 } else if (!justFors
&& context
&& (i
>= 0) && (i
<= 9)) {
508 s
= WCMD_strdupW(p
+2);
509 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
514 /* Replace use of %* if in batch program*/
515 } else if (!justFors
&& context
&& *(p
+1)=='*') {
516 WCHAR
*startOfParms
= NULL
;
517 s
= WCMD_strdupW(p
+2);
518 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
519 if (startOfParms
!= NULL
) strcpyW (p
, startOfParms
);
524 } else if (forVariable
&&
525 (CompareString (LOCALE_USER_DEFAULT
,
528 strlenW(forVariable
),
529 forVariable
, -1) == 2)) {
530 s
= WCMD_strdupW(p
+ strlenW(forVariable
));
531 strcpyW(p
, forValue
);
535 } else if (!justFors
) {
536 p
= WCMD_expand_envvar(p
, forVariable
, forValue
);
538 /* In a FOR loop, see if this is the variable to replace */
539 } else { /* Ignore %'s on second pass of batch program */
548 /*****************************************************************************
549 * Process one command. If the command is EXIT this routine does not return.
550 * We will recurse through here executing batch files.
554 void WCMD_execute (WCHAR
*command
, WCHAR
*redirects
,
555 WCHAR
*forVariable
, WCHAR
*forValue
,
558 WCHAR
*cmd
, *p
, *redir
;
560 DWORD count
, creationDisposition
;
563 SECURITY_ATTRIBUTES sa
;
565 HANDLE old_stdhandles
[3] = {INVALID_HANDLE_VALUE
,
566 INVALID_HANDLE_VALUE
,
567 INVALID_HANDLE_VALUE
};
568 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
572 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
573 wine_dbgstr_w(command
), cmdList
,
574 wine_dbgstr_w(forVariable
), wine_dbgstr_w(forValue
));
576 /* Move copy of the command onto the heap so it can be expanded */
577 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
578 strcpyW(new_cmd
, command
);
580 /* Expand variables in command line mode only (batch mode will
581 be expanded as the line is read in, except for 'for' loops) */
582 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
585 /* Show prompt before batch line IF echo is on and in batch program */
586 if (context
&& echo_mode
&& (cmd
[0] != '@')) {
588 WCMD_output_asis ( cmd
);
589 WCMD_output_asis ( newline
);
593 * Changing default drive has to be handled as a special case.
596 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
600 /* According to MSDN CreateProcess docs, special env vars record
601 the current directory on each drive, in the form =C:
602 so see if one specified, and if so go back to it */
603 strcpyW(envvar
, equalsW
);
604 strcatW(envvar
, cmd
);
605 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
606 static const WCHAR fmt
[] = {'%','s','\\','\0'};
607 wsprintf(cmd
, fmt
, cmd
);
609 status
= SetCurrentDirectory (cmd
);
610 if (!status
) WCMD_print_error ();
611 HeapFree( GetProcessHeap(), 0, cmd
);
615 sa
.nLength
= sizeof(sa
);
616 sa
.lpSecurityDescriptor
= NULL
;
617 sa
.bInheritHandle
= TRUE
;
620 * Redirect stdin, stdout and/or stderr if required.
623 if ((p
= strchrW(redirects
,'<')) != NULL
) {
624 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
625 FILE_ATTRIBUTE_NORMAL
, NULL
);
626 if (h
== INVALID_HANDLE_VALUE
) {
628 HeapFree( GetProcessHeap(), 0, cmd
);
631 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
632 SetStdHandle (STD_INPUT_HANDLE
, h
);
635 /* Scan the whole command looking for > and 2> */
637 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
648 creationDisposition
= OPEN_ALWAYS
;
652 creationDisposition
= CREATE_ALWAYS
;
655 /* Add support for 2>&1 */
658 int idx
= *(p
+1) - '0';
660 if (DuplicateHandle(GetCurrentProcess(),
661 GetStdHandle(idx_stdhandles
[idx
]),
664 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
665 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
667 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
670 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
671 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
672 FILE_ATTRIBUTE_NORMAL
, NULL
);
673 if (h
== INVALID_HANDLE_VALUE
) {
675 HeapFree( GetProcessHeap(), 0, cmd
);
678 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
679 INVALID_SET_FILE_POINTER
) {
682 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
685 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
686 SetStdHandle (idx_stdhandles
[handle
], h
);
690 * Strip leading whitespaces, and a '@' if supplied
692 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
693 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
694 if (whichcmd
[0] == '@') whichcmd
++;
697 * Check if the command entered is internal. If it is, pass the rest of the
698 * line down to the command. If not try to run a program.
702 while (IsCharAlphaNumeric(whichcmd
[count
])) {
705 for (i
=0; i
<=WCMD_EXIT
; i
++) {
706 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
707 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
709 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
710 WCMD_parse (p
, quals
, param1
, param2
);
711 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
716 WCMD_setshow_attrib ();
723 WCMD_setshow_default (p
);
726 WCMD_clear_screen ();
735 WCMD_setshow_date ();
739 WCMD_delete (p
, TRUE
);
745 WCMD_echo(&whichcmd
[count
]);
748 WCMD_for (p
, cmdList
);
757 WCMD_if (p
, cmdList
);
770 WCMD_setshow_path (p
);
776 WCMD_setshow_prompt ();
795 WCMD_setshow_env (p
);
801 WCMD_setshow_time ();
804 if (strlenW(&whichcmd
[count
]) > 0)
805 WCMD_title(&whichcmd
[count
+1]);
832 WCMD_assoc(p
, FALSE
);
841 WCMD_run_program (whichcmd
, 0);
843 HeapFree( GetProcessHeap(), 0, cmd
);
845 /* Restore old handles */
846 for (i
=0; i
<3; i
++) {
847 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
848 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
849 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
854 static void init_msvcrt_io_block(STARTUPINFO
* st
)
857 /* fetch the parent MSVCRT info block if any, so that the child can use the
858 * same handles as its grand-father
860 st_p
.cb
= sizeof(STARTUPINFO
);
861 GetStartupInfo(&st_p
);
862 st
->cbReserved2
= st_p
.cbReserved2
;
863 st
->lpReserved2
= st_p
.lpReserved2
;
864 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
866 /* Override the entries for fd 0,1,2 if we happened
867 * to change those std handles (this depends on the way wcmd sets
868 * it's new input & output handles)
870 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
871 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
874 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
875 char* flags
= (char*)(ptr
+ sizeof(unsigned));
876 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
878 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
879 st
->cbReserved2
= sz
;
880 st
->lpReserved2
= ptr
;
882 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
883 if (num
<= 0 || (flags
[0] & WX_OPEN
))
885 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
888 if (num
<= 1 || (flags
[1] & WX_OPEN
))
890 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
893 if (num
<= 2 || (flags
[2] & WX_OPEN
))
895 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
903 /******************************************************************************
906 * Execute a command line as an external program. Must allow recursion.
909 * Manual testing under windows shows PATHEXT plays a key part in this,
910 * and the search algorithm and precedence appears to be as follows.
913 * If directory supplied on command, just use that directory
914 * If extension supplied on command, look for that explicit name first
915 * Otherwise, search in each directory on the path
917 * If extension supplied on command, look for that explicit name first
918 * Then look for supplied name .* (even if extension supplied, so
919 * 'garbage.exe' will match 'garbage.exe.cmd')
920 * If any found, cycle through PATHEXT looking for name.exe one by one
922 * Once a match has been found, it is launched - Code currently uses
923 * findexecutable to achieve this which is left untouched.
926 void WCMD_run_program (WCHAR
*command
, int called
) {
928 WCHAR temp
[MAX_PATH
];
929 WCHAR pathtosearch
[MAXSTRING
];
931 WCHAR stemofsearch
[MAX_PATH
];
933 WCHAR pathext
[MAXSTRING
];
934 BOOL extensionsupplied
= FALSE
;
935 BOOL launched
= FALSE
;
937 BOOL assumeInternal
= FALSE
;
939 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
940 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
941 static const WCHAR delims
[] = {'/','\\',':','\0'};
943 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
944 if (!(*param1
) && !(*param2
))
947 /* Calculate the search path and stem to search for */
948 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
949 static const WCHAR curDir
[] = {'.',';','\0'};
950 strcpyW(pathtosearch
, curDir
);
951 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
952 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
953 static const WCHAR curDir
[] = {'.','\0'};
954 strcpyW (pathtosearch
, curDir
);
956 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
957 strcpyW(stemofsearch
, param1
);
961 /* Convert eg. ..\fred to include a directory by removing file part */
962 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
963 lastSlash
= strrchrW(pathtosearch
, '\\');
964 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
965 if (lastSlash
) *lastSlash
= 0x00;
966 strcpyW(stemofsearch
, lastSlash
+1);
969 /* Now extract PATHEXT */
970 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
971 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
972 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
975 '.','e','x','e','\0'};
976 strcpyW (pathext
, dfltPathExt
);
979 /* Loop through the search path, dir by dir */
980 pathposn
= pathtosearch
;
981 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
982 wine_dbgstr_w(stemofsearch
));
983 while (!launched
&& pathposn
) {
985 WCHAR thisDir
[MAX_PATH
] = {'\0'};
988 const WCHAR slashW
[] = {'\\','\0'};
990 /* Work on the first directory on the search path */
991 pos
= strchrW(pathposn
, ';');
993 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
994 thisDir
[(pos
-pathposn
)] = 0x00;
998 strcpyW(thisDir
, pathposn
);
1002 /* Since you can have eg. ..\.. on the path, need to expand
1003 to full information */
1004 strcpyW(temp
, thisDir
);
1005 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
1007 /* 1. If extension supplied, see if that file exists */
1008 strcatW(thisDir
, slashW
);
1009 strcatW(thisDir
, stemofsearch
);
1010 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1012 /* 1. If extension supplied, see if that file exists */
1013 if (extensionsupplied
) {
1014 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1019 /* 2. Any .* matches? */
1022 WIN32_FIND_DATA finddata
;
1023 static const WCHAR allFiles
[] = {'.','*','\0'};
1025 strcatW(thisDir
,allFiles
);
1026 h
= FindFirstFile(thisDir
, &finddata
);
1028 if (h
!= INVALID_HANDLE_VALUE
) {
1030 WCHAR
*thisExt
= pathext
;
1032 /* 3. Yes - Try each path ext */
1034 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1037 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1038 pos
[(nextExt
-thisExt
)] = 0x00;
1039 thisExt
= nextExt
+1;
1041 strcpyW(pos
, thisExt
);
1045 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1053 /* Internal programs won't be picked up by this search, so even
1054 though not found, try one last createprocess and wait for it
1056 Note: Ideally we could tell between a console app (wait) and a
1057 windows app, but the API's for it fail in this case */
1058 if (!found
&& pathposn
== NULL
) {
1059 WINE_TRACE("ASSUMING INTERNAL\n");
1060 assumeInternal
= TRUE
;
1062 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1065 /* Once found, launch it */
1066 if (found
|| assumeInternal
) {
1068 PROCESS_INFORMATION pe
;
1072 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1073 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1074 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1078 /* Special case BAT and CMD */
1079 if (ext
&& !strcmpiW(ext
, batExt
)) {
1080 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1082 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1083 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1087 /* thisDir contains the file to be launched, but with what?
1088 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1089 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1090 if ((INT_PTR
)hinst
< 32)
1093 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1095 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1096 st
.cb
= sizeof(STARTUPINFO
);
1097 init_msvcrt_io_block(&st
);
1099 /* Launch the process and if a CUI wait on it to complete
1100 Note: Launching internal wine processes cannot specify a full path to exe */
1101 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1102 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1103 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1104 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1105 /* strip first and last quote WCHARacters and try again */
1106 WCMD_opt_s_strip_quotes(command
);
1108 WCMD_run_program(command
, called
);
1112 WCMD_print_error ();
1113 /* If a command fails to launch, it sets errorlevel 9009 - which
1114 does not seem to have any associated constant definition */
1118 if (!assumeInternal
&& !console
) errorlevel
= 0;
1121 /* Always wait when called in a batch program context */
1122 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1123 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1124 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1126 CloseHandle(pe
.hProcess
);
1127 CloseHandle(pe
.hThread
);
1133 /* Not found anywhere - give up */
1134 SetLastError(ERROR_FILE_NOT_FOUND
);
1135 WCMD_print_error ();
1137 /* If a command fails to launch, it sets errorlevel 9009 - which
1138 does not seem to have any associated constant definition */
1144 /******************************************************************************
1147 * Display the prompt on STDout
1151 void WCMD_show_prompt (void) {
1154 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1157 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1159 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1160 sizeof(prompt_string
)/sizeof(WCHAR
));
1161 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1162 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1163 strcpyW (prompt_string
, dfltPrompt
);
1168 while (*p
!= '\0') {
1175 switch (toupper(*p
)) {
1189 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1208 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1214 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1216 strcatW (q
, curdir
);
1227 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1231 strcatW (q
, version_string
);
1238 if (pushd_directories
) {
1239 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1240 q
= q
+ pushd_directories
->u
.stackdepth
;
1248 WCMD_output_asis (out_string
);
1251 /****************************************************************************
1254 * Print the message for GetLastError
1257 void WCMD_print_error (void) {
1262 error_code
= GetLastError ();
1263 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1264 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1266 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1267 error_code
, GetLastError());
1271 WCMD_output_asis_len(lpMsgBuf
, lstrlen(lpMsgBuf
),
1272 GetStdHandle(STD_ERROR_HANDLE
));
1273 LocalFree ((HLOCAL
)lpMsgBuf
);
1274 WCMD_output_asis_len (newline
, lstrlen(newline
),
1275 GetStdHandle(STD_ERROR_HANDLE
));
1279 /*******************************************************************
1280 * WCMD_parse - parse a command into parameters and qualifiers.
1282 * On exit, all qualifiers are concatenated into q, the first string
1283 * not beginning with "/" is in p1 and the
1284 * second in p2. Any subsequent non-qualifier strings are lost.
1285 * Parameters in quotes are handled.
1288 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1292 *q
= *p1
= *p2
= '\0';
1297 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1298 *q
++ = toupperW (*s
++);
1308 while ((*s
!= '\0') && (*s
!= '"')) {
1309 if (p
== 0) *p1
++ = *s
++;
1310 else if (p
== 1) *p2
++ = *s
++;
1313 if (p
== 0) *p1
= '\0';
1314 if (p
== 1) *p2
= '\0';
1321 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
1322 && (*s
!= '=') && (*s
!= ',') ) {
1323 if (p
== 0) *p1
++ = *s
++;
1324 else if (p
== 1) *p2
++ = *s
++;
1327 /* Skip concurrent parms */
1328 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
1330 if (p
== 0) *p1
= '\0';
1331 if (p
== 1) *p2
= '\0';
1337 /*******************************************************************
1338 * WCMD_output_asis_len - send output to current standard output
1340 * Output a formatted unicode string. Ideally this will go to the console
1341 * and hence required WriteConsoleW to output it, however if file i/o is
1342 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1344 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
1349 /* If nothing to write, return (MORE does this sometimes) */
1352 /* Try to write as unicode assuming it is to a console */
1353 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
1355 /* If writing to console fails, assume its file
1356 i/o so convert to OEM codepage and output */
1358 BOOL usedDefaultChar
= FALSE
;
1359 DWORD convertedChars
;
1361 if (!unicodePipes
) {
1363 * Allocate buffer to use when writing to file. (Not freed, as one off)
1365 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1366 MAX_WRITECONSOLE_SIZE
);
1368 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1372 /* Convert to OEM, then output */
1373 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1374 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1375 "?", &usedDefaultChar
);
1376 WriteFile(device
, output_bufA
, convertedChars
,
1379 WriteFile(device
, message
, len
*sizeof(WCHAR
),
1386 /*******************************************************************
1387 * WCMD_output - send output to current standard output device.
1391 void WCMD_output (const WCHAR
*format
, ...) {
1397 va_start(ap
,format
);
1398 ret
= wvsprintf (string
, format
, ap
);
1399 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1400 WINE_ERR("Output truncated in WCMD_output\n" );
1401 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1405 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
1409 static int line_count
;
1410 static int max_height
;
1411 static int max_width
;
1412 static BOOL paged_mode
;
1413 static int numChars
;
1415 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1417 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1419 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1420 max_height
= consoleInfo
.dwSize
.Y
;
1421 max_width
= consoleInfo
.dwSize
.X
;
1429 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1432 void WCMD_leave_paged_mode(void)
1435 pagedMessage
= NULL
;
1438 /*******************************************************************
1439 * WCMD_output_asis - send output to current standard output device.
1440 * without formatting eg. when message contains '%'
1443 void WCMD_output_asis (const WCHAR
*message
) {
1451 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1455 if (*ptr
== '\n') ptr
++;
1456 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
),
1457 GetStdHandle(STD_OUTPUT_HANDLE
));
1460 if (++line_count
>= max_height
- 1) {
1462 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
),
1463 GetStdHandle(STD_OUTPUT_HANDLE
));
1464 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1465 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1468 } while (((message
= ptr
) != NULL
) && (*ptr
));
1470 WCMD_output_asis_len(message
, lstrlen(message
),
1471 GetStdHandle(STD_OUTPUT_HANDLE
));
1476 /***************************************************************************
1477 * WCMD_strtrim_leading_spaces
1479 * Remove leading spaces from a string. Return a pointer to the first
1480 * non-space character. Does not modify the input string
1483 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1488 while (*ptr
== ' ') ptr
++;
1492 /*************************************************************************
1493 * WCMD_strtrim_trailing_spaces
1495 * Remove trailing spaces from a string. This routine modifies the input
1496 * string by placing a null after the last non-space WCHARacter
1499 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1503 ptr
= string
+ strlenW (string
) - 1;
1504 while ((*ptr
== ' ') && (ptr
>= string
)) {
1510 /*************************************************************************
1511 * WCMD_opt_s_strip_quotes
1513 * Remove first and last quote WCHARacters, preserving all other text
1516 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1517 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1518 while((*dest
=*src
) != '\0') {
1525 while ((*dest
++=*lastq
++) != 0)
1530 /*************************************************************************
1533 * Handle pipes within a command - the DOS way using temporary files.
1536 void WCMD_pipe (CMD_LIST
**cmdEntry
, WCHAR
*var
, WCHAR
*val
) {
1539 WCHAR
*command
= (*cmdEntry
)->command
;
1540 WCHAR temp_path
[MAX_PATH
], temp_file
[MAX_PATH
], temp_file2
[MAX_PATH
], temp_cmd
[1024];
1541 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1542 static const WCHAR redirIn
[] = {'%','s',' ','<',' ','%','s','\0'};
1543 static const WCHAR redirBoth
[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1544 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1547 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1548 GetTempFileName (temp_path
, cmdW
, 0, temp_file
);
1549 p
= strchrW(command
, '|');
1551 wsprintf (temp_cmd
, redirOut
, command
, temp_file
);
1552 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1554 while ((p
= strchrW(command
, '|'))) {
1556 GetTempFileName (temp_path
, cmdW
, 0, temp_file2
);
1557 wsprintf (temp_cmd
, redirBoth
, command
, temp_file
, temp_file2
);
1558 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1559 DeleteFile (temp_file
);
1560 strcpyW (temp_file
, temp_file2
);
1563 wsprintf (temp_cmd
, redirIn
, command
, temp_file
);
1564 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1565 DeleteFile (temp_file
);
1568 /*************************************************************************
1569 * WCMD_expand_envvar
1571 * Expands environment variables, allowing for WCHARacter substitution
1573 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forVar
, WCHAR
*forVal
) {
1574 WCHAR
*endOfVar
= NULL
, *s
;
1575 WCHAR
*colonpos
= NULL
;
1576 WCHAR thisVar
[MAXSTRING
];
1577 WCHAR thisVarContents
[MAXSTRING
];
1578 WCHAR savedchar
= 0x00;
1581 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1582 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1583 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1584 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1585 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1586 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1587 static const WCHAR Cd
[] = {'C','D','\0'};
1588 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1589 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1590 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1591 static const WCHAR Delims
[] = {'%',' ',':','\0'};
1593 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
1594 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
1596 /* Find the end of the environment variable, and extract name */
1597 endOfVar
= strpbrkW(start
+1, Delims
);
1599 if (endOfVar
== NULL
|| *endOfVar
==' ') {
1601 /* In batch program, missing terminator for % and no following
1602 ':' just removes the '%' */
1604 s
= WCMD_strdupW(start
+ 1);
1610 /* In command processing, just ignore it - allows command line
1611 syntax like: for %i in (a.a) do echo %i */
1616 /* If ':' found, process remaining up until '%' (or stop at ':' if
1618 if (*endOfVar
==':') {
1619 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
1620 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
1623 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1624 thisVar
[(endOfVar
- start
)+1] = 0x00;
1625 colonpos
= strchrW(thisVar
+1, ':');
1627 /* If there's complex substitution, just need %var% for now
1628 to get the expanded data to play with */
1631 savedchar
= *(colonpos
+1);
1632 *(colonpos
+1) = 0x00;
1635 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1637 /* Expand to contents, if unchanged, return */
1638 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1639 /* override if existing env var called that name */
1640 if ((CompareString (LOCALE_USER_DEFAULT
,
1641 NORM_IGNORECASE
| SORT_STRINGSORT
,
1642 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1643 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1644 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1645 static const WCHAR fmt
[] = {'%','d','\0'};
1646 wsprintf(thisVarContents
, fmt
, errorlevel
);
1647 len
= strlenW(thisVarContents
);
1649 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1650 NORM_IGNORECASE
| SORT_STRINGSORT
,
1651 thisVar
, 6, DateP
, -1) == 2) &&
1652 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1653 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1655 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1656 NULL
, thisVarContents
, MAXSTRING
);
1657 len
= strlenW(thisVarContents
);
1659 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1660 NORM_IGNORECASE
| SORT_STRINGSORT
,
1661 thisVar
, 6, TimeP
, -1) == 2) &&
1662 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1663 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1664 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1665 NULL
, thisVarContents
, MAXSTRING
);
1666 len
= strlenW(thisVarContents
);
1668 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1669 NORM_IGNORECASE
| SORT_STRINGSORT
,
1670 thisVar
, 4, CdP
, -1) == 2) &&
1671 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1672 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1673 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1674 len
= strlenW(thisVarContents
);
1676 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1677 NORM_IGNORECASE
| SORT_STRINGSORT
,
1678 thisVar
, 8, RandomP
, -1) == 2) &&
1679 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1680 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1681 static const WCHAR fmt
[] = {'%','d','\0'};
1682 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1683 len
= strlenW(thisVarContents
);
1685 /* Look for a matching 'for' variable */
1686 } else if (forVar
&&
1687 (CompareString (LOCALE_USER_DEFAULT
,
1690 (colonpos
- thisVar
) - 1,
1691 forVar
, -1) == 2)) {
1692 strcpyW(thisVarContents
, forVal
);
1693 len
= strlenW(thisVarContents
);
1697 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1698 sizeof(thisVarContents
)/sizeof(WCHAR
));
1704 /* In a batch program, unknown env vars are replaced with nothing,
1705 note syntax %garbage:1,3% results in anything after the ':'
1707 From the command line, you just get back what you entered */
1708 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1710 /* Restore the complex part after the compare */
1713 *(colonpos
+1) = savedchar
;
1716 /* Command line - just ignore this */
1717 if (context
== NULL
) return endOfVar
+1;
1719 s
= WCMD_strdupW(endOfVar
+ 1);
1721 /* Batch - replace unknown env var with nothing */
1722 if (colonpos
== NULL
) {
1726 len
= strlenW(thisVar
);
1727 thisVar
[len
-1] = 0x00;
1728 /* If %:...% supplied, : is retained */
1729 if (colonpos
== thisVar
+1) {
1730 strcpyW (start
, colonpos
);
1732 strcpyW (start
, colonpos
+1);
1741 /* See if we need to do complex substitution (any ':'s), if not
1742 then our work here is done */
1743 if (colonpos
== NULL
) {
1744 s
= WCMD_strdupW(endOfVar
+ 1);
1745 strcpyW (start
, thisVarContents
);
1751 /* Restore complex bit */
1753 *(colonpos
+1) = savedchar
;
1756 Handle complex substitutions:
1757 xxx=yyy (replace xxx with yyy)
1758 *xxx=yyy (replace up to and including xxx with yyy)
1759 ~x (from x WCHARs in)
1760 ~-x (from x WCHARs from the end)
1761 ~x,y (from x WCHARs in for y WCHARacters)
1762 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1765 /* ~ is substring manipulation */
1766 if (savedchar
== '~') {
1768 int substrposition
, substrlength
= 0;
1769 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1772 substrposition
= atolW(colonpos
+2);
1773 if (commapos
) substrlength
= atolW(commapos
+1);
1775 s
= WCMD_strdupW(endOfVar
+ 1);
1778 if (substrposition
>= 0) {
1779 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1781 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1784 if (commapos
== NULL
) {
1785 strcpyW (start
, startCopy
); /* Copy the lot */
1786 } else if (substrlength
< 0) {
1788 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1789 if (copybytes
> len
) copybytes
= len
;
1790 else if (copybytes
< 0) copybytes
= 0;
1791 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1792 start
[copybytes
] = 0x00;
1794 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1795 start
[substrlength
] = 0x00;
1802 /* search and replace manipulation */
1804 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1805 WCHAR
*replacewith
= equalspos
+1;
1806 WCHAR
*found
= NULL
;
1810 s
= WCMD_strdupW(endOfVar
+ 1);
1811 if (equalspos
== NULL
) return start
+1;
1813 /* Null terminate both strings */
1814 thisVar
[strlenW(thisVar
)-1] = 0x00;
1817 /* Since we need to be case insensitive, copy the 2 buffers */
1818 searchIn
= WCMD_strdupW(thisVarContents
);
1819 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1820 searchFor
= WCMD_strdupW(colonpos
+1);
1821 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1824 /* Handle wildcard case */
1825 if (*(colonpos
+1) == '*') {
1826 /* Search for string to replace */
1827 found
= strstrW(searchIn
, searchFor
+1);
1830 /* Do replacement */
1831 strcpyW(start
, replacewith
);
1832 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1837 strcpyW(start
, thisVarContents
);
1842 /* Loop replacing all instances */
1843 WCHAR
*lastFound
= searchIn
;
1844 WCHAR
*outputposn
= start
;
1847 while ((found
= strstrW(lastFound
, searchFor
))) {
1848 lstrcpynW(outputposn
,
1849 thisVarContents
+ (lastFound
-searchIn
),
1850 (found
- lastFound
)+1);
1851 outputposn
= outputposn
+ (found
- lastFound
);
1852 strcatW(outputposn
, replacewith
);
1853 outputposn
= outputposn
+ strlenW(replacewith
);
1854 lastFound
= found
+ strlenW(searchFor
);
1857 thisVarContents
+ (lastFound
-searchIn
));
1858 strcatW(outputposn
, s
);
1867 /*************************************************************************
1869 * Load a string from the resource file, handling any error
1870 * Returns string retrieved from resource file
1872 WCHAR
*WCMD_LoadMessage(UINT id
) {
1873 static WCHAR msg
[2048];
1874 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1876 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1877 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1878 strcpyW(msg
, failedMsg
);
1883 /*************************************************************************
1885 * A wide version of strdup as its missing from unicode.h
1887 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1888 int len
=strlenW(input
)+1;
1889 /* Note: Use malloc not HeapAlloc to emulate strdup */
1890 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1891 memcpy(result
, input
, len
* sizeof(WCHAR
));
1895 /***************************************************************************
1898 * Read characters in from a console/file, returning result in Unicode
1899 * with signature identical to ReadFile
1901 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1902 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1906 /* Try to read from console as Unicode */
1907 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1909 /* If reading from console has failed we assume its file
1910 i/o so read in and convert from OEM codepage */
1915 * Allocate buffer to use when reading from file. Not freed
1917 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1918 MAX_WRITECONSOLE_SIZE
);
1920 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1924 /* Read from file (assume OEM codepage) */
1925 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1927 /* Convert from OEM */
1928 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
1935 /***************************************************************************
1938 * Domps out the parsed command line to ensure syntax is correct
1940 void WCMD_DumpCommands(CMD_LIST
*commands
) {
1941 WCHAR buffer
[MAXSTRING
];
1942 CMD_LIST
*thisCmd
= commands
;
1943 const WCHAR fmt
[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1944 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1947 WINE_TRACE("Parsed line:\n");
1948 while (thisCmd
!= NULL
) {
1949 sprintfW(buffer
, fmt
,
1951 thisCmd
->isAmphersand
?'Y':'N',
1952 thisCmd
->bracketDepth
,
1953 thisCmd
->nextcommand
,
1955 thisCmd
->redirects
);
1956 WINE_TRACE("%s\n", wine_dbgstr_w(buffer
));
1957 thisCmd
= thisCmd
->nextcommand
;
1961 /***************************************************************************
1964 * Adds a command to the current command list
1966 void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1967 WCHAR
*redirs
, int *redirLen
,
1968 WCHAR
**copyTo
, int **copyToLen
,
1969 BOOL isAmphersand
, int curDepth
,
1970 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1972 CMD_LIST
*thisEntry
= NULL
;
1974 /* Allocate storage for command */
1975 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
1977 /* Copy in the command */
1979 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
1980 (*commandLen
+1) * sizeof(WCHAR
));
1981 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1982 thisEntry
->command
[*commandLen
] = 0x00;
1984 /* Copy in the redirects */
1985 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
1986 (*redirLen
+1) * sizeof(WCHAR
));
1987 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1988 thisEntry
->redirects
[*redirLen
] = 0x00;
1990 /* Reset the lengths */
1993 *copyToLen
= commandLen
;
1997 thisEntry
->command
= NULL
;
2000 /* Fill in other fields */
2001 thisEntry
->nextcommand
= NULL
;
2002 thisEntry
->isAmphersand
= isAmphersand
;
2003 thisEntry
->bracketDepth
= curDepth
;
2005 (*lastEntry
)->nextcommand
= thisEntry
;
2007 *output
= thisEntry
;
2009 *lastEntry
= thisEntry
;
2012 /***************************************************************************
2013 * WCMD_ReadAndParseLine
2015 * Either uses supplied input or
2016 * Reads a file from the handle, and then...
2017 * Parse the text buffer, spliting into separate commands
2018 * - unquoted && strings split 2 commands but the 2nd is flagged as
2020 * - ( as the first character just ups the bracket depth
2021 * - unquoted ) when bracket depth > 0 terminates a bracket and
2022 * adds a CMD_LIST structure with null command
2023 * - Anything else gets put into the command string (including
2026 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
2029 BOOL inQuotes
= FALSE
;
2030 WCHAR curString
[MAXSTRING
];
2031 int curStringLen
= 0;
2032 WCHAR curRedirs
[MAXSTRING
];
2033 int curRedirsLen
= 0;
2037 CMD_LIST
*lastEntry
= NULL
;
2038 BOOL isAmphersand
= FALSE
;
2039 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
2040 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
2041 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
2042 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
2043 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
2049 BOOL onlyWhiteSpace
= FALSE
;
2050 BOOL lastWasWhiteSpace
= FALSE
;
2051 BOOL lastWasDo
= FALSE
;
2052 BOOL lastWasIn
= FALSE
;
2053 BOOL lastWasElse
= FALSE
;
2054 BOOL lastWasRedirect
= TRUE
;
2056 /* Allocate working space for a command read from keyboard, file etc */
2058 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
2060 /* If initial command read in, use that, otherwise get input from handle */
2061 if (optionalcmd
!= NULL
) {
2062 strcpyW(extraSpace
, optionalcmd
);
2063 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
2064 WINE_FIXME("No command nor handle supplied\n");
2066 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
2068 curPos
= extraSpace
;
2070 /* Handle truncated input - issue warning */
2071 if (strlenW(extraSpace
) == MAXSTRING
-1) {
2072 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
2073 WCMD_output_asis(extraSpace
);
2074 WCMD_output_asis(newline
);
2077 /* Replace env vars if in a batch context */
2078 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2080 /* Start with an empty string, copying to the command string */
2083 curCopyTo
= curString
;
2084 curLen
= &curStringLen
;
2085 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
2087 /* Parse every character on the line being processed */
2088 while (*curPos
!= 0x00) {
2093 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2094 lastWasWhiteSpace, onlyWhiteSpace);
2097 /* Certain commands need special handling */
2098 if (curStringLen
== 0 && curCopyTo
== curString
) {
2099 const WCHAR forDO
[] = {'d','o',' ','\0'};
2101 /* If command starts with 'rem', ignore any &&, ( etc */
2102 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2103 curPos
, 4, remCmd
, -1) == 2) {
2106 /* If command starts with 'for', handle ('s mid line after IN or DO */
2107 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2108 curPos
, 4, forCmd
, -1) == 2) {
2111 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2112 is only true in the command portion of the IF statement, but this
2113 should suffice for now
2114 FIXME: Silly syntax like "if 1(==1( (
2116 )" will be parsed wrong */
2117 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2118 curPos
, 3, ifCmd
, -1) == 2) {
2121 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2122 curPos
, 5, ifElse
, -1) == 2) {
2125 onlyWhiteSpace
= TRUE
;
2126 memcpy(&curCopyTo
[*curLen
], curPos
, 5*sizeof(WCHAR
));
2131 /* In a for loop, the DO command will follow a close bracket followed by
2132 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2133 is then 0, and all whitespace is skipped */
2135 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2136 curPos
, 3, forDO
, -1) == 2)) {
2137 WINE_TRACE("Found DO\n");
2139 onlyWhiteSpace
= TRUE
;
2140 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2145 } else if (curCopyTo
== curString
) {
2147 /* Special handling for the 'FOR' command */
2148 if (inFor
&& lastWasWhiteSpace
) {
2149 const WCHAR forIN
[] = {'i','n',' ','\0'};
2151 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2153 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2154 curPos
, 3, forIN
, -1) == 2) {
2155 WINE_TRACE("Found IN\n");
2157 onlyWhiteSpace
= TRUE
;
2158 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2166 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2167 so just use the default processing ie skip character specific
2169 if (!inRem
) thisChar
= *curPos
;
2170 else thisChar
= 'X'; /* Character with no special processing */
2172 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2176 case '=': /* drop through - ignore token delimiters at the start of a command */
2177 case ',': /* drop through - ignore token delimiters at the start of a command */
2178 case '\t':/* drop through - ignore token delimiters at the start of a command */
2180 /* If a redirect in place, it ends here */
2181 if (!inQuotes
&& !lastWasRedirect
) {
2183 /* If finishing off a redirect, add a whitespace delimiter */
2184 if (curCopyTo
== curRedirs
) {
2185 curCopyTo
[(*curLen
)++] = ' ';
2187 curCopyTo
= curString
;
2188 curLen
= &curStringLen
;
2191 curCopyTo
[(*curLen
)++] = *curPos
;
2194 /* Remember just processed whitespace */
2195 lastWasWhiteSpace
= TRUE
;
2199 case '>': /* drop through - handle redirect chars the same */
2201 /* Make a redirect start here */
2203 curCopyTo
= curRedirs
;
2204 curLen
= &curRedirsLen
;
2205 lastWasRedirect
= TRUE
;
2208 /* See if 1>, 2> etc, in which case we have some patching up
2210 if (curPos
!= extraSpace
&&
2211 *(curPos
-1)>='1' && *(curPos
-1)<='9') {
2214 curString
[curStringLen
] = 0x00;
2215 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2218 curCopyTo
[(*curLen
)++] = *curPos
;
2221 case '|': /* Pipe character only if not || */
2222 if (!inQuotes
&& *(curPos
++) == '|') {
2224 /* || is an alternative form of && but runs regardless */
2226 /* If finishing off a redirect, add a whitespace delimiter */
2227 if (curCopyTo
== curRedirs
) {
2228 curCopyTo
[(*curLen
)++] = ' ';
2231 /* If a redirect in place, it ends here */
2232 curCopyTo
= curString
;
2233 curLen
= &curStringLen
;
2234 curCopyTo
[(*curLen
)++] = *curPos
;
2235 lastWasRedirect
= FALSE
;
2237 } else if (inQuotes
) {
2238 curCopyTo
[(*curLen
)++] = *curPos
;
2239 lastWasRedirect
= FALSE
;
2242 /* Make a redirect start here */
2243 curCopyTo
= curRedirs
;
2244 curLen
= &curRedirsLen
;
2245 curCopyTo
[(*curLen
)++] = *curPos
;
2246 lastWasRedirect
= TRUE
;
2251 case '"': inQuotes
= !inQuotes
;
2252 curCopyTo
[(*curLen
)++] = *curPos
;
2253 lastWasRedirect
= FALSE
;
2256 case '(': /* If a '(' is the first non whitespace in a command portion
2257 ie start of line or just after &&, then we read until an
2258 unquoted ) is found */
2259 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2260 ", for(%d, In:%d, Do:%d)"
2261 ", if(%d, else:%d, lwe:%d)\n",
2264 inFor
, lastWasIn
, lastWasDo
,
2265 inIf
, inElse
, lastWasElse
);
2266 lastWasRedirect
= FALSE
;
2268 /* Ignore open brackets inside the for set */
2269 if (*curLen
== 0 && !inIn
) {
2272 /* If in quotes, ignore brackets */
2273 } else if (inQuotes
) {
2274 curCopyTo
[(*curLen
)++] = *curPos
;
2276 /* In a FOR loop, an unquoted '(' may occur straight after
2278 In an IF statement just handle it regardless as we don't
2280 In an ELSE statement, only allow it straight away after
2281 the ELSE and whitespace
2284 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2285 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2287 /* If entering into an 'IN', set inIn */
2288 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2289 WINE_TRACE("Inside an IN\n");
2293 /* Add the current command */
2294 WCMD_addCommand(curString
, &curStringLen
,
2295 curRedirs
, &curRedirsLen
,
2296 &curCopyTo
, &curLen
,
2297 isAmphersand
, curDepth
,
2298 &lastEntry
, output
);
2302 curCopyTo
[(*curLen
)++] = *curPos
;
2306 case '&': if (!inQuotes
&& *(curPos
+1) == '&') {
2307 curPos
++; /* Skip other & */
2308 lastWasRedirect
= FALSE
;
2310 /* Add an entry to the command list */
2311 if (curStringLen
> 0) {
2313 /* Add the current command */
2314 WCMD_addCommand(curString
, &curStringLen
,
2315 curRedirs
, &curRedirsLen
,
2316 &curCopyTo
, &curLen
,
2317 isAmphersand
, curDepth
,
2318 &lastEntry
, output
);
2321 isAmphersand
= TRUE
;
2323 curCopyTo
[(*curLen
)++] = *curPos
;
2327 case ')': if (!inQuotes
&& curDepth
> 0) {
2328 lastWasRedirect
= FALSE
;
2330 /* Add the current command if there is one */
2333 /* Add the current command */
2334 WCMD_addCommand(curString
, &curStringLen
,
2335 curRedirs
, &curRedirsLen
,
2336 &curCopyTo
, &curLen
,
2337 isAmphersand
, curDepth
,
2338 &lastEntry
, output
);
2341 /* Add an empty entry to the command list */
2342 isAmphersand
= FALSE
;
2343 WCMD_addCommand(NULL
, &curStringLen
,
2344 curRedirs
, &curRedirsLen
,
2345 &curCopyTo
, &curLen
,
2346 isAmphersand
, curDepth
,
2347 &lastEntry
, output
);
2350 /* Leave inIn if necessary */
2351 if (inIn
) inIn
= FALSE
;
2353 curCopyTo
[(*curLen
)++] = *curPos
;
2357 lastWasRedirect
= FALSE
;
2358 curCopyTo
[(*curLen
)++] = *curPos
;
2363 /* At various times we need to know if we have only skipped whitespace,
2364 so reset this variable and then it will remain true until a non
2365 whitespace is found */
2366 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2368 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2369 if (!lastWasWhiteSpace
) {
2370 lastWasIn
= lastWasDo
= FALSE
;
2373 /* If we have reached the end, add this command into the list */
2374 if (*curPos
== 0x00 && *curLen
> 0) {
2376 /* Add an entry to the command list */
2377 WCMD_addCommand(curString
, &curStringLen
,
2378 curRedirs
, &curRedirsLen
,
2379 &curCopyTo
, &curLen
,
2380 isAmphersand
, curDepth
,
2381 &lastEntry
, output
);
2384 /* If we have reached the end of the string, see if bracketing outstanding */
2385 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2387 isAmphersand
= FALSE
;
2389 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2391 /* Read more, skipping any blank lines */
2392 while (*extraSpace
== 0x00) {
2393 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2394 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2396 curPos
= extraSpace
;
2397 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2401 /* Dump out the parsed output */
2402 WCMD_DumpCommands(*output
);
2407 /***************************************************************************
2408 * WCMD_process_commands
2410 * Process all the commands read in so far
2412 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2413 WCHAR
*var
, WCHAR
*val
) {
2417 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2419 /* Loop through the commands, processing them one by one */
2422 CMD_LIST
*origCmd
= thisCmd
;
2424 /* If processing one bracket only, and we find the end bracket
2425 entry (or less), return */
2426 if (oneBracket
&& !thisCmd
->command
&&
2427 bdepth
<= thisCmd
->bracketDepth
) {
2428 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2429 thisCmd
, thisCmd
->nextcommand
);
2430 return thisCmd
->nextcommand
;
2433 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2434 about them and it will be handled in there)
2435 Also, skip over any batch labels (eg. :fred) */
2436 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2438 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2440 if (strchrW(thisCmd
->redirects
,'|') != NULL
) {
2441 WCMD_pipe (&thisCmd
, var
, val
);
2443 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2447 /* Step on unless the command itself already stepped on */
2448 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2453 /***************************************************************************
2454 * WCMD_free_commands
2456 * Frees the storage held for a parsed command line
2457 * - This is not done in the process_commands, as eventually the current
2458 * pointer will be modified within the commands, and hence a single free
2459 * routine is simpler
2461 void WCMD_free_commands(CMD_LIST
*cmds
) {
2463 /* Loop through the commands, freeing them one by one */
2465 CMD_LIST
*thisCmd
= cmds
;
2466 cmds
= cmds
->nextcommand
;
2467 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2468 HeapFree(GetProcessHeap(), 0, thisCmd
);