2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
34 const WCHAR inbuilt
[][10] = {
35 {'A','T','T','R','I','B','\0'},
36 {'C','A','L','L','\0'},
38 {'C','H','D','I','R','\0'},
40 {'C','O','P','Y','\0'},
41 {'C','T','T','Y','\0'},
42 {'D','A','T','E','\0'},
45 {'E','C','H','O','\0'},
46 {'E','R','A','S','E','\0'},
48 {'G','O','T','O','\0'},
49 {'H','E','L','P','\0'},
51 {'L','A','B','E','L','\0'},
53 {'M','K','D','I','R','\0'},
54 {'M','O','V','E','\0'},
55 {'P','A','T','H','\0'},
56 {'P','A','U','S','E','\0'},
57 {'P','R','O','M','P','T','\0'},
60 {'R','E','N','A','M','E','\0'},
62 {'R','M','D','I','R','\0'},
64 {'S','H','I','F','T','\0'},
65 {'T','I','M','E','\0'},
66 {'T','I','T','L','E','\0'},
67 {'T','Y','P','E','\0'},
68 {'V','E','R','I','F','Y','\0'},
71 {'E','N','D','L','O','C','A','L','\0'},
72 {'S','E','T','L','O','C','A','L','\0'},
73 {'P','U','S','H','D','\0'},
74 {'P','O','P','D','\0'},
75 {'A','S','S','O','C','\0'},
76 {'C','O','L','O','R','\0'},
77 {'F','T','Y','P','E','\0'},
78 {'M','O','R','E','\0'},
79 {'C','H','O','I','C','E','\0'},
80 {'E','X','I','T','\0'}
85 int echo_mode
= 1, verify_mode
= 0, defaultColor
= 7;
86 static int opt_c
, opt_k
, opt_s
;
87 const WCHAR newline
[] = {'\r','\n','\0'};
88 static const WCHAR equalsW
[] = {'=','\0'};
89 static const WCHAR closeBW
[] = {')','\0'};
91 WCHAR version_string
[100];
92 WCHAR quals
[MAX_PATH
], param1
[MAXSTRING
], param2
[MAXSTRING
];
93 BATCH_CONTEXT
*context
= NULL
;
94 extern struct env_stack
*pushd_directories
;
95 static const WCHAR
*pagedMessage
= NULL
;
96 static char *output_bufA
= NULL
;
97 #define MAX_WRITECONSOLE_SIZE 65535
98 BOOL unicodePipes
= FALSE
;
101 /*******************************************************************
102 * WCMD_output_asis_len - send output to current standard output
104 * Output a formatted unicode string. Ideally this will go to the console
105 * and hence required WriteConsoleW to output it, however if file i/o is
106 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
108 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
113 /* If nothing to write, return (MORE does this sometimes) */
116 /* Try to write as unicode assuming it is to a console */
117 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
119 /* If writing to console fails, assume its file
120 i/o so convert to OEM codepage and output */
122 BOOL usedDefaultChar
= FALSE
;
123 DWORD convertedChars
;
127 * Allocate buffer to use when writing to file. (Not freed, as one off)
129 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
130 MAX_WRITECONSOLE_SIZE
);
132 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
136 /* Convert to OEM, then output */
137 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
138 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
139 "?", &usedDefaultChar
);
140 WriteFile(device
, output_bufA
, convertedChars
,
143 WriteFile(device
, message
, len
*sizeof(WCHAR
),
150 /*******************************************************************
151 * WCMD_output - send output to current standard output device.
155 void WCMD_output (const WCHAR
*format
, ...) {
162 ret
= vsnprintfW(string
, sizeof(string
)/sizeof(WCHAR
), format
, ap
);
163 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
164 WINE_ERR("Output truncated in WCMD_output\n" );
165 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
169 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
173 static int line_count
;
174 static int max_height
;
175 static int max_width
;
176 static BOOL paged_mode
;
179 void WCMD_enter_paged_mode(const WCHAR
*msg
)
181 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
183 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
184 max_height
= consoleInfo
.dwSize
.Y
;
185 max_width
= consoleInfo
.dwSize
.X
;
193 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
196 void WCMD_leave_paged_mode(void)
202 /***************************************************************************
205 * Read characters in from a console/file, returning result in Unicode
206 * with signature identical to ReadFile
208 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
209 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
213 /* Try to read from console as Unicode */
214 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
216 /* If reading from console has failed we assume its file
217 i/o so read in and convert from OEM codepage */
222 * Allocate buffer to use when reading from file. Not freed
224 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
225 MAX_WRITECONSOLE_SIZE
);
227 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
231 /* Read from file (assume OEM codepage) */
232 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
234 /* Convert from OEM */
235 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
242 /*******************************************************************
243 * WCMD_output_asis - send output to current standard output device.
244 * without formatting eg. when message contains '%'
246 void WCMD_output_asis (const WCHAR
*message
) {
254 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
258 if (*ptr
== '\n') ptr
++;
259 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
),
260 GetStdHandle(STD_OUTPUT_HANDLE
));
263 if (++line_count
>= max_height
- 1) {
265 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
),
266 GetStdHandle(STD_OUTPUT_HANDLE
));
267 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
268 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
271 } while (((message
= ptr
) != NULL
) && (*ptr
));
273 WCMD_output_asis_len(message
, lstrlenW(message
),
274 GetStdHandle(STD_OUTPUT_HANDLE
));
278 /****************************************************************************
281 * Print the message for GetLastError
284 void WCMD_print_error (void) {
289 error_code
= GetLastError ();
290 status
= FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
291 NULL
, error_code
, 0, (LPWSTR
) &lpMsgBuf
, 0, NULL
);
293 WINE_FIXME ("Cannot display message for error %d, status %d\n",
294 error_code
, GetLastError());
298 WCMD_output_asis_len(lpMsgBuf
, lstrlenW(lpMsgBuf
),
299 GetStdHandle(STD_ERROR_HANDLE
));
300 LocalFree (lpMsgBuf
);
301 WCMD_output_asis_len (newline
, lstrlenW(newline
),
302 GetStdHandle(STD_ERROR_HANDLE
));
306 /******************************************************************************
309 * Display the prompt on STDout
313 static void WCMD_show_prompt (void) {
316 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
319 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
321 len
= GetEnvironmentVariableW(envPrompt
, prompt_string
,
322 sizeof(prompt_string
)/sizeof(WCHAR
));
323 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
324 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
325 strcpyW (prompt_string
, dfltPrompt
);
339 switch (toupper(*p
)) {
353 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
372 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
378 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
391 GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
395 strcatW (q
, version_string
);
402 if (pushd_directories
) {
403 memset(q
, '+', pushd_directories
->u
.stackdepth
);
404 q
= q
+ pushd_directories
->u
.stackdepth
;
412 WCMD_output_asis (out_string
);
416 /*************************************************************************
418 * A wide version of strdup as its missing from unicode.h
420 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
421 int len
=strlenW(input
)+1;
422 WCHAR
*result
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
423 memcpy(result
, input
, len
* sizeof(WCHAR
));
427 /*************************************************************************
429 * Replaces a portion of a Unicode string with the specified string.
430 * It's up to the caller to ensure there is enough space in the
431 * destination buffer.
433 void WCMD_strsubstW(WCHAR
* start
, WCHAR
* next
, WCHAR
* insert
, int len
) {
436 len
=insert
? lstrlenW(insert
) : 0;
437 if (start
+len
!= next
)
438 memmove(start
+len
, next
, (strlenW(next
) + 1) * sizeof(*next
));
440 memcpy(start
, insert
, len
* sizeof(*insert
));
443 /***************************************************************************
444 * WCMD_strtrim_leading_spaces
446 * Remove leading spaces from a string. Return a pointer to the first
447 * non-space character. Does not modify the input string
449 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
454 while (*ptr
== ' ') ptr
++;
458 /*************************************************************************
459 * WCMD_opt_s_strip_quotes
461 * Remove first and last quote WCHARacters, preserving all other text
463 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
464 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
465 while((*dest
=*src
) != '\0') {
472 while ((*dest
++=*lastq
++) != 0)
478 /*************************************************************************
479 * WCMD_is_magic_envvar
480 * Return TRUE if s is '%'magicvar'%'
481 * and is not masked by a real environment variable.
484 static inline BOOL
WCMD_is_magic_envvar(const WCHAR
*s
, const WCHAR
*magicvar
)
489 return FALSE
; /* Didn't begin with % */
491 if (len
< 2 || s
[len
-1] != '%')
492 return FALSE
; /* Didn't end with another % */
494 if (CompareStringW(LOCALE_USER_DEFAULT
,
495 NORM_IGNORECASE
| SORT_STRINGSORT
,
496 s
+1, len
-2, magicvar
, -1) != CSTR_EQUAL
) {
497 /* Name doesn't match. */
501 if (GetEnvironmentVariableW(magicvar
, NULL
, 0) > 0) {
502 /* Masked by real environment variable. */
509 /*************************************************************************
512 * Expands environment variables, allowing for WCHARacter substitution
514 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
, WCHAR
*forVar
, WCHAR
*forVal
) {
515 WCHAR
*endOfVar
= NULL
, *s
;
516 WCHAR
*colonpos
= NULL
;
517 WCHAR thisVar
[MAXSTRING
];
518 WCHAR thisVarContents
[MAXSTRING
];
519 WCHAR savedchar
= 0x00;
522 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
523 static const WCHAR Date
[] = {'D','A','T','E','\0'};
524 static const WCHAR Time
[] = {'T','I','M','E','\0'};
525 static const WCHAR Cd
[] = {'C','D','\0'};
526 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
527 static const WCHAR Delims
[] = {'%',' ',':','\0'};
529 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
530 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
532 /* Find the end of the environment variable, and extract name */
533 endOfVar
= strpbrkW(start
+1, Delims
);
535 if (endOfVar
== NULL
|| *endOfVar
==' ') {
537 /* In batch program, missing terminator for % and no following
538 ':' just removes the '%' */
540 WCMD_strsubstW(start
, start
+ 1, NULL
, 0);
544 /* In command processing, just ignore it - allows command line
545 syntax like: for %i in (a.a) do echo %i */
550 /* If ':' found, process remaining up until '%' (or stop at ':' if
552 if (*endOfVar
==':') {
553 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
554 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
557 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
558 thisVar
[(endOfVar
- start
)+1] = 0x00;
559 colonpos
= strchrW(thisVar
+1, ':');
561 /* If there's complex substitution, just need %var% for now
562 to get the expanded data to play with */
565 savedchar
= *(colonpos
+1);
566 *(colonpos
+1) = 0x00;
569 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
571 /* Expand to contents, if unchanged, return */
572 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
573 /* override if existing env var called that name */
574 if (WCMD_is_magic_envvar(thisVar
, ErrorLvl
)) {
575 static const WCHAR fmt
[] = {'%','d','\0'};
576 wsprintfW(thisVarContents
, fmt
, errorlevel
);
577 len
= strlenW(thisVarContents
);
578 } else if (WCMD_is_magic_envvar(thisVar
, Date
)) {
579 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
580 NULL
, thisVarContents
, MAXSTRING
);
581 len
= strlenW(thisVarContents
);
582 } else if (WCMD_is_magic_envvar(thisVar
, Time
)) {
583 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
584 NULL
, thisVarContents
, MAXSTRING
);
585 len
= strlenW(thisVarContents
);
586 } else if (WCMD_is_magic_envvar(thisVar
, Cd
)) {
587 GetCurrentDirectoryW(MAXSTRING
, thisVarContents
);
588 len
= strlenW(thisVarContents
);
589 } else if (WCMD_is_magic_envvar(thisVar
, Random
)) {
590 static const WCHAR fmt
[] = {'%','d','\0'};
591 wsprintfW(thisVarContents
, fmt
, rand() % 32768);
592 len
= strlenW(thisVarContents
);
594 /* Look for a matching 'for' variable */
596 (CompareStringW(LOCALE_USER_DEFAULT
,
599 (colonpos
- thisVar
) - 1,
601 strcpyW(thisVarContents
, forVal
);
602 len
= strlenW(thisVarContents
);
606 len
= ExpandEnvironmentStringsW(thisVar
, thisVarContents
,
607 sizeof(thisVarContents
)/sizeof(WCHAR
));
613 /* In a batch program, unknown env vars are replaced with nothing,
614 note syntax %garbage:1,3% results in anything after the ':'
616 From the command line, you just get back what you entered */
617 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
619 /* Restore the complex part after the compare */
622 *(colonpos
+1) = savedchar
;
625 /* Command line - just ignore this */
626 if (context
== NULL
) return endOfVar
+1;
629 /* Batch - replace unknown env var with nothing */
630 if (colonpos
== NULL
) {
631 WCMD_strsubstW(start
, endOfVar
+ 1, NULL
, 0);
633 len
= strlenW(thisVar
);
634 thisVar
[len
-1] = 0x00;
635 /* If %:...% supplied, : is retained */
636 if (colonpos
== thisVar
+1) {
637 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
, -1);
639 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
+ 1, -1);
646 /* See if we need to do complex substitution (any ':'s), if not
647 then our work here is done */
648 if (colonpos
== NULL
) {
649 WCMD_strsubstW(start
, endOfVar
+ 1, thisVarContents
, -1);
653 /* Restore complex bit */
655 *(colonpos
+1) = savedchar
;
658 Handle complex substitutions:
659 xxx=yyy (replace xxx with yyy)
660 *xxx=yyy (replace up to and including xxx with yyy)
661 ~x (from x WCHARs in)
662 ~-x (from x WCHARs from the end)
663 ~x,y (from x WCHARs in for y WCHARacters)
664 ~x,-y (from x WCHARs in until y WCHARacters from the end)
667 /* ~ is substring manipulation */
668 if (savedchar
== '~') {
670 int substrposition
, substrlength
= 0;
671 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
674 substrposition
= atolW(colonpos
+2);
675 if (commapos
) substrlength
= atolW(commapos
+1);
678 if (substrposition
>= 0) {
679 startCopy
= &thisVarContents
[min(substrposition
, len
)];
681 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
684 if (commapos
== NULL
) {
686 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, -1);
687 } else if (substrlength
< 0) {
689 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
690 if (copybytes
> len
) copybytes
= len
;
691 else if (copybytes
< 0) copybytes
= 0;
692 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, copybytes
);
694 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, substrlength
);
699 /* search and replace manipulation */
701 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
702 WCHAR
*replacewith
= equalspos
+1;
707 if (equalspos
== NULL
) return start
+1;
708 s
= WCMD_strdupW(endOfVar
+ 1);
710 /* Null terminate both strings */
711 thisVar
[strlenW(thisVar
)-1] = 0x00;
714 /* Since we need to be case insensitive, copy the 2 buffers */
715 searchIn
= WCMD_strdupW(thisVarContents
);
716 CharUpperBuffW(searchIn
, strlenW(thisVarContents
));
717 searchFor
= WCMD_strdupW(colonpos
+1);
718 CharUpperBuffW(searchFor
, strlenW(colonpos
+1));
720 /* Handle wildcard case */
721 if (*(colonpos
+1) == '*') {
722 /* Search for string to replace */
723 found
= strstrW(searchIn
, searchFor
+1);
727 strcpyW(start
, replacewith
);
728 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
732 strcpyW(start
, thisVarContents
);
737 /* Loop replacing all instances */
738 WCHAR
*lastFound
= searchIn
;
739 WCHAR
*outputposn
= start
;
742 while ((found
= strstrW(lastFound
, searchFor
))) {
743 lstrcpynW(outputposn
,
744 thisVarContents
+ (lastFound
-searchIn
),
745 (found
- lastFound
)+1);
746 outputposn
= outputposn
+ (found
- lastFound
);
747 strcatW(outputposn
, replacewith
);
748 outputposn
= outputposn
+ strlenW(replacewith
);
749 lastFound
= found
+ strlenW(searchFor
);
752 thisVarContents
+ (lastFound
-searchIn
));
753 strcatW(outputposn
, s
);
755 HeapFree(GetProcessHeap(), 0, s
);
756 HeapFree(GetProcessHeap(), 0, searchIn
);
757 HeapFree(GetProcessHeap(), 0, searchFor
);
763 /*****************************************************************************
764 * Expand the command. Native expands lines from batch programs as they are
765 * read in and not again, except for 'for' variable substitution.
766 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
768 static void handleExpansion(WCHAR
*cmd
, BOOL justFors
, WCHAR
*forVariable
, WCHAR
*forValue
) {
770 /* For commands in a context (batch program): */
771 /* Expand environment variables in a batch file %{0-9} first */
772 /* including support for any ~ modifiers */
774 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
775 /* names allowing environment variable overrides */
776 /* NOTE: To support the %PATH:xxx% syntax, also perform */
777 /* manual expansion of environment variables here */
783 while ((p
= strchrW(p
, '%'))) {
785 WINE_TRACE("Translate command:%s %d (at: %s)\n",
786 wine_dbgstr_w(cmd
), justFors
, wine_dbgstr_w(p
));
789 /* Don't touch %% unless its in Batch */
790 if (!justFors
&& *(p
+1) == '%') {
792 WCMD_strsubstW(p
, p
+1, NULL
, 0);
796 /* Replace %~ modifications if in batch program */
797 } else if (*(p
+1) == '~') {
798 WCMD_HandleTildaModifiers(&p
, forVariable
, forValue
, justFors
);
801 /* Replace use of %0...%9 if in batch program*/
802 } else if (!justFors
&& context
&& (i
>= 0) && (i
<= 9)) {
803 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
804 WCMD_strsubstW(p
, p
+2, t
, -1);
806 /* Replace use of %* if in batch program*/
807 } else if (!justFors
&& context
&& *(p
+1)=='*') {
808 WCHAR
*startOfParms
= NULL
;
809 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
810 if (startOfParms
!= NULL
)
811 WCMD_strsubstW(p
, p
+2, startOfParms
, -1);
813 WCMD_strsubstW(p
, p
+2, NULL
, 0);
815 } else if (forVariable
&&
816 (CompareStringW(LOCALE_USER_DEFAULT
,
819 strlenW(forVariable
),
820 forVariable
, -1) == 2)) {
821 WCMD_strsubstW(p
, p
+ strlenW(forVariable
), forValue
, -1);
823 } else if (!justFors
) {
824 p
= WCMD_expand_envvar(p
, forVariable
, forValue
);
826 /* In a FOR loop, see if this is the variable to replace */
827 } else { /* Ignore %'s on second pass of batch program */
836 /*******************************************************************
837 * WCMD_parse - parse a command into parameters and qualifiers.
839 * On exit, all qualifiers are concatenated into q, the first string
840 * not beginning with "/" is in p1 and the
841 * second in p2. Any subsequent non-qualifier strings are lost.
842 * Parameters in quotes are handled.
844 static void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
)
848 *q
= *p1
= *p2
= '\0';
853 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
854 *q
++ = toupperW (*s
++);
864 while ((*s
!= '\0') && (*s
!= '"')) {
865 if (p
== 0) *p1
++ = *s
++;
866 else if (p
== 1) *p2
++ = *s
++;
869 if (p
== 0) *p1
= '\0';
870 if (p
== 1) *p2
= '\0';
877 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
878 && (*s
!= '=') && (*s
!= ',') ) {
879 if (p
== 0) *p1
++ = *s
++;
880 else if (p
== 1) *p2
++ = *s
++;
883 /* Skip concurrent parms */
884 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
886 if (p
== 0) *p1
= '\0';
887 if (p
== 1) *p2
= '\0';
893 static void init_msvcrt_io_block(STARTUPINFOW
* st
)
896 /* fetch the parent MSVCRT info block if any, so that the child can use the
897 * same handles as its grand-father
899 st_p
.cb
= sizeof(STARTUPINFOW
);
900 GetStartupInfoW(&st_p
);
901 st
->cbReserved2
= st_p
.cbReserved2
;
902 st
->lpReserved2
= st_p
.lpReserved2
;
903 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
905 /* Override the entries for fd 0,1,2 if we happened
906 * to change those std handles (this depends on the way cmd sets
907 * its new input & output handles)
909 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
910 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
913 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
914 char* flags
= (char*)(ptr
+ sizeof(unsigned));
915 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
917 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
918 st
->cbReserved2
= sz
;
919 st
->lpReserved2
= ptr
;
921 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
922 if (num
<= 0 || (flags
[0] & WX_OPEN
))
924 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
927 if (num
<= 1 || (flags
[1] & WX_OPEN
))
929 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
932 if (num
<= 2 || (flags
[2] & WX_OPEN
))
934 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
942 /******************************************************************************
945 * Execute a command line as an external program. Must allow recursion.
948 * Manual testing under windows shows PATHEXT plays a key part in this,
949 * and the search algorithm and precedence appears to be as follows.
952 * If directory supplied on command, just use that directory
953 * If extension supplied on command, look for that explicit name first
954 * Otherwise, search in each directory on the path
956 * If extension supplied on command, look for that explicit name first
957 * Then look for supplied name .* (even if extension supplied, so
958 * 'garbage.exe' will match 'garbage.exe.cmd')
959 * If any found, cycle through PATHEXT looking for name.exe one by one
961 * Once a match has been found, it is launched - Code currently uses
962 * findexecutable to achieve this which is left untouched.
965 void WCMD_run_program (WCHAR
*command
, int called
) {
967 WCHAR temp
[MAX_PATH
];
968 WCHAR pathtosearch
[MAXSTRING
];
970 WCHAR stemofsearch
[MAX_PATH
]; /* maximum allowed executable name is
971 MAX_PATH, including null character */
973 WCHAR pathext
[MAXSTRING
];
974 BOOL extensionsupplied
= FALSE
;
975 BOOL launched
= FALSE
;
977 BOOL assumeInternal
= FALSE
;
979 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
980 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
981 static const WCHAR delims
[] = {'/','\\',':','\0'};
983 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
984 if (!(*param1
) && !(*param2
))
987 /* Calculate the search path and stem to search for */
988 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
989 static const WCHAR curDir
[] = {'.',';','\0'};
990 strcpyW(pathtosearch
, curDir
);
991 len
= GetEnvironmentVariableW(envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
992 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
993 static const WCHAR curDir
[] = {'.','\0'};
994 strcpyW (pathtosearch
, curDir
);
996 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
997 if (strlenW(param1
) >= MAX_PATH
)
999 WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG
));
1003 strcpyW(stemofsearch
, param1
);
1007 /* Convert eg. ..\fred to include a directory by removing file part */
1008 GetFullPathNameW(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
1009 lastSlash
= strrchrW(pathtosearch
, '\\');
1010 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1011 strcpyW(stemofsearch
, lastSlash
+1);
1013 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1014 c:\windows\a.bat syntax */
1015 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1018 /* Now extract PATHEXT */
1019 len
= GetEnvironmentVariableW(envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
1020 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
1021 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
1022 '.','c','o','m',';',
1023 '.','c','m','d',';',
1024 '.','e','x','e','\0'};
1025 strcpyW (pathext
, dfltPathExt
);
1028 /* Loop through the search path, dir by dir */
1029 pathposn
= pathtosearch
;
1030 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1031 wine_dbgstr_w(stemofsearch
));
1032 while (!launched
&& pathposn
) {
1034 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1037 const WCHAR slashW
[] = {'\\','\0'};
1039 /* Work on the first directory on the search path */
1040 pos
= strchrW(pathposn
, ';');
1042 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1043 thisDir
[(pos
-pathposn
)] = 0x00;
1047 strcpyW(thisDir
, pathposn
);
1051 /* Since you can have eg. ..\.. on the path, need to expand
1052 to full information */
1053 strcpyW(temp
, thisDir
);
1054 GetFullPathNameW(temp
, MAX_PATH
, thisDir
, NULL
);
1056 /* 1. If extension supplied, see if that file exists */
1057 strcatW(thisDir
, slashW
);
1058 strcatW(thisDir
, stemofsearch
);
1059 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1061 /* 1. If extension supplied, see if that file exists */
1062 if (extensionsupplied
) {
1063 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1068 /* 2. Any .* matches? */
1071 WIN32_FIND_DATAW finddata
;
1072 static const WCHAR allFiles
[] = {'.','*','\0'};
1074 strcatW(thisDir
,allFiles
);
1075 h
= FindFirstFileW(thisDir
, &finddata
);
1077 if (h
!= INVALID_HANDLE_VALUE
) {
1079 WCHAR
*thisExt
= pathext
;
1081 /* 3. Yes - Try each path ext */
1083 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1086 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1087 pos
[(nextExt
-thisExt
)] = 0x00;
1088 thisExt
= nextExt
+1;
1090 strcpyW(pos
, thisExt
);
1094 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1102 /* Internal programs won't be picked up by this search, so even
1103 though not found, try one last createprocess and wait for it
1105 Note: Ideally we could tell between a console app (wait) and a
1106 windows app, but the API's for it fail in this case */
1107 if (!found
&& pathposn
== NULL
) {
1108 WINE_TRACE("ASSUMING INTERNAL\n");
1109 assumeInternal
= TRUE
;
1111 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1114 /* Once found, launch it */
1115 if (found
|| assumeInternal
) {
1117 PROCESS_INFORMATION pe
;
1121 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1122 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1123 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1127 /* Special case BAT and CMD */
1128 if (ext
&& !strcmpiW(ext
, batExt
)) {
1129 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1131 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1132 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1136 /* thisDir contains the file to be launched, but with what?
1137 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1138 hinst
= FindExecutableW (thisDir
, NULL
, temp
);
1139 if ((INT_PTR
)hinst
< 32)
1142 console
= SHGetFileInfoW(temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1144 ZeroMemory (&st
, sizeof(STARTUPINFOW
));
1145 st
.cb
= sizeof(STARTUPINFOW
);
1146 init_msvcrt_io_block(&st
);
1148 /* Launch the process and if a CUI wait on it to complete
1149 Note: Launching internal wine processes cannot specify a full path to exe */
1150 status
= CreateProcessW(assumeInternal
?NULL
: thisDir
,
1151 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1152 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1153 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1154 /* strip first and last quote WCHARacters and try again */
1155 WCMD_opt_s_strip_quotes(command
);
1157 WCMD_run_program(command
, called
);
1164 if (!assumeInternal
&& !console
) errorlevel
= 0;
1167 /* Always wait when called in a batch program context */
1168 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1169 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1170 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1172 CloseHandle(pe
.hProcess
);
1173 CloseHandle(pe
.hThread
);
1179 /* Not found anywhere - give up */
1180 SetLastError(ERROR_FILE_NOT_FOUND
);
1181 WCMD_print_error ();
1183 /* If a command fails to launch, it sets errorlevel 9009 - which
1184 does not seem to have any associated constant definition */
1190 /*****************************************************************************
1191 * Process one command. If the command is EXIT this routine does not return.
1192 * We will recurse through here executing batch files.
1194 void WCMD_execute (WCHAR
*command
, WCHAR
*redirects
,
1195 WCHAR
*forVariable
, WCHAR
*forValue
,
1198 WCHAR
*cmd
, *p
, *redir
;
1200 DWORD count
, creationDisposition
;
1203 SECURITY_ATTRIBUTES sa
;
1204 WCHAR
*new_cmd
= NULL
;
1205 WCHAR
*new_redir
= NULL
;
1206 HANDLE old_stdhandles
[3] = {GetStdHandle (STD_INPUT_HANDLE
),
1207 GetStdHandle (STD_OUTPUT_HANDLE
),
1208 GetStdHandle (STD_ERROR_HANDLE
)};
1209 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
1214 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1215 wine_dbgstr_w(command
), cmdList
,
1216 wine_dbgstr_w(forVariable
), wine_dbgstr_w(forValue
));
1218 /* If the next command is a pipe then we implement pipes by redirecting
1219 the output from this command to a temp file and input into the
1220 next command from that temp file.
1221 FIXME: Use of named pipes would make more sense here as currently this
1222 process has to finish before the next one can start but this requires
1223 a change to not wait for the first app to finish but rather the pipe */
1224 if (cmdList
&& (*cmdList
)->nextcommand
&&
1225 (*cmdList
)->nextcommand
->prevDelim
== CMD_PIPE
) {
1227 WCHAR temp_path
[MAX_PATH
];
1228 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1230 /* Remember piping is in action */
1231 WINE_TRACE("Output needs to be piped\n");
1234 /* Generate a unique temporary filename */
1235 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1236 GetTempFileNameW(temp_path
, cmdW
, 0, (*cmdList
)->nextcommand
->pipeFile
);
1237 WINE_TRACE("Using temporary file of %s\n",
1238 wine_dbgstr_w((*cmdList
)->nextcommand
->pipeFile
));
1241 /* Move copy of the command onto the heap so it can be expanded */
1242 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
1245 WINE_ERR("Could not allocate memory for new_cmd\n");
1248 strcpyW(new_cmd
, command
);
1250 /* Move copy of the redirects onto the heap so it can be expanded */
1251 new_redir
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
1254 WINE_ERR("Could not allocate memory for new_redir\n");
1255 HeapFree( GetProcessHeap(), 0, new_cmd
);
1259 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1261 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1262 wsprintfW (new_redir
, redirOut
, redirects
, (*cmdList
)->nextcommand
->pipeFile
);
1263 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
1265 strcpyW(new_redir
, redirects
);
1268 /* Expand variables in command line mode only (batch mode will
1269 be expanded as the line is read in, except for 'for' loops) */
1270 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
1271 handleExpansion(new_redir
, (context
!= NULL
), forVariable
, forValue
);
1275 * Changing default drive has to be handled as a special case.
1278 if ((cmd
[1] == ':') && IsCharAlphaW(cmd
[0]) && (strlenW(cmd
) == 2)) {
1280 WCHAR dir
[MAX_PATH
];
1282 /* According to MSDN CreateProcess docs, special env vars record
1283 the current directory on each drive, in the form =C:
1284 so see if one specified, and if so go back to it */
1285 strcpyW(envvar
, equalsW
);
1286 strcatW(envvar
, cmd
);
1287 if (GetEnvironmentVariableW(envvar
, dir
, MAX_PATH
) == 0) {
1288 static const WCHAR fmt
[] = {'%','s','\\','\0'};
1289 wsprintfW(cmd
, fmt
, cmd
);
1290 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
1292 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
1293 status
= SetCurrentDirectoryW(cmd
);
1294 if (!status
) WCMD_print_error ();
1295 HeapFree( GetProcessHeap(), 0, cmd
);
1296 HeapFree( GetProcessHeap(), 0, new_redir
);
1300 sa
.nLength
= sizeof(sa
);
1301 sa
.lpSecurityDescriptor
= NULL
;
1302 sa
.bInheritHandle
= TRUE
;
1305 * Redirect stdin, stdout and/or stderr if required.
1308 /* STDIN could come from a preceding pipe, so delete on close if it does */
1309 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
1310 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
1311 h
= CreateFileW((*cmdList
)->pipeFile
, GENERIC_READ
,
1312 FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
1313 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
1314 if (h
== INVALID_HANDLE_VALUE
) {
1315 WCMD_print_error ();
1316 HeapFree( GetProcessHeap(), 0, cmd
);
1317 HeapFree( GetProcessHeap(), 0, new_redir
);
1320 SetStdHandle (STD_INPUT_HANDLE
, h
);
1322 /* No need to remember the temporary name any longer once opened */
1323 (*cmdList
)->pipeFile
[0] = 0x00;
1325 /* Otherwise STDIN could come from a '<' redirect */
1326 } else if ((p
= strchrW(new_redir
,'<')) != NULL
) {
1327 h
= CreateFileW(WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
1328 FILE_ATTRIBUTE_NORMAL
, NULL
);
1329 if (h
== INVALID_HANDLE_VALUE
) {
1330 WCMD_print_error ();
1331 HeapFree( GetProcessHeap(), 0, cmd
);
1332 HeapFree( GetProcessHeap(), 0, new_redir
);
1335 SetStdHandle (STD_INPUT_HANDLE
, h
);
1338 /* Scan the whole command looking for > and 2> */
1340 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
1351 creationDisposition
= OPEN_ALWAYS
;
1355 creationDisposition
= CREATE_ALWAYS
;
1358 /* Add support for 2>&1 */
1361 int idx
= *(p
+1) - '0';
1363 if (DuplicateHandle(GetCurrentProcess(),
1364 GetStdHandle(idx_stdhandles
[idx
]),
1365 GetCurrentProcess(),
1367 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
1368 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1370 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
1373 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
1374 h
= CreateFileW(param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
1375 FILE_ATTRIBUTE_NORMAL
, NULL
);
1376 if (h
== INVALID_HANDLE_VALUE
) {
1377 WCMD_print_error ();
1378 HeapFree( GetProcessHeap(), 0, cmd
);
1379 HeapFree( GetProcessHeap(), 0, new_redir
);
1382 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
1383 INVALID_SET_FILE_POINTER
) {
1384 WCMD_print_error ();
1386 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
1389 SetStdHandle (idx_stdhandles
[handle
], h
);
1393 * Strip leading whitespaces, and a '@' if supplied
1395 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
1396 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
1397 if (whichcmd
[0] == '@') whichcmd
++;
1400 * Check if the command entered is internal. If it is, pass the rest of the
1401 * line down to the command. If not try to run a program.
1405 while (IsCharAlphaNumericW(whichcmd
[count
])) {
1408 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1409 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1410 whichcmd
, count
, inbuilt
[i
], -1) == CSTR_EQUAL
) break;
1412 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
1413 WCMD_parse (p
, quals
, param1
, param2
);
1414 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1416 if (i
<= WCMD_EXIT
&& (p
[0] == '/') && (p
[1] == '?')) {
1417 /* this is a help request for a builtin program */
1419 memcpy(p
, whichcmd
, count
* sizeof(WCHAR
));
1427 WCMD_setshow_attrib ();
1434 WCMD_setshow_default (p
);
1437 WCMD_clear_screen ();
1446 WCMD_setshow_date ();
1456 WCMD_echo(&whichcmd
[count
]);
1459 WCMD_for (p
, cmdList
);
1462 WCMD_goto (cmdList
);
1468 WCMD_if (p
, cmdList
);
1481 WCMD_setshow_path (p
);
1487 WCMD_setshow_prompt ();
1497 WCMD_remove_dir (p
);
1506 WCMD_setshow_env (p
);
1512 WCMD_setshow_time ();
1515 if (strlenW(&whichcmd
[count
]) > 0)
1516 WCMD_title(&whichcmd
[count
+1]);
1522 WCMD_output(newline
);
1538 WCMD_assoc(p
, TRUE
);
1544 WCMD_assoc(p
, FALSE
);
1553 WCMD_exit (cmdList
);
1556 WCMD_run_program (whichcmd
, 0);
1558 HeapFree( GetProcessHeap(), 0, cmd
);
1559 HeapFree( GetProcessHeap(), 0, new_redir
);
1561 /* Restore old handles */
1562 for (i
=0; i
<3; i
++) {
1563 if (old_stdhandles
[i
] != GetStdHandle(idx_stdhandles
[i
])) {
1564 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
1565 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
1569 /*************************************************************************
1571 * Load a string from the resource file, handling any error
1572 * Returns string retrieved from resource file
1574 WCHAR
*WCMD_LoadMessage(UINT id
) {
1575 static WCHAR msg
[2048];
1576 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1578 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1579 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1580 strcpyW(msg
, failedMsg
);
1585 /***************************************************************************
1588 * Dumps out the parsed command line to ensure syntax is correct
1590 static void WCMD_DumpCommands(CMD_LIST
*commands
) {
1591 CMD_LIST
*thisCmd
= commands
;
1593 WINE_TRACE("Parsed line:\n");
1594 while (thisCmd
!= NULL
) {
1595 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1598 thisCmd
->bracketDepth
,
1599 thisCmd
->nextcommand
,
1600 wine_dbgstr_w(thisCmd
->command
),
1601 wine_dbgstr_w(thisCmd
->redirects
));
1602 thisCmd
= thisCmd
->nextcommand
;
1606 /***************************************************************************
1609 * Adds a command to the current command list
1611 static void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1612 WCHAR
*redirs
, int *redirLen
,
1613 WCHAR
**copyTo
, int **copyToLen
,
1614 CMD_DELIMITERS prevDelim
, int curDepth
,
1615 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1617 CMD_LIST
*thisEntry
= NULL
;
1619 /* Allocate storage for command */
1620 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
1622 /* Copy in the command */
1624 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
1625 (*commandLen
+1) * sizeof(WCHAR
));
1626 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1627 thisEntry
->command
[*commandLen
] = 0x00;
1629 /* Copy in the redirects */
1630 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
1631 (*redirLen
+1) * sizeof(WCHAR
));
1632 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1633 thisEntry
->redirects
[*redirLen
] = 0x00;
1634 thisEntry
->pipeFile
[0] = 0x00;
1636 /* Reset the lengths */
1639 *copyToLen
= commandLen
;
1643 thisEntry
->command
= NULL
;
1644 thisEntry
->redirects
= NULL
;
1645 thisEntry
->pipeFile
[0] = 0x00;
1648 /* Fill in other fields */
1649 thisEntry
->nextcommand
= NULL
;
1650 thisEntry
->prevDelim
= prevDelim
;
1651 thisEntry
->bracketDepth
= curDepth
;
1653 (*lastEntry
)->nextcommand
= thisEntry
;
1655 *output
= thisEntry
;
1657 *lastEntry
= thisEntry
;
1661 /***************************************************************************
1664 * Checks if the quote pointed to is the end-quote.
1668 * 1) The current parameter ends at EOL or at the beginning
1669 * of a redirection or pipe and not in a quote section.
1671 * 2) If the next character is a space and not in a quote section.
1673 * Returns TRUE if this is an end quote, and FALSE if it is not.
1676 static BOOL
WCMD_IsEndQuote(WCHAR
*quote
, int quoteIndex
)
1678 int quoteCount
= quoteIndex
;
1681 /* If we are not in a quoted section, then we are not an end-quote */
1687 /* Check how many quotes are left for this parameter */
1688 for(i
=0;quote
[i
];i
++)
1695 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1696 else if(((quoteCount
% 2) == 0)
1697 && ((quote
[i
] == '<') || (quote
[i
] == '>') || (quote
[i
] == '|') || (quote
[i
] == ' ')))
1703 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1705 if(quoteIndex
>= (quoteCount
/ 2))
1714 /***************************************************************************
1715 * WCMD_ReadAndParseLine
1717 * Either uses supplied input or
1718 * Reads a file from the handle, and then...
1719 * Parse the text buffer, splitting into separate commands
1720 * - unquoted && strings split 2 commands but the 2nd is flagged as
1722 * - ( as the first character just ups the bracket depth
1723 * - unquoted ) when bracket depth > 0 terminates a bracket and
1724 * adds a CMD_LIST structure with null command
1725 * - Anything else gets put into the command string (including
1728 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
1732 WCHAR curString
[MAXSTRING
];
1733 int curStringLen
= 0;
1734 WCHAR curRedirs
[MAXSTRING
];
1735 int curRedirsLen
= 0;
1739 CMD_LIST
*lastEntry
= NULL
;
1740 CMD_DELIMITERS prevDelim
= CMD_NONE
;
1741 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
1742 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
1743 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
1744 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
1745 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
1751 BOOL onlyWhiteSpace
= FALSE
;
1752 BOOL lastWasWhiteSpace
= FALSE
;
1753 BOOL lastWasDo
= FALSE
;
1754 BOOL lastWasIn
= FALSE
;
1755 BOOL lastWasElse
= FALSE
;
1756 BOOL lastWasRedirect
= TRUE
;
1758 /* Allocate working space for a command read from keyboard, file etc */
1760 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
1763 WINE_ERR("Could not allocate memory for extraSpace\n");
1767 /* If initial command read in, use that, otherwise get input from handle */
1768 if (optionalcmd
!= NULL
) {
1769 strcpyW(extraSpace
, optionalcmd
);
1770 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
1771 WINE_FIXME("No command nor handle supplied\n");
1773 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
1775 curPos
= extraSpace
;
1777 /* Handle truncated input - issue warning */
1778 if (strlenW(extraSpace
) == MAXSTRING
-1) {
1779 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
1780 WCMD_output_asis(extraSpace
);
1781 WCMD_output_asis(newline
);
1784 /* Replace env vars if in a batch context */
1785 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
1786 /* Show prompt before batch line IF echo is on and in batch program */
1787 if (context
&& echo_mode
&& extraSpace
[0] && (extraSpace
[0] != '@')) {
1788 const WCHAR spc
[]={' ','\0'};
1790 WCMD_output_asis(extraSpace
);
1791 /* I don't know why Windows puts a space here but it does */
1792 WCMD_output_asis(spc
);
1793 WCMD_output_asis(newline
);
1796 /* Start with an empty string, copying to the command string */
1799 curCopyTo
= curString
;
1800 curLen
= &curStringLen
;
1801 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
1803 /* Parse every character on the line being processed */
1804 while (*curPos
!= 0x00) {
1809 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1810 lastWasWhiteSpace, onlyWhiteSpace);
1813 /* Certain commands need special handling */
1814 if (curStringLen
== 0 && curCopyTo
== curString
) {
1815 const WCHAR forDO
[] = {'d','o',' ','\0'};
1817 /* If command starts with 'rem', ignore any &&, ( etc */
1818 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1819 curPos
, 4, remCmd
, -1) == 2) {
1822 /* If command starts with 'for', handle ('s mid line after IN or DO */
1823 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1824 curPos
, 4, forCmd
, -1) == 2) {
1827 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1828 is only true in the command portion of the IF statement, but this
1829 should suffice for now
1830 FIXME: Silly syntax like "if 1(==1( (
1832 )" will be parsed wrong */
1833 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1834 curPos
, 3, ifCmd
, -1) == 2) {
1837 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1838 curPos
, 5, ifElse
, -1) == 2) {
1841 onlyWhiteSpace
= TRUE
;
1842 memcpy(&curCopyTo
[*curLen
], curPos
, 5*sizeof(WCHAR
));
1847 /* In a for loop, the DO command will follow a close bracket followed by
1848 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1849 is then 0, and all whitespace is skipped */
1851 (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1852 curPos
, 3, forDO
, -1) == 2)) {
1853 WINE_TRACE("Found DO\n");
1855 onlyWhiteSpace
= TRUE
;
1856 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
1861 } else if (curCopyTo
== curString
) {
1863 /* Special handling for the 'FOR' command */
1864 if (inFor
&& lastWasWhiteSpace
) {
1865 const WCHAR forIN
[] = {'i','n',' ','\0'};
1867 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
1869 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1870 curPos
, 3, forIN
, -1) == 2) {
1871 WINE_TRACE("Found IN\n");
1873 onlyWhiteSpace
= TRUE
;
1874 memcpy(&curCopyTo
[*curLen
], curPos
, 3*sizeof(WCHAR
));
1882 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1883 so just use the default processing ie skip character specific
1885 if (!inRem
) thisChar
= *curPos
;
1886 else thisChar
= 'X'; /* Character with no special processing */
1888 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
1892 case '=': /* drop through - ignore token delimiters at the start of a command */
1893 case ',': /* drop through - ignore token delimiters at the start of a command */
1894 case '\t':/* drop through - ignore token delimiters at the start of a command */
1896 /* If a redirect in place, it ends here */
1897 if (!inQuotes
&& !lastWasRedirect
) {
1899 /* If finishing off a redirect, add a whitespace delimiter */
1900 if (curCopyTo
== curRedirs
) {
1901 curCopyTo
[(*curLen
)++] = ' ';
1903 curCopyTo
= curString
;
1904 curLen
= &curStringLen
;
1907 curCopyTo
[(*curLen
)++] = *curPos
;
1910 /* Remember just processed whitespace */
1911 lastWasWhiteSpace
= TRUE
;
1915 case '>': /* drop through - handle redirect chars the same */
1917 /* Make a redirect start here */
1919 curCopyTo
= curRedirs
;
1920 curLen
= &curRedirsLen
;
1921 lastWasRedirect
= TRUE
;
1924 /* See if 1>, 2> etc, in which case we have some patching up
1926 if (curPos
!= extraSpace
&&
1927 *(curPos
-1)>='1' && *(curPos
-1)<='9') {
1930 curString
[curStringLen
] = 0x00;
1931 curCopyTo
[(*curLen
)++] = *(curPos
-1);
1934 curCopyTo
[(*curLen
)++] = *curPos
;
1936 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1937 do not process that ampersand as an AND operator */
1938 if (thisChar
== '>' && *(curPos
+1) == '&') {
1939 curCopyTo
[(*curLen
)++] = *(curPos
+1);
1944 case '|': /* Pipe character only if not || */
1946 lastWasRedirect
= FALSE
;
1948 /* Add an entry to the command list */
1949 if (curStringLen
> 0) {
1951 /* Add the current command */
1952 WCMD_addCommand(curString
, &curStringLen
,
1953 curRedirs
, &curRedirsLen
,
1954 &curCopyTo
, &curLen
,
1955 prevDelim
, curDepth
,
1956 &lastEntry
, output
);
1960 if (*(curPos
+1) == '|') {
1961 curPos
++; /* Skip other | */
1962 prevDelim
= CMD_ONFAILURE
;
1964 prevDelim
= CMD_PIPE
;
1967 curCopyTo
[(*curLen
)++] = *curPos
;
1971 case '"': if (WCMD_IsEndQuote(curPos
, inQuotes
)) {
1974 inQuotes
++; /* Quotes within quotes are fun! */
1976 curCopyTo
[(*curLen
)++] = *curPos
;
1977 lastWasRedirect
= FALSE
;
1980 case '(': /* If a '(' is the first non whitespace in a command portion
1981 ie start of line or just after &&, then we read until an
1982 unquoted ) is found */
1983 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1984 ", for(%d, In:%d, Do:%d)"
1985 ", if(%d, else:%d, lwe:%d)\n",
1988 inFor
, lastWasIn
, lastWasDo
,
1989 inIf
, inElse
, lastWasElse
);
1990 lastWasRedirect
= FALSE
;
1992 /* Ignore open brackets inside the for set */
1993 if (*curLen
== 0 && !inIn
) {
1996 /* If in quotes, ignore brackets */
1997 } else if (inQuotes
) {
1998 curCopyTo
[(*curLen
)++] = *curPos
;
2000 /* In a FOR loop, an unquoted '(' may occur straight after
2002 In an IF statement just handle it regardless as we don't
2004 In an ELSE statement, only allow it straight away after
2005 the ELSE and whitespace
2008 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2009 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2011 /* If entering into an 'IN', set inIn */
2012 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2013 WINE_TRACE("Inside an IN\n");
2017 /* Add the current command */
2018 WCMD_addCommand(curString
, &curStringLen
,
2019 curRedirs
, &curRedirsLen
,
2020 &curCopyTo
, &curLen
,
2021 prevDelim
, curDepth
,
2022 &lastEntry
, output
);
2026 curCopyTo
[(*curLen
)++] = *curPos
;
2030 case '&': if (!inQuotes
) {
2031 lastWasRedirect
= FALSE
;
2033 /* Add an entry to the command list */
2034 if (curStringLen
> 0) {
2036 /* Add the current command */
2037 WCMD_addCommand(curString
, &curStringLen
,
2038 curRedirs
, &curRedirsLen
,
2039 &curCopyTo
, &curLen
,
2040 prevDelim
, curDepth
,
2041 &lastEntry
, output
);
2045 if (*(curPos
+1) == '&') {
2046 curPos
++; /* Skip other & */
2047 prevDelim
= CMD_ONSUCCESS
;
2049 prevDelim
= CMD_NONE
;
2052 curCopyTo
[(*curLen
)++] = *curPos
;
2056 case ')': if (!inQuotes
&& curDepth
> 0) {
2057 lastWasRedirect
= FALSE
;
2059 /* Add the current command if there is one */
2062 /* Add the current command */
2063 WCMD_addCommand(curString
, &curStringLen
,
2064 curRedirs
, &curRedirsLen
,
2065 &curCopyTo
, &curLen
,
2066 prevDelim
, curDepth
,
2067 &lastEntry
, output
);
2070 /* Add an empty entry to the command list */
2071 prevDelim
= CMD_NONE
;
2072 WCMD_addCommand(NULL
, &curStringLen
,
2073 curRedirs
, &curRedirsLen
,
2074 &curCopyTo
, &curLen
,
2075 prevDelim
, curDepth
,
2076 &lastEntry
, output
);
2079 /* Leave inIn if necessary */
2080 if (inIn
) inIn
= FALSE
;
2082 curCopyTo
[(*curLen
)++] = *curPos
;
2086 lastWasRedirect
= FALSE
;
2087 curCopyTo
[(*curLen
)++] = *curPos
;
2092 /* At various times we need to know if we have only skipped whitespace,
2093 so reset this variable and then it will remain true until a non
2094 whitespace is found */
2095 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2097 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2098 if (!lastWasWhiteSpace
) {
2099 lastWasIn
= lastWasDo
= FALSE
;
2102 /* If we have reached the end, add this command into the list */
2103 if (*curPos
== 0x00 && *curLen
> 0) {
2105 /* Add an entry to the command list */
2106 WCMD_addCommand(curString
, &curStringLen
,
2107 curRedirs
, &curRedirsLen
,
2108 &curCopyTo
, &curLen
,
2109 prevDelim
, curDepth
,
2110 &lastEntry
, output
);
2113 /* If we have reached the end of the string, see if bracketing outstanding */
2114 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2116 prevDelim
= CMD_NONE
;
2118 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2120 /* Read more, skipping any blank lines */
2121 while (*extraSpace
== 0x00) {
2122 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2123 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2125 curPos
= extraSpace
;
2126 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2127 /* Continue to echo commands IF echo is on and in batch program */
2128 if (context
&& echo_mode
&& extraSpace
[0] && (extraSpace
[0] != '@')) {
2129 WCMD_output_asis(extraSpace
);
2130 WCMD_output_asis(newline
);
2135 /* Dump out the parsed output */
2136 WCMD_DumpCommands(*output
);
2141 /***************************************************************************
2142 * WCMD_process_commands
2144 * Process all the commands read in so far
2146 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2147 WCHAR
*var
, WCHAR
*val
) {
2151 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2153 /* Loop through the commands, processing them one by one */
2156 CMD_LIST
*origCmd
= thisCmd
;
2158 /* If processing one bracket only, and we find the end bracket
2159 entry (or less), return */
2160 if (oneBracket
&& !thisCmd
->command
&&
2161 bdepth
<= thisCmd
->bracketDepth
) {
2162 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2163 thisCmd
, thisCmd
->nextcommand
);
2164 return thisCmd
->nextcommand
;
2167 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2168 about them and it will be handled in there)
2169 Also, skip over any batch labels (eg. :fred) */
2170 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2171 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2172 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2175 /* Step on unless the command itself already stepped on */
2176 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2181 /***************************************************************************
2182 * WCMD_free_commands
2184 * Frees the storage held for a parsed command line
2185 * - This is not done in the process_commands, as eventually the current
2186 * pointer will be modified within the commands, and hence a single free
2187 * routine is simpler
2189 void WCMD_free_commands(CMD_LIST
*cmds
) {
2191 /* Loop through the commands, freeing them one by one */
2193 CMD_LIST
*thisCmd
= cmds
;
2194 cmds
= cmds
->nextcommand
;
2195 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2196 HeapFree(GetProcessHeap(), 0, thisCmd
->redirects
);
2197 HeapFree(GetProcessHeap(), 0, thisCmd
);
2202 /*****************************************************************************
2203 * Main entry point. This is a console application so we have a main() not a
2207 int wmain (int argc
, WCHAR
*argvW
[])
2216 static const WCHAR autoexec
[] = {'\\','a','u','t','o','e','x','e','c','.',
2218 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2219 static const WCHAR defaultpromptW
[] = {'$','P','$','G','\0'};
2220 char ansiVersion
[100];
2221 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
2225 /* Pre initialize some messages */
2226 strcpy(ansiVersion
, PACKAGE_VERSION
);
2227 MultiByteToWideChar(CP_ACP
, 0, ansiVersion
, -1, string
, 1024);
2228 wsprintfW(version_string
, WCMD_LoadMessage(WCMD_VERSION
), string
);
2229 strcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
2232 opt_c
=opt_k
=opt_q
=opt_s
=0;
2236 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW
));
2237 if ((*argvW
)[0]!='/' || (*argvW
)[1]=='\0') {
2244 if (tolowerW(c
)=='c') {
2246 } else if (tolowerW(c
)=='q') {
2248 } else if (tolowerW(c
)=='k') {
2250 } else if (tolowerW(c
)=='s') {
2252 } else if (tolowerW(c
)=='a') {
2254 } else if (tolowerW(c
)=='u') {
2256 } else if (tolowerW(c
)=='t' && (*argvW
)[2]==':') {
2257 opt_t
=strtoulW(&(*argvW
)[3], NULL
, 16);
2258 } else if (tolowerW(c
)=='x' || tolowerW(c
)=='y') {
2259 /* Ignored for compatibility with Windows */
2262 if ((*argvW
)[2]==0) {
2266 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2271 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
2276 const WCHAR eoff
[] = {'O','F','F','\0'};
2280 if (opt_c
|| opt_k
) {
2286 /* opt_s left unflagged if the command starts with and contains exactly
2287 * one quoted string (exactly two quote characters). The quoted string
2288 * must be an executable name that has whitespace and must not have the
2289 * following characters: &<>()@^| */
2291 /* Build the command to execute */
2295 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
2297 int has_space
,bcount
;
2303 if( !*a
) has_space
=1;
2308 if (*a
==' ' || *a
=='\t') {
2310 } else if (*a
=='"') {
2311 /* doubling of '\' preceding a '"',
2312 * plus escaping of said '"'
2321 len
+=(a
-*arg
) + 1; /* for the separating space */
2324 len
+=2; /* for the quotes */
2332 /* check argvW[0] for a space and invalid characters */
2337 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
2338 || *p
=='@' || *p
=='^' || *p
=='|') {
2348 cmd
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
2354 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
2356 int has_space
,has_quote
;
2359 /* Check for quotes and spaces in this argument */
2360 has_space
=has_quote
=0;
2362 if( !*a
) has_space
=1;
2364 if (*a
==' ' || *a
=='\t') {
2368 } else if (*a
=='"') {
2376 /* Now transfer it to the command line */
2393 /* Double all the '\\' preceding this '"', plus one */
2394 for (i
=0;i
<=bcount
;i
++)
2413 p
--; /* remove last space */
2416 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
2418 /* strip first and last quote characters if opt_s; check for invalid
2419 * executable is done later */
2420 if (opt_s
&& *cmd
=='\"')
2421 WCMD_opt_s_strip_quotes(cmd
);
2425 /* If we do a "cmd /c command", we don't want to allocate a new
2426 * console since the command returns immediately. Rather, we use
2427 * the currently allocated input and output handles. This allows
2428 * us to pipe to and read from the command interpreter.
2431 /* Parse the command string, without reading any more input */
2432 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2433 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2434 WCMD_free_commands(toExecute
);
2437 HeapFree(GetProcessHeap(), 0, cmd
);
2441 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
2442 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
2443 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE
));
2445 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2447 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
2448 defaultColor
= opt_t
& 0xFF;
2453 /* Check HKCU\Software\Microsoft\Command Processor
2454 Then HKLM\Software\Microsoft\Command Processor
2455 for defaultcolour value
2456 Note Can be supplied as DWORD or REG_SZ
2457 Note2 When supplied as REG_SZ it's in decimal!!! */
2460 DWORD value
=0, size
=4;
2461 static const WCHAR regKeyW
[] = {'S','o','f','t','w','a','r','e','\\',
2462 'M','i','c','r','o','s','o','f','t','\\',
2463 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2464 static const WCHAR dfltColorW
[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2466 if (RegOpenKeyExW(HKEY_CURRENT_USER
, regKeyW
,
2467 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2470 /* See if DWORD or REG_SZ */
2471 if (RegQueryValueExW(key
, dfltColorW
, NULL
, &type
,
2472 NULL
, NULL
) == ERROR_SUCCESS
) {
2473 if (type
== REG_DWORD
) {
2474 size
= sizeof(DWORD
);
2475 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2476 (LPBYTE
)&value
, &size
);
2477 } else if (type
== REG_SZ
) {
2478 size
= sizeof(strvalue
)/sizeof(WCHAR
);
2479 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2480 (LPBYTE
)strvalue
, &size
);
2481 value
= strtoulW(strvalue
, NULL
, 10);
2487 if (value
== 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE
, regKeyW
,
2488 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2491 /* See if DWORD or REG_SZ */
2492 if (RegQueryValueExW(key
, dfltColorW
, NULL
, &type
,
2493 NULL
, NULL
) == ERROR_SUCCESS
) {
2494 if (type
== REG_DWORD
) {
2495 size
= sizeof(DWORD
);
2496 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2497 (LPBYTE
)&value
, &size
);
2498 } else if (type
== REG_SZ
) {
2499 size
= sizeof(strvalue
)/sizeof(WCHAR
);
2500 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2501 (LPBYTE
)strvalue
, &size
);
2502 value
= strtoulW(strvalue
, NULL
, 10);
2508 /* If one found, set the screen to that colour */
2509 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
2510 defaultColor
= value
& 0xFF;
2517 /* Save cwd into appropriate env var */
2518 GetCurrentDirectoryW(1024, string
);
2519 if (IsCharAlphaW(string
[0]) && string
[1] == ':') {
2520 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
2521 wsprintfW(envvar
, fmt
, string
[0]);
2522 SetEnvironmentVariableW(envvar
, string
);
2523 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
2527 /* Parse the command string, without reading any more input */
2528 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2529 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2530 WCMD_free_commands(toExecute
);
2532 HeapFree(GetProcessHeap(), 0, cmd
);
2536 * If there is an AUTOEXEC.BAT file, try to execute it.
2539 GetFullPathNameW (autoexec
, sizeof(string
)/sizeof(WCHAR
), string
, NULL
);
2540 h
= CreateFileW(string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
2541 if (h
!= INVALID_HANDLE_VALUE
) {
2544 WCMD_batch (autoexec
, autoexec
, 0, NULL
, INVALID_HANDLE_VALUE
);
2549 * Loop forever getting commands and executing them.
2552 SetEnvironmentVariableW(promptW
, defaultpromptW
);
2556 /* Read until EOF (which for std input is never, but if redirect
2557 in place, may occur */
2558 if (echo_mode
) WCMD_show_prompt();
2559 if (WCMD_ReadAndParseLine(NULL
, &toExecute
,
2560 GetStdHandle(STD_INPUT_HANDLE
)) == NULL
)
2562 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2563 WCMD_free_commands(toExecute
);