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 {'C','A','L','L','\0'},
37 {'C','H','D','I','R','\0'},
39 {'C','O','P','Y','\0'},
40 {'C','T','T','Y','\0'},
41 {'D','A','T','E','\0'},
44 {'E','C','H','O','\0'},
45 {'E','R','A','S','E','\0'},
47 {'G','O','T','O','\0'},
48 {'H','E','L','P','\0'},
50 {'L','A','B','E','L','\0'},
52 {'M','K','D','I','R','\0'},
53 {'M','O','V','E','\0'},
54 {'P','A','T','H','\0'},
55 {'P','A','U','S','E','\0'},
56 {'P','R','O','M','P','T','\0'},
59 {'R','E','N','A','M','E','\0'},
61 {'R','M','D','I','R','\0'},
63 {'S','H','I','F','T','\0'},
64 {'T','I','M','E','\0'},
65 {'T','I','T','L','E','\0'},
66 {'T','Y','P','E','\0'},
67 {'V','E','R','I','F','Y','\0'},
70 {'E','N','D','L','O','C','A','L','\0'},
71 {'S','E','T','L','O','C','A','L','\0'},
72 {'P','U','S','H','D','\0'},
73 {'P','O','P','D','\0'},
74 {'A','S','S','O','C','\0'},
75 {'C','O','L','O','R','\0'},
76 {'F','T','Y','P','E','\0'},
77 {'M','O','R','E','\0'},
78 {'C','H','O','I','C','E','\0'},
79 {'E','X','I','T','\0'}
82 const WCHAR externals
[NUM_EXTERNALS
][10] = {
83 {'A','T','T','R','I','B','\0'},
84 {'X','C','O','P','Y','\0'}
90 BOOL echo_mode
= TRUE
;
91 static int opt_c
, opt_k
, opt_s
;
92 const WCHAR newline
[] = {'\r','\n','\0'};
93 static const WCHAR equalsW
[] = {'=','\0'};
94 static const WCHAR closeBW
[] = {')','\0'};
96 WCHAR version_string
[100];
97 WCHAR quals
[MAX_PATH
], param1
[MAXSTRING
], param2
[MAXSTRING
];
98 BATCH_CONTEXT
*context
= NULL
;
99 extern struct env_stack
*pushd_directories
;
100 static const WCHAR
*pagedMessage
= NULL
;
101 static char *output_bufA
= NULL
;
102 #define MAX_WRITECONSOLE_SIZE 65535
103 static BOOL unicodePipes
= FALSE
;
106 * Returns a buffer for reading from/writing to file
109 static char *get_file_buffer(void)
112 output_bufA
= HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE
);
114 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
119 /*******************************************************************
120 * WCMD_output_asis_len - send output to current standard output
122 * Output a formatted unicode string. Ideally this will go to the console
123 * and hence required WriteConsoleW to output it, however if file i/o is
124 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
126 static void WCMD_output_asis_len(const WCHAR
*message
, int len
, HANDLE device
) {
131 /* If nothing to write, return (MORE does this sometimes) */
134 /* Try to write as unicode assuming it is to a console */
135 res
= WriteConsoleW(device
, message
, len
, &nOut
, NULL
);
137 /* If writing to console fails, assume its file
138 i/o so convert to OEM codepage and output */
140 BOOL usedDefaultChar
= FALSE
;
141 DWORD convertedChars
;
146 if (!(buffer
= get_file_buffer()))
149 /* Convert to OEM, then output */
150 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
151 len
, buffer
, MAX_WRITECONSOLE_SIZE
,
152 "?", &usedDefaultChar
);
153 WriteFile(device
, buffer
, convertedChars
,
156 WriteFile(device
, message
, len
*sizeof(WCHAR
),
163 /*******************************************************************
164 * WCMD_output - send output to current standard output device.
168 void WCMD_output (const WCHAR
*format
, ...) {
175 ret
= vsnprintfW(string
, sizeof(string
)/sizeof(WCHAR
), format
, ap
);
176 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
177 WINE_ERR("Output truncated\n" );
178 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
182 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_OUTPUT_HANDLE
));
185 /*******************************************************************
186 * WCMD_output_stderr - send output to current standard error device.
190 void WCMD_output_stderr (const WCHAR
*format
, ...) {
197 ret
= vsnprintfW(string
, sizeof(string
)/sizeof(WCHAR
), format
, ap
);
198 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
199 WINE_ERR("Output truncated\n" );
200 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
204 WCMD_output_asis_len(string
, ret
, GetStdHandle(STD_ERROR_HANDLE
));
207 static int line_count
;
208 static int max_height
;
209 static int max_width
;
210 static BOOL paged_mode
;
213 void WCMD_enter_paged_mode(const WCHAR
*msg
)
215 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
217 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
218 max_height
= consoleInfo
.dwSize
.Y
;
219 max_width
= consoleInfo
.dwSize
.X
;
227 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
230 void WCMD_leave_paged_mode(void)
236 /***************************************************************************
239 * Read characters in from a console/file, returning result in Unicode
241 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
, LPDWORD charsRead
)
246 if (WCMD_is_console_handle(hIn
))
247 /* Try to read from console as Unicode */
248 return ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
250 /* We assume it's a file handle and read then convert from assumed OEM codepage */
251 if (!(buffer
= get_file_buffer()))
254 if (!ReadFile(hIn
, buffer
, maxChars
, &numRead
, NULL
))
257 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, buffer
, numRead
, intoBuf
, maxChars
);
262 /*******************************************************************
263 * WCMD_output_asis_handle
265 * Send output to specified handle without formatting e.g. when message contains '%'
267 static void WCMD_output_asis_handle (DWORD std_handle
, const WCHAR
*message
) {
271 HANDLE handle
= GetStdHandle(std_handle
);
276 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
280 if (*ptr
== '\n') ptr
++;
281 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
), handle
);
284 if (++line_count
>= max_height
- 1) {
286 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
), handle
);
287 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
290 } while (((message
= ptr
) != NULL
) && (*ptr
));
292 WCMD_output_asis_len(message
, lstrlenW(message
), handle
);
296 /*******************************************************************
299 * Send output to current standard output device, without formatting
300 * e.g. when message contains '%'
302 void WCMD_output_asis (const WCHAR
*message
) {
303 WCMD_output_asis_handle(STD_OUTPUT_HANDLE
, message
);
306 /*******************************************************************
307 * WCMD_output_asis_stderr
309 * Send output to current standard error device, without formatting
310 * e.g. when message contains '%'
312 void WCMD_output_asis_stderr (const WCHAR
*message
) {
313 WCMD_output_asis_handle(STD_ERROR_HANDLE
, message
);
316 /****************************************************************************
319 * Print the message for GetLastError
322 void WCMD_print_error (void) {
327 error_code
= GetLastError ();
328 status
= FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
329 NULL
, error_code
, 0, (LPWSTR
) &lpMsgBuf
, 0, NULL
);
331 WINE_FIXME ("Cannot display message for error %d, status %d\n",
332 error_code
, GetLastError());
336 WCMD_output_asis_len(lpMsgBuf
, lstrlenW(lpMsgBuf
),
337 GetStdHandle(STD_ERROR_HANDLE
));
338 LocalFree (lpMsgBuf
);
339 WCMD_output_asis_len (newline
, lstrlenW(newline
),
340 GetStdHandle(STD_ERROR_HANDLE
));
344 /******************************************************************************
347 * Display the prompt on STDout
351 static void WCMD_show_prompt (void) {
354 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
357 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
359 len
= GetEnvironmentVariableW(envPrompt
, prompt_string
,
360 sizeof(prompt_string
)/sizeof(WCHAR
));
361 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
362 static const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
363 strcpyW (prompt_string
, dfltPrompt
);
377 switch (toupper(*p
)) {
391 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
410 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
416 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
429 GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
433 strcatW (q
, version_string
);
440 if (pushd_directories
) {
441 memset(q
, '+', pushd_directories
->u
.stackdepth
);
442 q
= q
+ pushd_directories
->u
.stackdepth
;
450 WCMD_output_asis (out_string
);
454 /*************************************************************************
456 * A wide version of strdup as its missing from unicode.h
458 WCHAR
*WCMD_strdupW(const WCHAR
*input
) {
459 int len
=strlenW(input
)+1;
460 WCHAR
*result
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
461 memcpy(result
, input
, len
* sizeof(WCHAR
));
465 /*************************************************************************
467 * Replaces a portion of a Unicode string with the specified string.
468 * It's up to the caller to ensure there is enough space in the
469 * destination buffer.
471 void WCMD_strsubstW(WCHAR
*start
, const WCHAR
*next
, const WCHAR
*insert
, int len
) {
474 len
=insert
? lstrlenW(insert
) : 0;
475 if (start
+len
!= next
)
476 memmove(start
+len
, next
, (strlenW(next
) + 1) * sizeof(*next
));
478 memcpy(start
, insert
, len
* sizeof(*insert
));
481 /***************************************************************************
482 * WCMD_skip_leading_spaces
484 * Return a pointer to the first non-whitespace character of string.
485 * Does not modify the input string.
487 WCHAR
*WCMD_skip_leading_spaces (WCHAR
*string
) {
492 while (*ptr
== ' ' || *ptr
== '\t') ptr
++;
496 /***************************************************************************
497 * WCMD_keyword_ws_found
499 * Checks if the string located at ptr matches a keyword (of length len)
500 * followed by a whitespace character (space or tab)
502 BOOL
WCMD_keyword_ws_found(const WCHAR
*keyword
, int len
, const WCHAR
*ptr
) {
503 return (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
504 ptr
, len
, keyword
, len
) == CSTR_EQUAL
)
505 && ((*(ptr
+ len
) == ' ') || (*(ptr
+ len
) == '\t'));
508 /*************************************************************************
509 * WCMD_opt_s_strip_quotes
511 * Remove first and last quote WCHARacters, preserving all other text
513 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
514 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
515 while((*dest
=*src
) != '\0') {
522 while ((*dest
++=*lastq
++) != 0)
528 /*************************************************************************
529 * WCMD_is_magic_envvar
530 * Return TRUE if s is '%'magicvar'%'
531 * and is not masked by a real environment variable.
534 static inline BOOL
WCMD_is_magic_envvar(const WCHAR
*s
, const WCHAR
*magicvar
)
539 return FALSE
; /* Didn't begin with % */
541 if (len
< 2 || s
[len
-1] != '%')
542 return FALSE
; /* Didn't end with another % */
544 if (CompareStringW(LOCALE_USER_DEFAULT
,
545 NORM_IGNORECASE
| SORT_STRINGSORT
,
546 s
+1, len
-2, magicvar
, -1) != CSTR_EQUAL
) {
547 /* Name doesn't match. */
551 if (GetEnvironmentVariableW(magicvar
, NULL
, 0) > 0) {
552 /* Masked by real environment variable. */
559 /*************************************************************************
562 * Expands environment variables, allowing for WCHARacter substitution
564 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
,
565 const WCHAR
*forVar
, const WCHAR
*forVal
) {
566 WCHAR
*endOfVar
= NULL
, *s
;
567 WCHAR
*colonpos
= NULL
;
568 WCHAR thisVar
[MAXSTRING
];
569 WCHAR thisVarContents
[MAXSTRING
];
570 WCHAR savedchar
= 0x00;
573 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
574 static const WCHAR Date
[] = {'D','A','T','E','\0'};
575 static const WCHAR Time
[] = {'T','I','M','E','\0'};
576 static const WCHAR Cd
[] = {'C','D','\0'};
577 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
578 static const WCHAR Delims
[] = {'%',' ',':','\0'};
580 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start
),
581 wine_dbgstr_w(forVal
), wine_dbgstr_w(forVar
));
583 /* Find the end of the environment variable, and extract name */
584 endOfVar
= strpbrkW(start
+1, Delims
);
586 if (endOfVar
== NULL
|| *endOfVar
==' ') {
588 /* In batch program, missing terminator for % and no following
589 ':' just removes the '%' */
591 WCMD_strsubstW(start
, start
+ 1, NULL
, 0);
595 /* In command processing, just ignore it - allows command line
596 syntax like: for %i in (a.a) do echo %i */
601 /* If ':' found, process remaining up until '%' (or stop at ':' if
603 if (*endOfVar
==':') {
604 WCHAR
*endOfVar2
= strchrW(endOfVar
+1, '%');
605 if (endOfVar2
!= NULL
) endOfVar
= endOfVar2
;
608 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
609 thisVar
[(endOfVar
- start
)+1] = 0x00;
610 colonpos
= strchrW(thisVar
+1, ':');
612 /* If there's complex substitution, just need %var% for now
613 to get the expanded data to play with */
616 savedchar
= *(colonpos
+1);
617 *(colonpos
+1) = 0x00;
620 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
622 /* Expand to contents, if unchanged, return */
623 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
624 /* override if existing env var called that name */
625 if (WCMD_is_magic_envvar(thisVar
, ErrorLvl
)) {
626 static const WCHAR fmt
[] = {'%','d','\0'};
627 wsprintfW(thisVarContents
, fmt
, errorlevel
);
628 len
= strlenW(thisVarContents
);
629 } else if (WCMD_is_magic_envvar(thisVar
, Date
)) {
630 GetDateFormatW(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
631 NULL
, thisVarContents
, MAXSTRING
);
632 len
= strlenW(thisVarContents
);
633 } else if (WCMD_is_magic_envvar(thisVar
, Time
)) {
634 GetTimeFormatW(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
635 NULL
, thisVarContents
, MAXSTRING
);
636 len
= strlenW(thisVarContents
);
637 } else if (WCMD_is_magic_envvar(thisVar
, Cd
)) {
638 GetCurrentDirectoryW(MAXSTRING
, thisVarContents
);
639 len
= strlenW(thisVarContents
);
640 } else if (WCMD_is_magic_envvar(thisVar
, Random
)) {
641 static const WCHAR fmt
[] = {'%','d','\0'};
642 wsprintfW(thisVarContents
, fmt
, rand() % 32768);
643 len
= strlenW(thisVarContents
);
645 /* Look for a matching 'for' variable */
647 (CompareStringW(LOCALE_USER_DEFAULT
,
650 (colonpos
- thisVar
) - 1,
651 forVar
, -1) == CSTR_EQUAL
)) {
652 strcpyW(thisVarContents
, forVal
);
653 len
= strlenW(thisVarContents
);
657 len
= ExpandEnvironmentStringsW(thisVar
, thisVarContents
,
658 sizeof(thisVarContents
)/sizeof(WCHAR
));
664 /* In a batch program, unknown env vars are replaced with nothing,
665 note syntax %garbage:1,3% results in anything after the ':'
667 From the command line, you just get back what you entered */
668 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
670 /* Restore the complex part after the compare */
673 *(colonpos
+1) = savedchar
;
676 /* Command line - just ignore this */
677 if (context
== NULL
) return endOfVar
+1;
680 /* Batch - replace unknown env var with nothing */
681 if (colonpos
== NULL
) {
682 WCMD_strsubstW(start
, endOfVar
+ 1, NULL
, 0);
684 len
= strlenW(thisVar
);
685 thisVar
[len
-1] = 0x00;
686 /* If %:...% supplied, : is retained */
687 if (colonpos
== thisVar
+1) {
688 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
, -1);
690 WCMD_strsubstW(start
, endOfVar
+ 1, colonpos
+ 1, -1);
697 /* See if we need to do complex substitution (any ':'s), if not
698 then our work here is done */
699 if (colonpos
== NULL
) {
700 WCMD_strsubstW(start
, endOfVar
+ 1, thisVarContents
, -1);
704 /* Restore complex bit */
706 *(colonpos
+1) = savedchar
;
709 Handle complex substitutions:
710 xxx=yyy (replace xxx with yyy)
711 *xxx=yyy (replace up to and including xxx with yyy)
712 ~x (from x WCHARs in)
713 ~-x (from x WCHARs from the end)
714 ~x,y (from x WCHARs in for y WCHARacters)
715 ~x,-y (from x WCHARs in until y WCHARacters from the end)
718 /* ~ is substring manipulation */
719 if (savedchar
== '~') {
721 int substrposition
, substrlength
= 0;
722 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
725 substrposition
= atolW(colonpos
+2);
726 if (commapos
) substrlength
= atolW(commapos
+1);
729 if (substrposition
>= 0) {
730 startCopy
= &thisVarContents
[min(substrposition
, len
)];
732 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
735 if (commapos
== NULL
) {
737 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, -1);
738 } else if (substrlength
< 0) {
740 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
741 if (copybytes
> len
) copybytes
= len
;
742 else if (copybytes
< 0) copybytes
= 0;
743 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, copybytes
);
745 WCMD_strsubstW(start
, endOfVar
+ 1, startCopy
, substrlength
);
750 /* search and replace manipulation */
752 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
753 WCHAR
*replacewith
= equalspos
+1;
758 if (equalspos
== NULL
) return start
+1;
759 s
= WCMD_strdupW(endOfVar
+ 1);
761 /* Null terminate both strings */
762 thisVar
[strlenW(thisVar
)-1] = 0x00;
765 /* Since we need to be case insensitive, copy the 2 buffers */
766 searchIn
= WCMD_strdupW(thisVarContents
);
767 CharUpperBuffW(searchIn
, strlenW(thisVarContents
));
768 searchFor
= WCMD_strdupW(colonpos
+1);
769 CharUpperBuffW(searchFor
, strlenW(colonpos
+1));
771 /* Handle wildcard case */
772 if (*(colonpos
+1) == '*') {
773 /* Search for string to replace */
774 found
= strstrW(searchIn
, searchFor
+1);
778 strcpyW(start
, replacewith
);
779 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
783 strcpyW(start
, thisVarContents
);
788 /* Loop replacing all instances */
789 WCHAR
*lastFound
= searchIn
;
790 WCHAR
*outputposn
= start
;
793 while ((found
= strstrW(lastFound
, searchFor
))) {
794 lstrcpynW(outputposn
,
795 thisVarContents
+ (lastFound
-searchIn
),
796 (found
- lastFound
)+1);
797 outputposn
= outputposn
+ (found
- lastFound
);
798 strcatW(outputposn
, replacewith
);
799 outputposn
= outputposn
+ strlenW(replacewith
);
800 lastFound
= found
+ strlenW(searchFor
);
803 thisVarContents
+ (lastFound
-searchIn
));
804 strcatW(outputposn
, s
);
806 HeapFree(GetProcessHeap(), 0, s
);
807 HeapFree(GetProcessHeap(), 0, searchIn
);
808 HeapFree(GetProcessHeap(), 0, searchFor
);
814 /*****************************************************************************
815 * Expand the command. Native expands lines from batch programs as they are
816 * read in and not again, except for 'for' variable substitution.
817 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
819 static void handleExpansion(WCHAR
*cmd
, BOOL justFors
,
820 const WCHAR
*forVariable
, const WCHAR
*forValue
) {
822 /* For commands in a context (batch program): */
823 /* Expand environment variables in a batch file %{0-9} first */
824 /* including support for any ~ modifiers */
826 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
827 /* names allowing environment variable overrides */
828 /* NOTE: To support the %PATH:xxx% syntax, also perform */
829 /* manual expansion of environment variables here */
835 while ((p
= strchrW(p
, '%'))) {
837 WINE_TRACE("Translate command:%s %d (at: %s)\n",
838 wine_dbgstr_w(cmd
), justFors
, wine_dbgstr_w(p
));
841 /* Don't touch %% unless its in Batch */
842 if (!justFors
&& *(p
+1) == '%') {
844 WCMD_strsubstW(p
, p
+1, NULL
, 0);
848 /* Replace %~ modifications if in batch program */
849 } else if (*(p
+1) == '~') {
850 WCMD_HandleTildaModifiers(&p
, forVariable
, forValue
, justFors
);
853 /* Replace use of %0...%9 if in batch program*/
854 } else if (!justFors
&& context
&& (i
>= 0) && (i
<= 9)) {
855 t
= WCMD_parameter(context
-> command
, i
+ context
-> shift_count
[i
], NULL
, NULL
);
856 WCMD_strsubstW(p
, p
+2, t
, -1);
858 /* Replace use of %* if in batch program*/
859 } else if (!justFors
&& context
&& *(p
+1)=='*') {
860 WCHAR
*startOfParms
= NULL
;
861 t
= WCMD_parameter(context
-> command
, 1, &startOfParms
, NULL
);
862 if (startOfParms
!= NULL
)
863 WCMD_strsubstW(p
, p
+2, startOfParms
, -1);
865 WCMD_strsubstW(p
, p
+2, NULL
, 0);
867 } else if (forVariable
&&
868 (CompareStringW(LOCALE_USER_DEFAULT
,
871 strlenW(forVariable
),
872 forVariable
, -1) == CSTR_EQUAL
)) {
873 WCMD_strsubstW(p
, p
+ strlenW(forVariable
), forValue
, -1);
875 } else if (!justFors
) {
876 p
= WCMD_expand_envvar(p
, forVariable
, forValue
);
878 /* In a FOR loop, see if this is the variable to replace */
879 } else { /* Ignore %'s on second pass of batch program */
888 /*******************************************************************
889 * WCMD_parse - parse a command into parameters and qualifiers.
891 * On exit, all qualifiers are concatenated into q, the first string
892 * not beginning with "/" is in p1 and the
893 * second in p2. Any subsequent non-qualifier strings are lost.
894 * Parameters in quotes are handled.
896 static void WCMD_parse (const WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
)
900 *q
= *p1
= *p2
= '\0';
905 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
906 *q
++ = toupperW (*s
++);
916 while ((*s
!= '\0') && (*s
!= '"')) {
917 if (p
== 0) *p1
++ = *s
++;
918 else if (p
== 1) *p2
++ = *s
++;
921 if (p
== 0) *p1
= '\0';
922 if (p
== 1) *p2
= '\0';
929 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')
930 && (*s
!= '=') && (*s
!= ',') ) {
931 if (p
== 0) *p1
++ = *s
++;
932 else if (p
== 1) *p2
++ = *s
++;
935 /* Skip concurrent parms */
936 while ((*s
== ' ') || (*s
== '\t') || (*s
== '=') || (*s
== ',') ) s
++;
938 if (p
== 0) *p1
= '\0';
939 if (p
== 1) *p2
= '\0';
945 static void init_msvcrt_io_block(STARTUPINFOW
* st
)
948 /* fetch the parent MSVCRT info block if any, so that the child can use the
949 * same handles as its grand-father
951 st_p
.cb
= sizeof(STARTUPINFOW
);
952 GetStartupInfoW(&st_p
);
953 st
->cbReserved2
= st_p
.cbReserved2
;
954 st
->lpReserved2
= st_p
.lpReserved2
;
955 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
957 /* Override the entries for fd 0,1,2 if we happened
958 * to change those std handles (this depends on the way cmd sets
959 * its new input & output handles)
961 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
962 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
965 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
966 char* flags
= (char*)(ptr
+ sizeof(unsigned));
967 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
969 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
970 st
->cbReserved2
= sz
;
971 st
->lpReserved2
= ptr
;
973 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
974 if (num
<= 0 || (flags
[0] & WX_OPEN
))
976 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
979 if (num
<= 1 || (flags
[1] & WX_OPEN
))
981 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
984 if (num
<= 2 || (flags
[2] & WX_OPEN
))
986 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
994 /******************************************************************************
997 * Execute a command line as an external program. Must allow recursion.
1000 * Manual testing under windows shows PATHEXT plays a key part in this,
1001 * and the search algorithm and precedence appears to be as follows.
1004 * If directory supplied on command, just use that directory
1005 * If extension supplied on command, look for that explicit name first
1006 * Otherwise, search in each directory on the path
1008 * If extension supplied on command, look for that explicit name first
1009 * Then look for supplied name .* (even if extension supplied, so
1010 * 'garbage.exe' will match 'garbage.exe.cmd')
1011 * If any found, cycle through PATHEXT looking for name.exe one by one
1013 * Once a match has been found, it is launched - Code currently uses
1014 * findexecutable to achieve this which is left untouched.
1017 void WCMD_run_program (WCHAR
*command
, int called
) {
1019 WCHAR temp
[MAX_PATH
];
1020 WCHAR pathtosearch
[MAXSTRING
];
1022 WCHAR stemofsearch
[MAX_PATH
]; /* maximum allowed executable name is
1023 MAX_PATH, including null character */
1025 WCHAR pathext
[MAXSTRING
];
1026 BOOL extensionsupplied
= FALSE
;
1027 BOOL launched
= FALSE
;
1029 BOOL assumeInternal
= FALSE
;
1031 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
1032 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
1033 static const WCHAR delims
[] = {'/','\\',':','\0'};
1035 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
1036 if (!(*param1
) && !(*param2
))
1039 /* Calculate the search path and stem to search for */
1040 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
1041 static const WCHAR curDir
[] = {'.',';','\0'};
1042 strcpyW(pathtosearch
, curDir
);
1043 len
= GetEnvironmentVariableW(envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
1044 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
1045 static const WCHAR curDir
[] = {'.','\0'};
1046 strcpyW (pathtosearch
, curDir
);
1048 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
1049 if (strlenW(param1
) >= MAX_PATH
)
1051 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG
));
1055 strcpyW(stemofsearch
, param1
);
1059 /* Convert eg. ..\fred to include a directory by removing file part */
1060 GetFullPathNameW(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
1061 lastSlash
= strrchrW(pathtosearch
, '\\');
1062 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
1063 strcpyW(stemofsearch
, lastSlash
+1);
1065 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1066 c:\windows\a.bat syntax */
1067 if (lastSlash
) *(lastSlash
+ 1) = 0x00;
1070 /* Now extract PATHEXT */
1071 len
= GetEnvironmentVariableW(envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
1072 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
1073 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
1074 '.','c','o','m',';',
1075 '.','c','m','d',';',
1076 '.','e','x','e','\0'};
1077 strcpyW (pathext
, dfltPathExt
);
1080 /* Loop through the search path, dir by dir */
1081 pathposn
= pathtosearch
;
1082 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
1083 wine_dbgstr_w(stemofsearch
));
1084 while (!launched
&& pathposn
) {
1086 WCHAR thisDir
[MAX_PATH
] = {'\0'};
1089 static const WCHAR slashW
[] = {'\\','\0'};
1091 /* Work on the first directory on the search path */
1092 pos
= strchrW(pathposn
, ';');
1094 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
1095 thisDir
[(pos
-pathposn
)] = 0x00;
1099 strcpyW(thisDir
, pathposn
);
1103 /* Since you can have eg. ..\.. on the path, need to expand
1104 to full information */
1105 strcpyW(temp
, thisDir
);
1106 GetFullPathNameW(temp
, MAX_PATH
, thisDir
, NULL
);
1108 /* 1. If extension supplied, see if that file exists */
1109 strcatW(thisDir
, slashW
);
1110 strcatW(thisDir
, stemofsearch
);
1111 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
1113 /* 1. If extension supplied, see if that file exists */
1114 if (extensionsupplied
) {
1115 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1120 /* 2. Any .* matches? */
1123 WIN32_FIND_DATAW finddata
;
1124 static const WCHAR allFiles
[] = {'.','*','\0'};
1126 strcatW(thisDir
,allFiles
);
1127 h
= FindFirstFileW(thisDir
, &finddata
);
1129 if (h
!= INVALID_HANDLE_VALUE
) {
1131 WCHAR
*thisExt
= pathext
;
1133 /* 3. Yes - Try each path ext */
1135 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1138 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1139 pos
[(nextExt
-thisExt
)] = 0x00;
1140 thisExt
= nextExt
+1;
1142 strcpyW(pos
, thisExt
);
1146 if (GetFileAttributesW(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1154 /* Internal programs won't be picked up by this search, so even
1155 though not found, try one last createprocess and wait for it
1157 Note: Ideally we could tell between a console app (wait) and a
1158 windows app, but the API's for it fail in this case */
1159 if (!found
&& pathposn
== NULL
) {
1160 WINE_TRACE("ASSUMING INTERNAL\n");
1161 assumeInternal
= TRUE
;
1163 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1166 /* Once found, launch it */
1167 if (found
|| assumeInternal
) {
1169 PROCESS_INFORMATION pe
;
1173 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1174 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1175 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1179 /* Special case BAT and CMD */
1180 if (ext
&& !strcmpiW(ext
, batExt
)) {
1181 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1183 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1184 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1188 /* thisDir contains the file to be launched, but with what?
1189 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1190 hinst
= FindExecutableW (thisDir
, NULL
, temp
);
1191 if ((INT_PTR
)hinst
< 32)
1194 console
= SHGetFileInfoW(temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1196 ZeroMemory (&st
, sizeof(STARTUPINFOW
));
1197 st
.cb
= sizeof(STARTUPINFOW
);
1198 init_msvcrt_io_block(&st
);
1200 /* Launch the process and if a CUI wait on it to complete
1201 Note: Launching internal wine processes cannot specify a full path to exe */
1202 status
= CreateProcessW(assumeInternal
?NULL
: thisDir
,
1203 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1204 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1205 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1206 /* strip first and last quote WCHARacters and try again */
1207 WCMD_opt_s_strip_quotes(command
);
1209 WCMD_run_program(command
, called
);
1216 if (!assumeInternal
&& !console
) errorlevel
= 0;
1219 /* Always wait when called in a batch program context */
1220 if (assumeInternal
|| context
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1221 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1222 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1224 CloseHandle(pe
.hProcess
);
1225 CloseHandle(pe
.hThread
);
1231 /* Not found anywhere - give up */
1232 SetLastError(ERROR_FILE_NOT_FOUND
);
1233 WCMD_print_error ();
1235 /* If a command fails to launch, it sets errorlevel 9009 - which
1236 does not seem to have any associated constant definition */
1242 /*****************************************************************************
1243 * Process one command. If the command is EXIT this routine does not return.
1244 * We will recurse through here executing batch files.
1246 void WCMD_execute (const WCHAR
*command
, const WCHAR
*redirects
,
1247 const WCHAR
*forVariable
, const WCHAR
*forValue
,
1250 WCHAR
*cmd
, *p
, *redir
;
1252 DWORD count
, creationDisposition
;
1255 SECURITY_ATTRIBUTES sa
;
1256 WCHAR
*new_cmd
= NULL
;
1257 WCHAR
*new_redir
= NULL
;
1258 HANDLE old_stdhandles
[3] = {GetStdHandle (STD_INPUT_HANDLE
),
1259 GetStdHandle (STD_OUTPUT_HANDLE
),
1260 GetStdHandle (STD_ERROR_HANDLE
)};
1261 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
1264 BOOL prev_echo_mode
, piped
= FALSE
;
1266 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1267 wine_dbgstr_w(command
), cmdList
,
1268 wine_dbgstr_w(forVariable
), wine_dbgstr_w(forValue
));
1270 /* If the next command is a pipe then we implement pipes by redirecting
1271 the output from this command to a temp file and input into the
1272 next command from that temp file.
1273 FIXME: Use of named pipes would make more sense here as currently this
1274 process has to finish before the next one can start but this requires
1275 a change to not wait for the first app to finish but rather the pipe */
1276 if (cmdList
&& (*cmdList
)->nextcommand
&&
1277 (*cmdList
)->nextcommand
->prevDelim
== CMD_PIPE
) {
1279 WCHAR temp_path
[MAX_PATH
];
1280 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1282 /* Remember piping is in action */
1283 WINE_TRACE("Output needs to be piped\n");
1286 /* Generate a unique temporary filename */
1287 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1288 GetTempFileNameW(temp_path
, cmdW
, 0, (*cmdList
)->nextcommand
->pipeFile
);
1289 WINE_TRACE("Using temporary file of %s\n",
1290 wine_dbgstr_w((*cmdList
)->nextcommand
->pipeFile
));
1293 /* Move copy of the command onto the heap so it can be expanded */
1294 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
1297 WINE_ERR("Could not allocate memory for new_cmd\n");
1300 strcpyW(new_cmd
, command
);
1302 /* Move copy of the redirects onto the heap so it can be expanded */
1303 new_redir
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
1306 WINE_ERR("Could not allocate memory for new_redir\n");
1307 HeapFree( GetProcessHeap(), 0, new_cmd
);
1311 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1313 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1314 wsprintfW (new_redir
, redirOut
, redirects
, (*cmdList
)->nextcommand
->pipeFile
);
1315 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir
));
1317 strcpyW(new_redir
, redirects
);
1320 /* Expand variables in command line mode only (batch mode will
1321 be expanded as the line is read in, except for 'for' loops) */
1322 handleExpansion(new_cmd
, (context
!= NULL
), forVariable
, forValue
);
1323 handleExpansion(new_redir
, (context
!= NULL
), forVariable
, forValue
);
1327 * Changing default drive has to be handled as a special case.
1330 if ((cmd
[1] == ':') && IsCharAlphaW(cmd
[0]) && (strlenW(cmd
) == 2)) {
1332 WCHAR dir
[MAX_PATH
];
1334 /* According to MSDN CreateProcess docs, special env vars record
1335 the current directory on each drive, in the form =C:
1336 so see if one specified, and if so go back to it */
1337 strcpyW(envvar
, equalsW
);
1338 strcatW(envvar
, cmd
);
1339 if (GetEnvironmentVariableW(envvar
, dir
, MAX_PATH
) == 0) {
1340 static const WCHAR fmt
[] = {'%','s','\\','\0'};
1341 wsprintfW(cmd
, fmt
, cmd
);
1342 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd
));
1344 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(cmd
));
1345 status
= SetCurrentDirectoryW(cmd
);
1346 if (!status
) WCMD_print_error ();
1347 HeapFree( GetProcessHeap(), 0, cmd
);
1348 HeapFree( GetProcessHeap(), 0, new_redir
);
1352 sa
.nLength
= sizeof(sa
);
1353 sa
.lpSecurityDescriptor
= NULL
;
1354 sa
.bInheritHandle
= TRUE
;
1357 * Redirect stdin, stdout and/or stderr if required.
1360 /* STDIN could come from a preceding pipe, so delete on close if it does */
1361 if (cmdList
&& (*cmdList
)->pipeFile
[0] != 0x00) {
1362 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList
)->pipeFile
));
1363 h
= CreateFileW((*cmdList
)->pipeFile
, GENERIC_READ
,
1364 FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
1365 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
1366 if (h
== INVALID_HANDLE_VALUE
) {
1367 WCMD_print_error ();
1368 HeapFree( GetProcessHeap(), 0, cmd
);
1369 HeapFree( GetProcessHeap(), 0, new_redir
);
1372 SetStdHandle (STD_INPUT_HANDLE
, h
);
1374 /* No need to remember the temporary name any longer once opened */
1375 (*cmdList
)->pipeFile
[0] = 0x00;
1377 /* Otherwise STDIN could come from a '<' redirect */
1378 } else if ((p
= strchrW(new_redir
,'<')) != NULL
) {
1379 h
= CreateFileW(WCMD_parameter(++p
, 0, NULL
, NULL
), GENERIC_READ
, FILE_SHARE_READ
,
1380 &sa
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1381 if (h
== INVALID_HANDLE_VALUE
) {
1382 WCMD_print_error ();
1383 HeapFree( GetProcessHeap(), 0, cmd
);
1384 HeapFree( GetProcessHeap(), 0, new_redir
);
1387 SetStdHandle (STD_INPUT_HANDLE
, h
);
1390 /* Scan the whole command looking for > and 2> */
1392 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
1395 if (p
> redir
&& (*(p
-1)=='2'))
1402 creationDisposition
= OPEN_ALWAYS
;
1406 creationDisposition
= CREATE_ALWAYS
;
1409 /* Add support for 2>&1 */
1412 int idx
= *(p
+1) - '0';
1414 if (DuplicateHandle(GetCurrentProcess(),
1415 GetStdHandle(idx_stdhandles
[idx
]),
1416 GetCurrentProcess(),
1418 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
1419 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1421 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
1424 WCHAR
*param
= WCMD_parameter(p
, 0, NULL
, NULL
);
1425 h
= CreateFileW(param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
1426 FILE_ATTRIBUTE_NORMAL
, NULL
);
1427 if (h
== INVALID_HANDLE_VALUE
) {
1428 WCMD_print_error ();
1429 HeapFree( GetProcessHeap(), 0, cmd
);
1430 HeapFree( GetProcessHeap(), 0, new_redir
);
1433 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
1434 INVALID_SET_FILE_POINTER
) {
1435 WCMD_print_error ();
1437 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
1440 SetStdHandle (idx_stdhandles
[handle
], h
);
1444 * Strip leading whitespaces, and a '@' if supplied
1446 whichcmd
= WCMD_skip_leading_spaces(cmd
);
1447 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
1448 if (whichcmd
[0] == '@') whichcmd
++;
1451 * Check if the command entered is internal. If it is, pass the rest of the
1452 * line down to the command. If not try to run a program.
1456 while (IsCharAlphaNumericW(whichcmd
[count
])) {
1459 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1460 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1461 whichcmd
, count
, inbuilt
[i
], -1) == CSTR_EQUAL
) break;
1463 p
= WCMD_skip_leading_spaces (&whichcmd
[count
]);
1464 WCMD_parse (p
, quals
, param1
, param2
);
1465 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1467 if (i
<= WCMD_EXIT
&& (p
[0] == '/') && (p
[1] == '?')) {
1468 /* this is a help request for a builtin program */
1470 memcpy(p
, whichcmd
, count
* sizeof(WCHAR
));
1482 WCMD_setshow_default (p
);
1485 WCMD_clear_screen ();
1494 WCMD_setshow_date ();
1504 WCMD_echo(&whichcmd
[count
]);
1507 WCMD_for (p
, cmdList
);
1510 WCMD_goto (cmdList
);
1516 WCMD_if (p
, cmdList
);
1519 WCMD_volume (TRUE
, p
);
1523 WCMD_create_dir (p
);
1529 WCMD_setshow_path (p
);
1535 WCMD_setshow_prompt ();
1545 WCMD_remove_dir (p
);
1554 WCMD_setshow_env (p
);
1560 WCMD_setshow_time ();
1563 if (strlenW(&whichcmd
[count
]) > 0)
1564 WCMD_title(&whichcmd
[count
+1]);
1570 WCMD_output(newline
);
1577 WCMD_volume (FALSE
, p
);
1586 WCMD_assoc(p
, TRUE
);
1592 WCMD_assoc(p
, FALSE
);
1601 WCMD_exit (cmdList
);
1604 prev_echo_mode
= echo_mode
;
1605 WCMD_run_program (whichcmd
, 0);
1606 echo_mode
= prev_echo_mode
;
1608 HeapFree( GetProcessHeap(), 0, cmd
);
1609 HeapFree( GetProcessHeap(), 0, new_redir
);
1611 /* Restore old handles */
1612 for (i
=0; i
<3; i
++) {
1613 if (old_stdhandles
[i
] != GetStdHandle(idx_stdhandles
[i
])) {
1614 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
1615 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
1620 /*************************************************************************
1622 * Load a string from the resource file, handling any error
1623 * Returns string retrieved from resource file
1625 WCHAR
*WCMD_LoadMessage(UINT id
) {
1626 static WCHAR msg
[2048];
1627 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1629 if (!LoadStringW(GetModuleHandleW(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1630 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1631 strcpyW(msg
, failedMsg
);
1636 /***************************************************************************
1639 * Dumps out the parsed command line to ensure syntax is correct
1641 static void WCMD_DumpCommands(CMD_LIST
*commands
) {
1642 CMD_LIST
*thisCmd
= commands
;
1644 WINE_TRACE("Parsed line:\n");
1645 while (thisCmd
!= NULL
) {
1646 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1649 thisCmd
->bracketDepth
,
1650 thisCmd
->nextcommand
,
1651 wine_dbgstr_w(thisCmd
->command
),
1652 wine_dbgstr_w(thisCmd
->redirects
));
1653 thisCmd
= thisCmd
->nextcommand
;
1657 /***************************************************************************
1660 * Adds a command to the current command list
1662 static void WCMD_addCommand(WCHAR
*command
, int *commandLen
,
1663 WCHAR
*redirs
, int *redirLen
,
1664 WCHAR
**copyTo
, int **copyToLen
,
1665 CMD_DELIMITERS prevDelim
, int curDepth
,
1666 CMD_LIST
**lastEntry
, CMD_LIST
**output
) {
1668 CMD_LIST
*thisEntry
= NULL
;
1670 /* Allocate storage for command */
1671 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
1673 /* Copy in the command */
1675 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
1676 (*commandLen
+1) * sizeof(WCHAR
));
1677 memcpy(thisEntry
->command
, command
, *commandLen
* sizeof(WCHAR
));
1678 thisEntry
->command
[*commandLen
] = 0x00;
1680 /* Copy in the redirects */
1681 thisEntry
->redirects
= HeapAlloc(GetProcessHeap(), 0,
1682 (*redirLen
+1) * sizeof(WCHAR
));
1683 memcpy(thisEntry
->redirects
, redirs
, *redirLen
* sizeof(WCHAR
));
1684 thisEntry
->redirects
[*redirLen
] = 0x00;
1685 thisEntry
->pipeFile
[0] = 0x00;
1687 /* Reset the lengths */
1690 *copyToLen
= commandLen
;
1694 thisEntry
->command
= NULL
;
1695 thisEntry
->redirects
= NULL
;
1696 thisEntry
->pipeFile
[0] = 0x00;
1699 /* Fill in other fields */
1700 thisEntry
->nextcommand
= NULL
;
1701 thisEntry
->prevDelim
= prevDelim
;
1702 thisEntry
->bracketDepth
= curDepth
;
1704 (*lastEntry
)->nextcommand
= thisEntry
;
1706 *output
= thisEntry
;
1708 *lastEntry
= thisEntry
;
1712 /***************************************************************************
1715 * Checks if the quote pointed to is the end-quote.
1719 * 1) The current parameter ends at EOL or at the beginning
1720 * of a redirection or pipe and not in a quote section.
1722 * 2) If the next character is a space and not in a quote section.
1724 * Returns TRUE if this is an end quote, and FALSE if it is not.
1727 static BOOL
WCMD_IsEndQuote(const WCHAR
*quote
, int quoteIndex
)
1729 int quoteCount
= quoteIndex
;
1732 /* If we are not in a quoted section, then we are not an end-quote */
1738 /* Check how many quotes are left for this parameter */
1739 for(i
=0;quote
[i
];i
++)
1746 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1747 else if(((quoteCount
% 2) == 0)
1748 && ((quote
[i
] == '<') || (quote
[i
] == '>') || (quote
[i
] == '|') || (quote
[i
] == ' ')))
1754 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1756 if(quoteIndex
>= (quoteCount
/ 2))
1765 /***************************************************************************
1766 * WCMD_ReadAndParseLine
1768 * Either uses supplied input or
1769 * Reads a file from the handle, and then...
1770 * Parse the text buffer, splitting into separate commands
1771 * - unquoted && strings split 2 commands but the 2nd is flagged as
1773 * - ( as the first character just ups the bracket depth
1774 * - unquoted ) when bracket depth > 0 terminates a bracket and
1775 * adds a CMD_LIST structure with null command
1776 * - Anything else gets put into the command string (including
1779 WCHAR
*WCMD_ReadAndParseLine(const WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
)
1783 WCHAR curString
[MAXSTRING
];
1784 int curStringLen
= 0;
1785 WCHAR curRedirs
[MAXSTRING
];
1786 int curRedirsLen
= 0;
1790 CMD_LIST
*lastEntry
= NULL
;
1791 CMD_DELIMITERS prevDelim
= CMD_NONE
;
1792 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
1793 static const WCHAR remCmd
[] = {'r','e','m'};
1794 static const WCHAR forCmd
[] = {'f','o','r'};
1795 static const WCHAR ifCmd
[] = {'i','f'};
1796 static const WCHAR ifElse
[] = {'e','l','s','e'};
1802 BOOL onlyWhiteSpace
= FALSE
;
1803 BOOL lastWasWhiteSpace
= FALSE
;
1804 BOOL lastWasDo
= FALSE
;
1805 BOOL lastWasIn
= FALSE
;
1806 BOOL lastWasElse
= FALSE
;
1807 BOOL lastWasRedirect
= TRUE
;
1809 /* Allocate working space for a command read from keyboard, file etc */
1811 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
1814 WINE_ERR("Could not allocate memory for extraSpace\n");
1818 /* If initial command read in, use that, otherwise get input from handle */
1819 if (optionalcmd
!= NULL
) {
1820 strcpyW(extraSpace
, optionalcmd
);
1821 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
1822 WINE_FIXME("No command nor handle supplied\n");
1824 if (!WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
))
1827 curPos
= extraSpace
;
1829 /* Handle truncated input - issue warning */
1830 if (strlenW(extraSpace
) == MAXSTRING
-1) {
1831 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
1832 WCMD_output_asis_stderr(extraSpace
);
1833 WCMD_output_asis_stderr(newline
);
1836 /* Replace env vars if in a batch context */
1837 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
1838 /* Show prompt before batch line IF echo is on and in batch program */
1839 if (context
&& echo_mode
&& extraSpace
[0] && (extraSpace
[0] != '@')) {
1840 static const WCHAR spc
[]={' ','\0'};
1841 static const WCHAR echoDot
[] = {'e','c','h','o','.'};
1842 static const WCHAR echoCol
[] = {'e','c','h','o',':'};
1843 const DWORD len
= sizeof(echoDot
)/sizeof(echoDot
[0]);
1844 DWORD curr_size
= strlenW(extraSpace
);
1845 DWORD min_len
= (curr_size
< len
? curr_size
: len
);
1847 WCMD_output_asis(extraSpace
);
1848 /* I don't know why Windows puts a space here but it does */
1849 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1850 if (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1851 extraSpace
, min_len
, echoDot
, len
) != CSTR_EQUAL
1852 && CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1853 extraSpace
, min_len
, echoCol
, len
) != CSTR_EQUAL
)
1855 WCMD_output_asis(spc
);
1857 WCMD_output_asis(newline
);
1860 /* Start with an empty string, copying to the command string */
1863 curCopyTo
= curString
;
1864 curLen
= &curStringLen
;
1865 lastWasRedirect
= FALSE
; /* Required for eg spaces between > and filename */
1867 /* Parse every character on the line being processed */
1868 while (*curPos
!= 0x00) {
1873 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1874 lastWasWhiteSpace, onlyWhiteSpace);
1877 /* Certain commands need special handling */
1878 if (curStringLen
== 0 && curCopyTo
== curString
) {
1879 static const WCHAR forDO
[] = {'d','o'};
1881 /* If command starts with 'rem ', ignore any &&, ( etc. */
1882 if (WCMD_keyword_ws_found(remCmd
, sizeof(remCmd
)/sizeof(remCmd
[0]), curPos
)) {
1885 } else if (WCMD_keyword_ws_found(forCmd
, sizeof(forCmd
)/sizeof(forCmd
[0]), curPos
)) {
1888 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1889 is only true in the command portion of the IF statement, but this
1890 should suffice for now
1891 FIXME: Silly syntax like "if 1(==1( (
1893 )" will be parsed wrong */
1894 } else if (WCMD_keyword_ws_found(ifCmd
, sizeof(ifCmd
)/sizeof(ifCmd
[0]), curPos
)) {
1897 } else if (WCMD_keyword_ws_found(ifElse
, sizeof(ifElse
)/sizeof(ifElse
[0]), curPos
)) {
1898 const int keyw_len
= sizeof(ifElse
)/sizeof(ifElse
[0]) + 1;
1901 onlyWhiteSpace
= TRUE
;
1902 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1903 (*curLen
)+=keyw_len
;
1907 /* In a for loop, the DO command will follow a close bracket followed by
1908 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1909 is then 0, and all whitespace is skipped */
1911 WCMD_keyword_ws_found(forDO
, sizeof(forDO
)/sizeof(forDO
[0]), curPos
)) {
1912 const int keyw_len
= sizeof(forDO
)/sizeof(forDO
[0]) + 1;
1913 WINE_TRACE("Found 'DO '\n");
1915 onlyWhiteSpace
= TRUE
;
1916 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1917 (*curLen
)+=keyw_len
;
1921 } else if (curCopyTo
== curString
) {
1923 /* Special handling for the 'FOR' command */
1924 if (inFor
&& lastWasWhiteSpace
) {
1925 static const WCHAR forIN
[] = {'i','n'};
1927 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
1929 if (WCMD_keyword_ws_found(forIN
, sizeof(forIN
)/sizeof(forIN
[0]), curPos
)) {
1930 const int keyw_len
= sizeof(forIN
)/sizeof(forIN
[0]) + 1;
1931 WINE_TRACE("Found 'IN '\n");
1933 onlyWhiteSpace
= TRUE
;
1934 memcpy(&curCopyTo
[*curLen
], curPos
, keyw_len
*sizeof(WCHAR
));
1935 (*curLen
)+=keyw_len
;
1942 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1943 so just use the default processing ie skip character specific
1945 if (!inRem
) thisChar
= *curPos
;
1946 else thisChar
= 'X'; /* Character with no special processing */
1948 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
1952 case '=': /* drop through - ignore token delimiters at the start of a command */
1953 case ',': /* drop through - ignore token delimiters at the start of a command */
1954 case '\t':/* drop through - ignore token delimiters at the start of a command */
1956 /* If a redirect in place, it ends here */
1957 if (!inQuotes
&& !lastWasRedirect
) {
1959 /* If finishing off a redirect, add a whitespace delimiter */
1960 if (curCopyTo
== curRedirs
) {
1961 curCopyTo
[(*curLen
)++] = ' ';
1963 curCopyTo
= curString
;
1964 curLen
= &curStringLen
;
1967 curCopyTo
[(*curLen
)++] = *curPos
;
1970 /* Remember just processed whitespace */
1971 lastWasWhiteSpace
= TRUE
;
1975 case '>': /* drop through - handle redirect chars the same */
1977 /* Make a redirect start here */
1979 curCopyTo
= curRedirs
;
1980 curLen
= &curRedirsLen
;
1981 lastWasRedirect
= TRUE
;
1984 /* See if 1>, 2> etc, in which case we have some patching up
1985 to do (provided there's a preceding whitespace, and enough
1986 chars read so far) */
1987 if (curStringLen
> 2
1988 && (*(curPos
-1)>='1') && (*(curPos
-1)<='9')
1989 && ((*(curPos
-2)==' ') || (*(curPos
-2)=='\t'))) {
1991 curString
[curStringLen
] = 0x00;
1992 curCopyTo
[(*curLen
)++] = *(curPos
-1);
1995 curCopyTo
[(*curLen
)++] = *curPos
;
1997 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1998 do not process that ampersand as an AND operator */
1999 if (thisChar
== '>' && *(curPos
+1) == '&') {
2000 curCopyTo
[(*curLen
)++] = *(curPos
+1);
2005 case '|': /* Pipe character only if not || */
2007 lastWasRedirect
= FALSE
;
2009 /* Add an entry to the command list */
2010 if (curStringLen
> 0) {
2012 /* Add the current command */
2013 WCMD_addCommand(curString
, &curStringLen
,
2014 curRedirs
, &curRedirsLen
,
2015 &curCopyTo
, &curLen
,
2016 prevDelim
, curDepth
,
2017 &lastEntry
, output
);
2021 if (*(curPos
+1) == '|') {
2022 curPos
++; /* Skip other | */
2023 prevDelim
= CMD_ONFAILURE
;
2025 prevDelim
= CMD_PIPE
;
2028 curCopyTo
[(*curLen
)++] = *curPos
;
2032 case '"': if (WCMD_IsEndQuote(curPos
, inQuotes
)) {
2035 inQuotes
++; /* Quotes within quotes are fun! */
2037 curCopyTo
[(*curLen
)++] = *curPos
;
2038 lastWasRedirect
= FALSE
;
2041 case '(': /* If a '(' is the first non whitespace in a command portion
2042 ie start of line or just after &&, then we read until an
2043 unquoted ) is found */
2044 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2045 ", for(%d, In:%d, Do:%d)"
2046 ", if(%d, else:%d, lwe:%d)\n",
2049 inFor
, lastWasIn
, lastWasDo
,
2050 inIf
, inElse
, lastWasElse
);
2051 lastWasRedirect
= FALSE
;
2053 /* Ignore open brackets inside the for set */
2054 if (*curLen
== 0 && !inIn
) {
2057 /* If in quotes, ignore brackets */
2058 } else if (inQuotes
) {
2059 curCopyTo
[(*curLen
)++] = *curPos
;
2061 /* In a FOR loop, an unquoted '(' may occur straight after
2063 In an IF statement just handle it regardless as we don't
2065 In an ELSE statement, only allow it straight away after
2066 the ELSE and whitespace
2069 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2070 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2072 /* If entering into an 'IN', set inIn */
2073 if (inFor
&& lastWasIn
&& onlyWhiteSpace
) {
2074 WINE_TRACE("Inside an IN\n");
2078 /* Add the current command */
2079 WCMD_addCommand(curString
, &curStringLen
,
2080 curRedirs
, &curRedirsLen
,
2081 &curCopyTo
, &curLen
,
2082 prevDelim
, curDepth
,
2083 &lastEntry
, output
);
2087 curCopyTo
[(*curLen
)++] = *curPos
;
2091 case '&': if (!inQuotes
) {
2092 lastWasRedirect
= FALSE
;
2094 /* Add an entry to the command list */
2095 if (curStringLen
> 0) {
2097 /* Add the current command */
2098 WCMD_addCommand(curString
, &curStringLen
,
2099 curRedirs
, &curRedirsLen
,
2100 &curCopyTo
, &curLen
,
2101 prevDelim
, curDepth
,
2102 &lastEntry
, output
);
2106 if (*(curPos
+1) == '&') {
2107 curPos
++; /* Skip other & */
2108 prevDelim
= CMD_ONSUCCESS
;
2110 prevDelim
= CMD_NONE
;
2113 curCopyTo
[(*curLen
)++] = *curPos
;
2117 case ')': if (!inQuotes
&& curDepth
> 0) {
2118 lastWasRedirect
= FALSE
;
2120 /* Add the current command if there is one */
2123 /* Add the current command */
2124 WCMD_addCommand(curString
, &curStringLen
,
2125 curRedirs
, &curRedirsLen
,
2126 &curCopyTo
, &curLen
,
2127 prevDelim
, curDepth
,
2128 &lastEntry
, output
);
2131 /* Add an empty entry to the command list */
2132 prevDelim
= CMD_NONE
;
2133 WCMD_addCommand(NULL
, &curStringLen
,
2134 curRedirs
, &curRedirsLen
,
2135 &curCopyTo
, &curLen
,
2136 prevDelim
, curDepth
,
2137 &lastEntry
, output
);
2140 /* Leave inIn if necessary */
2141 if (inIn
) inIn
= FALSE
;
2143 curCopyTo
[(*curLen
)++] = *curPos
;
2147 lastWasRedirect
= FALSE
;
2148 curCopyTo
[(*curLen
)++] = *curPos
;
2153 /* At various times we need to know if we have only skipped whitespace,
2154 so reset this variable and then it will remain true until a non
2155 whitespace is found */
2156 if ((thisChar
!= ' ') && (thisChar
!= '\t') && (thisChar
!= '\n'))
2157 onlyWhiteSpace
= FALSE
;
2159 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2160 if (!lastWasWhiteSpace
) {
2161 lastWasIn
= lastWasDo
= FALSE
;
2164 /* If we have reached the end, add this command into the list */
2165 if (*curPos
== 0x00 && *curLen
> 0) {
2167 /* Add an entry to the command list */
2168 WCMD_addCommand(curString
, &curStringLen
,
2169 curRedirs
, &curRedirsLen
,
2170 &curCopyTo
, &curLen
,
2171 prevDelim
, curDepth
,
2172 &lastEntry
, output
);
2175 /* If we have reached the end of the string, see if bracketing outstanding */
2176 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2178 prevDelim
= CMD_NONE
;
2180 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2182 /* Read more, skipping any blank lines */
2183 while (*extraSpace
== 0x00) {
2184 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2185 if (!WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
))
2188 curPos
= extraSpace
;
2189 if (context
) handleExpansion(extraSpace
, FALSE
, NULL
, NULL
);
2190 /* Continue to echo commands IF echo is on and in batch program */
2191 if (context
&& echo_mode
&& extraSpace
[0] && (extraSpace
[0] != '@')) {
2192 WCMD_output_asis(extraSpace
);
2193 WCMD_output_asis(newline
);
2198 /* Dump out the parsed output */
2199 WCMD_DumpCommands(*output
);
2204 /***************************************************************************
2205 * WCMD_process_commands
2207 * Process all the commands read in so far
2209 CMD_LIST
*WCMD_process_commands(CMD_LIST
*thisCmd
, BOOL oneBracket
,
2210 const WCHAR
*var
, const WCHAR
*val
) {
2214 if (thisCmd
&& oneBracket
) bdepth
= thisCmd
->bracketDepth
;
2216 /* Loop through the commands, processing them one by one */
2219 CMD_LIST
*origCmd
= thisCmd
;
2221 /* If processing one bracket only, and we find the end bracket
2222 entry (or less), return */
2223 if (oneBracket
&& !thisCmd
->command
&&
2224 bdepth
<= thisCmd
->bracketDepth
) {
2225 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2226 thisCmd
, thisCmd
->nextcommand
);
2227 return thisCmd
->nextcommand
;
2230 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2231 about them and it will be handled in there)
2232 Also, skip over any batch labels (eg. :fred) */
2233 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2234 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2235 WCMD_execute (thisCmd
->command
, thisCmd
->redirects
, var
, val
, &thisCmd
);
2238 /* Step on unless the command itself already stepped on */
2239 if (thisCmd
== origCmd
) thisCmd
= thisCmd
->nextcommand
;
2244 /***************************************************************************
2245 * WCMD_free_commands
2247 * Frees the storage held for a parsed command line
2248 * - This is not done in the process_commands, as eventually the current
2249 * pointer will be modified within the commands, and hence a single free
2250 * routine is simpler
2252 void WCMD_free_commands(CMD_LIST
*cmds
) {
2254 /* Loop through the commands, freeing them one by one */
2256 CMD_LIST
*thisCmd
= cmds
;
2257 cmds
= cmds
->nextcommand
;
2258 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2259 HeapFree(GetProcessHeap(), 0, thisCmd
->redirects
);
2260 HeapFree(GetProcessHeap(), 0, thisCmd
);
2265 /*****************************************************************************
2266 * Main entry point. This is a console application so we have a main() not a
2270 int wmain (int argc
, WCHAR
*argvW
[])
2278 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2279 static const WCHAR defaultpromptW
[] = {'$','P','$','G','\0'};
2280 char ansiVersion
[100];
2281 CMD_LIST
*toExecute
= NULL
; /* Commands left to be executed */
2285 /* Pre initialize some messages */
2286 strcpy(ansiVersion
, PACKAGE_VERSION
);
2287 MultiByteToWideChar(CP_ACP
, 0, ansiVersion
, -1, string
, 1024);
2288 wsprintfW(version_string
, WCMD_LoadMessage(WCMD_VERSION
), string
);
2289 strcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
2292 opt_c
=opt_k
=opt_q
=opt_s
=0;
2296 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW
));
2297 if ((*argvW
)[0]!='/' || (*argvW
)[1]=='\0') {
2304 if (tolowerW(c
)=='c') {
2306 } else if (tolowerW(c
)=='q') {
2308 } else if (tolowerW(c
)=='k') {
2310 } else if (tolowerW(c
)=='s') {
2312 } else if (tolowerW(c
)=='a') {
2314 } else if (tolowerW(c
)=='u') {
2316 } else if (tolowerW(c
)=='t' && (*argvW
)[2]==':') {
2317 opt_t
=strtoulW(&(*argvW
)[3], NULL
, 16);
2318 } else if (tolowerW(c
)=='x' || tolowerW(c
)=='y') {
2319 /* Ignored for compatibility with Windows */
2322 if ((*argvW
)[2]==0) {
2326 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2331 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
2336 static const WCHAR eoff
[] = {'O','F','F','\0'};
2340 if (opt_c
|| opt_k
) {
2346 /* opt_s left unflagged if the command starts with and contains exactly
2347 * one quoted string (exactly two quote characters). The quoted string
2348 * must be an executable name that has whitespace and must not have the
2349 * following characters: &<>()@^| */
2351 /* Build the command to execute */
2355 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
2357 int has_space
,bcount
;
2363 if( !*a
) has_space
=1;
2368 if (*a
==' ' || *a
=='\t') {
2370 } else if (*a
=='"') {
2371 /* doubling of '\' preceding a '"',
2372 * plus escaping of said '"'
2381 len
+=(a
-*arg
) + 1; /* for the separating space */
2384 len
+=2; /* for the quotes */
2392 /* check argvW[0] for a space and invalid characters */
2397 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
2398 || *p
=='@' || *p
=='^' || *p
=='|') {
2408 cmd
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
2414 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
2416 int has_space
,has_quote
;
2419 /* Check for quotes and spaces in this argument */
2420 has_space
=has_quote
=0;
2422 if( !*a
) has_space
=1;
2424 if (*a
==' ' || *a
=='\t') {
2428 } else if (*a
=='"') {
2436 /* Now transfer it to the command line */
2453 /* Double all the '\\' preceding this '"', plus one */
2454 for (i
=0;i
<=bcount
;i
++)
2473 p
--; /* remove last space */
2476 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
2478 /* strip first and last quote characters if opt_s; check for invalid
2479 * executable is done later */
2480 if (opt_s
&& *cmd
=='\"')
2481 WCMD_opt_s_strip_quotes(cmd
);
2485 /* If we do a "cmd /c command", we don't want to allocate a new
2486 * console since the command returns immediately. Rather, we use
2487 * the currently allocated input and output handles. This allows
2488 * us to pipe to and read from the command interpreter.
2491 /* Parse the command string, without reading any more input */
2492 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2493 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2494 WCMD_free_commands(toExecute
);
2497 HeapFree(GetProcessHeap(), 0, cmd
);
2501 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
2502 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
2503 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE
));
2505 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2507 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
2508 defaultColor
= opt_t
& 0xFF;
2513 /* Check HKCU\Software\Microsoft\Command Processor
2514 Then HKLM\Software\Microsoft\Command Processor
2515 for defaultcolour value
2516 Note Can be supplied as DWORD or REG_SZ
2517 Note2 When supplied as REG_SZ it's in decimal!!! */
2520 DWORD value
=0, size
=4;
2521 static const WCHAR regKeyW
[] = {'S','o','f','t','w','a','r','e','\\',
2522 'M','i','c','r','o','s','o','f','t','\\',
2523 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2524 static const WCHAR dfltColorW
[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2526 if (RegOpenKeyExW(HKEY_CURRENT_USER
, regKeyW
,
2527 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2530 /* See if DWORD or REG_SZ */
2531 if (RegQueryValueExW(key
, dfltColorW
, NULL
, &type
,
2532 NULL
, NULL
) == ERROR_SUCCESS
) {
2533 if (type
== REG_DWORD
) {
2534 size
= sizeof(DWORD
);
2535 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2536 (LPBYTE
)&value
, &size
);
2537 } else if (type
== REG_SZ
) {
2538 size
= sizeof(strvalue
)/sizeof(WCHAR
);
2539 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2540 (LPBYTE
)strvalue
, &size
);
2541 value
= strtoulW(strvalue
, NULL
, 10);
2547 if (value
== 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE
, regKeyW
,
2548 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
2551 /* See if DWORD or REG_SZ */
2552 if (RegQueryValueExW(key
, dfltColorW
, NULL
, &type
,
2553 NULL
, NULL
) == ERROR_SUCCESS
) {
2554 if (type
== REG_DWORD
) {
2555 size
= sizeof(DWORD
);
2556 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2557 (LPBYTE
)&value
, &size
);
2558 } else if (type
== REG_SZ
) {
2559 size
= sizeof(strvalue
)/sizeof(WCHAR
);
2560 RegQueryValueExW(key
, dfltColorW
, NULL
, NULL
,
2561 (LPBYTE
)strvalue
, &size
);
2562 value
= strtoulW(strvalue
, NULL
, 10);
2568 /* If one found, set the screen to that colour */
2569 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
2570 defaultColor
= value
& 0xFF;
2577 /* Save cwd into appropriate env var */
2578 GetCurrentDirectoryW(1024, string
);
2579 if (IsCharAlphaW(string
[0]) && string
[1] == ':') {
2580 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
2581 wsprintfW(envvar
, fmt
, string
[0]);
2582 SetEnvironmentVariableW(envvar
, string
);
2583 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar
), wine_dbgstr_w(string
));
2587 /* Parse the command string, without reading any more input */
2588 WCMD_ReadAndParseLine(cmd
, &toExecute
, INVALID_HANDLE_VALUE
);
2589 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2590 WCMD_free_commands(toExecute
);
2592 HeapFree(GetProcessHeap(), 0, cmd
);
2596 * Loop forever getting commands and executing them.
2599 SetEnvironmentVariableW(promptW
, defaultpromptW
);
2603 /* Read until EOF (which for std input is never, but if redirect
2604 in place, may occur */
2605 if (echo_mode
) WCMD_show_prompt();
2606 if (!WCMD_ReadAndParseLine(NULL
, &toExecute
, GetStdHandle(STD_INPUT_HANDLE
)))
2608 WCMD_process_commands(toExecute
, FALSE
, NULL
, NULL
);
2609 WCMD_free_commands(toExecute
);