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
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
34 const WCHAR inbuilt
[][10] = {
35 {'A','T','T','R','I','B','\0'},
36 {'C','A','L','L','\0'},
38 {'C','H','D','I','R','\0'},
40 {'C','O','P','Y','\0'},
41 {'C','T','T','Y','\0'},
42 {'D','A','T','E','\0'},
45 {'E','C','H','O','\0'},
46 {'E','R','A','S','E','\0'},
48 {'G','O','T','O','\0'},
49 {'H','E','L','P','\0'},
51 {'L','A','B','E','L','\0'},
53 {'M','K','D','I','R','\0'},
54 {'M','O','V','E','\0'},
55 {'P','A','T','H','\0'},
56 {'P','A','U','S','E','\0'},
57 {'P','R','O','M','P','T','\0'},
60 {'R','E','N','A','M','E','\0'},
62 {'R','M','D','I','R','\0'},
64 {'S','H','I','F','T','\0'},
65 {'T','I','M','E','\0'},
66 {'T','I','T','L','E','\0'},
67 {'T','Y','P','E','\0'},
68 {'V','E','R','I','F','Y','\0'},
71 {'E','N','D','L','O','C','A','L','\0'},
72 {'S','E','T','L','O','C','A','L','\0'},
73 {'P','U','S','H','D','\0'},
74 {'P','O','P','D','\0'},
75 {'A','S','S','O','C','\0'},
76 {'C','O','L','O','R','\0'},
77 {'F','T','Y','P','E','\0'},
78 {'M','O','R','E','\0'},
79 {'E','X','I','T','\0'}
84 int echo_mode
= 1, verify_mode
= 0, defaultColor
= 7;
85 static int opt_c
, opt_k
, opt_s
;
86 const WCHAR newline
[] = {'\n','\0'};
87 static const WCHAR equalsW
[] = {'=','\0'};
88 static const WCHAR closeBW
[] = {')','\0'};
90 WCHAR version_string
[100];
91 WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
92 BATCH_CONTEXT
*context
= NULL
;
93 extern struct env_stack
*pushd_directories
;
94 static const WCHAR
*pagedMessage
= NULL
;
95 static char *output_bufA
= NULL
;
96 #define MAX_WRITECONSOLE_SIZE 65535
97 BOOL unicodePipes
= FALSE
;
99 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forvar
, WCHAR
*forVal
);
100 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
);
102 /*****************************************************************************
103 * Main entry point. This is a console application so we have a main() not a
107 int wmain (int argc
, WCHAR
*argvW
[])
116 static const WCHAR autoexec
[] = {'\\','a','u','t','o','e','x','e','c','.',
118 char ansiVersion
[100];
119 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
121 /* Pre initialize some messages */
122 strcpy(ansiVersion
, PACKAGE_VERSION
);
123 MultiByteToWideChar(CP_ACP
, 0, ansiVersion
, -1, string
, 1024);
124 wsprintf(version_string
, WCMD_LoadMessage(WCMD_VERSION
), string
);
125 strcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
128 opt_c
=opt_k
=opt_q
=opt_s
=0;
132 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW
));
133 if ((*argvW
)[0]!='/' || (*argvW
)[1]=='\0') {
140 if (tolowerW(c
)=='c') {
142 } else if (tolowerW(c
)=='q') {
144 } else if (tolowerW(c
)=='k') {
146 } else if (tolowerW(c
)=='s') {
148 } else if (tolowerW(c
)=='a') {
150 } else if (tolowerW(c
)=='u') {
152 } else if (tolowerW(c
)=='t' && (*argvW
)[2]==':') {
153 opt_t
=strtoulW(&(*argvW
)[3], NULL
, 16);
154 } else if (tolowerW(c
)=='x' || tolowerW(c
)=='y') {
155 /* Ignored for compatibility with Windows */
158 if ((*argvW
)[2]==0) {
162 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
167 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
172 const WCHAR eoff
[] = {'O','F','F','\0'};
176 if (opt_c
|| opt_k
) {
182 /* opt_s left unflagged if the command starts with and contains exactly
183 * one quoted string (exactly two quote characters). The quoted string
184 * must be an executable name that has whitespace and must not have the
185 * following characters: &<>()@^| */
187 /* Build the command to execute */
191 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
193 int has_space
,bcount
;
199 if( !*a
) has_space
=1;
204 if (*a
==' ' || *a
=='\t') {
206 } else if (*a
=='"') {
207 /* doubling of '\' preceding a '"',
208 * plus escaping of said '"'
217 len
+=(a
-*arg
) + 1; /* for the separating space */
220 len
+=2; /* for the quotes */
228 /* check argvW[0] for a space and invalid characters */
233 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
234 || *p
=='@' || *p
=='^' || *p
=='|') {
244 cmd
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
250 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
252 int has_space
,has_quote
;
255 /* Check for quotes and spaces in this argument */
256 has_space
=has_quote
=0;
258 if( !*a
) has_space
=1;
260 if (*a
==' ' || *a
=='\t') {
264 } else if (*a
=='"') {
272 /* Now transfer it to the command line */
289 /* Double all the '\\' preceding this '"', plus one */
290 for (i
=0;i
<=bcount
;i
++)
309 p
--; /* remove last space */
312 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
314 /* strip first and last quote characters if opt_s; check for invalid
315 * executable is done later */
316 if (opt_s
&& *cmd
=='\"')
317 WCMD_opt_s_strip_quotes(cmd
);
321 /* If we do a "wcmd /c command", we don't want to allocate a new
322 * console since the command returns immediately. Rather, we use
323 * the currently allocated input and output handles. This allows
324 * us to pipe to and read from the command interpreter.
327 /* Parse the command string, without reading any more input */
328 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
329 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
330 WCMD_free_commands(toExecute
);
333 HeapFree(GetProcessHeap(), 0, cmd
);
337 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
338 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
339 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE
));
341 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
343 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
344 defaultColor
= opt_t
& 0xFF;
349 /* Check HKCU\Software\Microsoft\Command Processor
350 Then HKLM\Software\Microsoft\Command Processor
351 for defaultcolour value
352 Note Can be supplied as DWORD or REG_SZ
353 Note2 When supplied as REG_SZ it's in decimal!!! */
356 DWORD value
=0, size
=4;
357 static const WCHAR regKeyW
[] = {'S','o','f','t','w','a','r','e','\\',
358 'M','i','c','r','o','s','o','f','t','\\',
359 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
360 static const WCHAR dfltColorW
[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
362 if (RegOpenKeyEx(HKEY_CURRENT_USER
, regKeyW
,
363 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
366 /* See if DWORD or REG_SZ */
367 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
368 NULL
, NULL
) == ERROR_SUCCESS
) {
369 if (type
== REG_DWORD
) {
370 size
= sizeof(DWORD
);
371 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
372 (LPBYTE
)&value
, &size
);
373 } else if (type
== REG_SZ
) {
374 size
= sizeof(strvalue
)/sizeof(WCHAR
);
375 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
376 (LPBYTE
)strvalue
, &size
);
377 value
= strtoulW(strvalue
, NULL
, 10);
382 if (value
== 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE
, regKeyW
,
383 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
386 /* See if DWORD or REG_SZ */
387 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
388 NULL
, NULL
) == ERROR_SUCCESS
) {
389 if (type
== REG_DWORD
) {
390 size
= sizeof(DWORD
);
391 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
392 (LPBYTE
)&value
, &size
);
393 } else if (type
== REG_SZ
) {
394 size
= sizeof(strvalue
)/sizeof(WCHAR
);
395 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
396 (LPBYTE
)strvalue
, &size
);
397 value
= strtoulW(strvalue
, NULL
, 10);
402 /* If one found, set the screen to that colour */
403 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
404 defaultColor
= value
& 0xFF;
411 /* Save cwd into appropriate env var */
412 GetCurrentDirectory(1024, string
);
413 if (IsCharAlpha(string
[0]) && string
[1] == ':') {
414 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
415 wsprintf(envvar
, fmt
, string
[0]);
416 SetEnvironmentVariable(envvar
, string
);
420 /* Parse the command string, without reading any more input */
421 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
422 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
423 WCMD_free_commands(toExecute
);
425 HeapFree(GetProcessHeap(), 0, cmd
);
429 * If there is an AUTOEXEC.BAT file, try to execute it.
432 GetFullPathName (autoexec
, sizeof(string
)/sizeof(WCHAR
), string
, NULL
);
433 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
434 if (h
!= INVALID_HANDLE_VALUE
) {
437 WCMD_batch (autoexec
, autoexec
, 0, NULL
, INVALID_HANDLE_VALUE
);
442 * Loop forever getting commands and executing them.
448 /* Read until EOF (which for std input is never, but if redirect
449 in place, may occur */
451 if (WCMD_ReadAndParseLine(NULL
, &toExecute
,
452 GetStdHandle(STD_INPUT_HANDLE
)) == NULL
)
454 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
455 WCMD_free_commands(toExecute
);
461 /*****************************************************************************
462 * Expand the command. Native expands lines from batch programs as they are
463 * read in and not again, except for 'for' variable substitution.
464 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
466 void handleExpansion(WCHAR
*cmd
, BOOL justFors
, WCHAR
*forVariable
, WCHAR
*forValue
) {
468 /* For commands in a context (batch program): */
469 /* Expand environment variables in a batch file %{0-9} first */
470 /* including support for any ~ modifiers */
472 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
473 /* names allowing environment variable overrides */
474 /* NOTE: To support the %PATH:xxx% syntax, also perform */
475 /* manual expansion of environment variables here */
481 while ((p
= strchrW(p
, '%'))) {
483 WINE_TRACE("Translate command:%s %d (at: %s)\n",
484 wine_dbgstr_w(cmd
), justFors
, wine_dbgstr_w(p
));
487 /* Don't touch %% unless its in Batch */
488 if (!justFors
&& *(p
+1) == '%') {
490 s
= WCMD_strdupW(p
+1);
496 /* Replace %~ modifications if in batch program */
497 } else if (*(p
+1) == '~') {
498 WCMD_HandleTildaModifiers(&p
, forVariable
, forValue
, justFors
);
501 /* Replace use of %0...%9 if in batch program*/
502 } else if (!justFors
&& context
&& (i
>= 0) && (i
<= 9)) {
503 s
= WCMD_strdupW(p
+2);
504 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
509 /* Replace use of %* if in batch program*/
510 } else if (!justFors
&& context
&& *(p
+1)=='*') {
511 WCHAR
*startOfParms
= NULL
;
512 s
= WCMD_strdupW(p
+2);
513 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
514 if (startOfParms
!= NULL
) strcpyW (p
, startOfParms
);
519 } else if (forVariable
&&
520 (CompareString (LOCALE_USER_DEFAULT
,
523 strlenW(forVariable
),
524 forVariable
, -1) == 2)) {
525 s
= WCMD_strdupW(p
+ strlenW(forVariable
));
526 strcpyW(p
, forValue
);
530 } else if (!justFors
) {
531 p
= WCMD_expand_envvar(p
, forVariable
, forValue
);
533 /* In a FOR loop, see if this is the variable to replace */
534 } else { /* Ignore %'s on second pass of batch program */
543 /*****************************************************************************
544 * Process one command. If the command is EXIT this routine does not return.
545 * We will recurse through here executing batch files.
549 void WCMD_execute (WCHAR
*command
, WCHAR
*redirects
,
550 WCHAR
*forVariable
, WCHAR
*forValue
,
553 WCHAR
*cmd
, *p
, *redir
;
555 DWORD count
, creationDisposition
;
558 SECURITY_ATTRIBUTES sa
;
560 HANDLE old_stdhandles
[3] = {INVALID_HANDLE_VALUE
,
561 INVALID_HANDLE_VALUE
,
562 INVALID_HANDLE_VALUE
};
563 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
567 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
568 wine_dbgstr_w(command
), cmdList
,
569 wine_dbgstr_w(forVariable
), wine_dbgstr_w(forValue
));
571 /* Move copy of the command onto the heap so it can be expanded */
572 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
573 strcpyW(new_cmd
, command
);
575 /* Expand variables in command line mode only (batch mode will
576 be expanded as the line is read in, except for 'for' loops) */
577 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
580 /* Show prompt before batch line IF echo is on and in batch program */
581 if (context
&& echo_mode
&& (cmd
[0] != '@')) {
583 WCMD_output_asis ( cmd
);
584 WCMD_output_asis ( newline
);
588 * Changing default drive has to be handled as a special case.
591 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
595 /* According to MSDN CreateProcess docs, special env vars record
596 the current directory on each drive, in the form =C:
597 so see if one specified, and if so go back to it */
598 strcpyW(envvar
, equalsW
);
599 strcatW(envvar
, cmd
);
600 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
601 static const WCHAR fmt
[] = {'%','s','\\','\0'};
602 wsprintf(cmd
, fmt
, cmd
);
604 status
= SetCurrentDirectory (cmd
);
605 if (!status
) WCMD_print_error ();
606 HeapFree( GetProcessHeap(), 0, cmd
);
610 sa
.nLength
= sizeof(sa
);
611 sa
.lpSecurityDescriptor
= NULL
;
612 sa
.bInheritHandle
= TRUE
;
615 * Redirect stdin, stdout and/or stderr if required.
618 if ((p
= strchrW(redirects
,'<')) != NULL
) {
619 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
620 FILE_ATTRIBUTE_NORMAL
, NULL
);
621 if (h
== INVALID_HANDLE_VALUE
) {
623 HeapFree( GetProcessHeap(), 0, cmd
);
626 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
627 SetStdHandle (STD_INPUT_HANDLE
, h
);
630 /* Scan the whole command looking for > and 2> */
632 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
643 creationDisposition
= OPEN_ALWAYS
;
647 creationDisposition
= CREATE_ALWAYS
;
650 /* Add support for 2>&1 */
653 int idx
= *(p
+1) - '0';
655 if (DuplicateHandle(GetCurrentProcess(),
656 GetStdHandle(idx_stdhandles
[idx
]),
659 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
660 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
662 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
665 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
666 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
667 FILE_ATTRIBUTE_NORMAL
, NULL
);
668 if (h
== INVALID_HANDLE_VALUE
) {
670 HeapFree( GetProcessHeap(), 0, cmd
);
673 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
674 INVALID_SET_FILE_POINTER
) {
677 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
680 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
681 SetStdHandle (idx_stdhandles
[handle
], h
);
685 * Strip leading whitespaces, and a '@' if supplied
687 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
688 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
689 if (whichcmd
[0] == '@') whichcmd
++;
692 * Check if the command entered is internal. If it is, pass the rest of the
693 * line down to the command. If not try to run a program.
697 while (IsCharAlphaNumeric(whichcmd
[count
])) {
700 for (i
=0; i
<=WCMD_EXIT
; i
++) {
701 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
702 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
704 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
705 WCMD_parse (p
, quals
, param1
, param2
);
706 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
711 WCMD_setshow_attrib ();
718 WCMD_setshow_default (p
);
721 WCMD_clear_screen ();
730 WCMD_setshow_date ();
734 WCMD_delete (p
, TRUE
);
740 WCMD_echo(&whichcmd
[count
]);
743 WCMD_for (p
, cmdList
);
752 WCMD_if (p
, cmdList
);
765 WCMD_setshow_path (p
);
771 WCMD_setshow_prompt ();
790 WCMD_setshow_env (p
);
796 WCMD_setshow_time ();
799 if (strlenW(&whichcmd
[count
]) > 0)
800 WCMD_title(&whichcmd
[count
+1]);
827 WCMD_assoc(p
, FALSE
);
836 WCMD_run_program (whichcmd
, 0);
838 HeapFree( GetProcessHeap(), 0, cmd
);
840 /* Restore old handles */
841 for (i
=0; i
<3; i
++) {
842 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
843 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
844 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
849 static void init_msvcrt_io_block(STARTUPINFO
* st
)
852 /* fetch the parent MSVCRT info block if any, so that the child can use the
853 * same handles as its grand-father
855 st_p
.cb
= sizeof(STARTUPINFO
);
856 GetStartupInfo(&st_p
);
857 st
->cbReserved2
= st_p
.cbReserved2
;
858 st
->lpReserved2
= st_p
.lpReserved2
;
859 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
861 /* Override the entries for fd 0,1,2 if we happened
862 * to change those std handles (this depends on the way wcmd sets
863 * it's new input & output handles)
865 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
866 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
869 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
870 char* flags
= (char*)(ptr
+ sizeof(unsigned));
871 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
873 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
874 st
->cbReserved2
= sz
;
875 st
->lpReserved2
= ptr
;
877 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
878 if (num
<= 0 || (flags
[0] & WX_OPEN
))
880 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
883 if (num
<= 1 || (flags
[1] & WX_OPEN
))
885 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
888 if (num
<= 2 || (flags
[2] & WX_OPEN
))
890 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
898 /******************************************************************************
901 * Execute a command line as an external program. Must allow recursion.
904 * Manual testing under windows shows PATHEXT plays a key part in this,
905 * and the search algorithm and precedence appears to be as follows.
908 * If directory supplied on command, just use that directory
909 * If extension supplied on command, look for that explicit name first
910 * Otherwise, search in each directory on the path
912 * If extension supplied on command, look for that explicit name first
913 * Then look for supplied name .* (even if extension supplied, so
914 * 'garbage.exe' will match 'garbage.exe.cmd')
915 * If any found, cycle through PATHEXT looking for name.exe one by one
917 * Once a match has been found, it is launched - Code currently uses
918 * findexecutable to acheive this which is left untouched.
921 void WCMD_run_program (WCHAR
*command
, int called
) {
923 WCHAR temp
[MAX_PATH
];
924 WCHAR pathtosearch
[MAXSTRING
];
926 WCHAR stemofsearch
[MAX_PATH
];
928 WCHAR pathext
[MAXSTRING
];
929 BOOL extensionsupplied
= FALSE
;
930 BOOL launched
= FALSE
;
932 BOOL assumeInternal
= FALSE
;
934 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
935 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
936 static const WCHAR delims
[] = {'/','\\',':','\0'};
938 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
939 if (!(*param1
) && !(*param2
))
942 /* Calculate the search path and stem to search for */
943 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
944 static const WCHAR curDir
[] = {'.',';','\0'};
945 strcpyW(pathtosearch
, curDir
);
946 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
947 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
948 static const WCHAR curDir
[] = {'.','\0'};
949 strcpyW (pathtosearch
, curDir
);
951 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
952 strcpyW(stemofsearch
, param1
);
956 /* Convert eg. ..\fred to include a directory by removing file part */
957 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
958 lastSlash
= strrchrW(pathtosearch
, '\\');
959 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
960 if (lastSlash
) *lastSlash
= 0x00;
961 strcpyW(stemofsearch
, lastSlash
+1);
964 /* Now extract PATHEXT */
965 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
966 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
967 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
970 '.','e','x','e','\0'};
971 strcpyW (pathext
, dfltPathExt
);
974 /* Loop through the search path, dir by dir */
975 pathposn
= pathtosearch
;
976 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
977 wine_dbgstr_w(stemofsearch
));
978 while (!launched
&& pathposn
) {
980 WCHAR thisDir
[MAX_PATH
] = {'\0'};
983 const WCHAR slashW
[] = {'\\','\0'};
985 /* Work on the first directory on the search path */
986 pos
= strchrW(pathposn
, ';');
988 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
989 thisDir
[(pos
-pathposn
)] = 0x00;
993 strcpyW(thisDir
, pathposn
);
997 /* Since you can have eg. ..\.. on the path, need to expand
998 to full information */
999 strcpyW(temp
, thisDir
);
1000 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
1002 /* 1. If extension supplied, see if that file exists */
1003 strcatW(thisDir
, slashW
);
1004 strcatW(thisDir
, stemofsearch
);
1005 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1007 /* 1. If extension supplied, see if that file exists */
1008 if (extensionsupplied
) {
1009 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1014 /* 2. Any .* matches? */
1017 WIN32_FIND_DATA finddata
;
1018 static const WCHAR allFiles
[] = {'.','*','\0'};
1020 strcatW(thisDir
,allFiles
);
1021 h
= FindFirstFile(thisDir
, &finddata
);
1023 if (h
!= INVALID_HANDLE_VALUE
) {
1025 WCHAR
*thisExt
= pathext
;
1027 /* 3. Yes - Try each path ext */
1029 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1032 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1033 pos
[(nextExt
-thisExt
)] = 0x00;
1034 thisExt
= nextExt
+1;
1036 strcpyW(pos
, thisExt
);
1040 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1048 /* Internal programs won't be picked up by this search, so even
1049 though not found, try one last createprocess and wait for it
1051 Note: Ideally we could tell between a console app (wait) and a
1052 windows app, but the API's for it fail in this case */
1053 if (!found
&& pathposn
== NULL
) {
1054 WINE_TRACE("ASSUMING INTERNAL\n");
1055 assumeInternal
= TRUE
;
1057 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1060 /* Once found, launch it */
1061 if (found
|| assumeInternal
) {
1063 PROCESS_INFORMATION pe
;
1067 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1068 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1069 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1073 /* Special case BAT and CMD */
1074 if (ext
&& !strcmpiW(ext
, batExt
)) {
1075 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1077 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1078 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1082 /* thisDir contains the file to be launched, but with what?
1083 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1084 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1085 if ((INT_PTR
)hinst
< 32)
1088 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1090 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1091 st
.cb
= sizeof(STARTUPINFO
);
1092 init_msvcrt_io_block(&st
);
1094 /* Launch the process and if a CUI wait on it to complete
1095 Note: Launching internal wine processes cannot specify a full path to exe */
1096 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1097 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1098 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1099 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1100 /* strip first and last quote WCHARacters and try again */
1101 WCMD_opt_s_strip_quotes(command
);
1103 WCMD_run_program(command
, called
);
1107 WCMD_print_error ();
1108 /* If a command fails to launch, it sets errorlevel 9009 - which
1109 does not seem to have any associated constant definition */
1113 if (!assumeInternal
&& !console
) errorlevel
= 0;
1116 /* Always wait when called in a batch program context */
1117 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1118 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1119 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1121 CloseHandle(pe
.hProcess
);
1122 CloseHandle(pe
.hThread
);
1128 /* Not found anywhere - give up */
1129 SetLastError(ERROR_FILE_NOT_FOUND
);
1130 WCMD_print_error ();
1132 /* If a command fails to launch, it sets errorlevel 9009 - which
1133 does not seem to have any associated constant definition */
1139 /******************************************************************************
1142 * Display the prompt on STDout
1146 void WCMD_show_prompt (void) {
1149 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1152 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1154 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1155 sizeof(prompt_string
)/sizeof(WCHAR
));
1156 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1157 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1158 strcpyW (prompt_string
, dfltPrompt
);
1163 while (*p
!= '\0') {
1170 switch (toupper(*p
)) {
1184 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1203 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1209 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1211 strcatW (q
, curdir
);
1222 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1226 strcatW (q
, version_string
);
1233 if (pushd_directories
) {
1234 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1235 q
= q
+ pushd_directories
->u
.stackdepth
;
1243 WCMD_output_asis (out_string
);
1246 /****************************************************************************
1249 * Print the message for GetLastError
1252 void WCMD_print_error (void) {
1257 error_code
= GetLastError ();
1258 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1259 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1261 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1262 error_code
, GetLastError());
1266 WCMD_output_asis_len(lpMsgBuf
, lstrlen(lpMsgBuf
),
1267 GetStdHandle(STD_ERROR_HANDLE
));
1268 LocalFree ((HLOCAL
)lpMsgBuf
);
1269 WCMD_output_asis_len (newline
, lstrlen(newline
),
1270 GetStdHandle(STD_ERROR_HANDLE
));
1274 /*******************************************************************
1275 * WCMD_parse - parse a command into parameters and qualifiers.
1277 * On exit, all qualifiers are concatenated into q, the first string
1278 * not beginning with "/" is in p1 and the
1279 * second in p2. Any subsequent non-qualifier strings are lost.
1280 * Parameters in quotes are handled.
1283 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1287 *q
= *p1
= *p2
= '\0';
1292 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1293 *q
++ = toupperW (*s
++);
1303 while ((*s
!= '\0') && (*s
!= '"')) {
1304 if (p
== 0) *p1
++ = *s
++;
1305 else if (p
== 1) *p2
++ = *s
++;
1308 if (p
== 0) *p1
= '\0';
1309 if (p
== 1) *p2
= '\0';
1316 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
1317 && (*s
!= '=') && (*s
!= ',') ) {
1318 if (p
== 0) *p1
++ = *s
++;
1319 else if (p
== 1) *p2
++ = *s
++;
1322 /* Skip concurrent parms */
1323 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
1325 if (p
== 0) *p1
= '\0';
1326 if (p
== 1) *p2
= '\0';
1332 /*******************************************************************
1333 * WCMD_output_asis_len - send output to current standard output
1335 * Output a formatted unicode string. Ideally this will go to the console
1336 * and hence required WriteConsoleW to output it, however if file i/o is
1337 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1339 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
1344 /* If nothing to write, return (MORE does this sometimes) */
1347 /* Try to write as unicode assuming it is to a console */
1348 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
1350 /* If writing to console fails, assume its file
1351 i/o so convert to OEM codepage and output */
1353 BOOL usedDefaultChar
= FALSE
;
1354 DWORD convertedChars
;
1356 if (!unicodePipes
) {
1358 * Allocate buffer to use when writing to file. (Not freed, as one off)
1360 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1361 MAX_WRITECONSOLE_SIZE
);
1363 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1367 /* Convert to OEM, then output */
1368 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1369 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1370 "?", &usedDefaultChar
);
1371 WriteFile(device
, output_bufA
, convertedChars
,
1374 WriteFile(device
, message
, len
*sizeof(WCHAR
),
1381 /*******************************************************************
1382 * WCMD_output - send output to current standard output device.
1386 void WCMD_output (const WCHAR
*format
, ...) {
1392 va_start(ap
,format
);
1393 ret
= wvsprintf (string
, format
, ap
);
1394 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1395 WINE_ERR("Output truncated in WCMD_output\n" );
1396 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1400 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
1404 static int line_count
;
1405 static int max_height
;
1406 static int max_width
;
1407 static BOOL paged_mode
;
1408 static int numChars
;
1410 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1412 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1414 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1415 max_height
= consoleInfo
.dwSize
.Y
;
1416 max_width
= consoleInfo
.dwSize
.X
;
1424 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1427 void WCMD_leave_paged_mode(void)
1430 pagedMessage
= NULL
;
1433 /*******************************************************************
1434 * WCMD_output_asis - send output to current standard output device.
1435 * without formatting eg. when message contains '%'
1438 void WCMD_output_asis (const WCHAR
*message
) {
1446 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1450 if (*ptr
== '\n') ptr
++;
1451 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
),
1452 GetStdHandle(STD_OUTPUT_HANDLE
));
1455 if (++line_count
>= max_height
- 1) {
1457 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
),
1458 GetStdHandle(STD_OUTPUT_HANDLE
));
1459 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1460 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1463 } while (((message
= ptr
) != NULL
) && (*ptr
));
1465 WCMD_output_asis_len(message
, lstrlen(message
),
1466 GetStdHandle(STD_OUTPUT_HANDLE
));
1471 /***************************************************************************
1472 * WCMD_strtrim_leading_spaces
1474 * Remove leading spaces from a string. Return a pointer to the first
1475 * non-space character. Does not modify the input string
1478 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1483 while (*ptr
== ' ') ptr
++;
1487 /*************************************************************************
1488 * WCMD_strtrim_trailing_spaces
1490 * Remove trailing spaces from a string. This routine modifies the input
1491 * string by placing a null after the last non-space WCHARacter
1494 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1498 ptr
= string
+ strlenW (string
) - 1;
1499 while ((*ptr
== ' ') && (ptr
>= string
)) {
1505 /*************************************************************************
1506 * WCMD_opt_s_strip_quotes
1508 * Remove first and last quote WCHARacters, preserving all other text
1511 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1512 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1513 while((*dest
=*src
) != '\0') {
1520 while ((*dest
++=*lastq
++) != 0)
1525 /*************************************************************************
1528 * Handle pipes within a command - the DOS way using temporary files.
1531 void WCMD_pipe (CMD_LIST
**cmdEntry
, WCHAR
*var
, WCHAR
*val
) {
1534 WCHAR
*command
= (*cmdEntry
)->command
;
1535 WCHAR temp_path
[MAX_PATH
], temp_file
[MAX_PATH
], temp_file2
[MAX_PATH
], temp_cmd
[1024];
1536 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1537 static const WCHAR redirIn
[] = {'%','s',' ','<',' ','%','s','\0'};
1538 static const WCHAR redirBoth
[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1539 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1542 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1543 GetTempFileName (temp_path
, cmdW
, 0, temp_file
);
1544 p
= strchrW(command
, '|');
1546 wsprintf (temp_cmd
, redirOut
, command
, temp_file
);
1547 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1549 while ((p
= strchrW(command
, '|'))) {
1551 GetTempFileName (temp_path
, cmdW
, 0, temp_file2
);
1552 wsprintf (temp_cmd
, redirBoth
, command
, temp_file
, temp_file2
);
1553 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1554 DeleteFile (temp_file
);
1555 strcpyW (temp_file
, temp_file2
);
1558 wsprintf (temp_cmd
, redirIn
, command
, temp_file
);
1559 WCMD_execute (temp_cmd
, (*cmdEntry
)->redirects
, var
, val
, cmdEntry
);
1560 DeleteFile (temp_file
);
1563 /*************************************************************************
1564 * WCMD_expand_envvar
1566 * Expands environment variables, allowing for WCHARacter substitution
1568 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forVar
, WCHAR
*forVal
) {
1569 WCHAR
*endOfVar
= NULL
, *s
;
1570 WCHAR
*colonpos
= NULL
;
1571 WCHAR thisVar
[MAXSTRING
];
1572 WCHAR thisVarContents
[MAXSTRING
];
1573 WCHAR savedchar
= 0x00;
1576 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1577 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1578 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1579 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1580 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1581 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1582 static const WCHAR Cd
[] = {'C','D','\0'};
1583 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1584 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1585 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1586 static const WCHAR Delims
[] = {'%',' ',':','\0'};
1588 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
1589 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
1591 /* Find the end of the environment variable, and extract name */
1592 endOfVar
= strpbrkW(start
+1, Delims
);
1594 if (endOfVar
== NULL
|| *endOfVar
==' ') {
1596 /* In batch program, missing terminator for % and no following
1597 ':' just removes the '%' */
1599 s
= WCMD_strdupW(start
+ 1);
1605 /* In command processing, just ignore it - allows command line
1606 syntax like: for %i in (a.a) do echo %i */
1611 /* If ':' found, process remaining up until '%' (or stop at ':' if
1613 if (*endOfVar
==':') {
1614 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
1615 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
1618 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1619 thisVar
[(endOfVar
- start
)+1] = 0x00;
1620 colonpos
= strchrW(thisVar
+1, ':');
1622 /* If there's complex substitution, just need %var% for now
1623 to get the expanded data to play with */
1626 savedchar
= *(colonpos
+1);
1627 *(colonpos
+1) = 0x00;
1630 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1632 /* Expand to contents, if unchanged, return */
1633 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1634 /* override if existing env var called that name */
1635 if ((CompareString (LOCALE_USER_DEFAULT
,
1636 NORM_IGNORECASE
| SORT_STRINGSORT
,
1637 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1638 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1639 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1640 static const WCHAR fmt
[] = {'%','d','\0'};
1641 wsprintf(thisVarContents
, fmt
, errorlevel
);
1642 len
= strlenW(thisVarContents
);
1644 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1645 NORM_IGNORECASE
| SORT_STRINGSORT
,
1646 thisVar
, 6, DateP
, -1) == 2) &&
1647 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1648 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1650 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1651 NULL
, thisVarContents
, MAXSTRING
);
1652 len
= strlenW(thisVarContents
);
1654 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1655 NORM_IGNORECASE
| SORT_STRINGSORT
,
1656 thisVar
, 6, TimeP
, -1) == 2) &&
1657 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1658 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1659 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1660 NULL
, thisVarContents
, MAXSTRING
);
1661 len
= strlenW(thisVarContents
);
1663 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1664 NORM_IGNORECASE
| SORT_STRINGSORT
,
1665 thisVar
, 4, CdP
, -1) == 2) &&
1666 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1667 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1668 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1669 len
= strlenW(thisVarContents
);
1671 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1672 NORM_IGNORECASE
| SORT_STRINGSORT
,
1673 thisVar
, 8, RandomP
, -1) == 2) &&
1674 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1675 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1676 static const WCHAR fmt
[] = {'%','d','\0'};
1677 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1678 len
= strlenW(thisVarContents
);
1680 /* Look for a matching 'for' variable */
1681 } else if (forVar
&&
1682 (CompareString (LOCALE_USER_DEFAULT
,
1685 (colonpos
- thisVar
) - 1,
1686 forVar
, -1) == 2)) {
1687 strcpyW(thisVarContents
, forVal
);
1688 len
= strlenW(thisVarContents
);
1692 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1693 sizeof(thisVarContents
)/sizeof(WCHAR
));
1699 /* In a batch program, unknown env vars are replaced with nothing,
1700 note syntax %garbage:1,3% results in anything after the ':'
1702 From the command line, you just get back what you entered */
1703 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1705 /* Restore the complex part after the compare */
1708 *(colonpos
+1) = savedchar
;
1711 /* Command line - just ignore this */
1712 if (context
== NULL
) return endOfVar
+1;
1714 s
= WCMD_strdupW(endOfVar
+ 1);
1716 /* Batch - replace unknown env var with nothing */
1717 if (colonpos
== NULL
) {
1721 len
= strlenW(thisVar
);
1722 thisVar
[len
-1] = 0x00;
1723 /* If %:...% supplied, : is retained */
1724 if (colonpos
== thisVar
+1) {
1725 strcpyW (start
, colonpos
);
1727 strcpyW (start
, colonpos
+1);
1736 /* See if we need to do complex substitution (any ':'s), if not
1737 then our work here is done */
1738 if (colonpos
== NULL
) {
1739 s
= WCMD_strdupW(endOfVar
+ 1);
1740 strcpyW (start
, thisVarContents
);
1746 /* Restore complex bit */
1748 *(colonpos
+1) = savedchar
;
1751 Handle complex substitutions:
1752 xxx=yyy (replace xxx with yyy)
1753 *xxx=yyy (replace up to and including xxx with yyy)
1754 ~x (from x WCHARs in)
1755 ~-x (from x WCHARs from the end)
1756 ~x,y (from x WCHARs in for y WCHARacters)
1757 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1760 /* ~ is substring manipulation */
1761 if (savedchar
== '~') {
1763 int substrposition
, substrlength
= 0;
1764 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1767 substrposition
= atolW(colonpos
+2);
1768 if (commapos
) substrlength
= atolW(commapos
+1);
1770 s
= WCMD_strdupW(endOfVar
+ 1);
1773 if (substrposition
>= 0) {
1774 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1776 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1779 if (commapos
== NULL
) {
1780 strcpyW (start
, startCopy
); /* Copy the lot */
1781 } else if (substrlength
< 0) {
1783 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1784 if (copybytes
> len
) copybytes
= len
;
1785 else if (copybytes
< 0) copybytes
= 0;
1786 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1787 start
[copybytes
] = 0x00;
1789 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1790 start
[substrlength
] = 0x00;
1797 /* search and replace manipulation */
1799 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1800 WCHAR
*replacewith
= equalspos
+1;
1801 WCHAR
*found
= NULL
;
1805 s
= WCMD_strdupW(endOfVar
+ 1);
1806 if (equalspos
== NULL
) return start
+1;
1808 /* Null terminate both strings */
1809 thisVar
[strlenW(thisVar
)-1] = 0x00;
1812 /* Since we need to be case insensitive, copy the 2 buffers */
1813 searchIn
= WCMD_strdupW(thisVarContents
);
1814 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1815 searchFor
= WCMD_strdupW(colonpos
+1);
1816 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1819 /* Handle wildcard case */
1820 if (*(colonpos
+1) == '*') {
1821 /* Search for string to replace */
1822 found
= strstrW(searchIn
, searchFor
+1);
1825 /* Do replacement */
1826 strcpyW(start
, replacewith
);
1827 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1832 strcpyW(start
, thisVarContents
);
1837 /* Loop replacing all instances */
1838 WCHAR
*lastFound
= searchIn
;
1839 WCHAR
*outputposn
= start
;
1842 while ((found
= strstrW(lastFound
, searchFor
))) {
1843 lstrcpynW(outputposn
,
1844 thisVarContents
+ (lastFound
-searchIn
),
1845 (found
- lastFound
)+1);
1846 outputposn
= outputposn
+ (found
- lastFound
);
1847 strcatW(outputposn
, replacewith
);
1848 outputposn
= outputposn
+ strlenW(replacewith
);
1849 lastFound
= found
+ strlenW(searchFor
);
1852 thisVarContents
+ (lastFound
-searchIn
));
1853 strcatW(outputposn
, s
);
1862 /*************************************************************************
1864 * Load a string from the resource file, handling any error
1865 * Returns string retrieved from resource file
1867 WCHAR
*WCMD_LoadMessage(UINT id
) {
1868 static WCHAR msg
[2048];
1869 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1871 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1872 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1873 strcpyW(msg
, failedMsg
);
1878 /*************************************************************************
1880 * A wide version of strdup as its missing from unicode.h
1882 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1883 int len
=strlenW(input
)+1;
1884 /* Note: Use malloc not HeapAlloc to emulate strdup */
1885 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1886 memcpy(result
, input
, len
* sizeof(WCHAR
));
1890 /***************************************************************************
1893 * Read characters in from a console/file, returning result in Unicode
1894 * with signature identical to ReadFile
1896 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1897 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1901 /* Try to read from console as Unicode */
1902 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1904 /* If reading from console has failed we assume its file
1905 i/o so read in and convert from OEM codepage */
1910 * Allocate buffer to use when reading from file. Not freed
1912 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1913 MAX_WRITECONSOLE_SIZE
);
1915 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1919 /* Read from file (assume OEM codepage) */
1920 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1922 /* Convert from OEM */
1923 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
1930 /***************************************************************************
1933 * Domps out the parsed command line to ensure syntax is correct
1935 void WCMD_DumpCommands(CMD_LIST
*commands
) {
1936 WCHAR buffer
[MAXSTRING
];
1937 CMD_LIST
*thisCmd
= commands
;
1938 const WCHAR fmt
[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1939 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1942 WINE_TRACE("Parsed line:\n");
1943 while (thisCmd
!= NULL
) {
1944 sprintfW(buffer
, fmt
,
1946 thisCmd
->isAmphersand
?'Y':'N',
1947 thisCmd
->bracketDepth
,
1948 thisCmd
->nextcommand
,
1950 thisCmd
->redirects
);
1951 WINE_TRACE("%s\n", wine_dbgstr_w(buffer
));
1952 thisCmd
= thisCmd
->nextcommand
;
1956 /***************************************************************************
1959 * Adds a command to the current command list
1961 void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1962 WCHAR
*redirs
, int *redirLen
,
1963 WCHAR
**copyTo
, int **copyToLen
,
1964 BOOL isAmphersand
, int curDepth
,
1965 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1967 CMD_LIST
*thisEntry
= NULL
;
1969 /* Allocate storage for command */
1970 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
1972 /* Copy in the command */
1974 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
1975 (*commandLen
+1) * sizeof(WCHAR
));
1976 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1977 thisEntry
->command
[*commandLen
] = 0x00;
1979 /* Copy in the redirects */
1980 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
1981 (*redirLen
+1) * sizeof(WCHAR
));
1982 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1983 thisEntry
->redirects
[*redirLen
] = 0x00;
1985 /* Reset the lengths */
1988 *copyToLen
= commandLen
;
1992 thisEntry
->command
= NULL
;
1995 /* Fill in other fields */
1996 thisEntry
->nextcommand
= NULL
;
1997 thisEntry
->isAmphersand
= isAmphersand
;
1998 thisEntry
->bracketDepth
= curDepth
;
2000 (*lastEntry
)->nextcommand
= thisEntry
;
2002 *output
= thisEntry
;
2004 *lastEntry
= thisEntry
;
2007 /***************************************************************************
2008 * WCMD_ReadAndParseLine
2010 * Either uses supplied input or
2011 * Reads a file from the handle, and then...
2012 * Parse the text buffer, spliting into separate commands
2013 * - unquoted && strings split 2 commands but the 2nd is flagged as
2015 * - ( as the first character just ups the bracket depth
2016 * - unquoted ) when bracket depth > 0 terminates a bracket and
2017 * adds a CMD_LIST structure with null command
2018 * - Anything else gets put into the command string (including
2021 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
2024 BOOL inQuotes
= FALSE
;
2025 WCHAR curString
[MAXSTRING
];
2026 int curStringLen
= 0;
2027 WCHAR curRedirs
[MAXSTRING
];
2028 int curRedirsLen
= 0;
2032 CMD_LIST
*lastEntry
= NULL
;
2033 BOOL isAmphersand
= FALSE
;
2034 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
2035 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
2036 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
2037 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
2038 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
2044 BOOL onlyWhiteSpace
= FALSE
;
2045 BOOL lastWasWhiteSpace
= FALSE
;
2046 BOOL lastWasDo
= FALSE
;
2047 BOOL lastWasIn
= FALSE
;
2048 BOOL lastWasElse
= FALSE
;
2049 BOOL lastWasRedirect
= TRUE
;
2051 /* Allocate working space for a command read from keyboard, file etc */
2053 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
2055 /* If initial command read in, use that, otherwise get input from handle */
2056 if (optionalcmd
!= NULL
) {
2057 strcpyW(extraSpace
, optionalcmd
);
2058 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
2059 WINE_FIXME("No command nor handle supplied\n");
2061 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
2063 curPos
= extraSpace
;
2065 /* Handle truncated input - issue warning */
2066 if (strlenW(extraSpace
) == MAXSTRING
-1) {
2067 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
2068 WCMD_output_asis(extraSpace
);
2069 WCMD_output_asis(newline
);
2072 /* Replace env vars if in a batch context */
2073 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2075 /* Start with an empty string, copying to the command string */
2078 curCopyTo
= curString
;
2079 curLen
= &curStringLen
;
2080 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
2082 /* Parse every character on the line being processed */
2083 while (*curPos
!= 0x00) {
2088 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2089 lastWasWhiteSpace, onlyWhiteSpace);
2092 /* Certain commands need special handling */
2093 if (curStringLen
== 0 && curCopyTo
== curString
) {
2094 const WCHAR forDO
[] = {'d','o',' ','\0'};
2096 /* If command starts with 'rem', ignore any &&, ( etc */
2097 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2098 curPos
, 4, remCmd
, -1) == 2) {
2101 /* If command starts with 'for', handle ('s mid line after IN or DO */
2102 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2103 curPos
, 4, forCmd
, -1) == 2) {
2106 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2107 is only true in the command portion of the IF statement, but this
2108 should suffice for now
2109 FIXME: Silly syntax like "if 1(==1( (
2111 )" will be parsed wrong */
2112 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2113 curPos
, 3, ifCmd
, -1) == 2) {
2116 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2117 curPos
, 5, ifElse
, -1) == 2) {
2120 onlyWhiteSpace
= TRUE
;
2121 memcpy(&curCopyTo
[*curLen
], curPos
, 5*sizeof(WCHAR
));
2126 /* In a for loop, the DO command will follow a close bracket followed by
2127 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2128 is then 0, and all whitespace is skipped */
2130 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2131 curPos
, 3, forDO
, -1) == 2)) {
2132 WINE_TRACE("Found DO\n");
2134 onlyWhiteSpace
= TRUE
;
2135 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2140 } else if (curCopyTo
== curString
) {
2142 /* Special handling for the 'FOR' command */
2143 if (inFor
&& lastWasWhiteSpace
) {
2144 const WCHAR forIN
[] = {'i','n',' ','\0'};
2146 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2148 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2149 curPos
, 3, forIN
, -1) == 2) {
2150 WINE_TRACE("Found IN\n");
2152 onlyWhiteSpace
= TRUE
;
2153 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
2161 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2162 so just use the default processing ie skip character specific
2164 if (!inRem
) thisChar
= *curPos
;
2165 else thisChar
= 'X'; /* Character with no special processing */
2167 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2171 case '=': /* drop through - ignore token delimiters at the start of a command */
2172 case ',': /* drop through - ignore token delimiters at the start of a command */
2173 case '\t':/* drop through - ignore token delimiters at the start of a command */
2175 /* If a redirect in place, it ends here */
2176 if (!inQuotes
&& !lastWasRedirect
) {
2178 /* If finishing off a redirect, add a whitespace delimiter */
2179 if (curCopyTo
== curRedirs
) {
2180 curCopyTo
[(*curLen
)++] = ' ';
2182 curCopyTo
= curString
;
2183 curLen
= &curStringLen
;
2186 curCopyTo
[(*curLen
)++] = *curPos
;
2189 /* Remember just processed whitespace */
2190 lastWasWhiteSpace
= TRUE
;
2194 case '>': /* drop through - handle redirect chars the same */
2196 /* Make a redirect start here */
2198 curCopyTo
= curRedirs
;
2199 curLen
= &curRedirsLen
;
2200 lastWasRedirect
= TRUE
;
2203 /* See if 1>, 2> etc, in which case we have some patching up
2205 if (curPos
!= extraSpace
&&
2206 *(curPos
-1)>='1' && *(curPos
-1)<='9') {
2209 curString
[curStringLen
] = 0x00;
2210 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2213 curCopyTo
[(*curLen
)++] = *curPos
;
2216 case '|': /* Pipe character only if not || */
2217 if (!inQuotes
&& *(curPos
++) == '|') {
2219 /* || is an alternative form of && but runs regardless */
2221 /* If finishing off a redirect, add a whitespace delimiter */
2222 if (curCopyTo
== curRedirs
) {
2223 curCopyTo
[(*curLen
)++] = ' ';
2226 /* If a redirect in place, it ends here */
2227 curCopyTo
= curString
;
2228 curLen
= &curStringLen
;
2229 curCopyTo
[(*curLen
)++] = *curPos
;
2230 lastWasRedirect
= FALSE
;
2232 } else if (inQuotes
) {
2233 curCopyTo
[(*curLen
)++] = *curPos
;
2234 lastWasRedirect
= FALSE
;
2237 /* Make a redirect start here */
2238 curCopyTo
= curRedirs
;
2239 curLen
= &curRedirsLen
;
2240 curCopyTo
[(*curLen
)++] = *curPos
;
2241 lastWasRedirect
= TRUE
;
2246 case '"': inQuotes
= !inQuotes
;
2247 curCopyTo
[(*curLen
)++] = *curPos
;
2248 lastWasRedirect
= FALSE
;
2251 case '(': /* If a '(' is the first non whitespace in a command portion
2252 ie start of line or just after &&, then we read until an
2253 unquoted ) is found */
2254 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2255 ", for(%d, In:%d, Do:%d)"
2256 ", if(%d, else:%d, lwe:%d)\n",
2259 inFor
, lastWasIn
, lastWasDo
,
2260 inIf
, inElse
, lastWasElse
);
2261 lastWasRedirect
= FALSE
;
2263 /* Ignore open brackets inside the for set */
2264 if (*curLen
== 0 && !inIn
) {
2267 /* If in quotes, ignore brackets */
2268 } else if (inQuotes
) {
2269 curCopyTo
[(*curLen
)++] = *curPos
;
2271 /* In a FOR loop, an unquoted '(' may occur straight after
2273 In an IF statement just handle it regardless as we don't
2275 In an ELSE statement, only allow it straight away after
2276 the ELSE and whitespace
2279 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2280 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2282 /* If entering into an 'IN', set inIn */
2283 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2284 WINE_TRACE("Inside an IN\n");
2288 /* Add the current command */
2289 WCMD_addCommand(curString
, &curStringLen
,
2290 curRedirs
, &curRedirsLen
,
2291 &curCopyTo
, &curLen
,
2292 isAmphersand
, curDepth
,
2293 &lastEntry
, output
);
2297 curCopyTo
[(*curLen
)++] = *curPos
;
2301 case '&': if (!inQuotes
&& *(curPos
+1) == '&') {
2302 curPos
++; /* Skip other & */
2303 lastWasRedirect
= FALSE
;
2305 /* Add an entry to the command list */
2306 if (curStringLen
> 0) {
2308 /* Add the current command */
2309 WCMD_addCommand(curString
, &curStringLen
,
2310 curRedirs
, &curRedirsLen
,
2311 &curCopyTo
, &curLen
,
2312 isAmphersand
, curDepth
,
2313 &lastEntry
, output
);
2316 isAmphersand
= TRUE
;
2318 curCopyTo
[(*curLen
)++] = *curPos
;
2322 case ')': if (!inQuotes
&& curDepth
> 0) {
2323 lastWasRedirect
= FALSE
;
2325 /* Add the current command if there is one */
2328 /* Add the current command */
2329 WCMD_addCommand(curString
, &curStringLen
,
2330 curRedirs
, &curRedirsLen
,
2331 &curCopyTo
, &curLen
,
2332 isAmphersand
, curDepth
,
2333 &lastEntry
, output
);
2336 /* Add an empty entry to the command list */
2337 isAmphersand
= FALSE
;
2338 WCMD_addCommand(NULL
, &curStringLen
,
2339 curRedirs
, &curRedirsLen
,
2340 &curCopyTo
, &curLen
,
2341 isAmphersand
, curDepth
,
2342 &lastEntry
, output
);
2345 /* Leave inIn if necessary */
2346 if (inIn
) inIn
= FALSE
;
2348 curCopyTo
[(*curLen
)++] = *curPos
;
2352 lastWasRedirect
= FALSE
;
2353 curCopyTo
[(*curLen
)++] = *curPos
;
2358 /* At various times we need to know if we have only skipped whitespace,
2359 so reset this variable and then it will remain true until a non
2360 whitespace is found */
2361 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2363 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2364 if (!lastWasWhiteSpace
) {
2365 lastWasIn
= lastWasDo
= FALSE
;
2368 /* If we have reached the end, add this command into the list */
2369 if (*curPos
== 0x00 && *curLen
> 0) {
2371 /* Add an entry to the command list */
2372 WCMD_addCommand(curString
, &curStringLen
,
2373 curRedirs
, &curRedirsLen
,
2374 &curCopyTo
, &curLen
,
2375 isAmphersand
, curDepth
,
2376 &lastEntry
, output
);
2379 /* If we have reached the end of the string, see if bracketing outstanding */
2380 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2382 isAmphersand
= FALSE
;
2384 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2386 /* Read more, skipping any blank lines */
2387 while (*extraSpace
== 0x00) {
2388 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2389 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2391 curPos
= extraSpace
;
2392 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2396 /* Dump out the parsed output */
2397 WCMD_DumpCommands(*output
);
2402 /***************************************************************************
2403 * WCMD_process_commands
2405 * Process all the commands read in so far
2407 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2408 WCHAR
*var
, WCHAR
*val
) {
2412 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2414 /* Loop through the commands, processing them one by one */
2417 CMD_LIST
*origCmd
= thisCmd
;
2419 /* If processing one bracket only, and we find the end bracket
2420 entry (or less), return */
2421 if (oneBracket
&& !thisCmd
->command
&&
2422 bdepth
<= thisCmd
->bracketDepth
) {
2423 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2424 thisCmd
, thisCmd
->nextcommand
);
2425 return thisCmd
->nextcommand
;
2428 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2429 about them and it will be handled in there)
2430 Also, skip over any batch labels (eg. :fred) */
2431 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2433 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2435 if (strchrW(thisCmd
->redirects
,'|') != NULL
) {
2436 WCMD_pipe (&thisCmd
, var
, val
);
2438 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2442 /* Step on unless the command itself already stepped on */
2443 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2448 /***************************************************************************
2449 * WCMD_free_commands
2451 * Frees the storage held for a parsed command line
2452 * - This is not done in the process_commands, as eventually the current
2453 * pointer will be modified within the commands, and hence a single free
2454 * routine is simpler
2456 void WCMD_free_commands(CMD_LIST
*cmds
) {
2458 /* Loop through the commands, freeing them one by one */
2460 CMD_LIST
*thisCmd
= cmds
;
2461 cmds
= cmds
->nextcommand
;
2462 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2463 HeapFree(GetProcessHeap(), 0, thisCmd
);