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
= heap_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
, ...) {
129 __ms_va_start(ap
,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=%u, 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
, ...) {
154 __ms_va_start(ap
,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=%u, 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
, ...)
179 __ms_va_start(ap
,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=%u, 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 %d, status %d\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 (BOOL newLine
) {
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");
350 switch (toupper(*p
)) {
364 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
- (q
- out_string
));
383 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
389 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
391 lstrcatW (q
, curdir
);
402 GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
406 lstrcatW (q
, version_string
);
413 if (pushd_directories
) {
414 memset(q
, '+', pushd_directories
->u
.stackdepth
);
415 q
= q
+ pushd_directories
->u
.stackdepth
;
423 WCMD_output_asis (out_string
);
426 void *heap_xalloc(size_t size
)
430 ret
= heap_alloc(size
);
432 ERR("Out of memory\n");
439 /*************************************************************************
441 * Replaces a portion of a Unicode string with the specified string.
442 * It's up to the caller to ensure there is enough space in the
443 * destination buffer.
445 void WCMD_strsubstW(WCHAR
*start
, const WCHAR
*next
, const WCHAR
*insert
, int len
) {
448 len
=insert
? lstrlenW(insert
) : 0;
449 if (start
+len
!= next
)
450 memmove(start
+len
, next
, (lstrlenW(next
) + 1) * sizeof(*next
));
452 memcpy(start
, insert
, len
* sizeof(*insert
));
455 /***************************************************************************
456 * WCMD_skip_leading_spaces
458 * Return a pointer to the first non-whitespace character of string.
459 * Does not modify the input string.
461 WCHAR
*WCMD_skip_leading_spaces (WCHAR
*string
) {
466 while (*ptr
== ' ' || *ptr
== '\t') ptr
++;
470 /***************************************************************************
471 * WCMD_keyword_ws_found
473 * Checks if the string located at ptr matches a keyword (of length len)
474 * followed by a whitespace character (space or tab)
476 BOOL
WCMD_keyword_ws_found(const WCHAR
*keyword
, int len
, const WCHAR
*ptr
) {
477 return (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
478 ptr
, len
, keyword
, len
) == CSTR_EQUAL
)
479 && ((*(ptr
+ len
) == ' ') || (*(ptr
+ len
) == '\t'));
482 /*************************************************************************
485 * Remove first and last quote WCHARacters, preserving all other text
486 * Returns the location of the final quote
488 WCHAR
*WCMD_strip_quotes(WCHAR
*cmd
) {
489 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
, *lastquote
;
490 while((*dest
=*src
) != '\0') {
498 while ((*dest
++=*lastq
++) != 0)
505 /*************************************************************************
506 * WCMD_is_magic_envvar
507 * Return TRUE if s is '%'magicvar'%'
508 * and is not masked by a real environment variable.
511 static inline BOOL
WCMD_is_magic_envvar(const WCHAR
*s
, const WCHAR
*magicvar
)
516 return FALSE
; /* Didn't begin with % */
518 if (len
< 2 || s
[len
-1] != '%')
519 return FALSE
; /* Didn't end with another % */
521 if (CompareStringW(LOCALE_USER_DEFAULT
,
522 NORM_IGNORECASE
| SORT_STRINGSORT
,
523 s
+1, len
-2, magicvar
, -1) != CSTR_EQUAL
) {
524 /* Name doesn't match. */
528 if (GetEnvironmentVariableW(magicvar
, NULL
, 0) > 0) {
529 /* Masked by real environment variable. */
536 /*************************************************************************
539 * Expands environment variables, allowing for WCHARacter substitution
541 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR startchar
)
543 WCHAR
*endOfVar
= NULL
, *s
;
544 WCHAR
*colonpos
= NULL
;
545 WCHAR thisVar
[MAXSTRING
];
546 WCHAR thisVarContents
[MAXSTRING
];
547 WCHAR savedchar
= 0x00;
549 WCHAR Delims
[] = L
"%:"; /* First char gets replaced appropriately */
551 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start
), startchar
);
553 /* Find the end of the environment variable, and extract name */
554 Delims
[0] = startchar
;
555 endOfVar
= wcspbrk(start
+1, Delims
);
557 if (endOfVar
== NULL
|| *endOfVar
==' ') {
559 /* In batch program, missing terminator for % and no following
560 ':' just removes the '%' */
562 WCMD_strsubstW(start
, start
+ 1, NULL
, 0);
566 /* In command processing, just ignore it - allows command line
567 syntax like: for %i in (a.a) do echo %i */
572 /* If ':' found, process remaining up until '%' (or stop at ':' if
574 if (*endOfVar
==':') {
575 WCHAR
*endOfVar2
= wcschr(endOfVar
+1, startchar
);
576 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
579 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
580 thisVar
[(endOfVar
- start
)+1] = 0x00;
581 colonpos
= wcschr(thisVar
+1, ':');
583 /* If there's complex substitution, just need %var% for now
584 to get the expanded data to play with */
586 *colonpos
= startchar
;
587 savedchar
= *(colonpos
+1);
588 *(colonpos
+1) = 0x00;
591 /* By now, we know the variable we want to expand but it may be
592 surrounded by '!' if we are in delayed expansion - if so convert
594 if (startchar
=='!') {
596 thisVar
[(endOfVar
- start
)] = '%';
598 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
600 /* Expand to contents, if unchanged, return */
601 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
602 /* override if existing env var called that name */
603 if (WCMD_is_magic_envvar(thisVar
, L
"ERRORLEVEL")) {
604 wsprintfW(thisVarContents
, L
"%d", errorlevel
);
605 len
= lstrlenW(thisVarContents
);
606 } else if (WCMD_is_magic_envvar(thisVar
, L
"DATE")) {
607 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
608 NULL
, thisVarContents
, MAXSTRING
);
609 len
= lstrlenW(thisVarContents
);
610 } else if (WCMD_is_magic_envvar(thisVar
, L
"TIME")) {
611 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
612 NULL
, thisVarContents
, MAXSTRING
);
613 len
= lstrlenW(thisVarContents
);
614 } else if (WCMD_is_magic_envvar(thisVar
, L
"CD")) {
615 GetCurrentDirectoryW(MAXSTRING
, thisVarContents
);
616 len
= lstrlenW(thisVarContents
);
617 } else if (WCMD_is_magic_envvar(thisVar
, L
"RANDOM")) {
618 wsprintfW(thisVarContents
, L
"%d", rand() % 32768);
619 len
= lstrlenW(thisVarContents
);
622 len
= ExpandEnvironmentStringsW(thisVar
, thisVarContents
, ARRAY_SIZE(thisVarContents
));
628 /* In a batch program, unknown env vars are replaced with nothing,
629 note syntax %garbage:1,3% results in anything after the ':'
631 From the command line, you just get back what you entered */
632 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
634 /* Restore the complex part after the compare */
637 *(colonpos
+1) = savedchar
;
640 /* Command line - just ignore this */
641 if (context
== NULL
) return endOfVar
+1;
644 /* Batch - replace unknown env var with nothing */
645 if (colonpos
== NULL
) {
646 WCMD_strsubstW(start
, endOfVar
+ 1, NULL
, 0);
648 len
= lstrlenW(thisVar
);
649 thisVar
[len
-1] = 0x00;
650 /* If %:...% supplied, : is retained */
651 if (colonpos
== thisVar
+1) {
652 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
, -1);
654 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
+ 1, -1);
661 /* See if we need to do complex substitution (any ':'s), if not
662 then our work here is done */
663 if (colonpos
== NULL
) {
664 WCMD_strsubstW(start
, endOfVar
+ 1, thisVarContents
, -1);
668 /* Restore complex bit */
670 *(colonpos
+1) = savedchar
;
673 Handle complex substitutions:
674 xxx=yyy (replace xxx with yyy)
675 *xxx=yyy (replace up to and including xxx with yyy)
676 ~x (from x WCHARs in)
677 ~-x (from x WCHARs from the end)
678 ~x,y (from x WCHARs in for y WCHARacters)
679 ~x,-y (from x WCHARs in until y WCHARacters from the end)
682 /* ~ is substring manipulation */
683 if (savedchar
== '~') {
685 int substrposition
, substrlength
= 0;
686 WCHAR
*commapos
= wcschr(colonpos
+2, ',');
689 substrposition
= wcstol(colonpos
+2, NULL
, 10);
690 if (commapos
) substrlength
= wcstol(commapos
+1, NULL
, 10);
693 if (substrposition
>= 0) {
694 startCopy
= &thisVarContents
[min(substrposition
, len
)];
696 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
699 if (commapos
== NULL
) {
701 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, -1);
702 } else if (substrlength
< 0) {
704 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
705 if (copybytes
> len
) copybytes
= len
;
706 else if (copybytes
< 0) copybytes
= 0;
707 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, copybytes
);
709 substrlength
= min(substrlength
, len
- (startCopy
- thisVarContents
+ 1));
710 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, substrlength
);
713 /* search and replace manipulation */
715 WCHAR
*equalspos
= wcsstr(colonpos
, L
"=");
716 WCHAR
*replacewith
= equalspos
+1;
721 if (equalspos
== NULL
) return start
+1;
722 s
= heap_strdupW(endOfVar
+ 1);
724 /* Null terminate both strings */
725 thisVar
[lstrlenW(thisVar
)-1] = 0x00;
728 /* Since we need to be case insensitive, copy the 2 buffers */
729 searchIn
= heap_strdupW(thisVarContents
);
730 CharUpperBuffW(searchIn
, lstrlenW(thisVarContents
));
731 searchFor
= heap_strdupW(colonpos
+1);
732 CharUpperBuffW(searchFor
, lstrlenW(colonpos
+1));
734 /* Handle wildcard case */
735 if (*(colonpos
+1) == '*') {
736 /* Search for string to replace */
737 found
= wcsstr(searchIn
, searchFor
+1);
741 lstrcpyW(start
, replacewith
);
742 lstrcatW(start
, thisVarContents
+ (found
-searchIn
) + lstrlenW(searchFor
+1));
746 lstrcpyW(start
, thisVarContents
);
751 /* Loop replacing all instances */
752 WCHAR
*lastFound
= searchIn
;
753 WCHAR
*outputposn
= start
;
756 while ((found
= wcsstr(lastFound
, searchFor
))) {
757 lstrcpynW(outputposn
,
758 thisVarContents
+ (lastFound
-searchIn
),
759 (found
- lastFound
)+1);
760 outputposn
= outputposn
+ (found
- lastFound
);
761 lstrcatW(outputposn
, replacewith
);
762 outputposn
= outputposn
+ lstrlenW(replacewith
);
763 lastFound
= found
+ lstrlenW(searchFor
);
766 thisVarContents
+ (lastFound
-searchIn
));
767 lstrcatW(outputposn
, s
);
771 heap_free(searchFor
);
776 /*****************************************************************************
777 * Expand the command. Native expands lines from batch programs as they are
778 * read in and not again, except for 'for' variable substitution.
779 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
780 * atExecute is TRUE when the expansion is occurring as the command is executed
781 * rather than at parse time, i.e. delayed expansion and for loops need to be
784 static void handleExpansion(WCHAR
*cmd
, BOOL atExecute
, BOOL delayed
) {
786 /* For commands in a context (batch program): */
787 /* Expand environment variables in a batch file %{0-9} first */
788 /* including support for any ~ modifiers */
790 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
791 /* names allowing environment variable overrides */
792 /* NOTE: To support the %PATH:xxx% syntax, also perform */
793 /* manual expansion of environment variables here */
798 WCHAR
*delayedp
= NULL
;
799 WCHAR startchar
= '%';
802 /* Display the FOR variables in effect */
804 if (forloopcontext
.variable
[i
]) {
805 WINE_TRACE("FOR variable context: %c = '%s'\n",
806 i
<26?i
+'a':(i
-26)+'A',
807 wine_dbgstr_w(forloopcontext
.variable
[i
]));
811 /* Find the next environment variable delimiter */
812 normalp
= wcschr(p
, '%');
813 if (delayed
) delayedp
= wcschr(p
, '!');
814 if (!normalp
) p
= delayedp
;
815 else if (!delayedp
) p
= normalp
;
816 else p
= min(p
,delayedp
);
817 if (p
) startchar
= *p
;
821 WINE_TRACE("Translate command:%s %d (at: %s)\n",
822 wine_dbgstr_w(cmd
), atExecute
, wine_dbgstr_w(p
));
825 /* Don't touch %% unless it's in Batch */
826 if (!atExecute
&& *(p
+1) == startchar
) {
828 WCMD_strsubstW(p
, p
+1, NULL
, 0);
832 /* Replace %~ modifications if in batch program */
833 } else if (*(p
+1) == '~') {
834 WCMD_HandleTildeModifiers(&p
, atExecute
);
837 /* Replace use of %0...%9 if in batch program*/
838 } else if (!atExecute
&& context
&& (i
>= 0) && (i
<= 9) && startchar
== '%') {
839 t
= WCMD_parameter(context
-> command
, i
+ context
-> shift_count
[i
],
841 WCMD_strsubstW(p
, p
+2, t
, -1);
843 /* Replace use of %* if in batch program*/
844 } else if (!atExecute
&& context
&& *(p
+1)=='*' && startchar
== '%') {
845 WCHAR
*startOfParms
= NULL
;
846 WCHAR
*thisParm
= WCMD_parameter(context
-> command
, 0, &startOfParms
, TRUE
, TRUE
);
847 if (startOfParms
!= NULL
) {
848 startOfParms
+= lstrlenW(thisParm
);
849 while (*startOfParms
==' ' || *startOfParms
== '\t') startOfParms
++;
850 WCMD_strsubstW(p
, p
+2, startOfParms
, -1);
852 WCMD_strsubstW(p
, p
+2, NULL
, 0);
855 int forvaridx
= FOR_VAR_IDX(*(p
+1));
856 if (startchar
== '%' && forvaridx
!= -1 && forloopcontext
.variable
[forvaridx
]) {
857 /* Replace the 2 characters, % and for variable character */
858 WCMD_strsubstW(p
, p
+ 2, forloopcontext
.variable
[forvaridx
], -1);
859 } else if (!atExecute
|| startchar
== '!') {
860 p
= WCMD_expand_envvar(p
, startchar
);
862 /* In a FOR loop, see if this is the variable to replace */
863 } else { /* Ignore %'s on second pass of batch program */
868 /* Find the next environment variable delimiter */
869 normalp
= wcschr(p
, '%');
870 if (delayed
) delayedp
= wcschr(p
, '!');
871 if (!normalp
) p
= delayedp
;
872 else if (!delayedp
) p
= normalp
;
873 else p
= min(p
,delayedp
);
874 if (p
) startchar
= *p
;
881 /*******************************************************************
882 * WCMD_parse - parse a command into parameters and qualifiers.
884 * On exit, all qualifiers are concatenated into q, the first string
885 * not beginning with "/" is in p1 and the
886 * second in p2. Any subsequent non-qualifier strings are lost.
887 * Parameters in quotes are handled.
889 static void WCMD_parse (const WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
)
893 *q
= *p1
= *p2
= '\0';
898 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
899 *q
++ = towupper (*s
++);
909 while ((*s
!= '\0') && (*s
!= '"')) {
910 if (p
== 0) *p1
++ = *s
++;
911 else if (p
== 1) *p2
++ = *s
++;
914 if (p
== 0) *p1
= '\0';
915 if (p
== 1) *p2
= '\0';
922 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
923 && (*s
!= '=') && (*s
!= ',') ) {
924 if (p
== 0) *p1
++ = *s
++;
925 else if (p
== 1) *p2
++ = *s
++;
928 /* Skip concurrent parms */
929 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
931 if (p
== 0) *p1
= '\0';
932 if (p
== 1) *p2
= '\0';
938 static void init_msvcrt_io_block(STARTUPINFOW
* st
)
941 /* fetch the parent MSVCRT info block if any, so that the child can use the
942 * same handles as its grand-father
944 st_p
.cb
= sizeof(STARTUPINFOW
);
945 GetStartupInfoW(&st_p
);
946 st
->cbReserved2
= st_p
.cbReserved2
;
947 st
->lpReserved2
= st_p
.lpReserved2
;
948 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
950 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
956 /* Override the entries for fd 0,1,2 if we happened
957 * to change those std handles (this depends on the way cmd sets
958 * its new input & output handles)
960 sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
961 ptr
= heap_xalloc(sz
);
962 flags
= (char*)(ptr
+ sizeof(unsigned));
963 handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
965 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
966 st
->cbReserved2
= sz
;
967 st
->lpReserved2
= ptr
;
969 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
970 if (num
<= 0 || (flags
[0] & WX_OPEN
))
972 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
975 if (num
<= 1 || (flags
[1] & WX_OPEN
))
977 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
980 if (num
<= 2 || (flags
[2] & WX_OPEN
))
982 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
989 /******************************************************************************
992 * Execute a command line as an external program. Must allow recursion.
995 * Manual testing under windows shows PATHEXT plays a key part in this,
996 * and the search algorithm and precedence appears to be as follows.
999 * If directory supplied on command, just use that directory
1000 * If extension supplied on command, look for that explicit name first
1001 * Otherwise, search in each directory on the path
1003 * If extension supplied on command, look for that explicit name first
1004 * Then look for supplied name .* (even if extension supplied, so
1005 * 'garbage.exe' will match 'garbage.exe.cmd')
1006 * If any found, cycle through PATHEXT looking for name.exe one by one
1008 * Once a match has been found, it is launched - Code currently uses
1009 * findexecutable to achieve this which is left untouched.
1010 * If an executable has not been found, and we were launched through
1011 * a call, we need to check if the command is an internal command,
1012 * so go back through wcmd_execute.
1015 void WCMD_run_program (WCHAR
*command
, BOOL called
)
1017 WCHAR temp
[MAX_PATH
];
1018 WCHAR pathtosearch
[MAXSTRING
];
1020 WCHAR stemofsearch
[MAX_PATH
]; /* maximum allowed executable name is
1021 MAX_PATH, including null character */
1023 WCHAR pathext
[MAXSTRING
];
1025 BOOL extensionsupplied
= FALSE
;
1026 BOOL explicit_path
= FALSE
;
1030 /* Quick way to get the filename is to extract the first argument. */
1031 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command
), called
);
1032 firstParam
= WCMD_parameter(command
, 0, NULL
, FALSE
, TRUE
);
1033 if (!firstParam
) return;
1035 if (!firstParam
[0]) {
1040 /* Calculate the search path and stem to search for */
1041 if (wcspbrk(firstParam
, L
"/\\:") == NULL
) { /* No explicit path given, search path */
1042 lstrcpyW(pathtosearch
, L
".;");
1043 len
= GetEnvironmentVariableW(L
"PATH", &pathtosearch
[2], ARRAY_SIZE(pathtosearch
)-2);
1044 if ((len
== 0) || (len
>= ARRAY_SIZE(pathtosearch
) - 2)) {
1045 lstrcpyW(pathtosearch
, L
".");
1047 if (wcschr(firstParam
, '.') != NULL
) extensionsupplied
= TRUE
;
1048 if (lstrlenW(firstParam
) >= MAX_PATH
)
1050 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG
));
1054 lstrcpyW(stemofsearch
, firstParam
);
1058 /* Convert eg. ..\fred to include a directory by removing file part */
1059 GetFullPathNameW(firstParam
, ARRAY_SIZE(pathtosearch
), pathtosearch
, NULL
);
1060 lastSlash
= wcsrchr(pathtosearch
, '\\');
1061 if (lastSlash
&& wcschr(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1062 lstrcpyW(stemofsearch
, lastSlash
+1);
1064 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1065 c:\windows\a.bat syntax */
1066 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1067 explicit_path
= TRUE
;
1070 /* Now extract PATHEXT */
1071 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
1072 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
1073 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
1076 /* Loop through the search path, dir by dir */
1077 pathposn
= pathtosearch
;
1078 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1079 wine_dbgstr_w(stemofsearch
));
1081 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1085 BOOL inside_quotes
= FALSE
;
1089 lstrcpyW(thisDir
, pathposn
);
1094 /* Work on the next directory on the search path */
1096 while ((inside_quotes
|| *pos
!= ';') && *pos
!= 0)
1099 inside_quotes
= !inside_quotes
;
1103 if (*pos
) /* Reached semicolon */
1105 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1106 thisDir
[(pos
-pathposn
)] = 0x00;
1109 else /* Reached string end */
1111 lstrcpyW(thisDir
, pathposn
);
1116 length
= lstrlenW(thisDir
);
1117 if (thisDir
[length
- 1] == '"')
1118 thisDir
[length
- 1] = 0;
1120 if (*thisDir
!= '"')
1121 lstrcpyW(temp
, thisDir
);
1123 lstrcpyW(temp
, thisDir
+ 1);
1125 /* Since you can have eg. ..\.. on the path, need to expand
1126 to full information */
1127 GetFullPathNameW(temp
, MAX_PATH
, thisDir
, NULL
);
1130 /* 1. If extension supplied, see if that file exists */
1131 lstrcatW(thisDir
, L
"\\");
1132 lstrcatW(thisDir
, stemofsearch
);
1133 pos
= &thisDir
[lstrlenW(thisDir
)]; /* Pos = end of name */
1135 /* 1. If extension supplied, see if that file exists */
1136 if (extensionsupplied
) {
1137 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1142 /* 2. Any .* matches? */
1145 WIN32_FIND_DATAW finddata
;
1147 lstrcatW(thisDir
, L
".*");
1148 h
= FindFirstFileW(thisDir
, &finddata
);
1150 if (h
!= INVALID_HANDLE_VALUE
) {
1152 WCHAR
*thisExt
= pathext
;
1154 /* 3. Yes - Try each path ext */
1156 WCHAR
*nextExt
= wcschr(thisExt
, ';');
1159 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1160 pos
[(nextExt
-thisExt
)] = 0x00;
1161 thisExt
= nextExt
+1;
1163 lstrcpyW(pos
, thisExt
);
1167 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1175 /* Once found, launch it */
1178 PROCESS_INFORMATION pe
;
1182 WCHAR
*ext
= wcsrchr( thisDir
, '.' );
1184 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1186 /* Special case BAT and CMD */
1187 if (ext
&& (!wcsicmp(ext
, L
".bat") || !wcsicmp(ext
, L
".cmd"))) {
1188 BOOL oldinteractive
= interactive
;
1189 interactive
= FALSE
;
1190 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1191 interactive
= oldinteractive
;
1195 /* thisDir contains the file to be launched, but with what?
1196 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1197 hinst
= FindExecutableW (thisDir
, NULL
, temp
);
1198 if ((INT_PTR
)hinst
< 32)
1201 console
= SHGetFileInfoW(temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1203 ZeroMemory (&st
, sizeof(STARTUPINFOW
));
1204 st
.cb
= sizeof(STARTUPINFOW
);
1205 init_msvcrt_io_block(&st
);
1207 /* Launch the process and if a CUI wait on it to complete
1208 Note: Launching internal wine processes cannot specify a full path to exe */
1209 status
= CreateProcessW(thisDir
,
1210 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1211 heap_free(st
.lpReserved2
);
1212 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1213 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1214 /* strip first and last quote WCHARacters and try again */
1215 WCMD_strip_quotes(command
);
1217 WCMD_run_program(command
, called
);
1224 /* Always wait when non-interactive (cmd /c or in batch program),
1225 or for console applications */
1226 if (!interactive
|| (console
&& !HIWORD(console
)))
1227 WaitForSingleObject (pe
.hProcess
, INFINITE
);
1228 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1229 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1231 CloseHandle(pe
.hProcess
);
1232 CloseHandle(pe
.hThread
);
1238 /* Not found anywhere - were we called? */
1240 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
1242 /* Parse the command string, without reading any more input */
1243 WCMD_ReadAndParseLine(command
, &toExecute
, INVALID_HANDLE_VALUE
);
1244 WCMD_process_commands(toExecute
, FALSE
, called
);
1245 WCMD_free_commands(toExecute
);
1250 /* Not found anywhere - give up */
1251 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND
), command
);
1253 /* If a command fails to launch, it sets errorlevel 9009 - which
1254 does not seem to have any associated constant definition */
1260 /*****************************************************************************
1261 * Process one command. If the command is EXIT this routine does not return.
1262 * We will recurse through here executing batch files.
1263 * Note: If call is used to a non-existing program, we reparse the line and
1264 * try to run it as an internal command. 'retrycall' represents whether
1265 * we are attempting this retry.
1267 void WCMD_execute (const WCHAR
*command
, const WCHAR
*redirects
,
1268 CMD_LIST
**cmdList
, BOOL retrycall
)
1270 WCHAR
*cmd
, *parms_start
, *redir
;
1272 int status
, i
, cmd_index
;
1273 DWORD count
, creationDisposition
;
1276 SECURITY_ATTRIBUTES sa
;
1277 WCHAR
*new_cmd
= NULL
;
1278 WCHAR
*new_redir
= NULL
;
1279 HANDLE old_stdhandles
[3] = {GetStdHandle (STD_INPUT_HANDLE
),
1280 GetStdHandle (STD_OUTPUT_HANDLE
),
1281 GetStdHandle (STD_ERROR_HANDLE
)};
1282 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
1285 BOOL prev_echo_mode
, piped
= FALSE
;
1287 WINE_TRACE("command on entry:%s (%p)\n",
1288 wine_dbgstr_w(command
), cmdList
);
1290 /* Move copy of the command onto the heap so it can be expanded */
1291 new_cmd
= heap_xalloc(MAXSTRING
* sizeof(WCHAR
));
1292 lstrcpyW(new_cmd
, command
);
1295 /* Move copy of the redirects onto the heap so it can be expanded */
1296 new_redir
= heap_xalloc(MAXSTRING
* sizeof(WCHAR
));
1299 /* Strip leading whitespaces, and a '@' if supplied */
1300 whichcmd
= WCMD_skip_leading_spaces(cmd
);
1301 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
1302 if (whichcmd
[0] == '@') whichcmd
++;
1304 /* Check if the command entered is internal, and identify which one */
1306 while (IsCharAlphaNumericW(whichcmd
[count
])) {
1309 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1310 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1311 whichcmd
, count
, inbuilt
[i
], -1) == CSTR_EQUAL
) break;
1314 parms_start
= WCMD_skip_leading_spaces (&whichcmd
[count
]);
1316 /* If the next command is a pipe then we implement pipes by redirecting
1317 the output from this command to a temp file and input into the
1318 next command from that temp file.
1319 Note: Do not do this for a for or if statement as the pipe is for
1320 the individual statements, not the for or if itself.
1321 FIXME: Use of named pipes would make more sense here as currently this
1322 process has to finish before the next one can start but this requires
1323 a change to not wait for the first app to finish but rather the pipe */
1324 if (!(cmd_index
== WCMD_FOR
|| cmd_index
== WCMD_IF
) &&
1325 cmdList
&& (*cmdList
)->nextcommand
&&
1326 (*cmdList
)->nextcommand
->prevDelim
== CMD_PIPE
) {
1328 WCHAR temp_path
[MAX_PATH
];
1330 /* Remember piping is in action */
1331 WINE_TRACE("Output needs to be piped\n");
1334 /* Generate a unique temporary filename */
1335 GetTempPathW(ARRAY_SIZE(temp_path
), temp_path
);
1336 GetTempFileNameW(temp_path
, L
"CMD", 0, (*cmdList
)->nextcommand
->pipeFile
);
1337 WINE_TRACE("Using temporary file of %s\n",
1338 wine_dbgstr_w((*cmdList
)->nextcommand
->pipeFile
));
1341 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1343 wsprintfW (new_redir
, L
"%s > %s", redirects
, (*cmdList
)->nextcommand
->pipeFile
);
1344 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
1346 lstrcpyW(new_redir
, redirects
);
1349 /* Expand variables in command line mode only (batch mode will
1350 be expanded as the line is read in, except for 'for' loops) */
1351 handleExpansion(new_cmd
, (context
!= NULL
), delayedsubst
);
1352 handleExpansion(new_redir
, (context
!= NULL
), delayedsubst
);
1355 * Changing default drive has to be handled as a special case, anything
1356 * else if it exists after whitespace is ignored
1359 if ((cmd
[1] == ':') && IsCharAlphaW(cmd
[0]) &&
1360 (!cmd
[2] || cmd
[2] == ' ' || cmd
[2] == '\t')) {
1362 WCHAR dir
[MAX_PATH
];
1364 /* Ignore potential garbage on the same line */
1367 /* According to MSDN CreateProcess docs, special env vars record
1368 the current directory on each drive, in the form =C:
1369 so see if one specified, and if so go back to it */
1370 lstrcpyW(envvar
, L
"=");
1371 lstrcatW(envvar
, cmd
);
1372 if (GetEnvironmentVariableW(envvar
, dir
, MAX_PATH
) == 0) {
1373 wsprintfW(cmd
, L
"%s\\", cmd
);
1374 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
1376 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
1377 status
= SetCurrentDirectoryW(cmd
);
1378 if (!status
) WCMD_print_error ();
1380 heap_free(new_redir
);
1384 sa
.nLength
= sizeof(sa
);
1385 sa
.lpSecurityDescriptor
= NULL
;
1386 sa
.bInheritHandle
= TRUE
;
1389 * Redirect stdin, stdout and/or stderr if required.
1390 * Note: Do not do this for a for or if statement as the pipe is for
1391 * the individual statements, not the for or if itself.
1393 if (!(cmd_index
== WCMD_FOR
|| cmd_index
== WCMD_IF
)) {
1394 /* STDIN could come from a preceding pipe, so delete on close if it does */
1395 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
1396 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
1397 h
= CreateFileW((*cmdList
)->pipeFile
, GENERIC_READ
,
1398 FILE_SHARE_READ
| FILE_SHARE_WRITE
, &sa
, OPEN_EXISTING
,
1399 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
1400 if (h
== INVALID_HANDLE_VALUE
) {
1401 WCMD_print_error ();
1403 heap_free(new_redir
);
1406 SetStdHandle (STD_INPUT_HANDLE
, h
);
1408 /* No need to remember the temporary name any longer once opened */
1409 (*cmdList
)->pipeFile
[0] = 0x00;
1411 /* Otherwise STDIN could come from a '<' redirect */
1412 } else if ((pos
= wcschr(new_redir
,'<')) != NULL
) {
1413 h
= CreateFileW(WCMD_parameter(++pos
, 0, NULL
, FALSE
, FALSE
), GENERIC_READ
, FILE_SHARE_READ
,
1414 &sa
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1415 if (h
== INVALID_HANDLE_VALUE
) {
1416 WCMD_print_error ();
1418 heap_free(new_redir
);
1421 SetStdHandle (STD_INPUT_HANDLE
, h
);
1424 /* Scan the whole command looking for > and 2> */
1425 while (redir
!= NULL
&& ((pos
= wcschr(redir
,'>')) != NULL
)) {
1428 if (pos
> redir
&& (*(pos
-1)=='2'))
1435 creationDisposition
= OPEN_ALWAYS
;
1439 creationDisposition
= CREATE_ALWAYS
;
1442 /* Add support for 2>&1 */
1445 int idx
= *(pos
+1) - '0';
1447 if (DuplicateHandle(GetCurrentProcess(),
1448 GetStdHandle(idx_stdhandles
[idx
]),
1449 GetCurrentProcess(),
1451 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
1452 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1454 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
1457 WCHAR
*param
= WCMD_parameter(pos
, 0, NULL
, FALSE
, FALSE
);
1458 h
= CreateFileW(param
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_DELETE
,
1459 &sa
, creationDisposition
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1460 if (h
== INVALID_HANDLE_VALUE
) {
1461 WCMD_print_error ();
1463 heap_free(new_redir
);
1466 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
1467 INVALID_SET_FILE_POINTER
) {
1468 WCMD_print_error ();
1470 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
1473 SetStdHandle (idx_stdhandles
[handle
], h
);
1476 WINE_TRACE("Not touching redirects for a FOR or IF command\n");
1478 WCMD_parse (parms_start
, quals
, param1
, param2
);
1479 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1481 if (i
<= WCMD_EXIT
&& (parms_start
[0] == '/') && (parms_start
[1] == '?')) {
1482 /* this is a help request for a builtin program */
1484 memcpy(parms_start
, whichcmd
, count
* sizeof(WCHAR
));
1485 parms_start
[count
] = '\0';
1492 WCMD_call (parms_start
);
1496 WCMD_setshow_default (parms_start
);
1499 WCMD_clear_screen ();
1502 WCMD_copy (parms_start
);
1508 WCMD_setshow_date ();
1512 WCMD_delete (parms_start
);
1515 WCMD_directory (parms_start
);
1518 WCMD_echo(&whichcmd
[count
]);
1521 WCMD_goto (cmdList
);
1524 WCMD_give_help (parms_start
);
1527 WCMD_volume (TRUE
, parms_start
);
1531 WCMD_create_dir (parms_start
);
1537 WCMD_setshow_path (parms_start
);
1543 WCMD_setshow_prompt ();
1553 WCMD_remove_dir (parms_start
);
1556 WCMD_setlocal(parms_start
);
1562 WCMD_setshow_env (parms_start
);
1565 WCMD_shift (parms_start
);
1568 WCMD_start (parms_start
);
1571 WCMD_setshow_time ();
1574 if (lstrlenW(&whichcmd
[count
]) > 0)
1575 WCMD_title(&whichcmd
[count
+1]);
1578 WCMD_type (parms_start
);
1581 WCMD_output_asis(L
"\r\n");
1585 WCMD_verify (parms_start
);
1588 WCMD_volume (FALSE
, parms_start
);
1591 WCMD_pushd(parms_start
);
1597 WCMD_assoc(parms_start
, TRUE
);
1603 WCMD_assoc(parms_start
, FALSE
);
1606 WCMD_more(parms_start
);
1609 WCMD_choice(parms_start
);
1612 WCMD_mklink(parms_start
);
1615 WCMD_exit (cmdList
);
1619 /* Very oddly, probably because of all the special parsing required for
1620 these two commands, neither 'for' nor 'if' is supported when called,
1621 i.e. 'call if 1==1...' will fail. */
1623 if (i
==WCMD_FOR
) WCMD_for (parms_start
, cmdList
);
1624 else if (i
==WCMD_IF
) WCMD_if (parms_start
, cmdList
);
1627 /* else: drop through */
1629 prev_echo_mode
= echo_mode
;
1630 WCMD_run_program (whichcmd
, FALSE
);
1631 echo_mode
= prev_echo_mode
;
1634 heap_free(new_redir
);
1636 /* Restore old handles */
1637 for (i
=0; i
<3; i
++) {
1638 if (old_stdhandles
[i
] != GetStdHandle(idx_stdhandles
[i
])) {
1639 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
1640 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
1645 /*************************************************************************
1647 * Load a string from the resource file, handling any error
1648 * Returns string retrieved from resource file
1650 WCHAR
*WCMD_LoadMessage(UINT id
) {
1651 static WCHAR msg
[2048];
1653 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, ARRAY_SIZE(msg
))) {
1654 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1655 lstrcpyW(msg
, L
"Failed!");
1660 /***************************************************************************
1663 * Dumps out the parsed command line to ensure syntax is correct
1665 static void WCMD_DumpCommands(CMD_LIST
*commands
) {
1666 CMD_LIST
*thisCmd
= commands
;
1668 WINE_TRACE("Parsed line:\n");
1669 while (thisCmd
!= NULL
) {
1670 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1673 thisCmd
->bracketDepth
,
1674 thisCmd
->nextcommand
,
1675 wine_dbgstr_w(thisCmd
->command
),
1676 wine_dbgstr_w(thisCmd
->redirects
));
1677 thisCmd
= thisCmd
->nextcommand
;
1681 /***************************************************************************
1684 * Adds a command to the current command list
1686 static void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1687 WCHAR
*redirs
, int *redirLen
,
1688 WCHAR
**copyTo
, int **copyToLen
,
1689 CMD_DELIMITERS prevDelim
, int curDepth
,
1690 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1692 CMD_LIST
*thisEntry
= NULL
;
1694 /* Allocate storage for command */
1695 thisEntry
= heap_xalloc(sizeof(CMD_LIST
));
1697 /* Copy in the command */
1699 thisEntry
->command
= heap_xalloc((*commandLen
+1) * sizeof(WCHAR
));
1700 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1701 thisEntry
->command
[*commandLen
] = 0x00;
1703 /* Copy in the redirects */
1704 thisEntry
->redirects
= heap_xalloc((*redirLen
+1) * sizeof(WCHAR
));
1705 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1706 thisEntry
->redirects
[*redirLen
] = 0x00;
1707 thisEntry
->pipeFile
[0] = 0x00;
1709 /* Reset the lengths */
1712 *copyToLen
= commandLen
;
1716 thisEntry
->command
= NULL
;
1717 thisEntry
->redirects
= NULL
;
1718 thisEntry
->pipeFile
[0] = 0x00;
1721 /* Fill in other fields */
1722 thisEntry
->nextcommand
= NULL
;
1723 thisEntry
->prevDelim
= prevDelim
;
1724 thisEntry
->bracketDepth
= curDepth
;
1726 (*lastEntry
)->nextcommand
= thisEntry
;
1728 *output
= thisEntry
;
1730 *lastEntry
= thisEntry
;
1734 /***************************************************************************
1737 * Checks if the quote pointed to is the end-quote.
1741 * 1) The current parameter ends at EOL or at the beginning
1742 * of a redirection or pipe and not in a quote section.
1744 * 2) If the next character is a space and not in a quote section.
1746 * Returns TRUE if this is an end quote, and FALSE if it is not.
1749 static BOOL
WCMD_IsEndQuote(const WCHAR
*quote
, int quoteIndex
)
1751 int quoteCount
= quoteIndex
;
1754 /* If we are not in a quoted section, then we are not an end-quote */
1760 /* Check how many quotes are left for this parameter */
1761 for(i
=0;quote
[i
];i
++)
1768 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1769 else if(((quoteCount
% 2) == 0)
1770 && ((quote
[i
] == '<') || (quote
[i
] == '>') || (quote
[i
] == '|') || (quote
[i
] == ' ') ||
1777 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1779 if(quoteIndex
>= (quoteCount
/ 2))
1788 /***************************************************************************
1789 * WCMD_ReadAndParseLine
1791 * Either uses supplied input or
1792 * Reads a file from the handle, and then...
1793 * Parse the text buffer, splitting into separate commands
1794 * - unquoted && strings split 2 commands but the 2nd is flagged as
1796 * - ( as the first character just ups the bracket depth
1797 * - unquoted ) when bracket depth > 0 terminates a bracket and
1798 * adds a CMD_LIST structure with null command
1799 * - Anything else gets put into the command string (including
1802 WCHAR
*WCMD_ReadAndParseLine(const WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
)
1806 WCHAR curString
[MAXSTRING
];
1807 int curStringLen
= 0;
1808 WCHAR curRedirs
[MAXSTRING
];
1809 int curRedirsLen
= 0;
1813 CMD_LIST
*lastEntry
= NULL
;
1814 CMD_DELIMITERS prevDelim
= CMD_NONE
;
1815 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
1816 static const WCHAR remCmd
[] = {'r','e','m'};
1817 static const WCHAR forCmd
[] = {'f','o','r'};
1818 static const WCHAR ifCmd
[] = {'i','f'};
1819 static const WCHAR ifElse
[] = {'e','l','s','e'};
1820 BOOL inOneLine
= FALSE
;
1825 BOOL onlyWhiteSpace
= FALSE
;
1826 BOOL lastWasWhiteSpace
= FALSE
;
1827 BOOL lastWasDo
= FALSE
;
1828 BOOL lastWasIn
= FALSE
;
1829 BOOL lastWasElse
= FALSE
;
1830 BOOL lastWasRedirect
= TRUE
;
1831 BOOL lastWasCaret
= FALSE
;
1832 int lineCurDepth
; /* Bracket depth when line was read in */
1833 BOOL resetAtEndOfLine
= FALSE
; /* Do we need to reset curdepth at EOL */
1835 /* Allocate working space for a command read from keyboard, file etc */
1837 extraSpace
= heap_xalloc((MAXSTRING
+1) * sizeof(WCHAR
));
1840 WINE_ERR("Could not allocate memory for extraSpace\n");
1844 /* If initial command read in, use that, otherwise get input from handle */
1845 if (optionalcmd
!= NULL
) {
1846 lstrcpyW(extraSpace
, optionalcmd
);
1847 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
1848 WINE_FIXME("No command nor handle supplied\n");
1850 if (!WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
))
1853 curPos
= extraSpace
;
1855 /* Handle truncated input - issue warning */
1856 if (lstrlenW(extraSpace
) == MAXSTRING
-1) {
1857 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
1858 WCMD_output_asis_stderr(extraSpace
);
1859 WCMD_output_asis_stderr(L
"\r\n");
1862 /* Replace env vars if in a batch context */
1863 if (context
) handleExpansion(extraSpace
, FALSE
, FALSE
);
1865 /* Skip preceding whitespace */
1866 while (*curPos
== ' ' || *curPos
== '\t') curPos
++;
1868 /* Show prompt before batch line IF echo is on and in batch program */
1869 if (context
&& echo_mode
&& *curPos
&& (*curPos
!= '@')) {
1870 static const WCHAR echoDot
[] = {'e','c','h','o','.'};
1871 static const WCHAR echoCol
[] = {'e','c','h','o',':'};
1872 static const WCHAR echoSlash
[] = {'e','c','h','o','/'};
1873 const DWORD len
= ARRAY_SIZE(echoDot
);
1874 DWORD curr_size
= lstrlenW(curPos
);
1875 DWORD min_len
= (curr_size
< len
? curr_size
: len
);
1876 WCMD_show_prompt(TRUE
);
1877 WCMD_output_asis(curPos
);
1878 /* I don't know why Windows puts a space here but it does */
1879 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1880 if (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1881 curPos
, min_len
, echoDot
, len
) != CSTR_EQUAL
1882 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1883 curPos
, min_len
, echoCol
, len
) != CSTR_EQUAL
1884 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1885 curPos
, min_len
, echoSlash
, len
) != CSTR_EQUAL
)
1887 WCMD_output_asis(L
" ");
1889 WCMD_output_asis(L
"\r\n");
1892 /* Skip repeated 'no echo' characters */
1893 while (*curPos
== '@') curPos
++;
1895 /* Start with an empty string, copying to the command string */
1898 curCopyTo
= curString
;
1899 curLen
= &curStringLen
;
1900 lastWasRedirect
= FALSE
; /* Required e.g. for spaces between > and filename */
1901 lineCurDepth
= curDepth
; /* What was the curdepth at the beginning of the line */
1903 /* Parse every character on the line being processed */
1904 while (*curPos
!= 0x00) {
1909 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1910 lastWasWhiteSpace, onlyWhiteSpace);
1913 /* Prevent overflow caused by the caret escape char */
1914 if (*curLen
>= MAXSTRING
) {
1915 WINE_ERR("Overflow detected in command\n");
1919 /* Certain commands need special handling */
1920 if (curStringLen
== 0 && curCopyTo
== curString
) {
1921 static const WCHAR forDO
[] = {'d','o'};
1923 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
1924 if (WCMD_keyword_ws_found(remCmd
, ARRAY_SIZE(remCmd
), curPos
) || *curPos
== ':') {
1927 } else if (WCMD_keyword_ws_found(forCmd
, ARRAY_SIZE(forCmd
), curPos
)) {
1930 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1931 is only true in the command portion of the IF statement, but this
1932 should suffice for now.
1933 To be able to handle ('s in the condition part take as much as evaluate_if_condition
1934 would take and skip parsing it here. */
1935 } else if (WCMD_keyword_ws_found(ifCmd
, ARRAY_SIZE(ifCmd
), curPos
)) {
1936 int negate
; /* Negate condition */
1937 int test
; /* Condition evaluation result */
1942 p
= curPos
+(ARRAY_SIZE(ifCmd
));
1943 while (*p
== ' ' || *p
== '\t')
1945 WCMD_parse (p
, quals
, param1
, param2
);
1947 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
1948 set in a call to WCMD_parse before */
1949 if (evaluate_if_condition(p
, &command
, &test
, &negate
) != -1)
1951 int if_condition_len
= command
- curPos
;
1952 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
1953 wine_dbgstr_w(p
), wine_dbgstr_w(quals
), wine_dbgstr_w(param1
),
1954 wine_dbgstr_w(param2
), wine_dbgstr_w(command
), if_condition_len
);
1955 memcpy(&curCopyTo
[*curLen
], curPos
, if_condition_len
*sizeof(WCHAR
));
1956 (*curLen
)+=if_condition_len
;
1957 curPos
+=if_condition_len
;
1960 } else if (WCMD_keyword_ws_found(ifElse
, ARRAY_SIZE(ifElse
), curPos
)) {
1961 const int keyw_len
= ARRAY_SIZE(ifElse
) + 1;
1964 onlyWhiteSpace
= TRUE
;
1965 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1966 (*curLen
)+=keyw_len
;
1969 /* If we had a single line if XXX which reaches an else (needs odd
1970 syntax like if 1=1 command && (command) else command we pretended
1971 to add brackets for the if, so they are now over */
1972 if (resetAtEndOfLine
) {
1973 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth
);
1974 resetAtEndOfLine
= FALSE
;
1975 curDepth
= lineCurDepth
;
1979 /* In a for loop, the DO command will follow a close bracket followed by
1980 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1981 is then 0, and all whitespace is skipped */
1982 } else if (inFor
&& WCMD_keyword_ws_found(forDO
, ARRAY_SIZE(forDO
), curPos
)) {
1983 const int keyw_len
= ARRAY_SIZE(forDO
) + 1;
1984 WINE_TRACE("Found 'DO '\n");
1986 onlyWhiteSpace
= TRUE
;
1987 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1988 (*curLen
)+=keyw_len
;
1992 } else if (curCopyTo
== curString
) {
1994 /* Special handling for the 'FOR' command */
1995 if (inFor
&& lastWasWhiteSpace
) {
1996 static const WCHAR forIN
[] = {'i','n'};
1998 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2000 if (WCMD_keyword_ws_found(forIN
, ARRAY_SIZE(forIN
), curPos
)) {
2001 const int keyw_len
= ARRAY_SIZE(forIN
) + 1;
2002 WINE_TRACE("Found 'IN '\n");
2004 onlyWhiteSpace
= TRUE
;
2005 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
2006 (*curLen
)+=keyw_len
;
2013 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2014 the &&, quotes and redirection etc are ineffective, so just force
2015 the use of the default processing by skipping character specific
2017 if (!inOneLine
) thisChar
= *curPos
;
2018 else thisChar
= 'X'; /* Character with no special processing */
2020 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2021 lastWasCaret
= FALSE
;
2025 case '=': /* drop through - ignore token delimiters at the start of a command */
2026 case ',': /* drop through - ignore token delimiters at the start of a command */
2027 case '\t':/* drop through - ignore token delimiters at the start of a command */
2029 /* If a redirect in place, it ends here */
2030 if (!inQuotes
&& !lastWasRedirect
) {
2032 /* If finishing off a redirect, add a whitespace delimiter */
2033 if (curCopyTo
== curRedirs
) {
2034 curCopyTo
[(*curLen
)++] = ' ';
2036 curCopyTo
= curString
;
2037 curLen
= &curStringLen
;
2040 curCopyTo
[(*curLen
)++] = *curPos
;
2043 /* Remember just processed whitespace */
2044 lastWasWhiteSpace
= TRUE
;
2048 case '>': /* drop through - handle redirect chars the same */
2050 /* Make a redirect start here */
2052 curCopyTo
= curRedirs
;
2053 curLen
= &curRedirsLen
;
2054 lastWasRedirect
= TRUE
;
2057 /* See if 1>, 2> etc, in which case we have some patching up
2058 to do (provided there's a preceding whitespace, and enough
2059 chars read so far) */
2060 if (curStringLen
> 2
2061 && (*(curPos
-1)>='1') && (*(curPos
-1)<='9')
2062 && ((*(curPos
-2)==' ') || (*(curPos
-2)=='\t'))) {
2064 curString
[curStringLen
] = 0x00;
2065 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2068 curCopyTo
[(*curLen
)++] = *curPos
;
2070 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2071 do not process that ampersand as an AND operator */
2072 if (thisChar
== '>' && *(curPos
+1) == '&') {
2073 curCopyTo
[(*curLen
)++] = *(curPos
+1);
2078 case '|': /* Pipe character only if not || */
2080 lastWasRedirect
= FALSE
;
2082 /* Add an entry to the command list */
2083 if (curStringLen
> 0) {
2085 /* Add the current command */
2086 WCMD_addCommand(curString
, &curStringLen
,
2087 curRedirs
, &curRedirsLen
,
2088 &curCopyTo
, &curLen
,
2089 prevDelim
, curDepth
,
2090 &lastEntry
, output
);
2094 if (*(curPos
+1) == '|') {
2095 curPos
++; /* Skip other | */
2096 prevDelim
= CMD_ONFAILURE
;
2098 prevDelim
= CMD_PIPE
;
2101 /* If in an IF or ELSE statement, put subsequent chained
2102 commands at a higher depth as if brackets were supplied
2103 but remember to reset to the original depth at EOL */
2104 if ((inIf
|| inElse
) && curDepth
== lineCurDepth
) {
2106 resetAtEndOfLine
= TRUE
;
2109 curCopyTo
[(*curLen
)++] = *curPos
;
2113 case '"': if (WCMD_IsEndQuote(curPos
, inQuotes
)) {
2116 inQuotes
++; /* Quotes within quotes are fun! */
2118 curCopyTo
[(*curLen
)++] = *curPos
;
2119 lastWasRedirect
= FALSE
;
2122 case '(': /* If a '(' is the first non whitespace in a command portion
2123 ie start of line or just after &&, then we read until an
2124 unquoted ) is found */
2125 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2126 ", for(%d, In:%d, Do:%d)"
2127 ", if(%d, else:%d, lwe:%d)\n",
2130 inFor
, lastWasIn
, lastWasDo
,
2131 inIf
, inElse
, lastWasElse
);
2132 lastWasRedirect
= FALSE
;
2134 /* Ignore open brackets inside the for set */
2135 if (*curLen
== 0 && !inIn
) {
2138 /* If in quotes, ignore brackets */
2139 } else if (inQuotes
) {
2140 curCopyTo
[(*curLen
)++] = *curPos
;
2142 /* In a FOR loop, an unquoted '(' may occur straight after
2144 In an IF statement just handle it regardless as we don't
2146 In an ELSE statement, only allow it straight away after
2147 the ELSE and whitespace
2150 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2151 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2153 /* If entering into an 'IN', set inIn */
2154 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2155 WINE_TRACE("Inside an IN\n");
2159 /* Add the current command */
2160 WCMD_addCommand(curString
, &curStringLen
,
2161 curRedirs
, &curRedirsLen
,
2162 &curCopyTo
, &curLen
,
2163 prevDelim
, curDepth
,
2164 &lastEntry
, output
);
2168 curCopyTo
[(*curLen
)++] = *curPos
;
2172 case '^': if (!inQuotes
) {
2173 /* If we reach the end of the input, we need to wait for more */
2174 if (*(curPos
+1) == 0x00) {
2175 lastWasCaret
= TRUE
;
2176 WINE_TRACE("Caret found at end of line\n");
2181 curCopyTo
[(*curLen
)++] = *curPos
;
2184 case '&': if (!inQuotes
) {
2185 lastWasRedirect
= FALSE
;
2187 /* Add an entry to the command list */
2188 if (curStringLen
> 0) {
2190 /* Add the current command */
2191 WCMD_addCommand(curString
, &curStringLen
,
2192 curRedirs
, &curRedirsLen
,
2193 &curCopyTo
, &curLen
,
2194 prevDelim
, curDepth
,
2195 &lastEntry
, output
);
2199 if (*(curPos
+1) == '&') {
2200 curPos
++; /* Skip other & */
2201 prevDelim
= CMD_ONSUCCESS
;
2203 prevDelim
= CMD_NONE
;
2205 /* If in an IF or ELSE statement, put subsequent chained
2206 commands at a higher depth as if brackets were supplied
2207 but remember to reset to the original depth at EOL */
2208 if ((inIf
|| inElse
) && curDepth
== lineCurDepth
) {
2210 resetAtEndOfLine
= TRUE
;
2213 curCopyTo
[(*curLen
)++] = *curPos
;
2217 case ')': if (!inQuotes
&& curDepth
> 0) {
2218 lastWasRedirect
= FALSE
;
2220 /* Add the current command if there is one */
2223 /* Add the current command */
2224 WCMD_addCommand(curString
, &curStringLen
,
2225 curRedirs
, &curRedirsLen
,
2226 &curCopyTo
, &curLen
,
2227 prevDelim
, curDepth
,
2228 &lastEntry
, output
);
2231 /* Add an empty entry to the command list */
2232 prevDelim
= CMD_NONE
;
2233 WCMD_addCommand(NULL
, &curStringLen
,
2234 curRedirs
, &curRedirsLen
,
2235 &curCopyTo
, &curLen
,
2236 prevDelim
, curDepth
,
2237 &lastEntry
, output
);
2240 /* Leave inIn if necessary */
2241 if (inIn
) inIn
= FALSE
;
2243 curCopyTo
[(*curLen
)++] = *curPos
;
2247 lastWasRedirect
= FALSE
;
2248 curCopyTo
[(*curLen
)++] = *curPos
;
2253 /* At various times we need to know if we have only skipped whitespace,
2254 so reset this variable and then it will remain true until a non
2255 whitespace is found */
2256 if ((thisChar
!= ' ') && (thisChar
!= '\t') && (thisChar
!= '\n'))
2257 onlyWhiteSpace
= FALSE
;
2259 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2260 if (!lastWasWhiteSpace
) {
2261 lastWasIn
= lastWasDo
= FALSE
;
2264 /* If we have reached the end, add this command into the list
2265 Do not add command to list if escape char ^ was last */
2266 if (*curPos
== 0x00 && !lastWasCaret
&& *curLen
> 0) {
2268 /* Add an entry to the command list */
2269 WCMD_addCommand(curString
, &curStringLen
,
2270 curRedirs
, &curRedirsLen
,
2271 &curCopyTo
, &curLen
,
2272 prevDelim
, curDepth
,
2273 &lastEntry
, output
);
2275 /* If we had a single line if or else, and we pretended to add
2276 brackets, end them now */
2277 if (resetAtEndOfLine
) {
2278 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth
);
2279 resetAtEndOfLine
= FALSE
;
2280 curDepth
= lineCurDepth
;
2284 /* If we have reached the end of the string, see if bracketing or
2285 final caret is outstanding */
2286 if (*curPos
== 0x00 && (curDepth
> 0 || lastWasCaret
) &&
2287 readFrom
!= INVALID_HANDLE_VALUE
) {
2290 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2292 prevDelim
= CMD_NONE
;
2294 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2295 extraData
= extraSpace
;
2297 /* Read more, skipping any blank lines */
2299 WINE_TRACE("Read more input\n");
2300 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2301 if (!WCMD_fgets(extraData
, MAXSTRING
, readFrom
))
2304 /* Edge case for carets - a completely blank line (i.e. was just
2305 CRLF) is oddly added as an LF but then more data is received (but
2308 if (*extraSpace
== 0x00) {
2309 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2310 *extraData
++ = '\r';
2315 } while (*extraData
== 0x00);
2316 curPos
= extraSpace
;
2318 /* Skip preceding whitespace */
2319 while (*curPos
== ' ' || *curPos
== '\t') curPos
++;
2321 /* Replace env vars if in a batch context */
2322 if (context
) handleExpansion(curPos
, FALSE
, FALSE
);
2324 /* Continue to echo commands IF echo is on and in batch program */
2325 if (context
&& echo_mode
&& *curPos
&& *curPos
!= '@') {
2326 WCMD_output_asis(extraSpace
);
2327 WCMD_output_asis(L
"\r\n");
2330 /* Skip repeated 'no echo' characters and whitespace */
2331 while (*curPos
== '@' || *curPos
== ' ' || *curPos
== '\t') curPos
++;
2335 /* Dump out the parsed output */
2336 WCMD_DumpCommands(*output
);
2341 /***************************************************************************
2342 * WCMD_process_commands
2344 * Process all the commands read in so far
2346 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2351 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2353 /* Loop through the commands, processing them one by one */
2356 CMD_LIST
*origCmd
= thisCmd
;
2358 /* If processing one bracket only, and we find the end bracket
2359 entry (or less), return */
2360 if (oneBracket
&& !thisCmd
->command
&&
2361 bdepth
<= thisCmd
->bracketDepth
) {
2362 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2363 thisCmd
, thisCmd
->nextcommand
);
2364 return thisCmd
->nextcommand
;
2367 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2368 about them and it will be handled in there)
2369 Also, skip over any batch labels (eg. :fred) */
2370 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2371 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2372 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, &thisCmd
, retrycall
);
2375 /* Step on unless the command itself already stepped on */
2376 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2381 /***************************************************************************
2382 * WCMD_free_commands
2384 * Frees the storage held for a parsed command line
2385 * - This is not done in the process_commands, as eventually the current
2386 * pointer will be modified within the commands, and hence a single free
2387 * routine is simpler
2389 void WCMD_free_commands(CMD_LIST
*cmds
) {
2391 /* Loop through the commands, freeing them one by one */
2393 CMD_LIST
*thisCmd
= cmds
;
2394 cmds
= cmds
->nextcommand
;
2395 heap_free(thisCmd
->command
);
2396 heap_free(thisCmd
->redirects
);
2402 /*****************************************************************************
2403 * Main entry point. This is a console application so we have a main() not a
2407 int __cdecl
wmain (int argc
, WCHAR
*argvW
[])
2409 WCHAR
*cmdLine
= NULL
;
2413 BOOL promptNewLine
= TRUE
;
2416 WCHAR comspec
[MAX_PATH
];
2417 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
2418 RTL_OSVERSIONINFOEXW osv
;
2420 STARTUPINFOW startupInfo
;
2423 if (!GetEnvironmentVariableW(L
"COMSPEC", comspec
, ARRAY_SIZE(comspec
)))
2425 GetSystemDirectoryW(comspec
, ARRAY_SIZE(comspec
) - ARRAY_SIZE(L
"\\cmd.exe"));
2426 lstrcatW(comspec
, L
"\\cmd.exe");
2427 SetEnvironmentVariableW(L
"COMSPEC", comspec
);
2432 /* Get the windows version being emulated */
2433 osv
.dwOSVersionInfoSize
= sizeof(osv
);
2434 RtlGetVersion(&osv
);
2436 /* Pre initialize some messages */
2437 lstrcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
2438 sprintf(osver
, "%d.%d.%d", osv
.dwMajorVersion
, osv
.dwMinorVersion
, osv
.dwBuildNumber
);
2439 cmd
= WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION
), osver
);
2440 lstrcpyW(version_string
, cmd
);
2444 /* Can't use argc/argv as it will have stripped quotes from parameters
2445 * meaning cmd.exe /C echo "quoted string" is impossible
2447 cmdLine
= GetCommandLineW();
2448 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine
));
2450 while (*cmdLine
&& *cmdLine
!= '/') ++cmdLine
;
2452 opt_c
= opt_k
= opt_q
= opt_s
= FALSE
;
2454 for (arg
= cmdLine
; *arg
; ++arg
)
2459 switch (towlower(arg
[1]))
2462 unicodeOutput
= FALSE
;
2478 opt_t
= wcstoul(&arg
[3], NULL
, 16);
2481 unicodeOutput
= TRUE
;
2485 delayedsubst
= wcsnicmp(&arg
[3], L
"OFF", 3);
2496 while (*arg
&& wcschr(L
" \t,=;", *arg
)) arg
++;
2502 /* Until we start to read from the keyboard, stay as non-interactive */
2503 interactive
= FALSE
;
2505 SetEnvironmentVariableW(L
"PROMPT", L
"$P$G");
2507 if (opt_c
|| opt_k
) {
2509 WCHAR
*q1
= NULL
,*q2
= NULL
,*p
;
2512 cmd
= heap_strdupW(arg
);
2514 /* opt_s left unflagged if the command starts with and contains exactly
2515 * one quoted string (exactly two quote characters). The quoted string
2516 * must be an executable name that has whitespace and must not have the
2517 * following characters: &<>()@^| */
2520 /* 1. Confirm there is at least one quote */
2521 q1
= wcschr(arg
, '"');
2526 /* 2. Confirm there is a second quote */
2527 q2
= wcschr(q1
+1, '"');
2532 /* 3. Ensure there are no more quotes */
2533 if (wcschr(q2
+1, '"')) opt_s
=1;
2536 /* check first parameter for a space and invalid characters. There must not be any
2537 * invalid characters, but there must be one or more whitespace */
2542 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
2543 || *p
=='@' || *p
=='^' || *p
=='|') {
2547 if (*p
==' ' || *p
=='\t')
2553 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
2555 /* Finally, we only stay in new mode IF the first parameter is quoted and
2556 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2558 WCHAR
*thisArg
= WCMD_parameter(cmd
, 0, NULL
, FALSE
, TRUE
);
2559 WCHAR pathext
[MAXSTRING
];
2562 /* Now extract PATHEXT */
2563 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
2564 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
2565 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
2568 /* If the supplied parameter has any directory information, look there */
2569 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg
));
2570 if (wcschr(thisArg
, '\\') != NULL
) {
2572 GetFullPathNameW(thisArg
, ARRAY_SIZE(string
), string
, NULL
);
2573 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string
));
2574 p
= string
+ lstrlenW(string
);
2576 /* Does file exist with this name? */
2577 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
2578 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
2581 WCHAR
*thisExt
= pathext
;
2583 /* No - try with each of the PATHEXT extensions */
2584 while (!found
&& thisExt
) {
2585 WCHAR
*nextExt
= wcschr(thisExt
, ';');
2588 memcpy(p
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
2589 p
[(nextExt
-thisExt
)] = 0x00;
2590 thisExt
= nextExt
+1;
2592 lstrcpyW(p
, thisExt
);
2596 /* Does file exist with this extension appended? */
2597 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
2598 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
2604 /* Otherwise we now need to look in the path to see if we can find it */
2606 /* Does file exist with this name? */
2607 if (SearchPathW(NULL
, thisArg
, NULL
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
2608 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string
));
2611 WCHAR
*thisExt
= pathext
;
2613 /* No - try with each of the PATHEXT extensions */
2614 while (!found
&& thisExt
) {
2615 WCHAR
*nextExt
= wcschr(thisExt
, ';');
2619 nextExt
= nextExt
+1;
2624 /* Does file exist with this extension? */
2625 if (SearchPathW(NULL
, thisArg
, thisExt
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
2626 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string
),
2627 wine_dbgstr_w(thisExt
));
2635 /* If not found, drop back to old behaviour */
2637 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2643 /* strip first and last quote characters if opt_s; check for invalid
2644 * executable is done later */
2645 if (opt_s
&& *cmd
=='\"')
2646 WCMD_strip_quotes(cmd
);
2649 /* Save cwd into appropriate env var (Must be before the /c processing */
2650 GetCurrentDirectoryW(ARRAY_SIZE(string
), string
);
2651 if (IsCharAlphaW(string
[0]) && string
[1] == ':') {
2652 wsprintfW(envvar
, L
"=%c:", string
[0]);
2653 SetEnvironmentVariableW(envvar
, string
);
2654 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
2658 /* If we do a "cmd /c command", we don't want to allocate a new
2659 * console since the command returns immediately. Rather, we use
2660 * the currently allocated input and output handles. This allows
2661 * us to pipe to and read from the command interpreter.
2664 /* Parse the command string, without reading any more input */
2665 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2666 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2667 WCMD_free_commands(toExecute
);
2674 GetStartupInfoW(&startupInfo
);
2675 if (startupInfo
.lpTitle
!= NULL
)
2676 SetConsoleTitleW(startupInfo
.lpTitle
);
2678 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE
));
2680 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2682 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
2683 defaultColor
= opt_t
& 0xFF;
2688 /* Check HKCU\Software\Microsoft\Command Processor
2689 Then HKLM\Software\Microsoft\Command Processor
2690 for defaultcolour value
2691 Note Can be supplied as DWORD or REG_SZ
2692 Note2 When supplied as REG_SZ it's in decimal!!! */
2695 DWORD value
=0, size
=4;
2696 static const WCHAR regKeyW
[] = L
"Software\\Microsoft\\Command Processor";
2698 if (RegOpenKeyExW(HKEY_CURRENT_USER
, regKeyW
,
2699 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2702 /* See if DWORD or REG_SZ */
2703 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
, NULL
, NULL
) == ERROR_SUCCESS
) {
2704 if (type
== REG_DWORD
) {
2705 size
= sizeof(DWORD
);
2706 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
2707 } else if (type
== REG_SZ
) {
2708 size
= ARRAY_SIZE(strvalue
);
2709 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
2710 value
= wcstoul(strvalue
, NULL
, 10);
2716 if (value
== 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE
, regKeyW
,
2717 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2720 /* See if DWORD or REG_SZ */
2721 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
,
2722 NULL
, NULL
) == ERROR_SUCCESS
) {
2723 if (type
== REG_DWORD
) {
2724 size
= sizeof(DWORD
);
2725 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
2726 } else if (type
== REG_SZ
) {
2727 size
= ARRAY_SIZE(strvalue
);
2728 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
2729 value
= wcstoul(strvalue
, NULL
, 10);
2735 /* If one found, set the screen to that colour */
2736 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
2737 defaultColor
= value
& 0xFF;
2745 /* Parse the command string, without reading any more input */
2746 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2747 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2748 WCMD_free_commands(toExecute
);
2754 * Loop forever getting commands and executing them.
2758 if (!opt_k
) WCMD_version ();
2761 /* Read until EOF (which for std input is never, but if redirect
2762 in place, may occur */
2763 if (echo_mode
) WCMD_show_prompt(promptNewLine
);
2764 if (!WCMD_ReadAndParseLine(NULL
, &toExecute
, GetStdHandle(STD_INPUT_HANDLE
)))
2766 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2767 WCMD_free_commands(toExecute
);
2768 promptNewLine
= !!toExecute
;