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 extern const WCHAR inbuilt
[][10];
36 extern struct env_stack
*pushd_directories
;
38 BATCH_CONTEXT
*context
= NULL
;
40 WCHAR quals
[MAXSTRING
], param1
[MAXSTRING
], param2
[MAXSTRING
];
42 FOR_CONTEXT
*forloopcontext
; /* The 'for' loop context */
43 BOOL delayedsubst
= FALSE
; /* The current delayed substitution setting */
46 BOOL echo_mode
= TRUE
;
48 WCHAR anykey
[100], version_string
[100];
50 static BOOL opt_c
, opt_k
, opt_s
, unicodeOutput
= FALSE
;
52 /* Variables pertaining to paging */
53 static BOOL paged_mode
;
54 static const WCHAR
*pagedMessage
= NULL
;
55 static int line_count
;
56 static int max_height
;
60 #define MAX_WRITECONSOLE_SIZE 65535
63 * Returns a buffer for reading from/writing to file
66 static char *get_file_buffer(void)
68 static char *output_bufA
= NULL
;
70 output_bufA
= xalloc(MAX_WRITECONSOLE_SIZE
);
74 /*******************************************************************
75 * WCMD_output_asis_len - send output to current standard output
77 * Output a formatted unicode string. Ideally this will go to the console
78 * and hence required WriteConsoleW to output it, however if file i/o is
79 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
81 static void WCMD_output_asis_len(const WCHAR
*message
, DWORD len
, HANDLE device
)
86 /* If nothing to write, return (MORE does this sometimes) */
89 /* Try to write as unicode assuming it is to a console */
90 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
92 /* If writing to console fails, assume it's file
93 i/o so convert to OEM codepage and output */
95 BOOL usedDefaultChar
= FALSE
;
101 if (!(buffer
= get_file_buffer()))
104 /* Convert to OEM, then output */
105 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
106 len
, buffer
, MAX_WRITECONSOLE_SIZE
,
107 "?", &usedDefaultChar
);
108 WriteFile(device
, buffer
, convertedChars
,
111 WriteFile(device
, message
, len
*sizeof(WCHAR
),
118 /*******************************************************************
119 * WCMD_output - send output to current standard output device.
123 void WINAPIV
WCMD_output (const WCHAR
*format
, ...) {
131 len
= FormatMessageW(FORMAT_MESSAGE_FROM_STRING
|FORMAT_MESSAGE_ALLOCATE_BUFFER
,
132 format
, 0, 0, (LPWSTR
)&string
, 0, &ap
);
134 if (len
== 0 && GetLastError() != ERROR_NO_WORK_DONE
)
135 WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format
));
138 WCMD_output_asis_len(string
, len
, GetStdHandle(STD_OUTPUT_HANDLE
));
143 /*******************************************************************
144 * WCMD_output_stderr - send output to current standard error device.
148 void WINAPIV
WCMD_output_stderr (const WCHAR
*format
, ...) {
156 len
= FormatMessageW(FORMAT_MESSAGE_FROM_STRING
|FORMAT_MESSAGE_ALLOCATE_BUFFER
,
157 format
, 0, 0, (LPWSTR
)&string
, 0, &ap
);
159 if (len
== 0 && GetLastError() != ERROR_NO_WORK_DONE
)
160 WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format
));
163 WCMD_output_asis_len(string
, len
, GetStdHandle(STD_ERROR_HANDLE
));
168 /*******************************************************************
169 * WCMD_format_string - allocate a buffer and format a string
173 WCHAR
* WINAPIV
WCMD_format_string (const WCHAR
*format
, ...)
180 len
= FormatMessageW(FORMAT_MESSAGE_FROM_STRING
|FORMAT_MESSAGE_ALLOCATE_BUFFER
,
181 format
, 0, 0, (LPWSTR
)&string
, 0, &ap
);
183 if (len
== 0 && GetLastError() != ERROR_NO_WORK_DONE
) {
184 WINE_FIXME("Could not format string: le=%lu, fmt=%s\n", GetLastError(), wine_dbgstr_w(format
));
185 string
= (WCHAR
*)LocalAlloc(LMEM_FIXED
, 2);
191 void WCMD_enter_paged_mode(const WCHAR
*msg
)
193 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
195 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
196 max_height
= consoleInfo
.dwSize
.Y
;
197 max_width
= consoleInfo
.dwSize
.X
;
205 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
208 void WCMD_leave_paged_mode(void)
214 /***************************************************************************
217 * Read characters in from a console/file, returning result in Unicode
219 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
, LPDWORD charsRead
)
224 /* Try to read from console as Unicode */
225 if (ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
)) return TRUE
;
227 /* We assume it's a file handle and read then convert from assumed OEM codepage */
228 if (!(buffer
= get_file_buffer()))
231 if (!ReadFile(hIn
, buffer
, maxChars
, &numRead
, NULL
))
234 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, buffer
, numRead
, intoBuf
, maxChars
);
239 /*******************************************************************
240 * WCMD_output_asis_handle
242 * Send output to specified handle without formatting e.g. when message contains '%'
244 static void WCMD_output_asis_handle (DWORD std_handle
, const WCHAR
*message
) {
248 HANDLE handle
= GetStdHandle(std_handle
);
253 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
257 if (*ptr
== '\n') ptr
++;
258 WCMD_output_asis_len(message
, ptr
- message
, handle
);
260 if (++line_count
>= max_height
- 1) {
262 WCMD_output_asis_len(pagedMessage
, lstrlenW(pagedMessage
), handle
);
263 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, ARRAY_SIZE(string
), &count
);
265 } while (((message
= ptr
) != NULL
) && (*ptr
));
267 WCMD_output_asis_len(message
, lstrlenW(message
), handle
);
271 /*******************************************************************
274 * Send output to current standard output device, without formatting
275 * e.g. when message contains '%'
277 void WCMD_output_asis (const WCHAR
*message
) {
278 WCMD_output_asis_handle(STD_OUTPUT_HANDLE
, message
);
281 /*******************************************************************
282 * WCMD_output_asis_stderr
284 * Send output to current standard error device, without formatting
285 * e.g. when message contains '%'
287 void WCMD_output_asis_stderr (const WCHAR
*message
) {
288 WCMD_output_asis_handle(STD_ERROR_HANDLE
, message
);
291 /****************************************************************************
294 * Print the message for GetLastError
297 void WCMD_print_error (void) {
302 error_code
= GetLastError ();
303 status
= FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
304 NULL
, error_code
, 0, (LPWSTR
) &lpMsgBuf
, 0, NULL
);
306 WINE_FIXME ("Cannot display message for error %ld, status %ld\n",
307 error_code
, GetLastError());
311 WCMD_output_asis_len(lpMsgBuf
, lstrlenW(lpMsgBuf
),
312 GetStdHandle(STD_ERROR_HANDLE
));
313 LocalFree (lpMsgBuf
);
314 WCMD_output_asis_len(L
"\r\n", lstrlenW(L
"\r\n"), GetStdHandle(STD_ERROR_HANDLE
));
318 /******************************************************************************
321 * Display the prompt on STDout
325 static void WCMD_show_prompt(void)
328 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
332 len
= GetEnvironmentVariableW(L
"PROMPT", prompt_string
, ARRAY_SIZE(prompt_string
));
333 if ((len
== 0) || (len
>= ARRAY_SIZE(prompt_string
))) {
334 lstrcpyW(prompt_string
, L
"$P$G");
346 switch (towupper(*p
)) {
360 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
- (q
- out_string
));
379 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
385 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
387 lstrcatW (q
, curdir
);
398 GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
402 lstrcatW (q
, version_string
);
409 if (pushd_directories
) {
410 memset(q
, '+', pushd_directories
->u
.stackdepth
);
411 q
= q
+ pushd_directories
->u
.stackdepth
;
419 WCMD_output_asis (out_string
);
422 void *xrealloc(void *ptr
, size_t size
)
426 if (!(ret
= realloc(ptr
, size
)))
428 ERR("Out of memory\n");
435 /*************************************************************************
437 * Replaces a portion of a Unicode string with the specified string.
438 * It's up to the caller to ensure there is enough space in the
439 * destination buffer.
441 WCHAR
*WCMD_strsubstW(WCHAR
*start
, const WCHAR
*next
, const WCHAR
*insert
, int len
) {
444 len
=insert
? lstrlenW(insert
) : 0;
445 if (start
+len
!= next
)
446 memmove(start
+len
, next
, (lstrlenW(next
) + 1) * sizeof(*next
));
448 memcpy(start
, insert
, len
* sizeof(*insert
));
452 BOOL
WCMD_get_fullpath(const WCHAR
* in
, SIZE_T outsize
, WCHAR
* out
, WCHAR
** start
)
454 DWORD ret
= GetFullPathNameW(in
, outsize
, out
, start
);
455 if (!ret
) return FALSE
;
458 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_FILENAMETOOLONG
));
464 /***************************************************************************
465 * WCMD_skip_leading_spaces
467 * Return a pointer to the first non-whitespace character of string.
468 * Does not modify the input string.
470 WCHAR
*WCMD_skip_leading_spaces (WCHAR
*string
) {
475 while (*ptr
== ' ' || *ptr
== '\t') ptr
++;
479 /***************************************************************************
480 * WCMD_keyword_ws_found
482 * Checks if the string located at ptr matches a keyword (of length len)
483 * followed by a whitespace character (space or tab)
485 BOOL
WCMD_keyword_ws_found(const WCHAR
*keyword
, const WCHAR
*ptr
) {
486 const int len
= lstrlenW(keyword
);
487 return (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
488 ptr
, len
, keyword
, len
) == CSTR_EQUAL
)
489 && ((*(ptr
+ len
) == ' ') || (*(ptr
+ len
) == '\t'));
492 /*************************************************************************
495 * Remove first and last quote WCHARacters, preserving all other text
496 * Returns the location of the final quote
498 WCHAR
*WCMD_strip_quotes(WCHAR
*cmd
) {
499 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
, *lastquote
;
500 while((*dest
=*src
) != '\0') {
508 while ((*dest
++=*lastq
++) != 0)
515 /*************************************************************************
516 * WCMD_is_magic_envvar
517 * Return TRUE if s is '%'magicvar'%'
518 * and is not masked by a real environment variable.
521 static inline BOOL
WCMD_is_magic_envvar(const WCHAR
*s
, const WCHAR
*magicvar
)
526 return FALSE
; /* Didn't begin with % */
528 if (len
< 2 || s
[len
-1] != '%')
529 return FALSE
; /* Didn't end with another % */
531 if (CompareStringW(LOCALE_USER_DEFAULT
,
532 NORM_IGNORECASE
| SORT_STRINGSORT
,
533 s
+1, len
-2, magicvar
, -1) != CSTR_EQUAL
) {
534 /* Name doesn't match. */
538 if (GetEnvironmentVariableW(magicvar
, NULL
, 0) > 0) {
539 /* Masked by real environment variable. */
546 /*************************************************************************
549 * Expands environment variables, allowing for WCHARacter substitution
551 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR startchar
)
553 WCHAR
*endOfVar
= NULL
, *s
;
554 WCHAR
*colonpos
= NULL
;
555 WCHAR thisVar
[MAXSTRING
];
556 WCHAR thisVarContents
[MAXSTRING
];
557 WCHAR savedchar
= 0x00;
559 WCHAR Delims
[] = L
"%:"; /* First char gets replaced appropriately */
561 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start
), startchar
);
563 /* Find the end of the environment variable, and extract name */
564 Delims
[0] = startchar
;
565 endOfVar
= wcspbrk(start
+1, Delims
);
567 if (endOfVar
== NULL
|| *endOfVar
==' ') {
569 /* In batch program, missing terminator for % and no following
570 ':' just removes the '%' */
572 return WCMD_strsubstW(start
, start
+ 1, NULL
, 0);
575 /* In command processing, just ignore it - allows command line
576 syntax like: for %i in (a.a) do echo %i */
581 /* If ':' found, process remaining up until '%' (or stop at ':' if
583 if (*endOfVar
==':') {
584 WCHAR
*endOfVar2
= wcschr(endOfVar
+1, startchar
);
585 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
588 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
589 thisVar
[(endOfVar
- start
)+1] = 0x00;
590 colonpos
= wcschr(thisVar
+1, ':');
592 /* If there's complex substitution, just need %var% for now
593 to get the expanded data to play with */
596 savedchar
= *(colonpos
+1);
597 *(colonpos
+1) = 0x00;
600 /* By now, we know the variable we want to expand but it may be
601 surrounded by '!' if we are in delayed expansion - if so convert
603 if (startchar
=='!') {
605 thisVar
[(endOfVar
- start
)] = '%';
607 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
609 /* Expand to contents, if unchanged, return */
610 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
611 /* override if existing env var called that name */
612 if (WCMD_is_magic_envvar(thisVar
, L
"ERRORLEVEL")) {
613 len
= wsprintfW(thisVarContents
, L
"%d", errorlevel
);
614 } else if (WCMD_is_magic_envvar(thisVar
, L
"DATE")) {
615 len
= GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
616 NULL
, thisVarContents
, ARRAY_SIZE(thisVarContents
));
617 } else if (WCMD_is_magic_envvar(thisVar
, L
"TIME")) {
618 len
= GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
619 NULL
, thisVarContents
, ARRAY_SIZE(thisVarContents
));
620 } else if (WCMD_is_magic_envvar(thisVar
, L
"CD")) {
621 len
= GetCurrentDirectoryW(ARRAY_SIZE(thisVarContents
), thisVarContents
);
622 } else if (WCMD_is_magic_envvar(thisVar
, L
"RANDOM")) {
623 len
= wsprintfW(thisVarContents
, L
"%d", rand() % 32768);
625 if ((len
= ExpandEnvironmentStringsW(thisVar
, thisVarContents
, ARRAY_SIZE(thisVarContents
)))) len
--;
631 /* In a batch program, unknown env vars are replaced with nothing,
632 note syntax %garbage:1,3% results in anything after the ':'
634 From the command line, you just get back what you entered */
635 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
637 /* Restore the complex part after the compare */
640 *(colonpos
+1) = savedchar
;
643 /* Command line - just ignore this */
644 if (context
== NULL
) return endOfVar
+1;
646 /* Batch - replace unknown env var with nothing */
647 if (colonpos
== NULL
)
648 return WCMD_strsubstW(start
, endOfVar
+ 1, NULL
, 0);
649 len
= lstrlenW(thisVar
);
650 thisVar
[len
-1] = 0x00;
651 /* If %:...% supplied, : is retained */
652 if (colonpos
== thisVar
+1)
653 return WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
, -1);
654 return WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
+ 1, -1);
657 /* See if we need to do complex substitution (any ':'s), if not
658 then our work here is done */
659 if (colonpos
== NULL
)
660 return WCMD_strsubstW(start
, endOfVar
+ 1, thisVarContents
, -1);
662 /* Restore complex bit */
664 *(colonpos
+1) = savedchar
;
667 Handle complex substitutions:
668 xxx=yyy (replace xxx with yyy)
669 *xxx=yyy (replace up to and including xxx with yyy)
670 ~x (from x WCHARs in)
671 ~-x (from x WCHARs from the end)
672 ~x,y (from x WCHARs in for y WCHARacters)
673 ~x,-y (from x WCHARs in until y WCHARacters from the end)
676 /* ~ is substring manipulation */
677 if (savedchar
== '~') {
679 int substrposition
, substrlength
= 0;
680 WCHAR
*commapos
= wcschr(colonpos
+2, ',');
683 substrposition
= wcstol(colonpos
+2, NULL
, 10);
684 if (commapos
) substrlength
= wcstol(commapos
+1, NULL
, 10);
687 if (substrposition
>= 0) {
688 startCopy
= &thisVarContents
[min(substrposition
, len
- 1)];
690 startCopy
= &thisVarContents
[max(0, len
+ substrposition
)];
693 if (commapos
== NULL
)
695 return WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, -1);
696 if (substrlength
< 0) {
698 int copybytes
= len
+ substrlength
- (startCopy
- thisVarContents
);
699 if (copybytes
>= len
) copybytes
= len
- 1;
700 else if (copybytes
< 0) copybytes
= 0;
701 return WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, copybytes
);
703 substrlength
= min(substrlength
, len
- (startCopy
- thisVarContents
));
704 return WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, substrlength
);
705 /* search and replace manipulation */
707 WCHAR
*equalspos
= wcsstr(colonpos
, L
"=");
708 WCHAR
*replacewith
= equalspos
+1;
714 if (equalspos
== NULL
) return start
+1;
715 s
= xstrdupW(endOfVar
+ 1);
717 /* Null terminate both strings */
718 thisVar
[lstrlenW(thisVar
)-1] = 0x00;
721 /* Since we need to be case insensitive, copy the 2 buffers */
722 searchIn
= xstrdupW(thisVarContents
);
723 CharUpperBuffW(searchIn
, lstrlenW(thisVarContents
));
724 searchFor
= xstrdupW(colonpos
+ 1);
725 CharUpperBuffW(searchFor
, lstrlenW(colonpos
+1));
727 /* Handle wildcard case */
728 if (*(colonpos
+1) == '*') {
729 /* Search for string to replace */
730 found
= wcsstr(searchIn
, searchFor
+1);
734 lstrcpyW(start
, replacewith
);
735 lstrcatW(start
, thisVarContents
+ (found
-searchIn
) + lstrlenW(searchFor
+1));
736 ret
= start
+ wcslen(start
);
740 lstrcpyW(start
, thisVarContents
);
741 ret
= start
+ wcslen(start
);
745 /* Loop replacing all instances */
746 WCHAR
*lastFound
= searchIn
;
747 WCHAR
*outputposn
= start
;
750 while ((found
= wcsstr(lastFound
, searchFor
))) {
751 lstrcpynW(outputposn
,
752 thisVarContents
+ (lastFound
-searchIn
),
753 (found
- lastFound
)+1);
754 outputposn
= outputposn
+ (found
- lastFound
);
755 lstrcatW(outputposn
, replacewith
);
756 outputposn
= outputposn
+ lstrlenW(replacewith
);
757 lastFound
= found
+ lstrlenW(searchFor
);
760 thisVarContents
+ (lastFound
-searchIn
));
761 ret
= outputposn
+ wcslen(outputposn
);
762 lstrcatW(outputposn
, s
);
771 /*****************************************************************************
772 * Expand the command. Native expands lines from batch programs as they are
773 * read in and not again, except for 'for' variable substitution.
774 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
775 * atExecute is TRUE when the expansion is occurring as the command is executed
776 * rather than at parse time, i.e. delayed expansion and for loops need to be
779 static void handleExpansion(WCHAR
*cmd
, BOOL atExecute
) {
781 /* For commands in a context (batch program): */
782 /* Expand environment variables in a batch file %{0-9} first */
783 /* including support for any ~ modifiers */
785 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
786 /* names allowing environment variable overrides */
787 /* NOTE: To support the %PATH:xxx% syntax, also perform */
788 /* manual expansion of environment variables here */
793 BOOL delayed
= atExecute
? delayedsubst
: FALSE
;
794 WCHAR
*delayedp
= NULL
;
795 WCHAR startchar
= '%';
798 /* Display the FOR variables in effect */
799 for (i
=0;i
<MAX_FOR_VARIABLES
;i
++) {
800 if (forloopcontext
->variable
[i
]) {
801 WINE_TRACE("FOR variable context: %c = '%s'\n",
802 for_var_index_to_char(i
),
803 wine_dbgstr_w(forloopcontext
->variable
[i
]));
809 /* Find the next environment variable delimiter */
810 normalp
= wcschr(p
, '%');
811 if (delayed
) delayedp
= wcschr(p
, '!');
812 if (!normalp
) p
= delayedp
;
813 else if (!delayedp
) p
= normalp
;
814 else p
= min(p
,delayedp
);
818 WINE_TRACE("Translate command:%s %d (at: %s)\n",
819 wine_dbgstr_w(cmd
), atExecute
, wine_dbgstr_w(p
));
822 /* Don't touch %% unless it's in Batch */
823 if (!atExecute
&& *(p
+1) == startchar
) {
825 WCMD_strsubstW(p
, p
+1, NULL
, 0);
830 /* Replace %~ modifications if in batch program */
831 } else if (*(p
+1) == '~') {
832 WCMD_HandleTildeModifiers(&p
, atExecute
);
835 /* Replace use of %0...%9 if in batch program*/
836 } else if (!atExecute
&& context
&& (i
>= 0) && (i
<= 9) && startchar
== '%') {
837 t
= WCMD_parameter(context
-> command
, i
+ context
-> shift_count
[i
],
839 p
= WCMD_strsubstW(p
, p
+2, t
, -1);
841 /* Replace use of %* if in batch program*/
842 } else if (!atExecute
&& context
&& *(p
+1)=='*' && startchar
== '%') {
843 WCHAR
*startOfParms
= NULL
;
844 WCHAR
*thisParm
= WCMD_parameter(context
-> command
, 0, &startOfParms
, TRUE
, TRUE
);
845 if (startOfParms
!= NULL
) {
846 startOfParms
+= lstrlenW(thisParm
);
847 while (*startOfParms
==' ' || *startOfParms
== '\t') startOfParms
++;
848 p
= WCMD_strsubstW(p
, p
+2, startOfParms
, -1);
850 p
= WCMD_strsubstW(p
, p
+2, NULL
, 0);
853 int forvaridx
= for_var_char_to_index(*(p
+1));
854 if (startchar
== '%' && forvaridx
!= -1 && forloopcontext
->variable
[forvaridx
]) {
855 /* Replace the 2 characters, % and for variable character */
856 p
= WCMD_strsubstW(p
, p
+ 2, forloopcontext
->variable
[forvaridx
], -1);
857 } else if (!atExecute
|| startchar
== '!') {
858 p
= WCMD_expand_envvar(p
, startchar
);
860 /* In a FOR loop, see if this is the variable to replace */
861 } else { /* Ignore %'s on second pass of batch program */
869 /*******************************************************************
870 * WCMD_parse - parse a command into parameters and qualifiers.
872 * On exit, all qualifiers are concatenated into q, the first string
873 * not beginning with "/" is in p1 and the
874 * second in p2. Any subsequent non-qualifier strings are lost.
875 * Parameters in quotes are handled.
877 static void WCMD_parse (const WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
)
881 *q
= *p1
= *p2
= '\0';
886 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
887 *q
++ = towupper (*s
++);
897 while ((*s
!= '\0') && (*s
!= '"')) {
898 if (p
== 0) *p1
++ = *s
++;
899 else if (p
== 1) *p2
++ = *s
++;
902 if (p
== 0) *p1
= '\0';
903 if (p
== 1) *p2
= '\0';
910 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
911 && (*s
!= '=') && (*s
!= ',') ) {
912 if (p
== 0) *p1
++ = *s
++;
913 else if (p
== 1) *p2
++ = *s
++;
916 /* Skip concurrent parms */
917 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
919 if (p
== 0) *p1
= '\0';
920 if (p
== 1) *p2
= '\0';
926 void WCMD_expand(const WCHAR
*src
, WCHAR
*dst
)
929 handleExpansion(dst
, FALSE
);
930 WCMD_parse(dst
, quals
, param1
, param2
);
933 /* ============================== */
934 /* Data structures for commands */
935 /* ============================== */
937 static void redirection_dispose_list(CMD_REDIRECTION
*redir
)
941 CMD_REDIRECTION
*next
= redir
->next
;
947 static CMD_REDIRECTION
*redirection_create_file(enum CMD_REDIRECTION_KIND kind
, unsigned fd
, const WCHAR
*file
)
949 size_t len
= wcslen(file
) + 1;
950 CMD_REDIRECTION
*redir
= xalloc(offsetof(CMD_REDIRECTION
, file
[len
]));
954 memcpy(redir
->file
, file
, len
* sizeof(WCHAR
));
960 static CMD_REDIRECTION
*redirection_create_clone(unsigned fd
, unsigned fd_clone
)
962 CMD_REDIRECTION
*redir
= xalloc(sizeof(*redir
));
964 redir
->kind
= REDIR_WRITE_CLONE
;
966 redir
->clone
= fd_clone
;
972 static const char *debugstr_redirection(const CMD_REDIRECTION
*redir
)
976 case REDIR_READ_FROM
:
977 return wine_dbg_sprintf("%u< (%ls)", redir
->fd
, redir
->file
);
979 return wine_dbg_sprintf("%u> (%ls)", redir
->fd
, redir
->file
);
980 case REDIR_WRITE_APPEND
:
981 return wine_dbg_sprintf("%u>> (%ls)", redir
->fd
, redir
->file
);
982 case REDIR_WRITE_CLONE
:
983 return wine_dbg_sprintf("%u>&%u", redir
->fd
, redir
->clone
);
989 static WCHAR
*command_create(const WCHAR
*ptr
, size_t len
)
991 WCHAR
*command
= xalloc((len
+ 1) * sizeof(WCHAR
));
992 memcpy(command
, ptr
, len
* sizeof(WCHAR
));
993 command
[len
] = L
'\0';
997 static void for_control_dispose(CMD_FOR_CONTROL
*for_ctrl
)
999 free((void*)for_ctrl
->set
);
1000 switch (for_ctrl
->operator)
1002 case CMD_FOR_FILE_SET
:
1003 free((void*)for_ctrl
->delims
);
1004 free((void*)for_ctrl
->tokens
);
1006 case CMD_FOR_FILETREE
:
1007 free((void*)for_ctrl
->root_dir
);
1014 const char *debugstr_for_control(const CMD_FOR_CONTROL
*for_ctrl
)
1016 static const char* for_ctrl_strings
[] = {"tree", "file", "numbers"};
1017 const char *flags
, *options
;
1019 if (for_ctrl
->operator >= ARRAY_SIZE(for_ctrl_strings
))
1021 FIXME("Unexpected operator\n");
1022 return wine_dbg_sprintf("<<%u>>", for_ctrl
->operator);
1025 if (for_ctrl
->flags
)
1026 flags
= wine_dbg_sprintf("flags=%s%s%s ",
1027 (for_ctrl
->flags
& CMD_FOR_FLAG_TREE_RECURSE
) ? "~recurse" : "",
1028 (for_ctrl
->flags
& CMD_FOR_FLAG_TREE_INCLUDE_FILES
) ? "~+files" : "",
1029 (for_ctrl
->flags
& CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES
) ? "~+dirs" : "");
1032 switch (for_ctrl
->operator)
1034 case CMD_FOR_FILETREE
:
1035 options
= wine_dbg_sprintf("root=(%ls) ", for_ctrl
->root_dir
);
1037 case CMD_FOR_FILE_SET
:
1039 WCHAR eol_buf
[4] = {L
'\'', for_ctrl
->eol
, L
'\'', L
'\0'};
1040 const WCHAR
*eol
= for_ctrl
->eol
? eol_buf
: L
"<nul>";
1041 options
= wine_dbg_sprintf("eol=%ls skip=%d use_backq=%c delims=%s tokens=%s ",
1042 eol
, for_ctrl
->num_lines_to_skip
, for_ctrl
->use_backq
? 'Y' : 'N',
1043 wine_dbgstr_w(for_ctrl
->delims
), wine_dbgstr_w(for_ctrl
->tokens
));
1050 return wine_dbg_sprintf("[FOR] %s %s%s%%%c (%ls)",
1051 for_ctrl_strings
[for_ctrl
->operator], flags
, options
,
1052 for_var_index_to_char(for_ctrl
->variable_index
), for_ctrl
->set
);
1055 static void for_control_create(enum for_control_operator for_op
, unsigned flags
, const WCHAR
*options
, int var_idx
, CMD_FOR_CONTROL
*for_ctrl
)
1057 for_ctrl
->operator = for_op
;
1058 for_ctrl
->flags
= flags
;
1059 for_ctrl
->variable_index
= var_idx
;
1060 for_ctrl
->set
= NULL
;
1061 switch (for_ctrl
->operator)
1063 case CMD_FOR_FILETREE
:
1064 for_ctrl
->root_dir
= options
&& *options
? xstrdupW(options
) : NULL
;
1071 static void for_control_create_fileset(unsigned flags
, int var_idx
, WCHAR eol
, int num_lines_to_skip
, BOOL use_backq
,
1072 const WCHAR
*delims
, const WCHAR
*tokens
,
1073 CMD_FOR_CONTROL
*for_ctrl
)
1075 for_ctrl
->operator = CMD_FOR_FILE_SET
;
1076 for_ctrl
->flags
= flags
;
1077 for_ctrl
->variable_index
= var_idx
;
1078 for_ctrl
->set
= NULL
;
1080 for_ctrl
->eol
= eol
;
1081 for_ctrl
->use_backq
= use_backq
;
1082 for_ctrl
->num_lines_to_skip
= num_lines_to_skip
;
1083 for_ctrl
->delims
= delims
;
1084 for_ctrl
->tokens
= tokens
;
1087 static void for_control_append_set(CMD_FOR_CONTROL
*for_ctrl
, const WCHAR
*set
)
1091 for_ctrl
->set
= xrealloc((void*)for_ctrl
->set
,
1092 (wcslen(for_ctrl
->set
) + 1 + wcslen(set
) + 1) * sizeof(WCHAR
));
1093 wcscat((WCHAR
*)for_ctrl
->set
, L
" ");
1094 wcscat((WCHAR
*)for_ctrl
->set
, set
);
1097 for_ctrl
->set
= xstrdupW(set
);
1100 void if_condition_dispose(CMD_IF_CONDITION
*cond
)
1104 case CMD_IF_ERRORLEVEL
:
1106 case CMD_IF_DEFINED
:
1107 free((void*)cond
->operand
);
1109 case CMD_IF_BINOP_EQUAL
:
1110 case CMD_IF_BINOP_LSS
:
1111 case CMD_IF_BINOP_LEQ
:
1112 case CMD_IF_BINOP_EQU
:
1113 case CMD_IF_BINOP_NEQ
:
1114 case CMD_IF_BINOP_GEQ
:
1115 case CMD_IF_BINOP_GTR
:
1116 free((void*)cond
->left
);
1117 free((void*)cond
->right
);
1122 static BOOL
if_condition_parse(WCHAR
*start
, WCHAR
**end
, CMD_IF_CONDITION
*cond
)
1125 const WCHAR
*param_copy
;
1128 if (cond
) memset(cond
, 0, sizeof(*cond
));
1129 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, TRUE
, FALSE
);
1130 /* /I is the only option supported */
1131 if (!wcsicmp(param_copy
, L
"/I"))
1133 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, TRUE
, FALSE
);
1134 if (cond
) cond
->case_insensitive
= 1;
1136 if (!wcsicmp(param_copy
, L
"NOT"))
1138 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, TRUE
, FALSE
);
1139 if (cond
) cond
->negated
= 1;
1141 if (!wcsicmp(param_copy
, L
"errorlevel"))
1143 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, TRUE
, FALSE
);
1144 if (cond
) cond
->op
= CMD_IF_ERRORLEVEL
;
1145 if (cond
) cond
->operand
= wcsdup(param_copy
);
1147 else if (!wcsicmp(param_copy
, L
"exist"))
1149 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, FALSE
, FALSE
);
1150 if (cond
) cond
->op
= CMD_IF_EXIST
;
1151 if (cond
) cond
->operand
= wcsdup(param_copy
);
1153 else if (!wcsicmp(param_copy
, L
"defined"))
1155 param_copy
= WCMD_parameter(start
, narg
++, ¶m_start
, TRUE
, FALSE
);
1156 if (cond
) cond
->op
= CMD_IF_DEFINED
;
1157 if (cond
) cond
->operand
= wcsdup(param_copy
);
1159 else /* comparison operation */
1161 if (*param_copy
== L
'\0') return FALSE
;
1162 param_copy
= WCMD_parameter(start
, narg
- 1, ¶m_start
, TRUE
, FALSE
);
1163 if (cond
) cond
->left
= wcsdup(param_copy
);
1165 start
= WCMD_skip_leading_spaces(param_start
+ wcslen(param_copy
));
1167 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
1168 if (start
[0] == L
'=' && start
[1] == L
'=')
1170 start
+= 2; /* == */
1171 if (cond
) cond
->op
= CMD_IF_BINOP_EQUAL
;
1178 enum cond_operator binop
;
1180 allowed_operators
[] = {{L
"lss", CMD_IF_BINOP_LSS
},
1181 {L
"leq", CMD_IF_BINOP_LEQ
},
1182 {L
"equ", CMD_IF_BINOP_EQU
},
1183 {L
"neq", CMD_IF_BINOP_NEQ
},
1184 {L
"geq", CMD_IF_BINOP_GEQ
},
1185 {L
"gtr", CMD_IF_BINOP_GTR
},
1189 param_copy
= WCMD_parameter(start
, 0, ¶m_start
, FALSE
, FALSE
);
1190 for (i
= 0; i
< ARRAY_SIZE(allowed_operators
); i
++)
1191 if (!wcsicmp(param_copy
, allowed_operators
[i
].name
)) break;
1192 if (i
== ARRAY_SIZE(allowed_operators
))
1194 if (cond
) free((void*)cond
->left
);
1197 if (cond
) cond
->op
= allowed_operators
[i
].binop
;
1198 start
+= wcslen(param_copy
);
1201 param_copy
= WCMD_parameter(start
, 0, ¶m_start
, TRUE
, FALSE
);
1202 if (*param_copy
== L
'\0')
1204 if (cond
) free((void*)cond
->left
);
1207 if (cond
) cond
->right
= wcsdup(param_copy
);
1209 start
= param_start
+ wcslen(param_copy
);
1212 /* check all remaning args are present, and compute pointer to end of condition */
1213 param_copy
= WCMD_parameter(start
, narg
, end
, TRUE
, FALSE
);
1214 return cond
|| *param_copy
!= L
'\0';
1217 static const char *debugstr_if_condition(const CMD_IF_CONDITION
*cond
)
1219 const char *header
= wine_dbg_sprintf("{{%s%s", cond
->negated
? "not " : "", cond
->case_insensitive
? "nocase " : "");
1223 case CMD_IF_ERRORLEVEL
: return wine_dbg_sprintf("%serrorlevel %ls}}", header
, cond
->operand
);
1224 case CMD_IF_EXIST
: return wine_dbg_sprintf("%sexist %ls}}", header
, cond
->operand
);
1225 case CMD_IF_DEFINED
: return wine_dbg_sprintf("%sdefined %ls}}", header
, cond
->operand
);
1226 case CMD_IF_BINOP_EQUAL
: return wine_dbg_sprintf("%s%ls == %ls}}", header
, cond
->left
, cond
->right
);
1228 case CMD_IF_BINOP_LSS
: return wine_dbg_sprintf("%s%ls LSS %ls}}", header
, cond
->left
, cond
->right
);
1229 case CMD_IF_BINOP_LEQ
: return wine_dbg_sprintf("%s%ls LEQ %ls}}", header
, cond
->left
, cond
->right
);
1230 case CMD_IF_BINOP_EQU
: return wine_dbg_sprintf("%s%ls EQU %ls}}", header
, cond
->left
, cond
->right
);
1231 case CMD_IF_BINOP_NEQ
: return wine_dbg_sprintf("%s%ls NEQ %ls}}", header
, cond
->left
, cond
->right
);
1232 case CMD_IF_BINOP_GEQ
: return wine_dbg_sprintf("%s%ls GEQ %ls}}", header
, cond
->left
, cond
->right
);
1233 case CMD_IF_BINOP_GTR
: return wine_dbg_sprintf("%s%ls GTR %ls}}", header
, cond
->left
, cond
->right
);
1235 FIXME("Unexpected condition operator %u\n", cond
->op
);
1240 /***************************************************************************
1243 * Frees the storage held for a parsed command line
1244 * - This is not done in the process_commands, as eventually the current
1245 * pointer will be modified within the commands, and hence a single free
1246 * routine is simpler
1248 void node_dispose_tree(CMD_NODE
*node
)
1250 /* Loop through the commands, freeing them one by one */
1255 free(node
->command
);
1261 node_dispose_tree(node
->left
);
1262 node_dispose_tree(node
->right
);
1265 if_condition_dispose(&node
->condition
);
1266 node_dispose_tree(node
->then_block
);
1267 node_dispose_tree(node
->else_block
);
1270 for_control_dispose(&node
->for_ctrl
);
1271 node_dispose_tree(node
->do_block
);
1274 redirection_dispose_list(node
->redirects
);
1278 static CMD_NODE
*node_create_single(WCHAR
*c
)
1280 CMD_NODE
*new = xalloc(sizeof(CMD_NODE
));
1282 new->op
= CMD_SINGLE
;
1284 new->redirects
= NULL
;
1289 static CMD_NODE
*node_create_binary(CMD_OPERATOR op
, CMD_NODE
*l
, CMD_NODE
*r
)
1291 CMD_NODE
*new = xalloc(sizeof(CMD_NODE
));
1296 new->redirects
= NULL
;
1301 static CMD_NODE
*node_create_if(CMD_IF_CONDITION
*cond
, CMD_NODE
*then_block
, CMD_NODE
*else_block
)
1303 CMD_NODE
*new = xalloc(sizeof(CMD_NODE
));
1306 new->condition
= *cond
;
1307 new->then_block
= then_block
;
1308 new->else_block
= else_block
;
1309 new->redirects
= NULL
;
1314 static CMD_NODE
*node_create_for(CMD_FOR_CONTROL
*for_ctrl
, CMD_NODE
*do_block
)
1316 CMD_NODE
*new = xalloc(sizeof(CMD_NODE
));
1319 new->for_ctrl
= *for_ctrl
;
1320 new->do_block
= do_block
;
1321 new->redirects
= NULL
;
1326 static void init_msvcrt_io_block(STARTUPINFOW
* st
)
1329 /* fetch the parent MSVCRT info block if any, so that the child can use the
1330 * same handles as its grand-father
1332 st_p
.cb
= sizeof(STARTUPINFOW
);
1333 GetStartupInfoW(&st_p
);
1334 st
->cbReserved2
= st_p
.cbReserved2
;
1335 st
->lpReserved2
= st_p
.lpReserved2
;
1336 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
1338 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
1344 /* Override the entries for fd 0,1,2 if we happened
1345 * to change those std handles (this depends on the way cmd sets
1346 * its new input & output handles)
1348 sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
1350 flags
= (char*)(ptr
+ sizeof(unsigned));
1351 handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
1353 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
1354 st
->cbReserved2
= sz
;
1355 st
->lpReserved2
= ptr
;
1357 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
1358 if (num
<= 0 || (flags
[0] & WX_OPEN
))
1360 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
1361 flags
[0] |= WX_OPEN
;
1363 if (num
<= 1 || (flags
[1] & WX_OPEN
))
1365 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
1366 flags
[1] |= WX_OPEN
;
1368 if (num
<= 2 || (flags
[2] & WX_OPEN
))
1370 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
1371 flags
[2] |= WX_OPEN
;
1377 static RETURN_CODE
execute_single_command(const WCHAR
*command
);
1379 /******************************************************************************
1382 * Execute a command line as an external program. Must allow recursion.
1385 * Manual testing under windows shows PATHEXT plays a key part in this,
1386 * and the search algorithm and precedence appears to be as follows.
1389 * If directory supplied on command, just use that directory
1390 * If extension supplied on command, look for that explicit name first
1391 * Otherwise, search in each directory on the path
1393 * If extension supplied on command, look for that explicit name first
1394 * Then look for supplied name .* (even if extension supplied, so
1395 * 'garbage.exe' will match 'garbage.exe.cmd')
1396 * If any found, cycle through PATHEXT looking for name.exe one by one
1398 * Once a match has been found, it is launched - Code currently uses
1399 * findexecutable to achieve this which is left untouched.
1400 * If an executable has not been found, and we were launched through
1401 * a call, we need to check if the command is an internal command,
1402 * so go back through wcmd_execute.
1405 RETURN_CODE
WCMD_run_program(WCHAR
*command
, BOOL called
)
1407 WCHAR temp
[MAX_PATH
];
1408 WCHAR pathtosearch
[MAXSTRING
];
1410 WCHAR stemofsearch
[MAX_PATH
]; /* maximum allowed executable name is
1411 MAX_PATH, including null character */
1413 WCHAR pathext
[MAXSTRING
];
1415 BOOL extensionsupplied
= FALSE
;
1416 BOOL explicit_path
= FALSE
;
1420 /* Quick way to get the filename is to extract the first argument. */
1421 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command
), called
);
1422 firstParam
= WCMD_parameter(command
, 0, NULL
, FALSE
, TRUE
);
1424 if (!firstParam
[0]) return NO_ERROR
;
1426 /* Calculate the search path and stem to search for */
1427 if (wcspbrk(firstParam
, L
"/\\:") == NULL
) { /* No explicit path given, search path */
1428 lstrcpyW(pathtosearch
, L
".;");
1429 len
= GetEnvironmentVariableW(L
"PATH", &pathtosearch
[2], ARRAY_SIZE(pathtosearch
)-2);
1430 if ((len
== 0) || (len
>= ARRAY_SIZE(pathtosearch
) - 2)) {
1431 lstrcpyW(pathtosearch
, L
".");
1433 if (wcschr(firstParam
, '.') != NULL
) extensionsupplied
= TRUE
;
1434 if (lstrlenW(firstParam
) >= MAX_PATH
)
1436 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG
));
1437 return ERROR_INVALID_FUNCTION
;
1440 lstrcpyW(stemofsearch
, firstParam
);
1444 /* Convert eg. ..\fred to include a directory by removing file part */
1445 if (!WCMD_get_fullpath(firstParam
, ARRAY_SIZE(pathtosearch
), pathtosearch
, NULL
))
1446 return ERROR_INVALID_FUNCTION
;
1447 lastSlash
= wcsrchr(pathtosearch
, '\\');
1448 if (lastSlash
&& wcschr(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1449 lstrcpyW(stemofsearch
, lastSlash
+1);
1451 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1452 c:\windows\a.bat syntax */
1453 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1454 explicit_path
= TRUE
;
1457 /* Now extract PATHEXT */
1458 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
1459 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
1460 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
1463 /* Loop through the search path, dir by dir */
1464 pathposn
= pathtosearch
;
1465 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1466 wine_dbgstr_w(stemofsearch
));
1468 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1472 BOOL inside_quotes
= FALSE
;
1476 lstrcpyW(thisDir
, pathposn
);
1481 /* Work on the next directory on the search path */
1483 while ((inside_quotes
|| *pos
!= ';') && *pos
!= 0)
1486 inside_quotes
= !inside_quotes
;
1490 if (*pos
) /* Reached semicolon */
1492 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1493 thisDir
[(pos
-pathposn
)] = 0x00;
1496 else /* Reached string end */
1498 lstrcpyW(thisDir
, pathposn
);
1503 length
= lstrlenW(thisDir
);
1504 if (thisDir
[length
- 1] == '"')
1505 thisDir
[length
- 1] = 0;
1507 if (*thisDir
!= '"')
1508 lstrcpyW(temp
, thisDir
);
1510 lstrcpyW(temp
, thisDir
+ 1);
1512 /* When temp is an empty string, skip over it. This needs
1513 to be done before the expansion, because WCMD_get_fullpath
1514 fails when given an empty string */
1518 /* Since you can have eg. ..\.. on the path, need to expand
1519 to full information */
1520 if (!WCMD_get_fullpath(temp
, ARRAY_SIZE(thisDir
), thisDir
, NULL
))
1521 return ERROR_INVALID_FUNCTION
;
1524 /* 1. If extension supplied, see if that file exists */
1525 lstrcatW(thisDir
, L
"\\");
1526 lstrcatW(thisDir
, stemofsearch
);
1527 pos
= &thisDir
[lstrlenW(thisDir
)]; /* Pos = end of name */
1529 /* 1. If extension supplied, see if that file exists */
1530 if (extensionsupplied
) {
1531 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1536 /* 2. Any .* matches? */
1539 WIN32_FIND_DATAW finddata
;
1541 lstrcatW(thisDir
, L
".*");
1542 h
= FindFirstFileW(thisDir
, &finddata
);
1544 if (h
!= INVALID_HANDLE_VALUE
) {
1546 WCHAR
*thisExt
= pathext
;
1548 /* 3. Yes - Try each path ext */
1550 WCHAR
*nextExt
= wcschr(thisExt
, ';');
1553 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1554 pos
[(nextExt
-thisExt
)] = 0x00;
1555 thisExt
= nextExt
+1;
1557 lstrcpyW(pos
, thisExt
);
1561 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1569 /* Once found, launch it */
1572 PROCESS_INFORMATION pe
;
1576 WCHAR
*ext
= wcsrchr( thisDir
, '.' );
1578 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1580 /* Special case BAT and CMD */
1581 if (ext
&& (!wcsicmp(ext
, L
".bat") || !wcsicmp(ext
, L
".cmd"))) {
1582 RETURN_CODE return_code
;
1583 BOOL oldinteractive
= interactive
;
1585 interactive
= FALSE
;
1586 return_code
= WCMD_batch(thisDir
, command
, NULL
, INVALID_HANDLE_VALUE
);
1587 interactive
= oldinteractive
;
1588 if (context
&& !called
) {
1589 TRACE("Batch completed, but was not 'called' so skipping outer batch too\n");
1590 context
->skip_rest
= TRUE
;
1592 if (return_code
!= RETURN_CODE_ABORTED
)
1593 errorlevel
= return_code
;
1597 /* thisDir contains the file to be launched, but with what?
1598 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1599 hinst
= FindExecutableW (thisDir
, NULL
, temp
);
1600 if ((INT_PTR
)hinst
< 32)
1603 console
= SHGetFileInfoW(temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1605 ZeroMemory (&st
, sizeof(STARTUPINFOW
));
1606 st
.cb
= sizeof(STARTUPINFOW
);
1607 init_msvcrt_io_block(&st
);
1609 /* Launch the process and if a CUI wait on it to complete
1610 Note: Launching internal wine processes cannot specify a full path to exe */
1611 status
= CreateProcessW(thisDir
,
1612 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1613 free(st
.lpReserved2
);
1614 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1615 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1616 /* strip first and last quote WCHARacters and try again */
1617 WCMD_strip_quotes(command
);
1619 return WCMD_run_program(command
, called
);
1625 /* Always wait when non-interactive (cmd /c or in batch program),
1626 or for console applications */
1627 if (!interactive
|| (console
&& !HIWORD(console
)))
1628 WaitForSingleObject (pe
.hProcess
, INFINITE
);
1629 GetExitCodeProcess (pe
.hProcess
, &exit_code
);
1630 errorlevel
= (exit_code
== STILL_ACTIVE
) ? NO_ERROR
: exit_code
;
1632 CloseHandle(pe
.hProcess
);
1633 CloseHandle(pe
.hThread
);
1639 /* Not found anywhere - were we called? */
1641 return errorlevel
= execute_single_command(command
);
1643 /* Not found anywhere - give up */
1644 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND
), command
);
1646 /* If a command fails to launch, it sets errorlevel 9009 - which
1647 does not seem to have any associated constant definition */
1648 errorlevel
= RETURN_CODE_CANT_LAUNCH
;
1649 return ERROR_INVALID_FUNCTION
;
1652 static BOOL
set_std_redirections(CMD_REDIRECTION
*redir
)
1654 static DWORD std_index
[3] = {STD_INPUT_HANDLE
, STD_OUTPUT_HANDLE
, STD_ERROR_HANDLE
};
1655 static SECURITY_ATTRIBUTES sa
= {.nLength
= sizeof(sa
), .lpSecurityDescriptor
= NULL
, .bInheritHandle
= TRUE
};
1656 WCHAR expanded_filename
[MAXSTRING
];
1659 for (; redir
; redir
= redir
->next
)
1661 CMD_REDIRECTION
*next
;
1663 /* if we have several elements changing same std stream, only use last one */
1664 for (next
= redir
->next
; next
; next
= next
->next
)
1665 if (redir
->fd
== next
->fd
) break;
1667 switch (redir
->kind
)
1669 case REDIR_READ_FROM
:
1670 wcscpy(expanded_filename
, redir
->file
);
1671 handleExpansion(expanded_filename
, TRUE
);
1672 h
= CreateFileW(expanded_filename
, GENERIC_READ
, FILE_SHARE_READ
,
1673 &sa
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1674 if (h
== INVALID_HANDLE_VALUE
)
1676 WARN("Failed to open (%ls)\n", expanded_filename
);
1679 TRACE("Open (%ls) => %p\n", expanded_filename
, h
);
1681 case REDIR_WRITE_TO
:
1682 case REDIR_WRITE_APPEND
:
1684 DWORD disposition
= redir
->kind
== REDIR_WRITE_TO
? CREATE_ALWAYS
: OPEN_ALWAYS
;
1685 wcscpy(expanded_filename
, redir
->file
);
1686 handleExpansion(expanded_filename
, TRUE
);
1687 h
= CreateFileW(expanded_filename
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_DELETE
,
1688 &sa
, disposition
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1689 if (h
== INVALID_HANDLE_VALUE
)
1691 WARN("Failed to open (%ls)\n", expanded_filename
);
1694 TRACE("Open %u (%ls) => %p\n", redir
->fd
, expanded_filename
, h
);
1695 if (SetFilePointer(h
, 0, NULL
, FILE_END
) == INVALID_SET_FILE_POINTER
)
1699 case REDIR_WRITE_CLONE
:
1700 if (redir
->clone
> 2 || redir
->clone
== redir
->fd
)
1702 WARN("Can't duplicate %d from %d\n", redir
->fd
, redir
->clone
);
1705 if (!DuplicateHandle(GetCurrentProcess(),
1706 GetStdHandle(std_index
[redir
->clone
]),
1707 GetCurrentProcess(),
1709 0, TRUE
, DUPLICATE_SAME_ACCESS
))
1711 WARN("Duplicating handle failed with gle %ld\n", GetLastError());
1719 SetStdHandle(std_index
[redir
->fd
], h
);
1724 /*****************************************************************************
1725 * Process one command. If the command is EXIT this routine does not return.
1726 * We will recurse through here executing batch files.
1727 * Note: If call is used to a non-existing program, we reparse the line and
1728 * try to run it as an internal command. 'retrycall' represents whether
1729 * we are attempting this retry.
1731 static RETURN_CODE
execute_single_command(const WCHAR
*command
)
1733 RETURN_CODE return_code
;
1734 WCHAR
*cmd
, *parms_start
;
1735 int cmd_index
, count
;
1737 WCHAR
*new_cmd
= NULL
;
1738 BOOL prev_echo_mode
;
1740 TRACE("command on entry:%s\n", wine_dbgstr_w(command
));
1742 /* Move copy of the command onto the heap so it can be expanded */
1743 new_cmd
= xalloc(MAXSTRING
* sizeof(WCHAR
));
1744 lstrcpyW(new_cmd
, command
);
1747 /* Strip leading whitespaces, and a '@' if supplied */
1748 whichcmd
= WCMD_skip_leading_spaces(cmd
);
1749 TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
1750 if (whichcmd
[0] == '@') whichcmd
++;
1752 /* Check if the command entered is internal, and identify which one */
1754 while (IsCharAlphaNumericW(whichcmd
[count
])) {
1757 for (cmd_index
=0; cmd_index
<=WCMD_EXIT
; cmd_index
++) {
1758 if (count
&& CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1759 whichcmd
, count
, inbuilt
[cmd_index
], -1) == CSTR_EQUAL
) break;
1761 parms_start
= WCMD_skip_leading_spaces (&whichcmd
[count
]);
1763 handleExpansion(new_cmd
, TRUE
);
1766 * Changing default drive has to be handled as a special case, anything
1767 * else if it exists after whitespace is ignored
1770 if (cmd
[1] == L
':' && (!cmd
[2] || iswspace(cmd
[2]))) {
1772 WCHAR dir
[MAX_PATH
];
1774 /* Ignore potential garbage on the same line */
1777 /* According to MSDN CreateProcess docs, special env vars record
1778 the current directory on each drive, in the form =C:
1779 so see if one specified, and if so go back to it */
1780 lstrcpyW(envvar
, L
"=");
1781 lstrcatW(envvar
, cmd
);
1782 if (GetEnvironmentVariableW(envvar
, dir
, ARRAY_SIZE(dir
)) == 0) {
1783 wsprintfW(cmd
, L
"%s\\", cmd
);
1784 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
1786 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
1787 if (!SetCurrentDirectoryW(cmd
))
1790 return_code
= errorlevel
= ERROR_INVALID_FUNCTION
;
1792 else return_code
= NO_ERROR
;
1796 WCMD_parse (parms_start
, quals
, param1
, param2
);
1797 TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1799 if (cmd_index
<= WCMD_EXIT
&& (parms_start
[0] == '/') && (parms_start
[1] == '?')) {
1800 /* this is a help request for a builtin program */
1801 cmd_index
= WCMD_HELP
;
1802 memcpy(parms_start
, whichcmd
, count
* sizeof(WCHAR
));
1803 parms_start
[count
] = '\0';
1806 switch (cmd_index
) {
1809 return_code
= WCMD_call(parms_start
);
1813 return_code
= WCMD_setshow_default(parms_start
);
1816 return_code
= WCMD_clear_screen();
1819 return_code
= WCMD_copy(parms_start
);
1822 return_code
= WCMD_setshow_date();
1826 return_code
= WCMD_delete(parms_start
);
1829 return_code
= WCMD_directory(parms_start
);
1832 return_code
= WCMD_echo(&whichcmd
[count
]);
1835 return_code
= WCMD_goto();
1838 return_code
= WCMD_give_help(parms_start
);
1841 return_code
= WCMD_label();
1845 return_code
= WCMD_create_dir(parms_start
);
1848 return_code
= WCMD_move();
1851 return_code
= WCMD_setshow_path(parms_start
);
1854 return_code
= WCMD_pause();
1857 return_code
= WCMD_setshow_prompt();
1860 return_code
= NO_ERROR
;
1864 return_code
= WCMD_rename();
1868 return_code
= WCMD_remove_dir(parms_start
);
1871 return_code
= WCMD_setlocal(parms_start
);
1874 return_code
= WCMD_endlocal();
1877 return_code
= WCMD_setshow_env(parms_start
);
1880 return_code
= WCMD_shift(parms_start
);
1883 return_code
= WCMD_start(parms_start
);
1886 return_code
= WCMD_setshow_time();
1889 return_code
= WCMD_title(parms_start
);
1892 return_code
= WCMD_type(parms_start
);
1895 return_code
= WCMD_version();
1898 return_code
= WCMD_verify();
1901 return_code
= WCMD_volume();
1904 return_code
= WCMD_pushd(parms_start
);
1907 return_code
= WCMD_popd();
1910 return_code
= WCMD_assoc(parms_start
, TRUE
);
1913 return_code
= WCMD_color();
1916 return_code
= WCMD_assoc(parms_start
, FALSE
);
1919 return_code
= WCMD_more(parms_start
);
1922 return_code
= WCMD_choice(parms_start
);
1925 return_code
= WCMD_mklink(parms_start
);
1928 return_code
= WCMD_exit();
1931 prev_echo_mode
= echo_mode
;
1932 return_code
= WCMD_run_program(whichcmd
, FALSE
);
1933 echo_mode
= prev_echo_mode
;
1941 /*************************************************************************
1943 * Load a string from the resource file, handling any error
1944 * Returns string retrieved from resource file
1946 WCHAR
*WCMD_LoadMessage(UINT id
) {
1947 static WCHAR msg
[2048];
1949 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, ARRAY_SIZE(msg
))) {
1950 WINE_FIXME("LoadString failed with %ld\n", GetLastError());
1951 lstrcpyW(msg
, L
"Failed!");
1956 static WCHAR
*find_chr(WCHAR
*in
, WCHAR
*last
, const WCHAR
*delims
)
1958 for (; in
< last
; in
++)
1959 if (wcschr(delims
, *in
)) return in
;
1963 /***************************************************************************
1966 * Checks if the quote pointed to is the end-quote.
1970 * 1) The current parameter ends at EOL or at the beginning
1971 * of a redirection or pipe and not in a quote section.
1973 * 2) If the next character is a space and not in a quote section.
1975 * Returns TRUE if this is an end quote, and FALSE if it is not.
1978 static BOOL
WCMD_IsEndQuote(const WCHAR
*quote
, int quoteIndex
)
1980 int quoteCount
= quoteIndex
;
1983 /* If we are not in a quoted section, then we are not an end-quote */
1989 /* Check how many quotes are left for this parameter */
1990 for(i
=0;quote
[i
];i
++)
1997 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1998 else if(((quoteCount
% 2) == 0)
1999 && ((quote
[i
] == '<') || (quote
[i
] == '>') || (quote
[i
] == '|') || (quote
[i
] == ' ') ||
2006 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
2008 if(quoteIndex
>= (quoteCount
/ 2))
2017 static WCHAR
*for_fileset_option_split(WCHAR
*from
, const WCHAR
* key
)
2019 size_t len
= wcslen(key
);
2021 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2022 from
, len
, key
, len
) != CSTR_EQUAL
)
2025 if (len
&& key
[len
- 1] == L
'=')
2026 while (*from
&& *from
!= L
' ' && *from
!= L
'\t') from
++;
2030 static CMD_FOR_CONTROL
*for_control_parse(WCHAR
*opts_var
)
2032 CMD_FOR_CONTROL
*for_ctrl
;
2033 enum for_control_operator for_op
;
2034 WCHAR mode
= L
' ', option
;
2035 WCHAR options
[MAXSTRING
];
2042 /* native allows two options only in the /D /R case, a repetition of the option
2043 * and prints an error otherwise
2045 for (arg_index
= 0; ; arg_index
++)
2047 arg
= WCMD_parameter(opts_var
, arg_index
, NULL
, FALSE
, FALSE
);
2049 if (!arg
|| *arg
!= L
'/') break;
2050 option
= towupper(arg
[1]);
2051 if (mode
!= L
' ' && (mode
!= L
'D' || option
!= 'R') && mode
!= option
)
2068 /* error unexpected 'arg' at this time */
2069 WARN("for qualifier '%c' unhandled\n", *arg
);
2076 for_op
= CMD_FOR_FILETREE
;
2077 flags
= CMD_FOR_FLAG_TREE_INCLUDE_FILES
;
2080 for_op
= CMD_FOR_FILETREE
;
2081 flags
= CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES
;
2084 for_op
= CMD_FOR_FILETREE
;
2085 flags
= CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES
| CMD_FOR_FLAG_TREE_RECURSE
;
2088 for_op
= CMD_FOR_FILETREE
;
2089 flags
= CMD_FOR_FLAG_TREE_INCLUDE_FILES
| /*CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | */CMD_FOR_FLAG_TREE_RECURSE
;
2092 for_op
= CMD_FOR_NUMBERS
;
2095 for_op
= CMD_FOR_FILE_SET
;
2098 FIXME("Unexpected situation\n");
2102 if (mode
== L
'F' || mode
== L
'R')
2104 /* Retrieve next parameter to see if is root/options (raw form required
2105 * with for /f, or unquoted in for /r)
2107 arg
= WCMD_parameter(opts_var
, arg_index
, NULL
, for_op
== CMD_FOR_FILE_SET
, FALSE
);
2109 /* Next parm is either qualifier, path/options or variable -
2110 * only care about it if it is the path/options
2112 if (arg
&& *arg
!= L
'/' && *arg
!= L
'%')
2115 wcscpy(options
, arg
);
2119 /* Ensure line continues with variable */
2120 arg
= WCMD_parameter(opts_var
, arg_index
++, NULL
, FALSE
, FALSE
);
2121 if (!arg
|| *arg
!= L
'%' || (var_idx
= for_var_char_to_index(arg
[1])) == -1)
2122 goto syntax_error
; /* FIXME native prints the offending token "%<whatever>" was unexpected at this time */
2123 for_ctrl
= xalloc(sizeof(*for_ctrl
));
2124 if (for_op
== CMD_FOR_FILE_SET
)
2126 size_t len
= wcslen(options
);
2127 WCHAR
*p
= options
, *end
;
2129 int num_lines_to_skip
= 0;
2130 BOOL use_backq
= FALSE
;
2131 WCHAR
*delims
= NULL
, *tokens
= NULL
;
2132 /* strip enclosing double-quotes when present */
2133 if (len
>= 2 && p
[0] == L
'"' && p
[len
- 1] == L
'"')
2138 for ( ; *p
; p
= end
)
2140 p
= WCMD_skip_leading_spaces(p
);
2141 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
2142 if ((end
= for_fileset_option_split(p
, L
"eol=")))
2144 /* assuming one char for eol marker */
2145 if (end
!= p
+ 5) goto syntax_error
;
2148 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
2149 else if ((end
= for_fileset_option_split(p
, L
"skip=")))
2152 num_lines_to_skip
= wcstoul(p
+ 5, &nextchar
, 0);
2153 if (end
!= nextchar
) goto syntax_error
;
2155 /* Save if usebackq semantics are in effect */
2156 else if ((end
= for_fileset_option_split(p
, L
"usebackq")))
2158 /* Save the supplied delims */
2159 else if ((end
= for_fileset_option_split(p
, L
"delims=")))
2163 /* interpret space when last character of whole options string as part of delims= */
2164 if (end
[0] && !end
[1]) end
++;
2165 copy_len
= end
- (p
+ 7) /* delims= */;
2166 delims
= xalloc((copy_len
+ 1) * sizeof(WCHAR
));
2167 memcpy(delims
, p
+ 7, copy_len
* sizeof(WCHAR
));
2168 delims
[copy_len
] = L
'\0';
2170 /* Save the tokens being requested */
2171 else if ((end
= for_fileset_option_split(p
, L
"tokens=")))
2175 copy_len
= end
- (p
+ 7) /* tokens= */;
2176 tokens
= xalloc((copy_len
+ 1) * sizeof(WCHAR
));
2177 memcpy(tokens
, p
+ 7, copy_len
* sizeof(WCHAR
));
2178 tokens
[copy_len
] = L
'\0';
2182 WARN("FOR option not found %ls\n", p
);
2186 for_control_create_fileset(flags
, var_idx
, eol
, num_lines_to_skip
, use_backq
,
2187 delims
? delims
: xstrdupW(L
" \t"),
2188 tokens
? tokens
: xstrdupW(L
"1"), for_ctrl
);
2191 for_control_create(for_op
, flags
, options
, var_idx
, for_ctrl
);
2194 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2198 /* used to store additional information dedicated a given token */
2199 union token_parameter
2202 CMD_REDIRECTION
*redirection
;
2214 TKN_EOF
, TKN_EOL
, TKN_REDIRECTION
, TKN_FOR
, TKN_IN
, TKN_DO
, TKN_IF
, TKN_ELSE
,
2215 TKN_OPENPAR
, TKN_CLOSEPAR
, TKN_AMP
, TKN_BARBAR
, TKN_AMPAMP
, TKN_BAR
, TKN_COMMAND
,
2217 union token_parameter parameter
;
2220 unsigned opened_parenthesis
;
2223 static const char* debugstr_token(enum builder_token tkn
, union token_parameter tkn_pmt
)
2225 static const char *tokens
[] = {"EOF", "EOL", "REDIR", "FOR", "IN", "DO", "IF", "ELSE",
2226 "(", ")", "&", "||", "&&", "|", "CMD"};
2228 if (tkn
>= ARRAY_SIZE(tokens
)) return "<<<>>>";
2231 case TKN_COMMAND
: return wine_dbg_sprintf("%s {{%s}}", tokens
[tkn
], debugstr_w(tkn_pmt
.command
));
2232 case TKN_REDIRECTION
: return wine_dbg_sprintf("%s {{%s}}", tokens
[tkn
], debugstr_redirection(tkn_pmt
.redirection
));
2233 default: return wine_dbg_sprintf("%s", tokens
[tkn
]);
2237 static unsigned token_get_precedence(enum builder_token tkn
)
2241 case TKN_EOL
: return 5;
2242 case TKN_BAR
: return 4;
2243 case TKN_AMPAMP
: return 3;
2244 case TKN_BARBAR
: return 2;
2245 case TKN_AMP
: return 1;
2250 static void node_builder_init(struct node_builder
*builder
)
2252 memset(builder
, 0, sizeof(*builder
));
2255 static void node_builder_dispose(struct node_builder
*builder
)
2257 free(builder
->stack
);
2260 static void node_builder_push_token_parameter(struct node_builder
*builder
, enum builder_token tkn
, union token_parameter pmt
)
2262 if (builder
->allocated
<= builder
->num
)
2264 unsigned sz
= builder
->allocated
? 2 * builder
->allocated
: 64;
2265 builder
->stack
= xrealloc(builder
->stack
, sz
* sizeof(builder
->stack
[0]));
2266 builder
->allocated
= sz
;
2268 builder
->stack
[builder
->num
].token
= tkn
;
2269 builder
->stack
[builder
->num
].parameter
= pmt
;
2271 if (tkn
== TKN_OPENPAR
)
2272 builder
->opened_parenthesis
++;
2273 if (tkn
== TKN_CLOSEPAR
)
2274 builder
->opened_parenthesis
--;
2278 static void node_builder_push_token(struct node_builder
*builder
, enum builder_token tkn
)
2280 union token_parameter pmt
= {.none
= NULL
};
2281 node_builder_push_token_parameter(builder
, tkn
, pmt
);
2284 static enum builder_token
node_builder_peek_next_token(struct node_builder
*builder
, union token_parameter
*pmt
)
2286 enum builder_token tkn
;
2288 if (builder
->pos
>= builder
->num
)
2291 if (pmt
) pmt
->none
= NULL
;
2295 tkn
= builder
->stack
[builder
->pos
].token
;
2297 *pmt
= builder
->stack
[builder
->pos
].parameter
;
2302 static void node_builder_consume(struct node_builder
*builder
)
2304 builder
->stack
[builder
->pos
].parameter
.none
= NULL
;
2308 static BOOL
node_builder_expect_token(struct node_builder
*builder
, enum builder_token tkn
)
2310 if (builder
->pos
>= builder
->num
|| builder
->stack
[builder
->pos
].token
!= tkn
)
2312 node_builder_consume(builder
);
2316 static void redirection_list_append(CMD_REDIRECTION
**redir
, CMD_REDIRECTION
*last
)
2320 for ( ; *redir
; redir
= &(*redir
)->next
) {}
2325 static BOOL
node_builder_parse(struct node_builder
*builder
, unsigned precedence
, CMD_NODE
**result
)
2327 CMD_REDIRECTION
*redir
= NULL
;
2328 unsigned bogus_line
;
2329 CMD_NODE
*left
= NULL
, *right
;
2330 CMD_FOR_CONTROL
*for_ctrl
= NULL
;
2331 union token_parameter pmt
;
2332 enum builder_token tkn
;
2335 #define ERROR_IF(x) if (x) {bogus_line = __LINE__; goto error_handling;}
2338 tkn
= node_builder_peek_next_token(builder
, &pmt
);
2341 TRACE("\t%u/%u) %s\n", builder
->pos
, builder
->num
, debugstr_token(tkn
, pmt
));
2345 /* always an error to read past end of tokens */
2353 node_builder_consume(builder
);
2354 /* empty lines are allowed here */
2355 while ((tkn
= node_builder_peek_next_token(builder
, &pmt
)) == TKN_EOL
)
2356 node_builder_consume(builder
);
2357 ERROR_IF(!node_builder_parse(builder
, 0, &left
));
2358 /* temp before using precedence in chaining */
2359 while ((tkn
= node_builder_peek_next_token(builder
, &pmt
)) != TKN_CLOSEPAR
)
2361 ERROR_IF(tkn
!= TKN_EOL
);
2362 node_builder_consume(builder
);
2363 /* FIXME potential empty here?? */
2364 ERROR_IF(!node_builder_parse(builder
, 0, &right
));
2365 left
= node_create_binary(CMD_CONCAT
, left
, right
);
2367 node_builder_consume(builder
);
2368 /* if we had redirection before '(', add them up front */
2371 redirection_list_append(&redir
, left
->redirects
);
2372 left
->redirects
= redir
;
2375 /* just in case we're handling: "(if ...) > a"... to not trigger errors in TKN_REDIRECTION */
2376 while (node_builder_peek_next_token(builder
, &pmt
) == TKN_REDIRECTION
)
2378 redirection_list_append(&left
->redirects
, pmt
.redirection
);
2379 node_builder_consume(builder
);
2382 /* shouldn't appear here... error handling ? */
2384 /* following tokens act as a delimiter for inner context; return to upper */
2392 if (!(done
= token_get_precedence(tkn
) <= precedence
))
2394 node_builder_consume(builder
);
2395 if (node_builder_peek_next_token(builder
, &pmt
) == TKN_CLOSEPAR
)
2400 ERROR_IF(!node_builder_parse(builder
, token_get_precedence(tkn
), &right
));
2401 left
= node_create_binary(CMD_CONCAT
, left
, right
);
2406 if (!(done
= token_get_precedence(tkn
) <= precedence
))
2408 node_builder_consume(builder
);
2409 ERROR_IF(!node_builder_parse(builder
, token_get_precedence(tkn
), &right
));
2410 left
= node_create_binary(CMD_ONSUCCESS
, left
, right
);
2415 if (!(done
= token_get_precedence(tkn
) <= precedence
))
2417 node_builder_consume(builder
);
2418 ERROR_IF(!node_builder_parse(builder
, token_get_precedence(tkn
), &right
));
2419 left
= node_create_binary(CMD_PIPE
, left
, right
);
2424 if (!(done
= token_get_precedence(tkn
) <= precedence
))
2426 node_builder_consume(builder
);
2427 ERROR_IF(!node_builder_parse(builder
, token_get_precedence(tkn
), &right
));
2428 left
= node_create_binary(CMD_ONFAILURE
, left
, right
);
2433 left
= node_create_single(pmt
.command
);
2434 node_builder_consume(builder
);
2435 left
->redirects
= redir
;
2443 CMD_IF_CONDITION cond
;
2444 CMD_NODE
*then_block
;
2445 CMD_NODE
*else_block
;
2447 node_builder_consume(builder
);
2448 tkn
= node_builder_peek_next_token(builder
, &pmt
);
2449 ERROR_IF(tkn
!= TKN_COMMAND
);
2450 if (!wcscmp(pmt
.command
, L
"/?"))
2452 node_builder_consume(builder
);
2454 left
= node_create_single(command_create(L
"help if", 7));
2457 ERROR_IF(!if_condition_parse(pmt
.command
, &end
, &cond
));
2459 node_builder_consume(builder
);
2460 if (!node_builder_parse(builder
, 0, &then_block
))
2462 if_condition_dispose(&cond
);
2465 tkn
= node_builder_peek_next_token(builder
, NULL
);
2466 if (tkn
== TKN_ELSE
)
2468 node_builder_consume(builder
);
2469 if (!node_builder_parse(builder
, 0, &else_block
))
2471 if_condition_dispose(&cond
);
2472 node_dispose_tree(then_block
);
2478 left
= node_create_if(&cond
, then_block
, else_block
);
2487 node_builder_consume(builder
);
2488 tkn
= node_builder_peek_next_token(builder
, &pmt
);
2489 ERROR_IF(tkn
!= TKN_COMMAND
);
2490 if (!wcscmp(pmt
.command
, L
"/?"))
2492 node_builder_consume(builder
);
2494 left
= node_create_single(command_create(L
"help for", 8));
2497 node_builder_consume(builder
);
2498 for_ctrl
= for_control_parse(pmt
.command
);
2500 ERROR_IF(for_ctrl
== NULL
);
2501 ERROR_IF(!node_builder_expect_token(builder
, TKN_IN
));
2502 ERROR_IF(!node_builder_expect_token(builder
, TKN_OPENPAR
));
2505 tkn
= node_builder_peek_next_token(builder
, &pmt
);
2509 for_control_append_set(for_ctrl
, pmt
.command
);
2518 node_builder_consume(builder
);
2519 } while (tkn
!= TKN_CLOSEPAR
);
2520 ERROR_IF(!node_builder_expect_token(builder
, TKN_DO
));
2521 ERROR_IF(!node_builder_parse(builder
, 0, &do_block
));
2522 left
= node_create_for(for_ctrl
, do_block
);
2526 case TKN_REDIRECTION
:
2527 ERROR_IF(left
&& (left
->op
== CMD_IF
|| left
->op
== CMD_FOR
));
2528 redirection_list_append(left
? &left
->redirects
: &redir
, pmt
.redirection
);
2529 node_builder_consume(builder
);
2537 TRACE("Parser failed at line %u:token %s\n", bogus_line
, debugstr_token(tkn
, pmt
));
2538 node_dispose_tree(left
);
2539 redirection_dispose_list(redir
);
2540 if (for_ctrl
) for_control_dispose(for_ctrl
);
2545 static BOOL
node_builder_generate(struct node_builder
*builder
, CMD_NODE
**node
)
2547 union token_parameter tkn_pmt
;
2548 enum builder_token tkn
;
2550 if (builder
->opened_parenthesis
)
2552 TRACE("Brackets do not match, error out without executing.\n");
2553 WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADPAREN
));
2557 if (node_builder_parse(builder
, 0, node
) &&
2558 builder
->pos
+ 1 >= builder
->num
) /* consumed all tokens? */
2560 /* print error on first unused token */
2561 if (builder
->pos
< builder
->num
)
2563 WCHAR buffer
[MAXSTRING
];
2564 const WCHAR
*tknstr
;
2566 tkn
= node_builder_peek_next_token(builder
, &tkn_pmt
);
2570 tknstr
= tkn_pmt
.command
;
2573 tknstr
= WCMD_LoadMessage(WCMD_ENDOFFILE
);
2576 tknstr
= WCMD_LoadMessage(WCMD_ENDOFLINE
);
2578 case TKN_REDIRECTION
:
2579 MultiByteToWideChar(CP_ACP
, 0, debugstr_redirection(tkn_pmt
.redirection
), -1, buffer
, ARRAY_SIZE(buffer
));
2593 MultiByteToWideChar(CP_ACP
, 0, debugstr_token(tkn
, tkn_pmt
), -1, buffer
, ARRAY_SIZE(buffer
));
2597 FIXME("Unexpected situation\n");
2601 WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADTOKEN
), tknstr
);
2604 /* free remaining tokens */
2607 tkn
= node_builder_peek_next_token(builder
, &tkn_pmt
);
2608 if (tkn
== TKN_EOF
) break;
2609 if (tkn
== TKN_COMMAND
) free(tkn_pmt
.command
);
2610 if (tkn
== TKN_REDIRECTION
) redirection_dispose_list(tkn_pmt
.redirection
);
2611 node_builder_consume(builder
);
2618 static void lexer_push_command(struct node_builder
*builder
,
2619 WCHAR
*command
, int *commandLen
,
2620 WCHAR
*redirs
, int *redirLen
,
2621 WCHAR
**copyTo
, int **copyToLen
)
2623 union token_parameter tkn_pmt
;
2625 /* push first all redirections */
2629 WCHAR
*last
= redirs
+ *redirLen
;
2631 redirs
[*redirLen
] = 0;
2633 /* Create redirects, keeping order (eg "2>foo 1>&2") */
2634 for (pos
= redirs
; pos
; )
2636 WCHAR
*p
= find_chr(pos
, last
, L
"<>");
2643 filename
= WCMD_parameter(p
+ 1, 0, NULL
, FALSE
, FALSE
);
2644 tkn_pmt
.redirection
= redirection_create_file(REDIR_READ_FROM
, 0, filename
);
2649 unsigned op
= REDIR_WRITE_TO
;
2651 if (p
> redirs
&& p
[-1] >= L
'2' && p
[-1] <= L
'9') fd
= p
[-1] - L
'0';
2652 if (*++p
== L
'>') {p
++; op
= REDIR_WRITE_APPEND
;}
2653 if (*p
== L
'&' && (p
[1] >= L
'0' && p
[1] <= L
'9'))
2655 tkn_pmt
.redirection
= redirection_create_clone(fd
, p
[1] - '0');
2660 filename
= WCMD_parameter(p
, 0, NULL
, FALSE
, FALSE
);
2661 tkn_pmt
.redirection
= redirection_create_file(op
, fd
, filename
);
2665 node_builder_push_token_parameter(builder
, TKN_REDIRECTION
, tkn_pmt
);
2670 tkn_pmt
.command
= command_create(command
, *commandLen
);
2671 node_builder_push_token_parameter(builder
, TKN_COMMAND
, tkn_pmt
);
2673 /* Reset the lengths */
2676 *copyToLen
= commandLen
;
2680 static WCHAR
*fetch_next_line(BOOL feed
, BOOL first_line
, HANDLE from
, WCHAR
* buffer
)
2682 /* display prompt */
2683 if (interactive
&& !context
)
2685 /* native does is this way... not symmetrical wrt. echo_mode */
2687 WCMD_output_asis(WCMD_LoadMessage(WCMD_MOREPROMPT
));
2692 if (feed
&& !WCMD_fgets(buffer
, MAXSTRING
, from
))
2697 /* Handle truncated input - issue warning */
2698 if (wcslen(buffer
) == MAXSTRING
- 1)
2700 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
2701 WCMD_output_asis_stderr(buffer
);
2702 WCMD_output_asis_stderr(L
"\r\n");
2704 /* Replace env vars if in a batch context */
2705 handleExpansion(buffer
, FALSE
);
2707 buffer
= WCMD_skip_leading_spaces(buffer
);
2708 /* Show prompt before batch line IF echo is on and in batch program */
2709 if (context
&& echo_mode
&& *buffer
&& *buffer
!= '@')
2713 const size_t len
= wcslen(L
"echo.");
2714 size_t curr_size
= wcslen(buffer
);
2715 size_t min_len
= curr_size
< len
? curr_size
: len
;
2716 WCMD_output_asis(L
"\r\n");
2718 WCMD_output_asis(buffer
);
2719 /* I don't know why Windows puts a space here but it does */
2720 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
2721 if (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
2722 buffer
, min_len
, L
"echo.", len
) != CSTR_EQUAL
2723 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
2724 buffer
, min_len
, L
"echo:", len
) != CSTR_EQUAL
2725 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
2726 buffer
, min_len
, L
"echo/", len
) != CSTR_EQUAL
)
2728 WCMD_output_asis(L
" ");
2732 WCMD_output_asis(buffer
);
2734 WCMD_output_asis(L
"\r\n");
2737 /* Skip repeated 'no echo' characters and whitespace */
2738 while (*buffer
== '@' || *buffer
== L
' ' || *buffer
== L
'\t') buffer
++;
2742 /***************************************************************************
2743 * WCMD_ReadAndParseLine
2745 * Either uses supplied input or
2746 * Reads a file from the handle, and then...
2747 * Parse the text buffer, splitting into separate commands
2748 * - unquoted && strings split 2 commands but the 2nd is flagged as
2750 * - ( as the first character just ups the bracket depth
2751 * - unquoted ) when bracket depth > 0 terminates a bracket and
2752 * adds a CMD_LIST structure with null command
2753 * - Anything else gets put into the command string (including
2756 enum read_parse_line
WCMD_ReadAndParseLine(const WCHAR
*optionalcmd
, CMD_NODE
**output
, HANDLE readFrom
)
2760 WCHAR curString
[MAXSTRING
];
2761 int curStringLen
= 0;
2762 WCHAR curRedirs
[MAXSTRING
];
2763 int curRedirsLen
= 0;
2766 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
2767 BOOL inOneLine
= FALSE
;
2771 BOOL onlyWhiteSpace
= FALSE
;
2772 BOOL lastWasWhiteSpace
= FALSE
;
2773 BOOL lastWasDo
= FALSE
;
2774 BOOL lastWasIn
= FALSE
;
2775 BOOL lastWasElse
= FALSE
;
2776 BOOL lastWasRedirect
= TRUE
;
2777 BOOL ignoreBracket
= FALSE
; /* Some expressions after if (set) require */
2778 /* handling brackets as a normal character */
2779 BOOL acceptCommand
= TRUE
;
2780 struct node_builder builder
;
2784 /* Allocate working space for a command read from keyboard, file etc */
2786 extraSpace
= xalloc((MAXSTRING
+ 1) * sizeof(WCHAR
));
2788 /* If initial command read in, use that, otherwise get input from handle */
2790 wcscpy(extraSpace
, optionalcmd
);
2791 if (!(curPos
= fetch_next_line(optionalcmd
== NULL
, TRUE
, readFrom
, extraSpace
)))
2794 TRACE("About to parse line (%ls)\n", extraSpace
);
2796 node_builder_init(&builder
);
2798 /* Start with an empty string, copying to the command string */
2801 curCopyTo
= curString
;
2802 curLen
= &curStringLen
;
2803 lastWasRedirect
= FALSE
; /* Required e.g. for spaces between > and filename */
2804 onlyWhiteSpace
= TRUE
;
2806 /* Parse every character on the line being processed */
2807 while (*curPos
!= 0x00) {
2812 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2813 lastWasWhiteSpace, onlyWhiteSpace);
2816 /* Prevent overflow caused by the caret escape char */
2817 if (*curLen
>= MAXSTRING
) {
2818 WINE_ERR("Overflow detected in command\n");
2819 return RPL_SYNTAXERROR
;
2822 /* Certain commands need special handling */
2823 if (curStringLen
== 0 && curCopyTo
== curString
) {
2824 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
2825 if (WCMD_keyword_ws_found(L
"rem", curPos
) || *curPos
== ':') {
2828 } else if (WCMD_keyword_ws_found(L
"for", curPos
)) {
2830 node_builder_push_token(&builder
, TKN_FOR
);
2832 curPos
= WCMD_skip_leading_spaces(curPos
+ 3); /* "for */
2833 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
2834 is only true in the command portion of the IF statement, but this
2835 should suffice for now.
2836 To be able to handle ('s in the condition part take as much as evaluate_if_condition
2837 would take and skip parsing it here. */
2838 acceptCommand
= FALSE
;
2839 } else if (acceptCommand
&& WCMD_keyword_ws_found(L
"if", curPos
)) {
2842 node_builder_push_token(&builder
, TKN_IF
);
2846 curPos
= WCMD_skip_leading_spaces(curPos
+ 2); /* "if" */
2847 if (if_condition_parse(curPos
, &command
, NULL
))
2849 int if_condition_len
= command
- curPos
;
2850 TRACE("p: %s, command: %s, if_condition_len: %d\n",
2851 wine_dbgstr_w(curPos
), wine_dbgstr_w(command
), if_condition_len
);
2852 memcpy(&curCopyTo
[*curLen
], curPos
, if_condition_len
* sizeof(WCHAR
));
2853 (*curLen
) += if_condition_len
;
2854 curPos
+= if_condition_len
;
2856 /* FIXME we do parsing twice of condition (once here, second time in node_builder_parse) */
2857 lexer_push_command(&builder
, curString
, &curStringLen
,
2858 curRedirs
, &curRedirsLen
,
2859 &curCopyTo
, &curLen
);
2862 if (WCMD_keyword_ws_found(L
"set", curPos
))
2863 ignoreBracket
= TRUE
;
2864 acceptCommand
= TRUE
;
2865 onlyWhiteSpace
= TRUE
;
2867 } else if (WCMD_keyword_ws_found(L
"else", curPos
)) {
2870 acceptCommand
= TRUE
;
2871 onlyWhiteSpace
= TRUE
;
2872 node_builder_push_token(&builder
, TKN_ELSE
);
2874 curPos
= WCMD_skip_leading_spaces(curPos
+ 4 /* else */);
2877 /* In a for loop, the DO command will follow a close bracket followed by
2878 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2879 is then 0, and all whitespace is skipped */
2880 } else if (inFor
&& WCMD_keyword_ws_found(L
"do", curPos
)) {
2882 WINE_TRACE("Found 'DO '\n");
2884 acceptCommand
= TRUE
;
2885 onlyWhiteSpace
= TRUE
;
2887 node_builder_push_token(&builder
, TKN_DO
);
2888 curPos
= WCMD_skip_leading_spaces(curPos
+ 2 /* do */);
2891 } else if (curCopyTo
== curString
) {
2893 /* Special handling for the 'FOR' command */
2894 if (inFor
&& lastWasWhiteSpace
) {
2895 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2897 if (WCMD_keyword_ws_found(L
"in", curPos
)) {
2898 WINE_TRACE("Found 'IN '\n");
2900 lexer_push_command(&builder
, curString
, &curStringLen
,
2901 curRedirs
, &curRedirsLen
,
2902 &curCopyTo
, &curLen
);
2903 node_builder_push_token(&builder
, TKN_IN
);
2905 onlyWhiteSpace
= TRUE
;
2906 curPos
= WCMD_skip_leading_spaces(curPos
+ 2 /* in */);
2912 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2913 the &&, quotes and redirection etc are ineffective, so just force
2914 the use of the default processing by skipping character specific
2916 if (!inOneLine
) thisChar
= *curPos
;
2917 else thisChar
= 'X'; /* Character with no special processing */
2919 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2923 case '=': /* drop through - ignore token delimiters at the start of a command */
2924 case ',': /* drop through - ignore token delimiters at the start of a command */
2925 case '\t':/* drop through - ignore token delimiters at the start of a command */
2927 /* If a redirect in place, it ends here */
2928 if (!inQuotes
&& !lastWasRedirect
) {
2930 /* If finishing off a redirect, add a whitespace delimiter */
2931 if (curCopyTo
== curRedirs
) {
2932 curCopyTo
[(*curLen
)++] = ' ';
2933 if (curStringLen
== 0)
2934 onlyWhiteSpace
= TRUE
;
2936 curCopyTo
= curString
;
2937 curLen
= &curStringLen
;
2940 curCopyTo
[(*curLen
)++] = *curPos
;
2943 /* Remember just processed whitespace */
2944 lastWasWhiteSpace
= TRUE
;
2948 case '>': /* drop through - handle redirect chars the same */
2950 /* Make a redirect start here */
2952 curCopyTo
= curRedirs
;
2953 curLen
= &curRedirsLen
;
2954 lastWasRedirect
= TRUE
;
2957 /* See if 1>, 2> etc, in which case we have some patching up
2958 to do (provided there's a preceding whitespace, and enough
2959 chars read so far) */
2960 if (curPos
[-1] >= '1' && curPos
[-1] <= '9'
2961 && (curStringLen
== 1 ||
2962 curPos
[-2] == ' ' || curPos
[-2] == '\t')) {
2964 curString
[curStringLen
] = 0x00;
2965 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2968 curCopyTo
[(*curLen
)++] = *curPos
;
2970 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2971 do not process that ampersand as an AND operator */
2972 if (thisChar
== '>' && *(curPos
+1) == '&') {
2973 curCopyTo
[(*curLen
)++] = *(curPos
+1);
2978 case '|': /* Pipe character only if not || */
2980 lastWasRedirect
= FALSE
;
2982 lexer_push_command(&builder
, curString
, &curStringLen
,
2983 curRedirs
, &curRedirsLen
,
2984 &curCopyTo
, &curLen
);
2986 if (*(curPos
+1) == '|') {
2987 curPos
++; /* Skip other | */
2988 node_builder_push_token(&builder
, TKN_BARBAR
);
2990 node_builder_push_token(&builder
, TKN_BAR
);
2992 acceptCommand
= TRUE
;
2993 onlyWhiteSpace
= TRUE
;
2996 curCopyTo
[(*curLen
)++] = *curPos
;
3000 case '"': if (WCMD_IsEndQuote(curPos
, inQuotes
)) {
3003 inQuotes
++; /* Quotes within quotes are fun! */
3005 curCopyTo
[(*curLen
)++] = *curPos
;
3006 lastWasRedirect
= FALSE
;
3009 case '(': /* If a '(' is the first non whitespace in a command portion
3010 ie start of line or just after &&, then we read until an
3011 unquoted ) is found */
3012 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
3013 ", for(%d, In:%d, Do:%d)"
3014 ", if(%d, else:%d, lwe:%d)\n",
3017 inFor
, lastWasIn
, lastWasDo
,
3018 inIf
, inElse
, lastWasElse
);
3019 lastWasRedirect
= FALSE
;
3022 curCopyTo
[(*curLen
)++] = *curPos
;
3024 /* In a FOR loop, an unquoted '(' may occur straight after
3026 In an IF statement just handle it regardless as we don't
3028 In an ELSE statement, only allow it straight away after
3029 the ELSE and whitespace
3031 } else if ((acceptCommand
&& onlyWhiteSpace
) ||
3032 (inIf
&& !ignoreBracket
) ||
3033 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
3034 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
3036 /* Add the current command */
3037 lexer_push_command(&builder
, curString
, &curStringLen
,
3038 curRedirs
, &curRedirsLen
,
3039 &curCopyTo
, &curLen
);
3040 node_builder_push_token(&builder
, TKN_OPENPAR
);
3041 acceptCommand
= TRUE
;
3042 onlyWhiteSpace
= TRUE
;
3045 curCopyTo
[(*curLen
)++] = *curPos
;
3049 case '^': if (!inQuotes
) {
3050 /* If we reach the end of the input, we need to wait for more */
3051 if (curPos
[1] == L
'\0') {
3052 TRACE("Caret found at end of line\n");
3053 extraSpace
[0] = L
'^';
3054 if (!fetch_next_line(TRUE
, FALSE
, readFrom
, extraSpace
+ 1))
3056 if (!extraSpace
[1]) /* empty line */
3058 extraSpace
[1] = L
'\r';
3059 if (!fetch_next_line(TRUE
, FALSE
, readFrom
, extraSpace
+ 2))
3062 curPos
= extraSpace
;
3067 curCopyTo
[(*curLen
)++] = *curPos
;
3070 case '&': if (!inQuotes
) {
3071 lastWasRedirect
= FALSE
;
3073 /* Add an entry to the command list */
3074 lexer_push_command(&builder
, curString
, &curStringLen
,
3075 curRedirs
, &curRedirsLen
,
3076 &curCopyTo
, &curLen
);
3078 if (*(curPos
+1) == '&') {
3079 curPos
++; /* Skip other & */
3080 node_builder_push_token(&builder
, TKN_AMPAMP
);
3082 node_builder_push_token(&builder
, TKN_AMP
);
3084 acceptCommand
= TRUE
;
3085 onlyWhiteSpace
= TRUE
;
3088 curCopyTo
[(*curLen
)++] = *curPos
;
3092 case ')': if (!inQuotes
&& builder
.opened_parenthesis
> 0) {
3093 lastWasRedirect
= FALSE
;
3095 /* Add the current command if there is one */
3096 lexer_push_command(&builder
, curString
, &curStringLen
,
3097 curRedirs
, &curRedirsLen
,
3098 &curCopyTo
, &curLen
);
3099 node_builder_push_token(&builder
, TKN_CLOSEPAR
);
3100 acceptCommand
= FALSE
;
3101 onlyWhiteSpace
= TRUE
;
3104 curCopyTo
[(*curLen
)++] = *curPos
;
3108 lastWasRedirect
= FALSE
;
3109 curCopyTo
[(*curLen
)++] = *curPos
;
3114 /* At various times we need to know if we have only skipped whitespace,
3115 so reset this variable and then it will remain true until a non
3116 whitespace is found */
3117 if ((thisChar
!= ' ') && (thisChar
!= '\t') && (thisChar
!= '\n'))
3118 onlyWhiteSpace
= FALSE
;
3120 /* If we have reached the end, add this command into the list
3121 Do not add command to list if escape char ^ was last */
3122 if (*curPos
== L
'\0') {
3123 /* Add an entry to the command list */
3124 lexer_push_command(&builder
, curString
, &curStringLen
,
3125 curRedirs
, &curRedirsLen
,
3126 &curCopyTo
, &curLen
);
3127 node_builder_push_token(&builder
, TKN_EOL
);
3129 /* If we have reached the end of the string, see if bracketing is outstanding */
3130 if (builder
.opened_parenthesis
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
3131 TRACE("Need to read more data as outstanding brackets or carets\n");
3133 ignoreBracket
= FALSE
;
3135 acceptCommand
= TRUE
;
3136 onlyWhiteSpace
= TRUE
;
3138 /* fetch next non empty line */
3140 curPos
= fetch_next_line(TRUE
, FALSE
, readFrom
, extraSpace
);
3141 } while (curPos
&& *curPos
== L
'\0');
3143 curPos
= extraSpace
;
3148 ret
= node_builder_generate(&builder
, output
);
3149 node_builder_dispose(&builder
);
3151 return ret
? RPL_SUCCESS
: RPL_SYNTAXERROR
;
3154 static BOOL
if_condition_evaluate(CMD_IF_CONDITION
*cond
, int *test
)
3156 WCHAR expanded_left
[MAXSTRING
];
3157 WCHAR expanded_right
[MAXSTRING
];
3158 int (WINAPI
*cmp
)(const WCHAR
*, const WCHAR
*) = cond
->case_insensitive
? lstrcmpiW
: lstrcmpW
;
3160 TRACE("About to evaluate condition %s\n", debugstr_if_condition(cond
));
3164 case CMD_IF_ERRORLEVEL
:
3169 wcscpy(expanded_left
, cond
->operand
);
3170 handleExpansion(expanded_left
, TRUE
);
3171 level
= wcstol(expanded_left
, &endptr
, 10);
3172 if (*endptr
) return FALSE
;
3173 *test
= errorlevel
>= level
;
3179 WIN32_FIND_DATAW fd
;
3182 wcscpy(expanded_left
, cond
->operand
);
3183 handleExpansion(expanded_left
, TRUE
);
3184 if ((len
= wcslen(expanded_left
)))
3186 /* FindFirstFile does not like a directory path ending in '\' or '/', so append a '.' */
3187 if ((expanded_left
[len
- 1] == '\\' || expanded_left
[len
- 1] == '/') && len
< MAXSTRING
- 1)
3189 wcscat(expanded_left
, L
".");
3191 hff
= FindFirstFileW(expanded_left
, &fd
);
3192 *test
= (hff
!= INVALID_HANDLE_VALUE
);
3193 if (*test
) FindClose(hff
);
3197 case CMD_IF_DEFINED
:
3198 wcscpy(expanded_left
, cond
->operand
);
3199 handleExpansion(expanded_left
, TRUE
);
3200 *test
= GetEnvironmentVariableW(expanded_left
, NULL
, 0) > 0;
3202 case CMD_IF_BINOP_EQUAL
:
3203 wcscpy(expanded_left
, cond
->left
);
3204 handleExpansion(expanded_left
, TRUE
);
3205 wcscpy(expanded_right
, cond
->right
);
3206 handleExpansion(expanded_right
, TRUE
);
3208 /* == is a special case, as it always compares strings */
3209 *test
= (*cmp
)(expanded_left
, expanded_right
) == 0;
3213 int left_int
, right_int
;
3214 WCHAR
*end_left
, *end_right
;
3217 wcscpy(expanded_left
, cond
->left
);
3218 handleExpansion(expanded_left
, TRUE
);
3219 wcscpy(expanded_right
, cond
->right
);
3220 handleExpansion(expanded_right
, TRUE
);
3222 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
3223 left_int
= wcstol(expanded_left
, &end_left
, 0);
3224 right_int
= wcstol(expanded_right
, &end_right
, 0);
3225 if (end_left
> expanded_left
&& !*end_left
&& end_right
> expanded_right
&& !*end_right
)
3226 cmp_val
= left_int
- right_int
;
3228 cmp_val
= (*cmp
)(expanded_left
, expanded_right
);
3231 case CMD_IF_BINOP_LSS
: *test
= cmp_val
< 0; break;
3232 case CMD_IF_BINOP_LEQ
: *test
= cmp_val
<= 0; break;
3233 case CMD_IF_BINOP_EQU
: *test
= cmp_val
== 0; break;
3234 case CMD_IF_BINOP_NEQ
: *test
= cmp_val
!= 0; break;
3235 case CMD_IF_BINOP_GEQ
: *test
= cmp_val
>= 0; break;
3236 case CMD_IF_BINOP_GTR
: *test
= cmp_val
> 0; break;
3238 FIXME("Unexpected comparison operator %u\n", cond
->op
);
3244 if (cond
->negated
) *test
^= 1;
3248 static RETURN_CODE
for_loop_fileset_parse_line(CMD_NODE
*node
, int varidx
, WCHAR
*buffer
,
3249 WCHAR forf_eol
, const WCHAR
*forf_delims
, const WCHAR
*forf_tokens
)
3251 RETURN_CODE return_code
= NO_ERROR
;
3254 int nexttoken
, lasttoken
= -1;
3255 BOOL starfound
= FALSE
;
3256 BOOL thisduplicate
= FALSE
;
3257 BOOL anyduplicates
= FALSE
;
3259 static WCHAR emptyW
[] = L
"";
3261 /* Extract the parameters based on the tokens= value (There will always
3262 be some value, as if it is not supplied, it defaults to tokens=1).
3264 Count how many tokens are named in the line, identify the lowest
3265 Empty (set to null terminated string) that number of named variables
3266 While lasttoken != nextlowest
3267 %letter = parameter number 'nextlowest'
3268 letter++ (if >26 or >52 abort)
3269 Go through token= string finding next lowest number
3270 If token ends in * set %letter = raw position of token(nextnumber+1)
3273 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, &totalfound
,
3274 &starfound
, &thisduplicate
);
3276 TRACE("Using var=%lc on %d max\n", for_var_index_to_char(varidx
), totalfound
);
3277 /* Empty out variables */
3279 varoffset
< totalfound
&& for_var_index_in_range(varidx
, varoffset
);
3281 WCMD_set_for_loop_variable(varidx
+ varoffset
, emptyW
);
3283 /* Loop extracting the tokens
3284 * Note: nexttoken of 0 means there were no tokens requested, to handle
3285 * the special case of tokens=*
3288 TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer
));
3289 while (nexttoken
> 0 && (nexttoken
> lasttoken
))
3291 anyduplicates
|= thisduplicate
;
3293 if (!for_var_index_in_range(varidx
, varoffset
))
3295 WARN("Out of range offset\n");
3298 /* Extract the token number requested and set into the next variable context */
3299 parm
= WCMD_parameter_with_delims(buffer
, (nexttoken
-1), NULL
, TRUE
, FALSE
, forf_delims
);
3300 TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken
,
3301 varidx
+ varoffset
, wine_dbgstr_w(parm
));
3304 WCMD_set_for_loop_variable(varidx
+ varoffset
, parm
);
3308 /* Find the next token */
3309 lasttoken
= nexttoken
;
3310 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, NULL
,
3311 &starfound
, &thisduplicate
);
3313 /* If all the rest of the tokens were requested, and there is still space in
3314 * the variable range, write them now
3316 if (!anyduplicates
&& starfound
&& for_var_index_in_range(varidx
, varoffset
))
3319 WCMD_parameter_with_delims(buffer
, (nexttoken
-1), &parm
, FALSE
, FALSE
, forf_delims
);
3320 TRACE("Parsed all remaining tokens (%d) as parameter %s\n",
3321 varidx
+ varoffset
, wine_dbgstr_w(parm
));
3323 WCMD_set_for_loop_variable(varidx
+ varoffset
, parm
);
3326 /* Execute the body of the for loop with these values */
3327 if (forloopcontext
->variable
[varidx
] && forloopcontext
->variable
[varidx
][0] != forf_eol
)
3329 return_code
= node_execute(node
);
3333 TRACE("Skipping line because of eol\n");
3338 void WCMD_save_for_loop_context(BOOL reset
)
3340 FOR_CONTEXT
*new = xalloc(sizeof(*new));
3342 memset(new, 0, sizeof(*new));
3343 else /* clone existing */
3344 *new = *forloopcontext
;
3345 new->previous
= forloopcontext
;
3346 forloopcontext
= new;
3349 void WCMD_restore_for_loop_context(void)
3351 FOR_CONTEXT
*old
= forloopcontext
->previous
;
3355 FIXME("Unexpected situation\n");
3358 for (varidx
= 0; varidx
< MAX_FOR_VARIABLES
; varidx
++)
3360 if (forloopcontext
->variable
[varidx
] != old
->variable
[varidx
])
3361 free(forloopcontext
->variable
[varidx
]);
3363 free(forloopcontext
);
3364 forloopcontext
= old
;
3367 void WCMD_set_for_loop_variable(int var_idx
, const WCHAR
*value
)
3369 if (var_idx
< 0 || var_idx
>= MAX_FOR_VARIABLES
) return;
3370 if (forloopcontext
->previous
&&
3371 forloopcontext
->previous
->variable
[var_idx
] != forloopcontext
->variable
[var_idx
])
3372 free(forloopcontext
->variable
[var_idx
]);
3373 forloopcontext
->variable
[var_idx
] = xstrdupW(value
);
3376 static BOOL
match_ending_delim(WCHAR
*string
)
3378 WCHAR
*to
= string
+ wcslen(string
);
3380 /* strip trailing delim */
3381 if (to
> string
) to
--;
3382 if (to
> string
&& *to
== string
[0])
3387 WARN("Can't find ending delimiter (%ls)\n", string
);
3391 static RETURN_CODE
for_control_execute_from_FILE(CMD_FOR_CONTROL
*for_ctrl
, FILE *input
, CMD_NODE
*node
)
3393 WCHAR buffer
[MAXSTRING
];
3394 int skip_count
= for_ctrl
->num_lines_to_skip
;
3395 RETURN_CODE return_code
= NO_ERROR
;
3397 /* Read line by line until end of file */
3398 while (fgetws(buffer
, ARRAY_SIZE(buffer
), input
))
3404 TRACE("skipping %d\n", skip_count
);
3408 len
= wcslen(buffer
);
3409 /* Either our buffer isn't large enough to fit a full line, or there's a stray
3410 * '\0' in the buffer.
3412 if (!feof(input
) && (len
== 0 || (buffer
[len
- 1] != '\n' && buffer
[len
- 1] != '\r')))
3414 while (len
&& (buffer
[len
- 1] == '\n' || buffer
[len
- 1] == '\r'))
3415 buffer
[--len
] = L
'\0';
3416 return_code
= for_loop_fileset_parse_line(node
, for_ctrl
->variable_index
, buffer
,
3417 for_ctrl
->eol
, for_ctrl
->delims
, for_ctrl
->tokens
);
3423 static RETURN_CODE
for_control_execute_fileset(CMD_FOR_CONTROL
*for_ctrl
, CMD_NODE
*node
)
3425 RETURN_CODE return_code
= NO_ERROR
;
3426 WCHAR set
[MAXSTRING
];
3432 wcscpy(set
, for_ctrl
->set
);
3433 handleExpansion(set
, TRUE
);
3435 args
= WCMD_skip_leading_spaces(set
);
3436 for (len
= wcslen(args
); len
&& (args
[len
- 1] == L
' ' || args
[len
- 1] == L
'\t'); len
--)
3437 args
[len
- 1] = L
'\0';
3438 if (args
[0] == (for_ctrl
->use_backq
? L
'\'' : L
'"') && match_ending_delim(args
))
3441 if (!for_ctrl
->num_lines_to_skip
)
3443 return_code
= for_loop_fileset_parse_line(node
, for_ctrl
->variable_index
, args
,
3444 for_ctrl
->eol
, for_ctrl
->delims
, for_ctrl
->tokens
);
3447 else if (args
[0] == (for_ctrl
->use_backq
? L
'`' : L
'\'') && match_ending_delim(args
))
3449 WCHAR temp_cmd
[MAX_PATH
];
3452 wsprintfW(temp_cmd
, L
"CMD.EXE /C %s", args
);
3453 TRACE("Reading output of '%s'\n", wine_dbgstr_w(temp_cmd
));
3454 input
= _wpopen(temp_cmd
, L
"rt,ccs=unicode");
3458 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), args
);
3459 return errorlevel
= ERROR_INVALID_FUNCTION
; /* FOR loop aborts at first failure here */
3461 return_code
= for_control_execute_from_FILE(for_ctrl
, input
, node
);
3468 WCHAR
*element
= WCMD_parameter(args
, i
, NULL
, TRUE
, FALSE
);
3469 if (!element
|| !*element
) break;
3470 if (element
[0] == L
'"' && match_ending_delim(element
)) element
++;
3471 /* Open the file, read line by line and process */
3472 TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(element
));
3473 input
= _wfopen(element
, L
"rt,ccs=unicode");
3477 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), element
);
3478 return errorlevel
= ERROR_INVALID_FUNCTION
; /* FOR loop aborts at first failure here */
3480 return_code
= for_control_execute_from_FILE(for_ctrl
, input
, node
);
3488 static RETURN_CODE
for_control_execute_set(CMD_FOR_CONTROL
*for_ctrl
, const WCHAR
*from_dir
, size_t ref_len
, CMD_NODE
*node
)
3490 RETURN_CODE return_code
= NO_ERROR
;
3492 WCHAR set
[MAXSTRING
];
3493 WCHAR buffer
[MAX_PATH
];
3498 len
= wcslen(from_dir
) + 1;
3499 wcscpy(buffer
, from_dir
);
3500 wcscat(buffer
, L
"\\");
3505 wcscpy(set
, for_ctrl
->set
);
3506 handleExpansion(set
, TRUE
);
3509 WCHAR
*element
= WCMD_parameter(set
, i
, NULL
, TRUE
, FALSE
);
3510 if (!element
|| !*element
) break;
3511 if (len
+ wcslen(element
) + 1 >= ARRAY_SIZE(buffer
)) continue;
3513 wcscpy(&buffer
[len
], element
);
3515 TRACE("Doing set element %ls\n", buffer
);
3517 if (wcspbrk(element
, L
"?*"))
3519 WIN32_FIND_DATAW fd
;
3520 HANDLE hff
= FindFirstFileW(buffer
, &fd
);
3521 size_t insert_pos
= (wcsrchr(buffer
, L
'\\') ? wcsrchr(buffer
, L
'\\') + 1 - buffer
: 0);
3523 if (hff
== INVALID_HANDLE_VALUE
)
3525 TRACE("Couldn't FindFirstFile on %ls\n", buffer
);
3530 TRACE("Considering %ls\n", fd
.cFileName
);
3531 if (!lstrcmpW(fd
.cFileName
, L
"..") || !lstrcmpW(fd
.cFileName
, L
".")) continue;
3532 if (!(for_ctrl
->flags
& CMD_FOR_FLAG_TREE_INCLUDE_FILES
) &&
3533 !(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
3535 if (!(for_ctrl
->flags
& CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES
) &&
3536 (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
3539 if (insert_pos
+ wcslen(fd
.cFileName
) + 1 >= ARRAY_SIZE(buffer
)) continue;
3540 wcscpy(&buffer
[insert_pos
], fd
.cFileName
);
3541 WCMD_set_for_loop_variable(for_ctrl
->variable_index
, buffer
);
3542 return_code
= node_execute(node
);
3543 } while (FindNextFileW(hff
, &fd
) != 0);
3548 WCMD_set_for_loop_variable(for_ctrl
->variable_index
, buffer
);
3549 return_code
= node_execute(node
);
3555 static RETURN_CODE
for_control_execute_walk_files(CMD_FOR_CONTROL
*for_ctrl
, CMD_NODE
*node
)
3557 DIRECTORY_STACK
*dirs_to_walk
;
3559 RETURN_CODE return_code
= NO_ERROR
;
3561 if (for_ctrl
->root_dir
)
3563 WCHAR buffer
[MAXSTRING
];
3565 wcscpy(buffer
, for_ctrl
->root_dir
);
3566 handleExpansion(buffer
, TRUE
);
3567 dirs_to_walk
= WCMD_dir_stack_create(buffer
, NULL
);
3569 else dirs_to_walk
= WCMD_dir_stack_create(NULL
, NULL
);
3570 ref_len
= wcslen(dirs_to_walk
->dirName
);
3572 while (dirs_to_walk
)
3574 TRACE("About to walk %p %ls for %s\n", dirs_to_walk
, dirs_to_walk
->dirName
, debugstr_for_control(for_ctrl
));
3575 if (for_ctrl
->flags
& CMD_FOR_FLAG_TREE_RECURSE
)
3576 WCMD_add_dirstowalk(dirs_to_walk
);
3578 return_code
= for_control_execute_set(for_ctrl
, dirs_to_walk
->dirName
, ref_len
, node
);
3579 /* If we are walking directories, move on to any which remain */
3580 dirs_to_walk
= WCMD_dir_stack_free(dirs_to_walk
);
3582 TRACE("Finished all directories.\n");
3587 static RETURN_CODE
for_control_execute_numbers(CMD_FOR_CONTROL
*for_ctrl
, CMD_NODE
*node
)
3589 RETURN_CODE return_code
= NO_ERROR
;
3590 WCHAR set
[MAXSTRING
];
3591 int numbers
[3] = {0, 0, 0}, var
;
3594 wcscpy(set
, for_ctrl
->set
);
3595 handleExpansion(set
, TRUE
);
3597 /* Note: native doesn't check the actual number of parameters, and set
3598 * them by default to 0.
3599 * so (-10 1) is interpreted as (-10 1 0)
3600 * and (10) loops for ever !!!
3602 for (i
= 0; i
< ARRAY_SIZE(numbers
); i
++)
3604 WCHAR
*element
= WCMD_parameter(set
, i
, NULL
, FALSE
, FALSE
);
3605 if (!element
|| !*element
) break;
3606 /* native doesn't no error handling */
3607 numbers
[i
] = wcstol(element
, NULL
, 0);
3610 for (var
= numbers
[0];
3611 (numbers
[1] < 0) ? var
>= numbers
[2] : var
<= numbers
[2];
3616 swprintf(tmp
, ARRAY_SIZE(tmp
), L
"%d", var
);
3617 WCMD_set_for_loop_variable(for_ctrl
->variable_index
, tmp
);
3618 TRACE("Processing FOR number %s\n", wine_dbgstr_w(tmp
));
3619 return_code
= node_execute(node
);
3624 static RETURN_CODE
for_control_execute(CMD_FOR_CONTROL
*for_ctrl
, CMD_NODE
*node
)
3626 RETURN_CODE return_code
;
3628 if (!for_ctrl
->set
) return NO_ERROR
;
3630 WCMD_save_for_loop_context(FALSE
);
3632 switch (for_ctrl
->operator)
3634 case CMD_FOR_FILETREE
:
3635 if (for_ctrl
->flags
& CMD_FOR_FLAG_TREE_RECURSE
)
3636 return_code
= for_control_execute_walk_files(for_ctrl
, node
);
3638 return_code
= for_control_execute_set(for_ctrl
, NULL
, 0, node
);
3640 case CMD_FOR_FILE_SET
:
3641 return_code
= for_control_execute_fileset(for_ctrl
, node
);
3643 case CMD_FOR_NUMBERS
:
3644 return_code
= for_control_execute_numbers(for_ctrl
, node
);
3647 return_code
= NO_ERROR
;
3650 WCMD_restore_for_loop_context();
3654 RETURN_CODE
node_execute(CMD_NODE
*node
)
3656 HANDLE old_stdhandles
[3] = {GetStdHandle (STD_INPUT_HANDLE
),
3657 GetStdHandle (STD_OUTPUT_HANDLE
),
3658 GetStdHandle (STD_ERROR_HANDLE
)};
3659 static DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
, STD_OUTPUT_HANDLE
, STD_ERROR_HANDLE
};
3661 RETURN_CODE return_code
;
3664 if (!node
) return NO_ERROR
;
3665 if (!set_std_redirections(node
->redirects
))
3668 return_code
= ERROR_INVALID_FUNCTION
;
3670 else switch (node
->op
)
3673 if (node
->command
[0] != ':')
3674 return_code
= execute_single_command(node
->command
);
3675 else return_code
= NO_ERROR
;
3678 return_code
= node_execute(node
->left
);
3679 if (return_code
!= RETURN_CODE_ABORTED
)
3680 return_code
= node_execute(node
->right
);
3683 return_code
= node_execute(node
->left
);
3684 if (return_code
== NO_ERROR
)
3685 return_code
= node_execute(node
->right
);
3688 return_code
= node_execute(node
->left
);
3689 if (return_code
!= NO_ERROR
&& return_code
!= RETURN_CODE_ABORTED
)
3691 /* that's needed for commands (POPD, RMDIR) that don't set errorlevel in case of failure. */
3692 errorlevel
= return_code
;
3693 return_code
= node_execute(node
->right
);
3698 static SECURITY_ATTRIBUTES sa
= {.nLength
= sizeof(sa
), .lpSecurityDescriptor
= NULL
, .bInheritHandle
= TRUE
};
3699 WCHAR temp_path
[MAX_PATH
];
3700 WCHAR filename
[MAX_PATH
];
3701 CMD_REDIRECTION
*output
;
3702 HANDLE saved_output
;
3703 BATCH_CONTEXT
*saved_context
= context
;
3705 /* pipe LHS & RHS are run outside of any batch context */
3707 /* FIXME: a real pipe instead of writing to an intermediate file would be
3709 * But waiting for completion of commands will require more work.
3711 /* FIXME check precedence (eg foo > a | more)
3712 * with following code, | has higher precedence than > a
3713 * (which is likely wrong IIRC, and not what previous code was doing)
3715 /* Generate a unique temporary filename */
3716 GetTempPathW(ARRAY_SIZE(temp_path
), temp_path
);
3717 GetTempFileNameW(temp_path
, L
"CMD", 0, filename
);
3718 TRACE("Using temporary file of %ls\n", filename
);
3720 saved_output
= GetStdHandle(STD_OUTPUT_HANDLE
);
3721 /* set output for left hand side command */
3722 output
= redirection_create_file(REDIR_WRITE_TO
, 1, filename
);
3723 if (set_std_redirections(output
))
3725 RETURN_CODE return_code_left
= node_execute(node
->left
);
3726 CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE
));
3727 SetStdHandle(STD_OUTPUT_HANDLE
, saved_output
);
3729 if (errorlevel
== RETURN_CODE_CANT_LAUNCH
&& saved_context
)
3731 return_code
= ERROR_INVALID_FUNCTION
;
3732 if (return_code_left
!= RETURN_CODE_ABORTED
&& errorlevel
!= RETURN_CODE_CANT_LAUNCH
)
3734 HANDLE h
= CreateFileW(filename
, GENERIC_READ
,
3735 FILE_SHARE_READ
| FILE_SHARE_WRITE
, &sa
, OPEN_EXISTING
,
3736 FILE_ATTRIBUTE_NORMAL
, NULL
);
3737 if (h
!= INVALID_HANDLE_VALUE
)
3739 SetStdHandle(STD_INPUT_HANDLE
, h
);
3740 return_code
= node_execute(node
->right
);
3741 if (errorlevel
== RETURN_CODE_CANT_LAUNCH
&& saved_context
)
3745 DeleteFileW(filename
);
3746 errorlevel
= return_code
;
3748 else return_code
= ERROR_INVALID_FUNCTION
;
3749 redirection_dispose_list(output
);
3750 context
= saved_context
;
3754 if (if_condition_evaluate(&node
->condition
, &test
))
3755 return_code
= node_execute(test
? node
->then_block
: node
->else_block
);
3757 return_code
= ERROR_INVALID_FUNCTION
;
3760 return_code
= for_control_execute(&node
->for_ctrl
, node
->do_block
);
3763 FIXME("Unexpected operator %u\n", node
->op
);
3764 return_code
= ERROR_INVALID_FUNCTION
;
3766 /* Restore old handles */
3767 for (i
= 0; i
< 3; i
++)
3769 if (old_stdhandles
[i
] != GetStdHandle(idx_stdhandles
[i
]))
3771 CloseHandle(GetStdHandle(idx_stdhandles
[i
]));
3772 SetStdHandle(idx_stdhandles
[i
], old_stdhandles
[i
]);
3778 static BOOL WINAPI
my_event_handler(DWORD ctrl
)
3781 return ctrl
== CTRL_C_EVENT
;
3785 /*****************************************************************************
3786 * Main entry point. This is a console application so we have a main() not a
3790 int __cdecl
wmain (int argc
, WCHAR
*argvW
[])
3792 WCHAR
*cmdLine
= NULL
;
3798 WCHAR comspec
[MAX_PATH
];
3799 CMD_NODE
*toExecute
= NULL
; /* Commands left to be executed */
3800 RTL_OSVERSIONINFOEXW osv
;
3802 STARTUPINFOW startupInfo
;
3804 enum read_parse_line rpl_status
;
3806 if (!GetEnvironmentVariableW(L
"COMSPEC", comspec
, ARRAY_SIZE(comspec
)))
3808 GetSystemDirectoryW(comspec
, ARRAY_SIZE(comspec
) - ARRAY_SIZE(L
"\\cmd.exe"));
3809 lstrcatW(comspec
, L
"\\cmd.exe");
3810 SetEnvironmentVariableW(L
"COMSPEC", comspec
);
3815 /* Get the windows version being emulated */
3816 osv
.dwOSVersionInfoSize
= sizeof(osv
);
3817 RtlGetVersion(&osv
);
3819 /* Pre initialize some messages */
3820 lstrcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
3821 sprintf(osver
, "%ld.%ld.%ld", osv
.dwMajorVersion
, osv
.dwMinorVersion
, osv
.dwBuildNumber
);
3822 cmd
= WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION
), osver
);
3823 lstrcpyW(version_string
, cmd
);
3827 /* init for loop context */
3828 forloopcontext
= NULL
;
3829 WCMD_save_for_loop_context(TRUE
);
3831 /* Can't use argc/argv as it will have stripped quotes from parameters
3832 * meaning cmd.exe /C echo "quoted string" is impossible
3834 cmdLine
= GetCommandLineW();
3835 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine
));
3837 while (*cmdLine
&& *cmdLine
!= '/') ++cmdLine
;
3839 opt_c
= opt_k
= opt_q
= opt_s
= FALSE
;
3841 for (arg
= cmdLine
; *arg
; ++arg
)
3846 switch (towlower(arg
[1]))
3849 unicodeOutput
= FALSE
;
3865 opt_t
= wcstoul(&arg
[3], NULL
, 16);
3868 unicodeOutput
= TRUE
;
3872 delayedsubst
= wcsnicmp(&arg
[3], L
"OFF", 3);
3883 while (*arg
&& wcschr(L
" \t,=;", *arg
)) arg
++;
3889 /* Until we start to read from the keyboard, stay as non-interactive */
3890 interactive
= FALSE
;
3892 SetEnvironmentVariableW(L
"PROMPT", L
"$P$G");
3894 if (opt_c
|| opt_k
) {
3896 WCHAR
*q1
= NULL
,*q2
= NULL
,*p
;
3899 cmd
= xstrdupW(arg
);
3901 /* opt_s left unflagged if the command starts with and contains exactly
3902 * one quoted string (exactly two quote characters). The quoted string
3903 * must be an executable name that has whitespace and must not have the
3904 * following characters: &<>()@^| */
3907 /* 1. Confirm there is at least one quote */
3908 q1
= wcschr(arg
, '"');
3913 /* 2. Confirm there is a second quote */
3914 q2
= wcschr(q1
+1, '"');
3919 /* 3. Ensure there are no more quotes */
3920 if (wcschr(q2
+1, '"')) opt_s
=1;
3923 /* check first parameter for a space and invalid characters. There must not be any
3924 * invalid characters, but there must be one or more whitespace */
3929 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
3930 || *p
=='@' || *p
=='^' || *p
=='|') {
3934 if (*p
==' ' || *p
=='\t')
3940 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
3942 /* Finally, we only stay in new mode IF the first parameter is quoted and
3943 is a valid executable, i.e. must exist, otherwise drop back to old mode */
3945 WCHAR
*thisArg
= WCMD_parameter(cmd
, 0, NULL
, FALSE
, TRUE
);
3946 WCHAR pathext
[MAXSTRING
];
3949 /* Now extract PATHEXT */
3950 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
3951 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
3952 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
3955 /* If the supplied parameter has any directory information, look there */
3956 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg
));
3957 if (wcschr(thisArg
, '\\') != NULL
) {
3959 if (!WCMD_get_fullpath(thisArg
, ARRAY_SIZE(string
), string
, NULL
)) return FALSE
;
3960 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string
));
3961 p
= string
+ lstrlenW(string
);
3963 /* Does file exist with this name? */
3964 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
3965 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
3968 WCHAR
*thisExt
= pathext
;
3970 /* No - try with each of the PATHEXT extensions */
3971 while (!found
&& thisExt
) {
3972 WCHAR
*nextExt
= wcschr(thisExt
, ';');
3975 memcpy(p
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
3976 p
[(nextExt
-thisExt
)] = 0x00;
3977 thisExt
= nextExt
+1;
3979 lstrcpyW(p
, thisExt
);
3983 /* Does file exist with this extension appended? */
3984 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
3985 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
3991 /* Otherwise we now need to look in the path to see if we can find it */
3993 /* Does file exist with this name? */
3994 if (SearchPathW(NULL
, thisArg
, NULL
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
3995 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string
));
3998 WCHAR
*thisExt
= pathext
;
4000 /* No - try with each of the PATHEXT extensions */
4001 while (!found
&& thisExt
) {
4002 WCHAR
*nextExt
= wcschr(thisExt
, ';');
4006 nextExt
= nextExt
+1;
4011 /* Does file exist with this extension? */
4012 if (SearchPathW(NULL
, thisArg
, thisExt
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
4013 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string
),
4014 wine_dbgstr_w(thisExt
));
4022 /* If not found, drop back to old behaviour */
4024 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
4030 /* strip first and last quote characters if opt_s; check for invalid
4031 * executable is done later */
4032 if (opt_s
&& *cmd
=='\"')
4033 WCMD_strip_quotes(cmd
);
4037 SetConsoleCtrlHandler(my_event_handler
, TRUE
);
4040 /* Save cwd into appropriate env var (Must be before the /c processing */
4041 GetCurrentDirectoryW(ARRAY_SIZE(string
), string
);
4042 if (IsCharAlphaW(string
[0]) && string
[1] == ':') {
4043 wsprintfW(envvar
, L
"=%c:", string
[0]);
4044 SetEnvironmentVariableW(envvar
, string
);
4045 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
4049 /* If we do a "cmd /c command", we don't want to allocate a new
4050 * console since the command returns immediately. Rather, we use
4051 * the currently allocated input and output handles. This allows
4052 * us to pipe to and read from the command interpreter.
4055 /* Parse the command string, without reading any more input */
4056 rpl_status
= WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
4057 if (rpl_status
== RPL_SUCCESS
&& toExecute
)
4059 node_execute(toExecute
);
4060 node_dispose_tree(toExecute
);
4062 else if (rpl_status
== RPL_SYNTAXERROR
)
4063 errorlevel
= RETURN_CODE_SYNTAX_ERROR
;
4068 GetStartupInfoW(&startupInfo
);
4069 if (startupInfo
.lpTitle
!= NULL
)
4070 SetConsoleTitleW(startupInfo
.lpTitle
);
4072 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE
));
4074 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
4076 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
4077 defaultColor
= opt_t
& 0xFF;
4082 /* Check HKCU\Software\Microsoft\Command Processor
4083 Then HKLM\Software\Microsoft\Command Processor
4084 for defaultcolour value
4085 Note Can be supplied as DWORD or REG_SZ
4086 Note2 When supplied as REG_SZ it's in decimal!!! */
4089 DWORD value
=0, size
=4;
4090 static const WCHAR regKeyW
[] = L
"Software\\Microsoft\\Command Processor";
4092 if (RegOpenKeyExW(HKEY_CURRENT_USER
, regKeyW
,
4093 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
4096 /* See if DWORD or REG_SZ */
4097 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
, NULL
, NULL
) == ERROR_SUCCESS
) {
4098 if (type
== REG_DWORD
) {
4099 size
= sizeof(DWORD
);
4100 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
4101 } else if (type
== REG_SZ
) {
4102 size
= sizeof(strvalue
);
4103 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
4104 value
= wcstoul(strvalue
, NULL
, 10);
4110 if (value
== 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE
, regKeyW
,
4111 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
4114 /* See if DWORD or REG_SZ */
4115 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
,
4116 NULL
, NULL
) == ERROR_SUCCESS
) {
4117 if (type
== REG_DWORD
) {
4118 size
= sizeof(DWORD
);
4119 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
4120 } else if (type
== REG_SZ
) {
4121 size
= sizeof(strvalue
);
4122 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
4123 value
= wcstoul(strvalue
, NULL
, 10);
4129 /* If one found, set the screen to that colour */
4130 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
4131 defaultColor
= value
& 0xFF;
4140 rpl_status
= WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
4141 /* Parse the command string, without reading any more input */
4142 if (rpl_status
== RPL_SUCCESS
&& toExecute
)
4144 node_execute(toExecute
);
4145 node_dispose_tree(toExecute
);
4147 else if (rpl_status
== RPL_SYNTAXERROR
)
4148 errorlevel
= RETURN_CODE_SYNTAX_ERROR
;
4153 * Loop forever getting commands and executing them.
4157 if (!opt_k
) WCMD_output_asis(version_string
);
4158 if (echo_mode
) WCMD_output_asis(L
"\r\n");
4159 /* Read until EOF (which for std input is never, but if redirect in place, may occur */
4160 while ((rpl_status
= WCMD_ReadAndParseLine(NULL
, &toExecute
, GetStdHandle(STD_INPUT_HANDLE
))) != RPL_EOF
)
4162 if (rpl_status
== RPL_SUCCESS
&& toExecute
)
4164 node_execute(toExecute
);
4165 node_dispose_tree(toExecute
);
4166 if (echo_mode
) WCMD_output_asis(L
"\r\n");