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 (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 *xalloc(size_t 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 BOOL
WCMD_get_fullpath(const WCHAR
* in
, SIZE_T outsize
, WCHAR
* out
, WCHAR
** start
)
457 DWORD ret
= GetFullPathNameW(in
, outsize
, out
, start
);
458 if (!ret
) return FALSE
;
461 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_FILENAMETOOLONG
));
467 /***************************************************************************
468 * WCMD_skip_leading_spaces
470 * Return a pointer to the first non-whitespace character of string.
471 * Does not modify the input string.
473 WCHAR
*WCMD_skip_leading_spaces (WCHAR
*string
) {
478 while (*ptr
== ' ' || *ptr
== '\t') ptr
++;
482 /***************************************************************************
483 * WCMD_keyword_ws_found
485 * Checks if the string located at ptr matches a keyword (of length len)
486 * followed by a whitespace character (space or tab)
488 BOOL
WCMD_keyword_ws_found(const WCHAR
*keyword
, const WCHAR
*ptr
) {
489 const int len
= lstrlenW(keyword
);
490 return (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
491 ptr
, len
, keyword
, len
) == CSTR_EQUAL
)
492 && ((*(ptr
+ len
) == ' ') || (*(ptr
+ len
) == '\t'));
495 /*************************************************************************
498 * Remove first and last quote WCHARacters, preserving all other text
499 * Returns the location of the final quote
501 WCHAR
*WCMD_strip_quotes(WCHAR
*cmd
) {
502 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
, *lastquote
;
503 while((*dest
=*src
) != '\0') {
511 while ((*dest
++=*lastq
++) != 0)
518 /*************************************************************************
519 * WCMD_is_magic_envvar
520 * Return TRUE if s is '%'magicvar'%'
521 * and is not masked by a real environment variable.
524 static inline BOOL
WCMD_is_magic_envvar(const WCHAR
*s
, const WCHAR
*magicvar
)
529 return FALSE
; /* Didn't begin with % */
531 if (len
< 2 || s
[len
-1] != '%')
532 return FALSE
; /* Didn't end with another % */
534 if (CompareStringW(LOCALE_USER_DEFAULT
,
535 NORM_IGNORECASE
| SORT_STRINGSORT
,
536 s
+1, len
-2, magicvar
, -1) != CSTR_EQUAL
) {
537 /* Name doesn't match. */
541 if (GetEnvironmentVariableW(magicvar
, NULL
, 0) > 0) {
542 /* Masked by real environment variable. */
549 /*************************************************************************
552 * Expands environment variables, allowing for WCHARacter substitution
554 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR startchar
)
556 WCHAR
*endOfVar
= NULL
, *s
;
557 WCHAR
*colonpos
= NULL
;
558 WCHAR thisVar
[MAXSTRING
];
559 WCHAR thisVarContents
[MAXSTRING
];
560 WCHAR savedchar
= 0x00;
562 WCHAR Delims
[] = L
"%:"; /* First char gets replaced appropriately */
564 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start
), startchar
);
566 /* Find the end of the environment variable, and extract name */
567 Delims
[0] = startchar
;
568 endOfVar
= wcspbrk(start
+1, Delims
);
570 if (endOfVar
== NULL
|| *endOfVar
==' ') {
572 /* In batch program, missing terminator for % and no following
573 ':' just removes the '%' */
575 WCMD_strsubstW(start
, start
+ 1, NULL
, 0);
579 /* In command processing, just ignore it - allows command line
580 syntax like: for %i in (a.a) do echo %i */
585 /* If ':' found, process remaining up until '%' (or stop at ':' if
587 if (*endOfVar
==':') {
588 WCHAR
*endOfVar2
= wcschr(endOfVar
+1, startchar
);
589 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
592 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
593 thisVar
[(endOfVar
- start
)+1] = 0x00;
594 colonpos
= wcschr(thisVar
+1, ':');
596 /* If there's complex substitution, just need %var% for now
597 to get the expanded data to play with */
599 *colonpos
= startchar
;
600 savedchar
= *(colonpos
+1);
601 *(colonpos
+1) = 0x00;
604 /* By now, we know the variable we want to expand but it may be
605 surrounded by '!' if we are in delayed expansion - if so convert
607 if (startchar
=='!') {
609 thisVar
[(endOfVar
- start
)] = '%';
611 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
613 /* Expand to contents, if unchanged, return */
614 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
615 /* override if existing env var called that name */
616 if (WCMD_is_magic_envvar(thisVar
, L
"ERRORLEVEL")) {
617 wsprintfW(thisVarContents
, L
"%d", errorlevel
);
618 len
= lstrlenW(thisVarContents
);
619 } else if (WCMD_is_magic_envvar(thisVar
, L
"DATE")) {
620 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
621 NULL
, thisVarContents
, MAXSTRING
);
622 len
= lstrlenW(thisVarContents
);
623 } else if (WCMD_is_magic_envvar(thisVar
, L
"TIME")) {
624 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
625 NULL
, thisVarContents
, MAXSTRING
);
626 len
= lstrlenW(thisVarContents
);
627 } else if (WCMD_is_magic_envvar(thisVar
, L
"CD")) {
628 GetCurrentDirectoryW(MAXSTRING
, thisVarContents
);
629 len
= lstrlenW(thisVarContents
);
630 } else if (WCMD_is_magic_envvar(thisVar
, L
"RANDOM")) {
631 wsprintfW(thisVarContents
, L
"%d", rand() % 32768);
632 len
= lstrlenW(thisVarContents
);
635 len
= ExpandEnvironmentStringsW(thisVar
, thisVarContents
, ARRAY_SIZE(thisVarContents
));
641 /* In a batch program, unknown env vars are replaced with nothing,
642 note syntax %garbage:1,3% results in anything after the ':'
644 From the command line, you just get back what you entered */
645 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
647 /* Restore the complex part after the compare */
650 *(colonpos
+1) = savedchar
;
653 /* Command line - just ignore this */
654 if (context
== NULL
) return endOfVar
+1;
657 /* Batch - replace unknown env var with nothing */
658 if (colonpos
== NULL
) {
659 WCMD_strsubstW(start
, endOfVar
+ 1, NULL
, 0);
661 len
= lstrlenW(thisVar
);
662 thisVar
[len
-1] = 0x00;
663 /* If %:...% supplied, : is retained */
664 if (colonpos
== thisVar
+1) {
665 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
, -1);
667 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
+ 1, -1);
674 /* See if we need to do complex substitution (any ':'s), if not
675 then our work here is done */
676 if (colonpos
== NULL
) {
677 WCMD_strsubstW(start
, endOfVar
+ 1, thisVarContents
, -1);
681 /* Restore complex bit */
683 *(colonpos
+1) = savedchar
;
686 Handle complex substitutions:
687 xxx=yyy (replace xxx with yyy)
688 *xxx=yyy (replace up to and including xxx with yyy)
689 ~x (from x WCHARs in)
690 ~-x (from x WCHARs from the end)
691 ~x,y (from x WCHARs in for y WCHARacters)
692 ~x,-y (from x WCHARs in until y WCHARacters from the end)
695 /* ~ is substring manipulation */
696 if (savedchar
== '~') {
698 int substrposition
, substrlength
= 0;
699 WCHAR
*commapos
= wcschr(colonpos
+2, ',');
702 substrposition
= wcstol(colonpos
+2, NULL
, 10);
703 if (commapos
) substrlength
= wcstol(commapos
+1, NULL
, 10);
706 if (substrposition
>= 0) {
707 startCopy
= &thisVarContents
[min(substrposition
, len
)];
709 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
712 if (commapos
== NULL
) {
714 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, -1);
715 } else if (substrlength
< 0) {
717 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
718 if (copybytes
> len
) copybytes
= len
;
719 else if (copybytes
< 0) copybytes
= 0;
720 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, copybytes
);
722 substrlength
= min(substrlength
, len
- (startCopy
- thisVarContents
+ 1));
723 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, substrlength
);
726 /* search and replace manipulation */
728 WCHAR
*equalspos
= wcsstr(colonpos
, L
"=");
729 WCHAR
*replacewith
= equalspos
+1;
734 if (equalspos
== NULL
) return start
+1;
735 s
= xstrdupW(endOfVar
+ 1);
737 /* Null terminate both strings */
738 thisVar
[lstrlenW(thisVar
)-1] = 0x00;
741 /* Since we need to be case insensitive, copy the 2 buffers */
742 searchIn
= xstrdupW(thisVarContents
);
743 CharUpperBuffW(searchIn
, lstrlenW(thisVarContents
));
744 searchFor
= xstrdupW(colonpos
+ 1);
745 CharUpperBuffW(searchFor
, lstrlenW(colonpos
+1));
747 /* Handle wildcard case */
748 if (*(colonpos
+1) == '*') {
749 /* Search for string to replace */
750 found
= wcsstr(searchIn
, searchFor
+1);
754 lstrcpyW(start
, replacewith
);
755 lstrcatW(start
, thisVarContents
+ (found
-searchIn
) + lstrlenW(searchFor
+1));
759 lstrcpyW(start
, thisVarContents
);
764 /* Loop replacing all instances */
765 WCHAR
*lastFound
= searchIn
;
766 WCHAR
*outputposn
= start
;
769 while ((found
= wcsstr(lastFound
, searchFor
))) {
770 lstrcpynW(outputposn
,
771 thisVarContents
+ (lastFound
-searchIn
),
772 (found
- lastFound
)+1);
773 outputposn
= outputposn
+ (found
- lastFound
);
774 lstrcatW(outputposn
, replacewith
);
775 outputposn
= outputposn
+ lstrlenW(replacewith
);
776 lastFound
= found
+ lstrlenW(searchFor
);
779 thisVarContents
+ (lastFound
-searchIn
));
780 lstrcatW(outputposn
, s
);
789 /*****************************************************************************
790 * Expand the command. Native expands lines from batch programs as they are
791 * read in and not again, except for 'for' variable substitution.
792 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
793 * atExecute is TRUE when the expansion is occurring as the command is executed
794 * rather than at parse time, i.e. delayed expansion and for loops need to be
797 static void handleExpansion(WCHAR
*cmd
, BOOL atExecute
, BOOL delayed
) {
799 /* For commands in a context (batch program): */
800 /* Expand environment variables in a batch file %{0-9} first */
801 /* including support for any ~ modifiers */
803 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
804 /* names allowing environment variable overrides */
805 /* NOTE: To support the %PATH:xxx% syntax, also perform */
806 /* manual expansion of environment variables here */
811 WCHAR
*delayedp
= NULL
;
812 WCHAR startchar
= '%';
815 /* Display the FOR variables in effect */
817 if (forloopcontext
.variable
[i
]) {
818 WINE_TRACE("FOR variable context: %c = '%s'\n",
819 i
<26?i
+'a':(i
-26)+'A',
820 wine_dbgstr_w(forloopcontext
.variable
[i
]));
824 /* Find the next environment variable delimiter */
825 normalp
= wcschr(p
, '%');
826 if (delayed
) delayedp
= wcschr(p
, '!');
827 if (!normalp
) p
= delayedp
;
828 else if (!delayedp
) p
= normalp
;
829 else p
= min(p
,delayedp
);
830 if (p
) startchar
= *p
;
834 WINE_TRACE("Translate command:%s %d (at: %s)\n",
835 wine_dbgstr_w(cmd
), atExecute
, wine_dbgstr_w(p
));
838 /* Don't touch %% unless it's in Batch */
839 if (!atExecute
&& *(p
+1) == startchar
) {
841 WCMD_strsubstW(p
, p
+1, NULL
, 0);
845 /* Replace %~ modifications if in batch program */
846 } else if (*(p
+1) == '~') {
847 WCMD_HandleTildeModifiers(&p
, atExecute
);
850 /* Replace use of %0...%9 if in batch program*/
851 } else if (!atExecute
&& context
&& (i
>= 0) && (i
<= 9) && startchar
== '%') {
852 t
= WCMD_parameter(context
-> command
, i
+ context
-> shift_count
[i
],
854 WCMD_strsubstW(p
, p
+2, t
, -1);
856 /* Replace use of %* if in batch program*/
857 } else if (!atExecute
&& context
&& *(p
+1)=='*' && startchar
== '%') {
858 WCHAR
*startOfParms
= NULL
;
859 WCHAR
*thisParm
= WCMD_parameter(context
-> command
, 0, &startOfParms
, TRUE
, TRUE
);
860 if (startOfParms
!= NULL
) {
861 startOfParms
+= lstrlenW(thisParm
);
862 while (*startOfParms
==' ' || *startOfParms
== '\t') startOfParms
++;
863 WCMD_strsubstW(p
, p
+2, startOfParms
, -1);
865 WCMD_strsubstW(p
, p
+2, NULL
, 0);
868 int forvaridx
= FOR_VAR_IDX(*(p
+1));
869 if (startchar
== '%' && forvaridx
!= -1 && forloopcontext
.variable
[forvaridx
]) {
870 /* Replace the 2 characters, % and for variable character */
871 WCMD_strsubstW(p
, p
+ 2, forloopcontext
.variable
[forvaridx
], -1);
872 } else if (!atExecute
|| startchar
== '!') {
873 p
= WCMD_expand_envvar(p
, startchar
);
875 /* In a FOR loop, see if this is the variable to replace */
876 } else { /* Ignore %'s on second pass of batch program */
881 /* Find the next environment variable delimiter */
882 normalp
= wcschr(p
, '%');
883 if (delayed
) delayedp
= wcschr(p
, '!');
884 if (!normalp
) p
= delayedp
;
885 else if (!delayedp
) p
= normalp
;
886 else p
= min(p
,delayedp
);
887 if (p
) startchar
= *p
;
894 /*******************************************************************
895 * WCMD_parse - parse a command into parameters and qualifiers.
897 * On exit, all qualifiers are concatenated into q, the first string
898 * not beginning with "/" is in p1 and the
899 * second in p2. Any subsequent non-qualifier strings are lost.
900 * Parameters in quotes are handled.
902 static void WCMD_parse (const WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
)
906 *q
= *p1
= *p2
= '\0';
911 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
912 *q
++ = towupper (*s
++);
922 while ((*s
!= '\0') && (*s
!= '"')) {
923 if (p
== 0) *p1
++ = *s
++;
924 else if (p
== 1) *p2
++ = *s
++;
927 if (p
== 0) *p1
= '\0';
928 if (p
== 1) *p2
= '\0';
935 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
936 && (*s
!= '=') && (*s
!= ',') ) {
937 if (p
== 0) *p1
++ = *s
++;
938 else if (p
== 1) *p2
++ = *s
++;
941 /* Skip concurrent parms */
942 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
944 if (p
== 0) *p1
= '\0';
945 if (p
== 1) *p2
= '\0';
951 static void init_msvcrt_io_block(STARTUPINFOW
* st
)
954 /* fetch the parent MSVCRT info block if any, so that the child can use the
955 * same handles as its grand-father
957 st_p
.cb
= sizeof(STARTUPINFOW
);
958 GetStartupInfoW(&st_p
);
959 st
->cbReserved2
= st_p
.cbReserved2
;
960 st
->lpReserved2
= st_p
.lpReserved2
;
961 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
963 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
969 /* Override the entries for fd 0,1,2 if we happened
970 * to change those std handles (this depends on the way cmd sets
971 * its new input & output handles)
973 sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
975 flags
= (char*)(ptr
+ sizeof(unsigned));
976 handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
978 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
979 st
->cbReserved2
= sz
;
980 st
->lpReserved2
= ptr
;
982 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
983 if (num
<= 0 || (flags
[0] & WX_OPEN
))
985 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
988 if (num
<= 1 || (flags
[1] & WX_OPEN
))
990 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
993 if (num
<= 2 || (flags
[2] & WX_OPEN
))
995 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
1002 /******************************************************************************
1005 * Execute a command line as an external program. Must allow recursion.
1008 * Manual testing under windows shows PATHEXT plays a key part in this,
1009 * and the search algorithm and precedence appears to be as follows.
1012 * If directory supplied on command, just use that directory
1013 * If extension supplied on command, look for that explicit name first
1014 * Otherwise, search in each directory on the path
1016 * If extension supplied on command, look for that explicit name first
1017 * Then look for supplied name .* (even if extension supplied, so
1018 * 'garbage.exe' will match 'garbage.exe.cmd')
1019 * If any found, cycle through PATHEXT looking for name.exe one by one
1021 * Once a match has been found, it is launched - Code currently uses
1022 * findexecutable to achieve this which is left untouched.
1023 * If an executable has not been found, and we were launched through
1024 * a call, we need to check if the command is an internal command,
1025 * so go back through wcmd_execute.
1028 void WCMD_run_program (WCHAR
*command
, BOOL called
)
1030 WCHAR temp
[MAX_PATH
];
1031 WCHAR pathtosearch
[MAXSTRING
];
1033 WCHAR stemofsearch
[MAX_PATH
]; /* maximum allowed executable name is
1034 MAX_PATH, including null character */
1036 WCHAR pathext
[MAXSTRING
];
1038 BOOL extensionsupplied
= FALSE
;
1039 BOOL explicit_path
= FALSE
;
1043 /* Quick way to get the filename is to extract the first argument. */
1044 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command
), called
);
1045 firstParam
= WCMD_parameter(command
, 0, NULL
, FALSE
, TRUE
);
1046 if (!firstParam
) return;
1048 if (!firstParam
[0]) {
1053 /* Calculate the search path and stem to search for */
1054 if (wcspbrk(firstParam
, L
"/\\:") == NULL
) { /* No explicit path given, search path */
1055 lstrcpyW(pathtosearch
, L
".;");
1056 len
= GetEnvironmentVariableW(L
"PATH", &pathtosearch
[2], ARRAY_SIZE(pathtosearch
)-2);
1057 if ((len
== 0) || (len
>= ARRAY_SIZE(pathtosearch
) - 2)) {
1058 lstrcpyW(pathtosearch
, L
".");
1060 if (wcschr(firstParam
, '.') != NULL
) extensionsupplied
= TRUE
;
1061 if (lstrlenW(firstParam
) >= MAX_PATH
)
1063 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG
));
1067 lstrcpyW(stemofsearch
, firstParam
);
1071 /* Convert eg. ..\fred to include a directory by removing file part */
1072 if (!WCMD_get_fullpath(firstParam
, ARRAY_SIZE(pathtosearch
), pathtosearch
, NULL
)) return;
1073 lastSlash
= wcsrchr(pathtosearch
, '\\');
1074 if (lastSlash
&& wcschr(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1075 lstrcpyW(stemofsearch
, lastSlash
+1);
1077 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1078 c:\windows\a.bat syntax */
1079 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1080 explicit_path
= TRUE
;
1083 /* Now extract PATHEXT */
1084 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
1085 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
1086 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
1089 /* Loop through the search path, dir by dir */
1090 pathposn
= pathtosearch
;
1091 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1092 wine_dbgstr_w(stemofsearch
));
1094 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1098 BOOL inside_quotes
= FALSE
;
1102 lstrcpyW(thisDir
, pathposn
);
1107 /* Work on the next directory on the search path */
1109 while ((inside_quotes
|| *pos
!= ';') && *pos
!= 0)
1112 inside_quotes
= !inside_quotes
;
1116 if (*pos
) /* Reached semicolon */
1118 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1119 thisDir
[(pos
-pathposn
)] = 0x00;
1122 else /* Reached string end */
1124 lstrcpyW(thisDir
, pathposn
);
1129 length
= lstrlenW(thisDir
);
1130 if (thisDir
[length
- 1] == '"')
1131 thisDir
[length
- 1] = 0;
1133 if (*thisDir
!= '"')
1134 lstrcpyW(temp
, thisDir
);
1136 lstrcpyW(temp
, thisDir
+ 1);
1138 /* Since you can have eg. ..\.. on the path, need to expand
1139 to full information */
1140 if (!WCMD_get_fullpath(temp
, ARRAY_SIZE(thisDir
), thisDir
, NULL
)) return;
1143 /* 1. If extension supplied, see if that file exists */
1144 lstrcatW(thisDir
, L
"\\");
1145 lstrcatW(thisDir
, stemofsearch
);
1146 pos
= &thisDir
[lstrlenW(thisDir
)]; /* Pos = end of name */
1148 /* 1. If extension supplied, see if that file exists */
1149 if (extensionsupplied
) {
1150 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1155 /* 2. Any .* matches? */
1158 WIN32_FIND_DATAW finddata
;
1160 lstrcatW(thisDir
, L
".*");
1161 h
= FindFirstFileW(thisDir
, &finddata
);
1163 if (h
!= INVALID_HANDLE_VALUE
) {
1165 WCHAR
*thisExt
= pathext
;
1167 /* 3. Yes - Try each path ext */
1169 WCHAR
*nextExt
= wcschr(thisExt
, ';');
1172 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1173 pos
[(nextExt
-thisExt
)] = 0x00;
1174 thisExt
= nextExt
+1;
1176 lstrcpyW(pos
, thisExt
);
1180 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1188 /* Once found, launch it */
1191 PROCESS_INFORMATION pe
;
1195 WCHAR
*ext
= wcsrchr( thisDir
, '.' );
1197 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1199 /* Special case BAT and CMD */
1200 if (ext
&& (!wcsicmp(ext
, L
".bat") || !wcsicmp(ext
, L
".cmd"))) {
1201 BOOL oldinteractive
= interactive
;
1202 interactive
= FALSE
;
1203 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1204 interactive
= oldinteractive
;
1208 /* thisDir contains the file to be launched, but with what?
1209 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1210 hinst
= FindExecutableW (thisDir
, NULL
, temp
);
1211 if ((INT_PTR
)hinst
< 32)
1214 console
= SHGetFileInfoW(temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1216 ZeroMemory (&st
, sizeof(STARTUPINFOW
));
1217 st
.cb
= sizeof(STARTUPINFOW
);
1218 init_msvcrt_io_block(&st
);
1220 /* Launch the process and if a CUI wait on it to complete
1221 Note: Launching internal wine processes cannot specify a full path to exe */
1222 status
= CreateProcessW(thisDir
,
1223 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1224 free(st
.lpReserved2
);
1225 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1226 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1227 /* strip first and last quote WCHARacters and try again */
1228 WCMD_strip_quotes(command
);
1230 WCMD_run_program(command
, called
);
1237 /* Always wait when non-interactive (cmd /c or in batch program),
1238 or for console applications */
1239 if (!interactive
|| (console
&& !HIWORD(console
)))
1240 WaitForSingleObject (pe
.hProcess
, INFINITE
);
1241 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1242 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1244 CloseHandle(pe
.hProcess
);
1245 CloseHandle(pe
.hThread
);
1251 /* Not found anywhere - were we called? */
1253 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
1255 /* Parse the command string, without reading any more input */
1256 WCMD_ReadAndParseLine(command
, &toExecute
, INVALID_HANDLE_VALUE
);
1257 WCMD_process_commands(toExecute
, FALSE
, called
);
1258 WCMD_free_commands(toExecute
);
1263 /* Not found anywhere - give up */
1264 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND
), command
);
1266 /* If a command fails to launch, it sets errorlevel 9009 - which
1267 does not seem to have any associated constant definition */
1273 /*****************************************************************************
1274 * Process one command. If the command is EXIT this routine does not return.
1275 * We will recurse through here executing batch files.
1276 * Note: If call is used to a non-existing program, we reparse the line and
1277 * try to run it as an internal command. 'retrycall' represents whether
1278 * we are attempting this retry.
1280 void WCMD_execute (const WCHAR
*command
, const WCHAR
*redirects
,
1281 CMD_LIST
**cmdList
, BOOL retrycall
)
1283 WCHAR
*cmd
, *parms_start
, *redir
;
1285 int status
, i
, cmd_index
;
1286 DWORD count
, creationDisposition
;
1289 SECURITY_ATTRIBUTES sa
;
1290 WCHAR
*new_cmd
= NULL
;
1291 WCHAR
*new_redir
= NULL
;
1292 HANDLE old_stdhandles
[3] = {GetStdHandle (STD_INPUT_HANDLE
),
1293 GetStdHandle (STD_OUTPUT_HANDLE
),
1294 GetStdHandle (STD_ERROR_HANDLE
)};
1295 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
1298 BOOL prev_echo_mode
, piped
= FALSE
;
1300 WINE_TRACE("command on entry:%s (%p)\n",
1301 wine_dbgstr_w(command
), cmdList
);
1303 /* Move copy of the command onto the heap so it can be expanded */
1304 new_cmd
= xalloc(MAXSTRING
* sizeof(WCHAR
));
1305 lstrcpyW(new_cmd
, command
);
1308 /* Move copy of the redirects onto the heap so it can be expanded */
1309 new_redir
= xalloc(MAXSTRING
* sizeof(WCHAR
));
1312 /* Strip leading whitespaces, and a '@' if supplied */
1313 whichcmd
= WCMD_skip_leading_spaces(cmd
);
1314 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
1315 if (whichcmd
[0] == '@') whichcmd
++;
1317 /* Check if the command entered is internal, and identify which one */
1319 while (IsCharAlphaNumericW(whichcmd
[count
])) {
1322 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1323 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1324 whichcmd
, count
, inbuilt
[i
], -1) == CSTR_EQUAL
) break;
1327 parms_start
= WCMD_skip_leading_spaces (&whichcmd
[count
]);
1329 /* If the next command is a pipe then we implement pipes by redirecting
1330 the output from this command to a temp file and input into the
1331 next command from that temp file.
1332 Note: Do not do this for a for or if statement as the pipe is for
1333 the individual statements, not the for or if itself.
1334 FIXME: Use of named pipes would make more sense here as currently this
1335 process has to finish before the next one can start but this requires
1336 a change to not wait for the first app to finish but rather the pipe */
1337 if (!(cmd_index
== WCMD_FOR
|| cmd_index
== WCMD_IF
) &&
1338 cmdList
&& (*cmdList
)->nextcommand
&&
1339 (*cmdList
)->nextcommand
->prevDelim
== CMD_PIPE
) {
1341 WCHAR temp_path
[MAX_PATH
];
1343 /* Remember piping is in action */
1344 WINE_TRACE("Output needs to be piped\n");
1347 /* Generate a unique temporary filename */
1348 GetTempPathW(ARRAY_SIZE(temp_path
), temp_path
);
1349 GetTempFileNameW(temp_path
, L
"CMD", 0, (*cmdList
)->nextcommand
->pipeFile
);
1350 WINE_TRACE("Using temporary file of %s\n",
1351 wine_dbgstr_w((*cmdList
)->nextcommand
->pipeFile
));
1354 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1356 wsprintfW (new_redir
, L
"%s > %s", redirects
, (*cmdList
)->nextcommand
->pipeFile
);
1357 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
1359 lstrcpyW(new_redir
, redirects
);
1362 /* Expand variables in command line mode only (batch mode will
1363 be expanded as the line is read in, except for 'for' loops) */
1364 handleExpansion(new_cmd
, (context
!= NULL
), delayedsubst
);
1365 handleExpansion(new_redir
, (context
!= NULL
), delayedsubst
);
1368 * Changing default drive has to be handled as a special case, anything
1369 * else if it exists after whitespace is ignored
1372 if ((cmd
[1] == ':') && IsCharAlphaW(cmd
[0]) &&
1373 (!cmd
[2] || cmd
[2] == ' ' || cmd
[2] == '\t')) {
1375 WCHAR dir
[MAX_PATH
];
1377 /* Ignore potential garbage on the same line */
1380 /* According to MSDN CreateProcess docs, special env vars record
1381 the current directory on each drive, in the form =C:
1382 so see if one specified, and if so go back to it */
1383 lstrcpyW(envvar
, L
"=");
1384 lstrcatW(envvar
, cmd
);
1385 if (GetEnvironmentVariableW(envvar
, dir
, MAX_PATH
) == 0) {
1386 wsprintfW(cmd
, L
"%s\\", cmd
);
1387 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
1389 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
1390 status
= SetCurrentDirectoryW(cmd
);
1391 if (!status
) WCMD_print_error ();
1397 sa
.nLength
= sizeof(sa
);
1398 sa
.lpSecurityDescriptor
= NULL
;
1399 sa
.bInheritHandle
= TRUE
;
1402 * Redirect stdin, stdout and/or stderr if required.
1403 * Note: Do not do this for a for or if statement as the pipe is for
1404 * the individual statements, not the for or if itself.
1406 if (!(cmd_index
== WCMD_FOR
|| cmd_index
== WCMD_IF
)) {
1407 /* STDIN could come from a preceding pipe, so delete on close if it does */
1408 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
1409 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
1410 h
= CreateFileW((*cmdList
)->pipeFile
, GENERIC_READ
,
1411 FILE_SHARE_READ
| FILE_SHARE_WRITE
, &sa
, OPEN_EXISTING
,
1412 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
1413 if (h
== INVALID_HANDLE_VALUE
) {
1414 WCMD_print_error ();
1419 SetStdHandle (STD_INPUT_HANDLE
, h
);
1421 /* No need to remember the temporary name any longer once opened */
1422 (*cmdList
)->pipeFile
[0] = 0x00;
1424 /* Otherwise STDIN could come from a '<' redirect */
1425 } else if ((pos
= wcschr(new_redir
,'<')) != NULL
) {
1426 h
= CreateFileW(WCMD_parameter(++pos
, 0, NULL
, FALSE
, FALSE
), GENERIC_READ
, FILE_SHARE_READ
,
1427 &sa
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1428 if (h
== INVALID_HANDLE_VALUE
) {
1429 WCMD_print_error ();
1434 SetStdHandle (STD_INPUT_HANDLE
, h
);
1437 /* Scan the whole command looking for > and 2> */
1438 while (redir
!= NULL
&& ((pos
= wcschr(redir
,'>')) != NULL
)) {
1441 if (pos
> redir
&& (*(pos
-1)=='2'))
1448 creationDisposition
= OPEN_ALWAYS
;
1452 creationDisposition
= CREATE_ALWAYS
;
1455 /* Add support for 2>&1 */
1458 int idx
= *(pos
+1) - '0';
1460 if (DuplicateHandle(GetCurrentProcess(),
1461 GetStdHandle(idx_stdhandles
[idx
]),
1462 GetCurrentProcess(),
1464 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
1465 WINE_FIXME("Duplicating handle failed with gle %ld\n", GetLastError());
1467 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
1470 WCHAR
*param
= WCMD_parameter(pos
, 0, NULL
, FALSE
, FALSE
);
1471 h
= CreateFileW(param
, GENERIC_WRITE
, FILE_SHARE_READ
| FILE_SHARE_DELETE
,
1472 &sa
, creationDisposition
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1473 if (h
== INVALID_HANDLE_VALUE
) {
1474 WCMD_print_error ();
1479 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
1480 INVALID_SET_FILE_POINTER
) {
1481 WCMD_print_error ();
1483 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
1486 SetStdHandle (idx_stdhandles
[handle
], h
);
1489 WINE_TRACE("Not touching redirects for a FOR or IF command\n");
1491 WCMD_parse (parms_start
, quals
, param1
, param2
);
1492 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1494 if (i
<= WCMD_EXIT
&& (parms_start
[0] == '/') && (parms_start
[1] == '?')) {
1495 /* this is a help request for a builtin program */
1497 memcpy(parms_start
, whichcmd
, count
* sizeof(WCHAR
));
1498 parms_start
[count
] = '\0';
1505 WCMD_call (parms_start
);
1509 WCMD_setshow_default (parms_start
);
1512 WCMD_clear_screen ();
1515 WCMD_copy (parms_start
);
1521 WCMD_setshow_date ();
1525 WCMD_delete (parms_start
);
1528 WCMD_directory (parms_start
);
1531 WCMD_echo(&whichcmd
[count
]);
1534 WCMD_goto (cmdList
);
1537 WCMD_give_help (parms_start
);
1540 WCMD_volume (TRUE
, parms_start
);
1544 WCMD_create_dir (parms_start
);
1550 WCMD_setshow_path (parms_start
);
1556 WCMD_setshow_prompt ();
1566 WCMD_remove_dir (parms_start
);
1569 WCMD_setlocal(parms_start
);
1575 WCMD_setshow_env (parms_start
);
1578 WCMD_shift (parms_start
);
1581 WCMD_start (parms_start
);
1584 WCMD_setshow_time ();
1587 if (lstrlenW(&whichcmd
[count
]) > 0)
1588 WCMD_title(&whichcmd
[count
+1]);
1591 WCMD_type (parms_start
);
1594 WCMD_output_asis(L
"\r\n");
1598 WCMD_verify (parms_start
);
1601 WCMD_volume (FALSE
, parms_start
);
1604 WCMD_pushd(parms_start
);
1610 WCMD_assoc(parms_start
, TRUE
);
1616 WCMD_assoc(parms_start
, FALSE
);
1619 WCMD_more(parms_start
);
1622 WCMD_choice(parms_start
);
1625 WCMD_mklink(parms_start
);
1628 WCMD_exit (cmdList
);
1632 /* Very oddly, probably because of all the special parsing required for
1633 these two commands, neither 'for' nor 'if' is supported when called,
1634 i.e. 'call if 1==1...' will fail. */
1636 if (i
==WCMD_FOR
) WCMD_for (parms_start
, cmdList
);
1637 else if (i
==WCMD_IF
) WCMD_if (parms_start
, cmdList
);
1640 /* else: drop through */
1642 prev_echo_mode
= echo_mode
;
1643 WCMD_run_program (whichcmd
, FALSE
);
1644 echo_mode
= prev_echo_mode
;
1649 /* Restore old handles */
1650 for (i
=0; i
<3; i
++) {
1651 if (old_stdhandles
[i
] != GetStdHandle(idx_stdhandles
[i
])) {
1652 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
1653 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
1658 /*************************************************************************
1660 * Load a string from the resource file, handling any error
1661 * Returns string retrieved from resource file
1663 WCHAR
*WCMD_LoadMessage(UINT id
) {
1664 static WCHAR msg
[2048];
1666 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, ARRAY_SIZE(msg
))) {
1667 WINE_FIXME("LoadString failed with %ld\n", GetLastError());
1668 lstrcpyW(msg
, L
"Failed!");
1673 /***************************************************************************
1676 * Dumps out the parsed command line to ensure syntax is correct
1678 static void WCMD_DumpCommands(CMD_LIST
*commands
) {
1679 CMD_LIST
*thisCmd
= commands
;
1681 WINE_TRACE("Parsed line:\n");
1682 while (thisCmd
!= NULL
) {
1683 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1686 thisCmd
->bracketDepth
,
1687 thisCmd
->nextcommand
,
1688 wine_dbgstr_w(thisCmd
->command
),
1689 wine_dbgstr_w(thisCmd
->redirects
));
1690 thisCmd
= thisCmd
->nextcommand
;
1694 /***************************************************************************
1697 * Adds a command to the current command list
1699 static void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1700 WCHAR
*redirs
, int *redirLen
,
1701 WCHAR
**copyTo
, int **copyToLen
,
1702 CMD_DELIMITERS prevDelim
, int curDepth
,
1703 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1705 CMD_LIST
*thisEntry
= NULL
;
1707 /* Allocate storage for command */
1708 thisEntry
= xalloc(sizeof(CMD_LIST
));
1710 /* Copy in the command */
1712 thisEntry
->command
= xalloc((*commandLen
+ 1) * sizeof(WCHAR
));
1713 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1714 thisEntry
->command
[*commandLen
] = 0x00;
1716 /* Copy in the redirects */
1717 thisEntry
->redirects
= xalloc((*redirLen
+ 1) * sizeof(WCHAR
));
1718 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1719 thisEntry
->redirects
[*redirLen
] = 0x00;
1720 thisEntry
->pipeFile
[0] = 0x00;
1722 /* Reset the lengths */
1725 *copyToLen
= commandLen
;
1729 thisEntry
->command
= NULL
;
1730 thisEntry
->redirects
= NULL
;
1731 thisEntry
->pipeFile
[0] = 0x00;
1734 /* Fill in other fields */
1735 thisEntry
->nextcommand
= NULL
;
1736 thisEntry
->prevDelim
= prevDelim
;
1737 thisEntry
->bracketDepth
= curDepth
;
1739 (*lastEntry
)->nextcommand
= thisEntry
;
1741 *output
= thisEntry
;
1743 *lastEntry
= thisEntry
;
1747 /***************************************************************************
1750 * Checks if the quote pointed to is the end-quote.
1754 * 1) The current parameter ends at EOL or at the beginning
1755 * of a redirection or pipe and not in a quote section.
1757 * 2) If the next character is a space and not in a quote section.
1759 * Returns TRUE if this is an end quote, and FALSE if it is not.
1762 static BOOL
WCMD_IsEndQuote(const WCHAR
*quote
, int quoteIndex
)
1764 int quoteCount
= quoteIndex
;
1767 /* If we are not in a quoted section, then we are not an end-quote */
1773 /* Check how many quotes are left for this parameter */
1774 for(i
=0;quote
[i
];i
++)
1781 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1782 else if(((quoteCount
% 2) == 0)
1783 && ((quote
[i
] == '<') || (quote
[i
] == '>') || (quote
[i
] == '|') || (quote
[i
] == ' ') ||
1790 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1792 if(quoteIndex
>= (quoteCount
/ 2))
1801 /***************************************************************************
1802 * WCMD_ReadAndParseLine
1804 * Either uses supplied input or
1805 * Reads a file from the handle, and then...
1806 * Parse the text buffer, splitting into separate commands
1807 * - unquoted && strings split 2 commands but the 2nd is flagged as
1809 * - ( as the first character just ups the bracket depth
1810 * - unquoted ) when bracket depth > 0 terminates a bracket and
1811 * adds a CMD_LIST structure with null command
1812 * - Anything else gets put into the command string (including
1815 WCHAR
*WCMD_ReadAndParseLine(const WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
)
1819 WCHAR curString
[MAXSTRING
];
1820 int curStringLen
= 0;
1821 WCHAR curRedirs
[MAXSTRING
];
1822 int curRedirsLen
= 0;
1826 CMD_LIST
*lastEntry
= NULL
;
1827 CMD_DELIMITERS prevDelim
= CMD_NONE
;
1828 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
1829 BOOL inOneLine
= FALSE
;
1834 BOOL onlyWhiteSpace
= FALSE
;
1835 BOOL lastWasWhiteSpace
= FALSE
;
1836 BOOL lastWasDo
= FALSE
;
1837 BOOL lastWasIn
= FALSE
;
1838 BOOL lastWasElse
= FALSE
;
1839 BOOL lastWasRedirect
= TRUE
;
1840 BOOL lastWasCaret
= FALSE
;
1841 BOOL ignoreBracket
= FALSE
; /* Some expressions after if (set) require */
1842 /* handling brackets as a normal character */
1843 int lineCurDepth
; /* Bracket depth when line was read in */
1844 BOOL resetAtEndOfLine
= FALSE
; /* Do we need to reset curdepth at EOL */
1846 /* Allocate working space for a command read from keyboard, file etc */
1848 extraSpace
= xalloc((MAXSTRING
+ 1) * sizeof(WCHAR
));
1851 WINE_ERR("Could not allocate memory for extraSpace\n");
1855 /* If initial command read in, use that, otherwise get input from handle */
1856 if (optionalcmd
!= NULL
) {
1857 lstrcpyW(extraSpace
, optionalcmd
);
1858 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
1859 WINE_FIXME("No command nor handle supplied\n");
1861 if (!WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
))
1864 curPos
= extraSpace
;
1866 /* Handle truncated input - issue warning */
1867 if (lstrlenW(extraSpace
) == MAXSTRING
-1) {
1868 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
1869 WCMD_output_asis_stderr(extraSpace
);
1870 WCMD_output_asis_stderr(L
"\r\n");
1873 /* Replace env vars if in a batch context */
1874 if (context
) handleExpansion(extraSpace
, FALSE
, FALSE
);
1876 /* Skip preceding whitespace */
1877 while (*curPos
== ' ' || *curPos
== '\t') curPos
++;
1879 /* Show prompt before batch line IF echo is on and in batch program */
1880 if (context
&& echo_mode
&& *curPos
&& (*curPos
!= '@')) {
1881 const DWORD len
= lstrlenW(L
"echo.");
1882 DWORD curr_size
= lstrlenW(curPos
);
1883 DWORD min_len
= (curr_size
< len
? curr_size
: len
);
1884 WCMD_show_prompt(TRUE
);
1885 WCMD_output_asis(curPos
);
1886 /* I don't know why Windows puts a space here but it does */
1887 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1888 if (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1889 curPos
, min_len
, L
"echo.", len
) != CSTR_EQUAL
1890 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1891 curPos
, min_len
, L
"echo:", len
) != CSTR_EQUAL
1892 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1893 curPos
, min_len
, L
"echo/", len
) != CSTR_EQUAL
)
1895 WCMD_output_asis(L
" ");
1897 WCMD_output_asis(L
"\r\n");
1900 /* Skip repeated 'no echo' characters */
1901 while (*curPos
== '@') curPos
++;
1903 /* Start with an empty string, copying to the command string */
1906 curCopyTo
= curString
;
1907 curLen
= &curStringLen
;
1908 lastWasRedirect
= FALSE
; /* Required e.g. for spaces between > and filename */
1909 lineCurDepth
= curDepth
; /* What was the curdepth at the beginning of the line */
1911 /* Parse every character on the line being processed */
1912 while (*curPos
!= 0x00) {
1917 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1918 lastWasWhiteSpace, onlyWhiteSpace);
1921 /* Prevent overflow caused by the caret escape char */
1922 if (*curLen
>= MAXSTRING
) {
1923 WINE_ERR("Overflow detected in command\n");
1927 /* Certain commands need special handling */
1928 if (curStringLen
== 0 && curCopyTo
== curString
) {
1929 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
1930 if (WCMD_keyword_ws_found(L
"rem", curPos
) || *curPos
== ':') {
1933 } else if (WCMD_keyword_ws_found(L
"for", curPos
)) {
1936 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1937 is only true in the command portion of the IF statement, but this
1938 should suffice for now.
1939 To be able to handle ('s in the condition part take as much as evaluate_if_condition
1940 would take and skip parsing it here. */
1941 } else if (WCMD_keyword_ws_found(L
"if", curPos
)) {
1942 int negate
; /* Negate condition */
1943 int test
; /* Condition evaluation result */
1948 p
= curPos
+(lstrlenW(L
"if"));
1949 while (*p
== ' ' || *p
== '\t')
1951 WCMD_parse (p
, quals
, param1
, param2
);
1953 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
1954 set in a call to WCMD_parse before */
1955 if (evaluate_if_condition(p
, &command
, &test
, &negate
) != -1)
1957 int if_condition_len
= command
- curPos
;
1958 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
1959 wine_dbgstr_w(p
), wine_dbgstr_w(quals
), wine_dbgstr_w(param1
),
1960 wine_dbgstr_w(param2
), wine_dbgstr_w(command
), if_condition_len
);
1961 memcpy(&curCopyTo
[*curLen
], curPos
, if_condition_len
*sizeof(WCHAR
));
1962 (*curLen
)+=if_condition_len
;
1963 curPos
+=if_condition_len
;
1966 if (WCMD_keyword_ws_found(L
"set", curPos
))
1967 ignoreBracket
= TRUE
;
1969 } else if (WCMD_keyword_ws_found(L
"else", curPos
)) {
1970 const int keyw_len
= lstrlenW(L
"else") + 1;
1973 onlyWhiteSpace
= TRUE
;
1974 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1975 (*curLen
)+=keyw_len
;
1978 /* If we had a single line if XXX which reaches an else (needs odd
1979 syntax like if 1=1 command && (command) else command we pretended
1980 to add brackets for the if, so they are now over */
1981 if (resetAtEndOfLine
) {
1982 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth
);
1983 resetAtEndOfLine
= FALSE
;
1984 curDepth
= lineCurDepth
;
1988 /* In a for loop, the DO command will follow a close bracket followed by
1989 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1990 is then 0, and all whitespace is skipped */
1991 } else if (inFor
&& WCMD_keyword_ws_found(L
"do", curPos
)) {
1992 const int keyw_len
= lstrlenW(L
"do") + 1;
1993 WINE_TRACE("Found 'DO '\n");
1995 onlyWhiteSpace
= TRUE
;
1996 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1997 (*curLen
)+=keyw_len
;
2001 } else if (curCopyTo
== curString
) {
2003 /* Special handling for the 'FOR' command */
2004 if (inFor
&& lastWasWhiteSpace
) {
2005 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2007 if (WCMD_keyword_ws_found(L
"in", curPos
)) {
2008 const int keyw_len
= lstrlenW(L
"in") + 1;
2009 WINE_TRACE("Found 'IN '\n");
2011 onlyWhiteSpace
= TRUE
;
2012 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
2013 (*curLen
)+=keyw_len
;
2020 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2021 the &&, quotes and redirection etc are ineffective, so just force
2022 the use of the default processing by skipping character specific
2024 if (!inOneLine
) thisChar
= *curPos
;
2025 else thisChar
= 'X'; /* Character with no special processing */
2027 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2028 lastWasCaret
= FALSE
;
2032 case '=': /* drop through - ignore token delimiters at the start of a command */
2033 case ',': /* drop through - ignore token delimiters at the start of a command */
2034 case '\t':/* drop through - ignore token delimiters at the start of a command */
2036 /* If a redirect in place, it ends here */
2037 if (!inQuotes
&& !lastWasRedirect
) {
2039 /* If finishing off a redirect, add a whitespace delimiter */
2040 if (curCopyTo
== curRedirs
) {
2041 curCopyTo
[(*curLen
)++] = ' ';
2043 curCopyTo
= curString
;
2044 curLen
= &curStringLen
;
2047 curCopyTo
[(*curLen
)++] = *curPos
;
2050 /* Remember just processed whitespace */
2051 lastWasWhiteSpace
= TRUE
;
2055 case '>': /* drop through - handle redirect chars the same */
2057 /* Make a redirect start here */
2059 curCopyTo
= curRedirs
;
2060 curLen
= &curRedirsLen
;
2061 lastWasRedirect
= TRUE
;
2064 /* See if 1>, 2> etc, in which case we have some patching up
2065 to do (provided there's a preceding whitespace, and enough
2066 chars read so far) */
2067 if (curStringLen
> 2
2068 && (*(curPos
-1)>='1') && (*(curPos
-1)<='9')
2069 && ((*(curPos
-2)==' ') || (*(curPos
-2)=='\t'))) {
2071 curString
[curStringLen
] = 0x00;
2072 curCopyTo
[(*curLen
)++] = *(curPos
-1);
2075 curCopyTo
[(*curLen
)++] = *curPos
;
2077 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2078 do not process that ampersand as an AND operator */
2079 if (thisChar
== '>' && *(curPos
+1) == '&') {
2080 curCopyTo
[(*curLen
)++] = *(curPos
+1);
2085 case '|': /* Pipe character only if not || */
2087 lastWasRedirect
= FALSE
;
2089 /* Add an entry to the command list */
2090 if (curStringLen
> 0) {
2092 /* Add the current command */
2093 WCMD_addCommand(curString
, &curStringLen
,
2094 curRedirs
, &curRedirsLen
,
2095 &curCopyTo
, &curLen
,
2096 prevDelim
, curDepth
,
2097 &lastEntry
, output
);
2101 if (*(curPos
+1) == '|') {
2102 curPos
++; /* Skip other | */
2103 prevDelim
= CMD_ONFAILURE
;
2105 prevDelim
= CMD_PIPE
;
2108 /* If in an IF or ELSE statement, put subsequent chained
2109 commands at a higher depth as if brackets were supplied
2110 but remember to reset to the original depth at EOL */
2111 if ((inIf
|| inElse
) && curDepth
== lineCurDepth
) {
2113 resetAtEndOfLine
= TRUE
;
2116 curCopyTo
[(*curLen
)++] = *curPos
;
2120 case '"': if (WCMD_IsEndQuote(curPos
, inQuotes
)) {
2123 inQuotes
++; /* Quotes within quotes are fun! */
2125 curCopyTo
[(*curLen
)++] = *curPos
;
2126 lastWasRedirect
= FALSE
;
2129 case '(': /* If a '(' is the first non whitespace in a command portion
2130 ie start of line or just after &&, then we read until an
2131 unquoted ) is found */
2132 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2133 ", for(%d, In:%d, Do:%d)"
2134 ", if(%d, else:%d, lwe:%d)\n",
2137 inFor
, lastWasIn
, lastWasDo
,
2138 inIf
, inElse
, lastWasElse
);
2139 lastWasRedirect
= FALSE
;
2141 /* Ignore open brackets inside the for set */
2142 if (*curLen
== 0 && !inIn
) {
2145 /* If in quotes, ignore brackets */
2146 } else if (inQuotes
) {
2147 curCopyTo
[(*curLen
)++] = *curPos
;
2149 /* In a FOR loop, an unquoted '(' may occur straight after
2151 In an IF statement just handle it regardless as we don't
2153 In an ELSE statement, only allow it straight away after
2154 the ELSE and whitespace
2156 } else if ((inIf
&& !ignoreBracket
) ||
2157 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2158 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2160 /* If entering into an 'IN', set inIn */
2161 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2162 WINE_TRACE("Inside an IN\n");
2166 /* Add the current command */
2167 WCMD_addCommand(curString
, &curStringLen
,
2168 curRedirs
, &curRedirsLen
,
2169 &curCopyTo
, &curLen
,
2170 prevDelim
, curDepth
,
2171 &lastEntry
, output
);
2175 curCopyTo
[(*curLen
)++] = *curPos
;
2179 case '^': if (!inQuotes
) {
2180 /* If we reach the end of the input, we need to wait for more */
2181 if (*(curPos
+1) == 0x00) {
2182 lastWasCaret
= TRUE
;
2183 WINE_TRACE("Caret found at end of line\n");
2188 curCopyTo
[(*curLen
)++] = *curPos
;
2191 case '&': if (!inQuotes
) {
2192 lastWasRedirect
= FALSE
;
2194 /* Add an entry to the command list */
2195 if (curStringLen
> 0) {
2197 /* Add the current command */
2198 WCMD_addCommand(curString
, &curStringLen
,
2199 curRedirs
, &curRedirsLen
,
2200 &curCopyTo
, &curLen
,
2201 prevDelim
, curDepth
,
2202 &lastEntry
, output
);
2206 if (*(curPos
+1) == '&') {
2207 curPos
++; /* Skip other & */
2208 prevDelim
= CMD_ONSUCCESS
;
2210 prevDelim
= CMD_NONE
;
2212 /* If in an IF or ELSE statement, put subsequent chained
2213 commands at a higher depth as if brackets were supplied
2214 but remember to reset to the original depth at EOL */
2215 if ((inIf
|| inElse
) && curDepth
== lineCurDepth
) {
2217 resetAtEndOfLine
= TRUE
;
2220 curCopyTo
[(*curLen
)++] = *curPos
;
2224 case ')': if (!inQuotes
&& curDepth
> 0) {
2225 lastWasRedirect
= FALSE
;
2227 /* Add the current command if there is one */
2230 /* Add the current command */
2231 WCMD_addCommand(curString
, &curStringLen
,
2232 curRedirs
, &curRedirsLen
,
2233 &curCopyTo
, &curLen
,
2234 prevDelim
, curDepth
,
2235 &lastEntry
, output
);
2238 /* Add an empty entry to the command list */
2239 prevDelim
= CMD_NONE
;
2240 WCMD_addCommand(NULL
, &curStringLen
,
2241 curRedirs
, &curRedirsLen
,
2242 &curCopyTo
, &curLen
,
2243 prevDelim
, curDepth
,
2244 &lastEntry
, output
);
2247 /* Leave inIn if necessary */
2248 if (inIn
) inIn
= FALSE
;
2250 curCopyTo
[(*curLen
)++] = *curPos
;
2254 lastWasRedirect
= FALSE
;
2255 curCopyTo
[(*curLen
)++] = *curPos
;
2260 /* At various times we need to know if we have only skipped whitespace,
2261 so reset this variable and then it will remain true until a non
2262 whitespace is found */
2263 if ((thisChar
!= ' ') && (thisChar
!= '\t') && (thisChar
!= '\n'))
2264 onlyWhiteSpace
= FALSE
;
2266 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2267 if (!lastWasWhiteSpace
) {
2268 lastWasIn
= lastWasDo
= FALSE
;
2271 /* If we have reached the end, add this command into the list
2272 Do not add command to list if escape char ^ was last */
2273 if (*curPos
== 0x00 && !lastWasCaret
&& *curLen
> 0) {
2275 /* Add an entry to the command list */
2276 WCMD_addCommand(curString
, &curStringLen
,
2277 curRedirs
, &curRedirsLen
,
2278 &curCopyTo
, &curLen
,
2279 prevDelim
, curDepth
,
2280 &lastEntry
, output
);
2282 /* If we had a single line if or else, and we pretended to add
2283 brackets, end them now */
2284 if (resetAtEndOfLine
) {
2285 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth
);
2286 resetAtEndOfLine
= FALSE
;
2287 curDepth
= lineCurDepth
;
2291 /* If we have reached the end of the string, see if bracketing or
2292 final caret is outstanding */
2293 if (*curPos
== 0x00 && (curDepth
> 0 || lastWasCaret
) &&
2294 readFrom
!= INVALID_HANDLE_VALUE
) {
2297 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2299 prevDelim
= CMD_NONE
;
2301 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2302 extraData
= extraSpace
;
2304 /* Read more, skipping any blank lines */
2306 WINE_TRACE("Read more input\n");
2307 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2308 if (!WCMD_fgets(extraData
, MAXSTRING
, readFrom
))
2311 /* Edge case for carets - a completely blank line (i.e. was just
2312 CRLF) is oddly added as an LF but then more data is received (but
2315 if (*extraSpace
== 0x00) {
2316 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2317 *extraData
++ = '\r';
2322 } while (*extraData
== 0x00);
2323 curPos
= extraSpace
;
2325 /* Skip preceding whitespace */
2326 while (*curPos
== ' ' || *curPos
== '\t') curPos
++;
2328 /* Replace env vars if in a batch context */
2329 if (context
) handleExpansion(curPos
, FALSE
, FALSE
);
2331 /* Continue to echo commands IF echo is on and in batch program */
2332 if (context
&& echo_mode
&& *curPos
&& *curPos
!= '@') {
2333 WCMD_output_asis(extraSpace
);
2334 WCMD_output_asis(L
"\r\n");
2337 /* Skip repeated 'no echo' characters and whitespace */
2338 while (*curPos
== '@' || *curPos
== ' ' || *curPos
== '\t') curPos
++;
2342 /* Dump out the parsed output */
2343 WCMD_DumpCommands(*output
);
2348 /***************************************************************************
2349 * WCMD_process_commands
2351 * Process all the commands read in so far
2353 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2358 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2360 /* Loop through the commands, processing them one by one */
2363 CMD_LIST
*origCmd
= thisCmd
;
2365 /* If processing one bracket only, and we find the end bracket
2366 entry (or less), return */
2367 if (oneBracket
&& !thisCmd
->command
&&
2368 bdepth
<= thisCmd
->bracketDepth
) {
2369 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2370 thisCmd
, thisCmd
->nextcommand
);
2371 return thisCmd
->nextcommand
;
2374 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2375 about them and it will be handled in there)
2376 Also, skip over any batch labels (eg. :fred) */
2377 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2378 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2379 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, &thisCmd
, retrycall
);
2382 /* Step on unless the command itself already stepped on */
2383 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2388 /***************************************************************************
2389 * WCMD_free_commands
2391 * Frees the storage held for a parsed command line
2392 * - This is not done in the process_commands, as eventually the current
2393 * pointer will be modified within the commands, and hence a single free
2394 * routine is simpler
2396 void WCMD_free_commands(CMD_LIST
*cmds
) {
2398 /* Loop through the commands, freeing them one by one */
2400 CMD_LIST
*thisCmd
= cmds
;
2401 cmds
= cmds
->nextcommand
;
2402 free(thisCmd
->command
);
2403 free(thisCmd
->redirects
);
2409 /*****************************************************************************
2410 * Main entry point. This is a console application so we have a main() not a
2414 int __cdecl
wmain (int argc
, WCHAR
*argvW
[])
2416 WCHAR
*cmdLine
= NULL
;
2420 BOOL promptNewLine
= TRUE
;
2423 WCHAR comspec
[MAX_PATH
];
2424 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
2425 RTL_OSVERSIONINFOEXW osv
;
2427 STARTUPINFOW startupInfo
;
2430 if (!GetEnvironmentVariableW(L
"COMSPEC", comspec
, ARRAY_SIZE(comspec
)))
2432 GetSystemDirectoryW(comspec
, ARRAY_SIZE(comspec
) - ARRAY_SIZE(L
"\\cmd.exe"));
2433 lstrcatW(comspec
, L
"\\cmd.exe");
2434 SetEnvironmentVariableW(L
"COMSPEC", comspec
);
2439 /* Get the windows version being emulated */
2440 osv
.dwOSVersionInfoSize
= sizeof(osv
);
2441 RtlGetVersion(&osv
);
2443 /* Pre initialize some messages */
2444 lstrcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
2445 sprintf(osver
, "%ld.%ld.%ld", osv
.dwMajorVersion
, osv
.dwMinorVersion
, osv
.dwBuildNumber
);
2446 cmd
= WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION
), osver
);
2447 lstrcpyW(version_string
, cmd
);
2451 /* Can't use argc/argv as it will have stripped quotes from parameters
2452 * meaning cmd.exe /C echo "quoted string" is impossible
2454 cmdLine
= GetCommandLineW();
2455 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine
));
2457 while (*cmdLine
&& *cmdLine
!= '/') ++cmdLine
;
2459 opt_c
= opt_k
= opt_q
= opt_s
= FALSE
;
2461 for (arg
= cmdLine
; *arg
; ++arg
)
2466 switch (towlower(arg
[1]))
2469 unicodeOutput
= FALSE
;
2485 opt_t
= wcstoul(&arg
[3], NULL
, 16);
2488 unicodeOutput
= TRUE
;
2492 delayedsubst
= wcsnicmp(&arg
[3], L
"OFF", 3);
2503 while (*arg
&& wcschr(L
" \t,=;", *arg
)) arg
++;
2509 /* Until we start to read from the keyboard, stay as non-interactive */
2510 interactive
= FALSE
;
2512 SetEnvironmentVariableW(L
"PROMPT", L
"$P$G");
2514 if (opt_c
|| opt_k
) {
2516 WCHAR
*q1
= NULL
,*q2
= NULL
,*p
;
2519 cmd
= xstrdupW(arg
);
2521 /* opt_s left unflagged if the command starts with and contains exactly
2522 * one quoted string (exactly two quote characters). The quoted string
2523 * must be an executable name that has whitespace and must not have the
2524 * following characters: &<>()@^| */
2527 /* 1. Confirm there is at least one quote */
2528 q1
= wcschr(arg
, '"');
2533 /* 2. Confirm there is a second quote */
2534 q2
= wcschr(q1
+1, '"');
2539 /* 3. Ensure there are no more quotes */
2540 if (wcschr(q2
+1, '"')) opt_s
=1;
2543 /* check first parameter for a space and invalid characters. There must not be any
2544 * invalid characters, but there must be one or more whitespace */
2549 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
2550 || *p
=='@' || *p
=='^' || *p
=='|') {
2554 if (*p
==' ' || *p
=='\t')
2560 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
2562 /* Finally, we only stay in new mode IF the first parameter is quoted and
2563 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2565 WCHAR
*thisArg
= WCMD_parameter(cmd
, 0, NULL
, FALSE
, TRUE
);
2566 WCHAR pathext
[MAXSTRING
];
2569 /* Now extract PATHEXT */
2570 len
= GetEnvironmentVariableW(L
"PATHEXT", pathext
, ARRAY_SIZE(pathext
));
2571 if ((len
== 0) || (len
>= ARRAY_SIZE(pathext
))) {
2572 lstrcpyW(pathext
, L
".bat;.com;.cmd;.exe");
2575 /* If the supplied parameter has any directory information, look there */
2576 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg
));
2577 if (wcschr(thisArg
, '\\') != NULL
) {
2579 if (!WCMD_get_fullpath(thisArg
, ARRAY_SIZE(string
), string
, NULL
)) return FALSE
;
2580 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string
));
2581 p
= string
+ lstrlenW(string
);
2583 /* Does file exist with this name? */
2584 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
2585 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
2588 WCHAR
*thisExt
= pathext
;
2590 /* No - try with each of the PATHEXT extensions */
2591 while (!found
&& thisExt
) {
2592 WCHAR
*nextExt
= wcschr(thisExt
, ';');
2595 memcpy(p
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
2596 p
[(nextExt
-thisExt
)] = 0x00;
2597 thisExt
= nextExt
+1;
2599 lstrcpyW(p
, thisExt
);
2603 /* Does file exist with this extension appended? */
2604 if (GetFileAttributesW(string
) != INVALID_FILE_ATTRIBUTES
) {
2605 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string
));
2611 /* Otherwise we now need to look in the path to see if we can find it */
2613 /* Does file exist with this name? */
2614 if (SearchPathW(NULL
, thisArg
, NULL
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
2615 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string
));
2618 WCHAR
*thisExt
= pathext
;
2620 /* No - try with each of the PATHEXT extensions */
2621 while (!found
&& thisExt
) {
2622 WCHAR
*nextExt
= wcschr(thisExt
, ';');
2626 nextExt
= nextExt
+1;
2631 /* Does file exist with this extension? */
2632 if (SearchPathW(NULL
, thisArg
, thisExt
, ARRAY_SIZE(string
), string
, NULL
) != 0) {
2633 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string
),
2634 wine_dbgstr_w(thisExt
));
2642 /* If not found, drop back to old behaviour */
2644 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2650 /* strip first and last quote characters if opt_s; check for invalid
2651 * executable is done later */
2652 if (opt_s
&& *cmd
=='\"')
2653 WCMD_strip_quotes(cmd
);
2656 /* Save cwd into appropriate env var (Must be before the /c processing */
2657 GetCurrentDirectoryW(ARRAY_SIZE(string
), string
);
2658 if (IsCharAlphaW(string
[0]) && string
[1] == ':') {
2659 wsprintfW(envvar
, L
"=%c:", string
[0]);
2660 SetEnvironmentVariableW(envvar
, string
);
2661 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
2665 /* If we do a "cmd /c command", we don't want to allocate a new
2666 * console since the command returns immediately. Rather, we use
2667 * the currently allocated input and output handles. This allows
2668 * us to pipe to and read from the command interpreter.
2671 /* Parse the command string, without reading any more input */
2672 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2673 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2674 WCMD_free_commands(toExecute
);
2681 GetStartupInfoW(&startupInfo
);
2682 if (startupInfo
.lpTitle
!= NULL
)
2683 SetConsoleTitleW(startupInfo
.lpTitle
);
2685 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE
));
2687 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2689 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
2690 defaultColor
= opt_t
& 0xFF;
2695 /* Check HKCU\Software\Microsoft\Command Processor
2696 Then HKLM\Software\Microsoft\Command Processor
2697 for defaultcolour value
2698 Note Can be supplied as DWORD or REG_SZ
2699 Note2 When supplied as REG_SZ it's in decimal!!! */
2702 DWORD value
=0, size
=4;
2703 static const WCHAR regKeyW
[] = L
"Software\\Microsoft\\Command Processor";
2705 if (RegOpenKeyExW(HKEY_CURRENT_USER
, regKeyW
,
2706 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2709 /* See if DWORD or REG_SZ */
2710 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
, NULL
, NULL
) == ERROR_SUCCESS
) {
2711 if (type
== REG_DWORD
) {
2712 size
= sizeof(DWORD
);
2713 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
2714 } else if (type
== REG_SZ
) {
2715 size
= sizeof(strvalue
);
2716 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
2717 value
= wcstoul(strvalue
, NULL
, 10);
2723 if (value
== 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE
, regKeyW
,
2724 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2727 /* See if DWORD or REG_SZ */
2728 if (RegQueryValueExW(key
, L
"DefaultColor", NULL
, &type
,
2729 NULL
, NULL
) == ERROR_SUCCESS
) {
2730 if (type
== REG_DWORD
) {
2731 size
= sizeof(DWORD
);
2732 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)&value
, &size
);
2733 } else if (type
== REG_SZ
) {
2734 size
= sizeof(strvalue
);
2735 RegQueryValueExW(key
, L
"DefaultColor", NULL
, NULL
, (BYTE
*)strvalue
, &size
);
2736 value
= wcstoul(strvalue
, NULL
, 10);
2742 /* If one found, set the screen to that colour */
2743 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
2744 defaultColor
= value
& 0xFF;
2752 /* Parse the command string, without reading any more input */
2753 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2754 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2755 WCMD_free_commands(toExecute
);
2761 * Loop forever getting commands and executing them.
2765 if (!opt_k
) WCMD_version ();
2768 /* Read until EOF (which for std input is never, but if redirect
2769 in place, may occur */
2770 if (echo_mode
) WCMD_show_prompt(promptNewLine
);
2771 if (!WCMD_ReadAndParseLine(NULL
, &toExecute
, GetStdHandle(STD_INPUT_HANDLE
)))
2773 WCMD_process_commands(toExecute
, FALSE
, FALSE
);
2774 WCMD_free_commands(toExecute
);
2775 promptNewLine
= !!toExecute
;