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'}
89 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
90 static int opt_c, opt_k, opt_s;
91 const WCHAR newline[] = {'\r','\n','\0'};
92 static const WCHAR equalsW[] = {'=','\0'};
93 static const WCHAR closeBW[] = {')','\0'};
95 WCHAR version_string[100];
96 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
97 BATCH_CONTEXT *context = NULL;
98 extern struct env_stack *pushd_directories;
99 static const WCHAR *pagedMessage = NULL;
100 static char *output_bufA = NULL;
101 #define MAX_WRITECONSOLE_SIZE 65535
102 BOOL unicodePipes = FALSE;
105 /*******************************************************************
106 * WCMD_output_asis_len - send output to current standard output
108 * Output a formatted unicode string. Ideally this will go to the console
109 * and hence required WriteConsoleW to output it, however if file i/o is
110 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
112 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
117 /* If nothing to write, return (MORE does this sometimes) */
120 /* Try to write as unicode assuming it is to a console */
121 res = WriteConsoleW(device, message, len, &nOut, NULL);
123 /* If writing to console fails, assume its file
124 i/o so convert to OEM codepage and output */
126 BOOL usedDefaultChar = FALSE;
127 DWORD convertedChars;
131 * Allocate buffer to use when writing to file. (Not freed, as one off)
133 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
134 MAX_WRITECONSOLE_SIZE);
136 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
140 /* Convert to OEM, then output */
141 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
142 len, output_bufA, MAX_WRITECONSOLE_SIZE,
143 "?", &usedDefaultChar);
144 WriteFile(device, output_bufA, convertedChars,
147 WriteFile(device, message, len*sizeof(WCHAR),
154 /*******************************************************************
155 * WCMD_output - send output to current standard output device.
159 void WCMD_output (const WCHAR *format, ...) {
166 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
167 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
168 WINE_ERR("Output truncated in WCMD_output\n" );
169 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
173 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
177 static int line_count;
178 static int max_height;
179 static int max_width;
180 static BOOL paged_mode;
183 void WCMD_enter_paged_mode(const WCHAR *msg)
185 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
187 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
188 max_height = consoleInfo.dwSize.Y;
189 max_width = consoleInfo.dwSize.X;
197 pagedMessage = (msg==NULL)? anykey : msg;
200 void WCMD_leave_paged_mode(void)
206 /***************************************************************************
209 * Read characters in from a console/file, returning result in Unicode
210 * with signature identical to ReadFile
212 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
213 LPDWORD charsRead, const LPOVERLAPPED unused) {
217 /* Try to read from console as Unicode */
218 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
220 /* If reading from console has failed we assume its file
221 i/o so read in and convert from OEM codepage */
226 * Allocate buffer to use when reading from file. Not freed
228 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
229 MAX_WRITECONSOLE_SIZE);
231 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
235 /* Read from file (assume OEM codepage) */
236 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
238 /* Convert from OEM */
239 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
246 /*******************************************************************
247 * WCMD_output_asis_handle
249 * Send output to specified handle without formatting e.g. when message contains '%'
251 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
255 HANDLE handle = GetStdHandle(std_handle);
260 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
264 if (*ptr == '\n') ptr++;
265 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message), handle);
268 if (++line_count >= max_height - 1) {
270 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
271 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
272 sizeof(string)/sizeof(WCHAR), &count, NULL);
275 } while (((message = ptr) != NULL) && (*ptr));
277 WCMD_output_asis_len(message, lstrlenW(message), handle);
281 /*******************************************************************
284 * Send output to current standard output device, without formatting
285 * e.g. when message contains '%'
287 void WCMD_output_asis (const WCHAR *message) {
288 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
291 /*******************************************************************
292 * WCMD_output_asis_stderr
294 * Send output to current standard error device, without formatting
295 * e.g. when message contains '%'
297 void WCMD_output_asis_stderr (const WCHAR *message) {
298 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
301 /****************************************************************************
304 * Print the message for GetLastError
307 void WCMD_print_error (void) {
312 error_code = GetLastError ();
313 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
314 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
316 WINE_FIXME ("Cannot display message for error %d, status %d\n",
317 error_code, GetLastError());
321 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
322 GetStdHandle(STD_ERROR_HANDLE));
323 LocalFree (lpMsgBuf);
324 WCMD_output_asis_len (newline, lstrlenW(newline),
325 GetStdHandle(STD_ERROR_HANDLE));
329 /******************************************************************************
332 * Display the prompt on STDout
336 static void WCMD_show_prompt (void) {
339 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
342 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
344 len = GetEnvironmentVariableW(envPrompt, prompt_string,
345 sizeof(prompt_string)/sizeof(WCHAR));
346 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
347 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
348 strcpyW (prompt_string, dfltPrompt);
362 switch (toupper(*p)) {
376 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
395 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
401 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
414 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
418 strcatW (q, version_string);
425 if (pushd_directories) {
426 memset(q, '+', pushd_directories->u.stackdepth);
427 q = q + pushd_directories->u.stackdepth;
435 WCMD_output_asis (out_string);
439 /*************************************************************************
441 * A wide version of strdup as its missing from unicode.h
443 WCHAR *WCMD_strdupW(const WCHAR *input) {
444 int len=strlenW(input)+1;
445 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
446 memcpy(result, input, len * sizeof(WCHAR));
450 /*************************************************************************
452 * Replaces a portion of a Unicode string with the specified string.
453 * It's up to the caller to ensure there is enough space in the
454 * destination buffer.
456 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
459 len=insert ? lstrlenW(insert) : 0;
460 if (start+len != next)
461 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
463 memcpy(start, insert, len * sizeof(*insert));
466 /***************************************************************************
467 * WCMD_skip_leading_spaces
469 * Return a pointer to the first non-whitespace character of string.
470 * Does not modify the input string.
472 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
477 while (*ptr == ' ' || *ptr == '\t') ptr++;
481 /***************************************************************************
482 * WCMD_keyword_ws_found
484 * Checks if the string located at ptr matches a keyword (of length len)
485 * followed by a whitespace character (space or tab)
487 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
488 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
489 ptr, len, keyword, len) == CSTR_EQUAL)
490 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
493 /*************************************************************************
494 * WCMD_opt_s_strip_quotes
496 * Remove first and last quote WCHARacters, preserving all other text
498 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
499 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
500 while((*dest=*src) != '\0') {
507 while ((*dest++=*lastq++) != 0)
513 /*************************************************************************
514 * WCMD_is_magic_envvar
515 * Return TRUE if s is '%'magicvar'%'
516 * and is not masked by a real environment variable.
519 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
524 return FALSE; /* Didn't begin with % */
526 if (len < 2 || s[len-1] != '%')
527 return FALSE; /* Didn't end with another % */
529 if (CompareStringW(LOCALE_USER_DEFAULT,
530 NORM_IGNORECASE | SORT_STRINGSORT,
531 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
532 /* Name doesn't match. */
536 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
537 /* Masked by real environment variable. */
544 /*************************************************************************
547 * Expands environment variables, allowing for WCHARacter substitution
549 static WCHAR *WCMD_expand_envvar(WCHAR *start,
550 const WCHAR *forVar, const WCHAR *forVal) {
551 WCHAR *endOfVar = NULL, *s;
552 WCHAR *colonpos = NULL;
553 WCHAR thisVar[MAXSTRING];
554 WCHAR thisVarContents[MAXSTRING];
555 WCHAR savedchar = 0x00;
558 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
559 static const WCHAR Date[] = {'D','A','T','E','\0'};
560 static const WCHAR Time[] = {'T','I','M','E','\0'};
561 static const WCHAR Cd[] = {'C','D','\0'};
562 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
563 static const WCHAR Delims[] = {'%',' ',':','\0'};
565 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
566 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
568 /* Find the end of the environment variable, and extract name */
569 endOfVar = strpbrkW(start+1, Delims);
571 if (endOfVar == NULL || *endOfVar==' ') {
573 /* In batch program, missing terminator for % and no following
574 ':' just removes the '%' */
576 WCMD_strsubstW(start, start + 1, NULL, 0);
580 /* In command processing, just ignore it - allows command line
581 syntax like: for %i in (a.a) do echo %i */
586 /* If ':' found, process remaining up until '%' (or stop at ':' if
588 if (*endOfVar==':') {
589 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
590 if (endOfVar2 != NULL) endOfVar = endOfVar2;
593 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
594 thisVar[(endOfVar - start)+1] = 0x00;
595 colonpos = strchrW(thisVar+1, ':');
597 /* If there's complex substitution, just need %var% for now
598 to get the expanded data to play with */
601 savedchar = *(colonpos+1);
602 *(colonpos+1) = 0x00;
605 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
607 /* Expand to contents, if unchanged, return */
608 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
609 /* override if existing env var called that name */
610 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
611 static const WCHAR fmt[] = {'%','d','\0'};
612 wsprintfW(thisVarContents, fmt, errorlevel);
613 len = strlenW(thisVarContents);
614 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
615 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
616 NULL, thisVarContents, MAXSTRING);
617 len = strlenW(thisVarContents);
618 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
619 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
620 NULL, thisVarContents, MAXSTRING);
621 len = strlenW(thisVarContents);
622 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
623 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
624 len = strlenW(thisVarContents);
625 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
626 static const WCHAR fmt[] = {'%','d','\0'};
627 wsprintfW(thisVarContents, fmt, rand() % 32768);
628 len = strlenW(thisVarContents);
630 /* Look for a matching 'for' variable */
632 (CompareStringW(LOCALE_USER_DEFAULT,
635 (colonpos - thisVar) - 1,
636 forVar, -1) == CSTR_EQUAL)) {
637 strcpyW(thisVarContents, forVal);
638 len = strlenW(thisVarContents);
642 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
643 sizeof(thisVarContents)/sizeof(WCHAR));
649 /* In a batch program, unknown env vars are replaced with nothing,
650 note syntax %garbage:1,3% results in anything after the ':'
652 From the command line, you just get back what you entered */
653 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
655 /* Restore the complex part after the compare */
658 *(colonpos+1) = savedchar;
661 /* Command line - just ignore this */
662 if (context == NULL) return endOfVar+1;
665 /* Batch - replace unknown env var with nothing */
666 if (colonpos == NULL) {
667 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
669 len = strlenW(thisVar);
670 thisVar[len-1] = 0x00;
671 /* If %:...% supplied, : is retained */
672 if (colonpos == thisVar+1) {
673 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
675 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
682 /* See if we need to do complex substitution (any ':'s), if not
683 then our work here is done */
684 if (colonpos == NULL) {
685 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
689 /* Restore complex bit */
691 *(colonpos+1) = savedchar;
694 Handle complex substitutions:
695 xxx=yyy (replace xxx with yyy)
696 *xxx=yyy (replace up to and including xxx with yyy)
697 ~x (from x WCHARs in)
698 ~-x (from x WCHARs from the end)
699 ~x,y (from x WCHARs in for y WCHARacters)
700 ~x,-y (from x WCHARs in until y WCHARacters from the end)
703 /* ~ is substring manipulation */
704 if (savedchar == '~') {
706 int substrposition, substrlength = 0;
707 WCHAR *commapos = strchrW(colonpos+2, ',');
710 substrposition = atolW(colonpos+2);
711 if (commapos) substrlength = atolW(commapos+1);
714 if (substrposition >= 0) {
715 startCopy = &thisVarContents[min(substrposition, len)];
717 startCopy = &thisVarContents[max(0, len+substrposition-1)];
720 if (commapos == NULL) {
722 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
723 } else if (substrlength < 0) {
725 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
726 if (copybytes > len) copybytes = len;
727 else if (copybytes < 0) copybytes = 0;
728 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
730 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
735 /* search and replace manipulation */
737 WCHAR *equalspos = strstrW(colonpos, equalsW);
738 WCHAR *replacewith = equalspos+1;
743 if (equalspos == NULL) return start+1;
744 s = WCMD_strdupW(endOfVar + 1);
746 /* Null terminate both strings */
747 thisVar[strlenW(thisVar)-1] = 0x00;
750 /* Since we need to be case insensitive, copy the 2 buffers */
751 searchIn = WCMD_strdupW(thisVarContents);
752 CharUpperBuffW(searchIn, strlenW(thisVarContents));
753 searchFor = WCMD_strdupW(colonpos+1);
754 CharUpperBuffW(searchFor, strlenW(colonpos+1));
756 /* Handle wildcard case */
757 if (*(colonpos+1) == '*') {
758 /* Search for string to replace */
759 found = strstrW(searchIn, searchFor+1);
763 strcpyW(start, replacewith);
764 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
768 strcpyW(start, thisVarContents);
773 /* Loop replacing all instances */
774 WCHAR *lastFound = searchIn;
775 WCHAR *outputposn = start;
778 while ((found = strstrW(lastFound, searchFor))) {
779 lstrcpynW(outputposn,
780 thisVarContents + (lastFound-searchIn),
781 (found - lastFound)+1);
782 outputposn = outputposn + (found - lastFound);
783 strcatW(outputposn, replacewith);
784 outputposn = outputposn + strlenW(replacewith);
785 lastFound = found + strlenW(searchFor);
788 thisVarContents + (lastFound-searchIn));
789 strcatW(outputposn, s);
791 HeapFree(GetProcessHeap(), 0, s);
792 HeapFree(GetProcessHeap(), 0, searchIn);
793 HeapFree(GetProcessHeap(), 0, searchFor);
799 /*****************************************************************************
800 * Expand the command. Native expands lines from batch programs as they are
801 * read in and not again, except for 'for' variable substitution.
802 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
804 static void handleExpansion(WCHAR *cmd, BOOL justFors,
805 const WCHAR *forVariable, const WCHAR *forValue) {
807 /* For commands in a context (batch program): */
808 /* Expand environment variables in a batch file %{0-9} first */
809 /* including support for any ~ modifiers */
811 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
812 /* names allowing environment variable overrides */
813 /* NOTE: To support the %PATH:xxx% syntax, also perform */
814 /* manual expansion of environment variables here */
820 while ((p = strchrW(p, '%'))) {
822 WINE_TRACE("Translate command:%s %d (at: %s)\n",
823 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
826 /* Don't touch %% unless its in Batch */
827 if (!justFors && *(p+1) == '%') {
829 WCMD_strsubstW(p, p+1, NULL, 0);
833 /* Replace %~ modifications if in batch program */
834 } else if (*(p+1) == '~') {
835 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
838 /* Replace use of %0...%9 if in batch program*/
839 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
840 t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
841 WCMD_strsubstW(p, p+2, t, -1);
843 /* Replace use of %* if in batch program*/
844 } else if (!justFors && context && *(p+1)=='*') {
845 WCHAR *startOfParms = NULL;
846 t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
847 if (startOfParms != NULL)
848 WCMD_strsubstW(p, p+2, startOfParms, -1);
850 WCMD_strsubstW(p, p+2, NULL, 0);
852 } else if (forVariable &&
853 (CompareStringW(LOCALE_USER_DEFAULT,
856 strlenW(forVariable),
857 forVariable, -1) == CSTR_EQUAL)) {
858 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
860 } else if (!justFors) {
861 p = WCMD_expand_envvar(p, forVariable, forValue);
863 /* In a FOR loop, see if this is the variable to replace */
864 } else { /* Ignore %'s on second pass of batch program */
873 /*******************************************************************
874 * WCMD_parse - parse a command into parameters and qualifiers.
876 * On exit, all qualifiers are concatenated into q, the first string
877 * not beginning with "/" is in p1 and the
878 * second in p2. Any subsequent non-qualifier strings are lost.
879 * Parameters in quotes are handled.
881 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
885 *q = *p1 = *p2 = '\0';
890 while ((*s != '\0') && (*s != ' ') && *s != '/') {
891 *q++ = toupperW (*s++);
901 while ((*s != '\0') && (*s != '"')) {
902 if (p == 0) *p1++ = *s++;
903 else if (p == 1) *p2++ = *s++;
906 if (p == 0) *p1 = '\0';
907 if (p == 1) *p2 = '\0';
914 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
915 && (*s != '=') && (*s != ',') ) {
916 if (p == 0) *p1++ = *s++;
917 else if (p == 1) *p2++ = *s++;
920 /* Skip concurrent parms */
921 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
923 if (p == 0) *p1 = '\0';
924 if (p == 1) *p2 = '\0';
930 static void init_msvcrt_io_block(STARTUPINFOW* st)
933 /* fetch the parent MSVCRT info block if any, so that the child can use the
934 * same handles as its grand-father
936 st_p.cb = sizeof(STARTUPINFOW);
937 GetStartupInfoW(&st_p);
938 st->cbReserved2 = st_p.cbReserved2;
939 st->lpReserved2 = st_p.lpReserved2;
940 if (st_p.cbReserved2 && st_p.lpReserved2)
942 /* Override the entries for fd 0,1,2 if we happened
943 * to change those std handles (this depends on the way cmd sets
944 * its new input & output handles)
946 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
947 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
950 unsigned num = *(unsigned*)st_p.lpReserved2;
951 char* flags = (char*)(ptr + sizeof(unsigned));
952 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
954 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
955 st->cbReserved2 = sz;
956 st->lpReserved2 = ptr;
958 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
959 if (num <= 0 || (flags[0] & WX_OPEN))
961 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
964 if (num <= 1 || (flags[1] & WX_OPEN))
966 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
969 if (num <= 2 || (flags[2] & WX_OPEN))
971 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
979 /******************************************************************************
982 * Execute a command line as an external program. Must allow recursion.
985 * Manual testing under windows shows PATHEXT plays a key part in this,
986 * and the search algorithm and precedence appears to be as follows.
989 * If directory supplied on command, just use that directory
990 * If extension supplied on command, look for that explicit name first
991 * Otherwise, search in each directory on the path
993 * If extension supplied on command, look for that explicit name first
994 * Then look for supplied name .* (even if extension supplied, so
995 * 'garbage.exe' will match 'garbage.exe.cmd')
996 * If any found, cycle through PATHEXT looking for name.exe one by one
998 * Once a match has been found, it is launched - Code currently uses
999 * findexecutable to achieve this which is left untouched.
1002 void WCMD_run_program (WCHAR *command, int called) {
1004 WCHAR temp[MAX_PATH];
1005 WCHAR pathtosearch[MAXSTRING];
1007 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1008 MAX_PATH, including null character */
1010 WCHAR pathext[MAXSTRING];
1011 BOOL extensionsupplied = FALSE;
1012 BOOL launched = FALSE;
1014 BOOL assumeInternal = FALSE;
1016 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1017 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1018 static const WCHAR delims[] = {'/','\\',':','\0'};
1020 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
1021 if (!(*param1) && !(*param2))
1024 /* Calculate the search path and stem to search for */
1025 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
1026 static const WCHAR curDir[] = {'.',';','\0'};
1027 strcpyW(pathtosearch, curDir);
1028 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1029 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1030 static const WCHAR curDir[] = {'.','\0'};
1031 strcpyW (pathtosearch, curDir);
1033 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1034 if (strlenW(param1) >= MAX_PATH)
1036 WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
1040 strcpyW(stemofsearch, param1);
1044 /* Convert eg. ..\fred to include a directory by removing file part */
1045 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1046 lastSlash = strrchrW(pathtosearch, '\\');
1047 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1048 strcpyW(stemofsearch, lastSlash+1);
1050 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1051 c:\windows\a.bat syntax */
1052 if (lastSlash) *(lastSlash + 1) = 0x00;
1055 /* Now extract PATHEXT */
1056 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1057 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1058 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1059 '.','c','o','m',';',
1060 '.','c','m','d',';',
1061 '.','e','x','e','\0'};
1062 strcpyW (pathext, dfltPathExt);
1065 /* Loop through the search path, dir by dir */
1066 pathposn = pathtosearch;
1067 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1068 wine_dbgstr_w(stemofsearch));
1069 while (!launched && pathposn) {
1071 WCHAR thisDir[MAX_PATH] = {'\0'};
1074 static const WCHAR slashW[] = {'\\','\0'};
1076 /* Work on the first directory on the search path */
1077 pos = strchrW(pathposn, ';');
1079 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1080 thisDir[(pos-pathposn)] = 0x00;
1084 strcpyW(thisDir, pathposn);
1088 /* Since you can have eg. ..\.. on the path, need to expand
1089 to full information */
1090 strcpyW(temp, thisDir);
1091 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1093 /* 1. If extension supplied, see if that file exists */
1094 strcatW(thisDir, slashW);
1095 strcatW(thisDir, stemofsearch);
1096 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1098 /* 1. If extension supplied, see if that file exists */
1099 if (extensionsupplied) {
1100 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1105 /* 2. Any .* matches? */
1108 WIN32_FIND_DATAW finddata;
1109 static const WCHAR allFiles[] = {'.','*','\0'};
1111 strcatW(thisDir,allFiles);
1112 h = FindFirstFileW(thisDir, &finddata);
1114 if (h != INVALID_HANDLE_VALUE) {
1116 WCHAR *thisExt = pathext;
1118 /* 3. Yes - Try each path ext */
1120 WCHAR *nextExt = strchrW(thisExt, ';');
1123 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1124 pos[(nextExt-thisExt)] = 0x00;
1125 thisExt = nextExt+1;
1127 strcpyW(pos, thisExt);
1131 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1139 /* Internal programs won't be picked up by this search, so even
1140 though not found, try one last createprocess and wait for it
1142 Note: Ideally we could tell between a console app (wait) and a
1143 windows app, but the API's for it fail in this case */
1144 if (!found && pathposn == NULL) {
1145 WINE_TRACE("ASSUMING INTERNAL\n");
1146 assumeInternal = TRUE;
1148 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1151 /* Once found, launch it */
1152 if (found || assumeInternal) {
1154 PROCESS_INFORMATION pe;
1158 WCHAR *ext = strrchrW( thisDir, '.' );
1159 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1160 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1164 /* Special case BAT and CMD */
1165 if (ext && !strcmpiW(ext, batExt)) {
1166 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1168 } else if (ext && !strcmpiW(ext, cmdExt)) {
1169 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1173 /* thisDir contains the file to be launched, but with what?
1174 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1175 hinst = FindExecutableW (thisDir, NULL, temp);
1176 if ((INT_PTR)hinst < 32)
1179 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1181 ZeroMemory (&st, sizeof(STARTUPINFOW));
1182 st.cb = sizeof(STARTUPINFOW);
1183 init_msvcrt_io_block(&st);
1185 /* Launch the process and if a CUI wait on it to complete
1186 Note: Launching internal wine processes cannot specify a full path to exe */
1187 status = CreateProcessW(assumeInternal?NULL : thisDir,
1188 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1189 if ((opt_c || opt_k) && !opt_s && !status
1190 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1191 /* strip first and last quote WCHARacters and try again */
1192 WCMD_opt_s_strip_quotes(command);
1194 WCMD_run_program(command, called);
1201 if (!assumeInternal && !console) errorlevel = 0;
1204 /* Always wait when called in a batch program context */
1205 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1206 GetExitCodeProcess (pe.hProcess, &errorlevel);
1207 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1209 CloseHandle(pe.hProcess);
1210 CloseHandle(pe.hThread);
1216 /* Not found anywhere - give up */
1217 SetLastError(ERROR_FILE_NOT_FOUND);
1218 WCMD_print_error ();
1220 /* If a command fails to launch, it sets errorlevel 9009 - which
1221 does not seem to have any associated constant definition */
1227 /*****************************************************************************
1228 * Process one command. If the command is EXIT this routine does not return.
1229 * We will recurse through here executing batch files.
1231 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1232 const WCHAR *forVariable, const WCHAR *forValue,
1235 WCHAR *cmd, *p, *redir;
1236 int status, i, prev_echo_mode;
1237 DWORD count, creationDisposition;
1240 SECURITY_ATTRIBUTES sa;
1241 WCHAR *new_cmd = NULL;
1242 WCHAR *new_redir = NULL;
1243 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1244 GetStdHandle (STD_OUTPUT_HANDLE),
1245 GetStdHandle (STD_ERROR_HANDLE)};
1246 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1251 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1252 wine_dbgstr_w(command), cmdList,
1253 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1255 /* If the next command is a pipe then we implement pipes by redirecting
1256 the output from this command to a temp file and input into the
1257 next command from that temp file.
1258 FIXME: Use of named pipes would make more sense here as currently this
1259 process has to finish before the next one can start but this requires
1260 a change to not wait for the first app to finish but rather the pipe */
1261 if (cmdList && (*cmdList)->nextcommand &&
1262 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1264 WCHAR temp_path[MAX_PATH];
1265 static const WCHAR cmdW[] = {'C','M','D','\0'};
1267 /* Remember piping is in action */
1268 WINE_TRACE("Output needs to be piped\n");
1271 /* Generate a unique temporary filename */
1272 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1273 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1274 WINE_TRACE("Using temporary file of %s\n",
1275 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1278 /* Move copy of the command onto the heap so it can be expanded */
1279 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1282 WINE_ERR("Could not allocate memory for new_cmd\n");
1285 strcpyW(new_cmd, command);
1287 /* Move copy of the redirects onto the heap so it can be expanded */
1288 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1291 WINE_ERR("Could not allocate memory for new_redir\n");
1292 HeapFree( GetProcessHeap(), 0, new_cmd );
1296 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1298 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1299 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1300 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1302 strcpyW(new_redir, redirects);
1305 /* Expand variables in command line mode only (batch mode will
1306 be expanded as the line is read in, except for 'for' loops) */
1307 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1308 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1312 * Changing default drive has to be handled as a special case.
1315 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1317 WCHAR dir[MAX_PATH];
1319 /* According to MSDN CreateProcess docs, special env vars record
1320 the current directory on each drive, in the form =C:
1321 so see if one specified, and if so go back to it */
1322 strcpyW(envvar, equalsW);
1323 strcatW(envvar, cmd);
1324 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1325 static const WCHAR fmt[] = {'%','s','\\','\0'};
1326 wsprintfW(cmd, fmt, cmd);
1327 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1329 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1330 status = SetCurrentDirectoryW(cmd);
1331 if (!status) WCMD_print_error ();
1332 HeapFree( GetProcessHeap(), 0, cmd );
1333 HeapFree( GetProcessHeap(), 0, new_redir );
1337 sa.nLength = sizeof(sa);
1338 sa.lpSecurityDescriptor = NULL;
1339 sa.bInheritHandle = TRUE;
1342 * Redirect stdin, stdout and/or stderr if required.
1345 /* STDIN could come from a preceding pipe, so delete on close if it does */
1346 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1347 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1348 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1349 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1350 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1351 if (h == INVALID_HANDLE_VALUE) {
1352 WCMD_print_error ();
1353 HeapFree( GetProcessHeap(), 0, cmd );
1354 HeapFree( GetProcessHeap(), 0, new_redir );
1357 SetStdHandle (STD_INPUT_HANDLE, h);
1359 /* No need to remember the temporary name any longer once opened */
1360 (*cmdList)->pipeFile[0] = 0x00;
1362 /* Otherwise STDIN could come from a '<' redirect */
1363 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1364 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1365 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 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);
1375 /* Scan the whole command looking for > and 2> */
1377 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1380 if (p > redir && (*(p-1)=='2'))
1387 creationDisposition = OPEN_ALWAYS;
1391 creationDisposition = CREATE_ALWAYS;
1394 /* Add support for 2>&1 */
1397 int idx = *(p+1) - '0';
1399 if (DuplicateHandle(GetCurrentProcess(),
1400 GetStdHandle(idx_stdhandles[idx]),
1401 GetCurrentProcess(),
1403 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1404 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1406 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1409 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1410 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1411 FILE_ATTRIBUTE_NORMAL, NULL);
1412 if (h == INVALID_HANDLE_VALUE) {
1413 WCMD_print_error ();
1414 HeapFree( GetProcessHeap(), 0, cmd );
1415 HeapFree( GetProcessHeap(), 0, new_redir );
1418 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1419 INVALID_SET_FILE_POINTER) {
1420 WCMD_print_error ();
1422 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1425 SetStdHandle (idx_stdhandles[handle], h);
1429 * Strip leading whitespaces, and a '@' if supplied
1431 whichcmd = WCMD_skip_leading_spaces(cmd);
1432 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1433 if (whichcmd[0] == '@') whichcmd++;
1436 * Check if the command entered is internal. If it is, pass the rest of the
1437 * line down to the command. If not try to run a program.
1441 while (IsCharAlphaNumericW(whichcmd[count])) {
1444 for (i=0; i<=WCMD_EXIT; i++) {
1445 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1446 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1448 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1449 WCMD_parse (p, quals, param1, param2);
1450 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1452 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1453 /* this is a help request for a builtin program */
1455 memcpy(p, whichcmd, count * sizeof(WCHAR));
1467 WCMD_setshow_default (p);
1470 WCMD_clear_screen ();
1479 WCMD_setshow_date ();
1489 WCMD_echo(&whichcmd[count]);
1492 WCMD_for (p, cmdList);
1495 WCMD_goto (cmdList);
1501 WCMD_if (p, cmdList);
1508 WCMD_create_dir (p);
1514 WCMD_setshow_path (p);
1520 WCMD_setshow_prompt ();
1530 WCMD_remove_dir (p);
1539 WCMD_setshow_env (p);
1545 WCMD_setshow_time ();
1548 if (strlenW(&whichcmd[count]) > 0)
1549 WCMD_title(&whichcmd[count+1]);
1555 WCMD_output(newline);
1571 WCMD_assoc(p, TRUE);
1577 WCMD_assoc(p, FALSE);
1586 WCMD_exit (cmdList);
1589 prev_echo_mode = echo_mode;
1590 WCMD_run_program (whichcmd, 0);
1591 echo_mode = prev_echo_mode;
1593 HeapFree( GetProcessHeap(), 0, cmd );
1594 HeapFree( GetProcessHeap(), 0, new_redir );
1596 /* Restore old handles */
1597 for (i=0; i<3; i++) {
1598 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1599 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1600 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1605 /*************************************************************************
1607 * Load a string from the resource file, handling any error
1608 * Returns string retrieved from resource file
1610 WCHAR *WCMD_LoadMessage(UINT id) {
1611 static WCHAR msg[2048];
1612 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1614 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1615 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1616 strcpyW(msg, failedMsg);
1621 /***************************************************************************
1624 * Dumps out the parsed command line to ensure syntax is correct
1626 static void WCMD_DumpCommands(CMD_LIST *commands) {
1627 CMD_LIST *thisCmd = commands;
1629 WINE_TRACE("Parsed line:\n");
1630 while (thisCmd != NULL) {
1631 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1634 thisCmd->bracketDepth,
1635 thisCmd->nextcommand,
1636 wine_dbgstr_w(thisCmd->command),
1637 wine_dbgstr_w(thisCmd->redirects));
1638 thisCmd = thisCmd->nextcommand;
1642 /***************************************************************************
1645 * Adds a command to the current command list
1647 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1648 WCHAR *redirs, int *redirLen,
1649 WCHAR **copyTo, int **copyToLen,
1650 CMD_DELIMITERS prevDelim, int curDepth,
1651 CMD_LIST **lastEntry, CMD_LIST **output) {
1653 CMD_LIST *thisEntry = NULL;
1655 /* Allocate storage for command */
1656 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1658 /* Copy in the command */
1660 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1661 (*commandLen+1) * sizeof(WCHAR));
1662 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1663 thisEntry->command[*commandLen] = 0x00;
1665 /* Copy in the redirects */
1666 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1667 (*redirLen+1) * sizeof(WCHAR));
1668 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1669 thisEntry->redirects[*redirLen] = 0x00;
1670 thisEntry->pipeFile[0] = 0x00;
1672 /* Reset the lengths */
1675 *copyToLen = commandLen;
1679 thisEntry->command = NULL;
1680 thisEntry->redirects = NULL;
1681 thisEntry->pipeFile[0] = 0x00;
1684 /* Fill in other fields */
1685 thisEntry->nextcommand = NULL;
1686 thisEntry->prevDelim = prevDelim;
1687 thisEntry->bracketDepth = curDepth;
1689 (*lastEntry)->nextcommand = thisEntry;
1691 *output = thisEntry;
1693 *lastEntry = thisEntry;
1697 /***************************************************************************
1700 * Checks if the quote pointed to is the end-quote.
1704 * 1) The current parameter ends at EOL or at the beginning
1705 * of a redirection or pipe and not in a quote section.
1707 * 2) If the next character is a space and not in a quote section.
1709 * Returns TRUE if this is an end quote, and FALSE if it is not.
1712 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1714 int quoteCount = quoteIndex;
1717 /* If we are not in a quoted section, then we are not an end-quote */
1723 /* Check how many quotes are left for this parameter */
1724 for(i=0;quote[i];i++)
1731 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1732 else if(((quoteCount % 2) == 0)
1733 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1739 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1741 if(quoteIndex >= (quoteCount / 2))
1750 /***************************************************************************
1751 * WCMD_ReadAndParseLine
1753 * Either uses supplied input or
1754 * Reads a file from the handle, and then...
1755 * Parse the text buffer, splitting into separate commands
1756 * - unquoted && strings split 2 commands but the 2nd is flagged as
1758 * - ( as the first character just ups the bracket depth
1759 * - unquoted ) when bracket depth > 0 terminates a bracket and
1760 * adds a CMD_LIST structure with null command
1761 * - Anything else gets put into the command string (including
1764 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1768 WCHAR curString[MAXSTRING];
1769 int curStringLen = 0;
1770 WCHAR curRedirs[MAXSTRING];
1771 int curRedirsLen = 0;
1775 CMD_LIST *lastEntry = NULL;
1776 CMD_DELIMITERS prevDelim = CMD_NONE;
1777 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1778 static const WCHAR remCmd[] = {'r','e','m'};
1779 static const WCHAR forCmd[] = {'f','o','r'};
1780 static const WCHAR ifCmd[] = {'i','f'};
1781 static const WCHAR ifElse[] = {'e','l','s','e'};
1787 BOOL onlyWhiteSpace = FALSE;
1788 BOOL lastWasWhiteSpace = FALSE;
1789 BOOL lastWasDo = FALSE;
1790 BOOL lastWasIn = FALSE;
1791 BOOL lastWasElse = FALSE;
1792 BOOL lastWasRedirect = TRUE;
1794 /* Allocate working space for a command read from keyboard, file etc */
1796 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1799 WINE_ERR("Could not allocate memory for extraSpace\n");
1803 /* If initial command read in, use that, otherwise get input from handle */
1804 if (optionalcmd != NULL) {
1805 strcpyW(extraSpace, optionalcmd);
1806 } else if (readFrom == INVALID_HANDLE_VALUE) {
1807 WINE_FIXME("No command nor handle supplied\n");
1809 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1811 curPos = extraSpace;
1813 /* Handle truncated input - issue warning */
1814 if (strlenW(extraSpace) == MAXSTRING -1) {
1815 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1816 WCMD_output_asis(extraSpace);
1817 WCMD_output_asis(newline);
1820 /* Replace env vars if in a batch context */
1821 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1822 /* Show prompt before batch line IF echo is on and in batch program */
1823 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1824 static const WCHAR spc[]={' ','\0'};
1825 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1826 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1827 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1828 DWORD curr_size = strlenW(extraSpace);
1829 DWORD min_len = (curr_size < len ? curr_size : len);
1831 WCMD_output_asis(extraSpace);
1832 /* I don't know why Windows puts a space here but it does */
1833 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1834 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1835 extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1836 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1837 extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1839 WCMD_output_asis(spc);
1841 WCMD_output_asis(newline);
1844 /* Start with an empty string, copying to the command string */
1847 curCopyTo = curString;
1848 curLen = &curStringLen;
1849 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1851 /* Parse every character on the line being processed */
1852 while (*curPos != 0x00) {
1857 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1858 lastWasWhiteSpace, onlyWhiteSpace);
1861 /* Certain commands need special handling */
1862 if (curStringLen == 0 && curCopyTo == curString) {
1863 static const WCHAR forDO[] = {'d','o'};
1865 /* If command starts with 'rem ', ignore any &&, ( etc. */
1866 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1869 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1872 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1873 is only true in the command portion of the IF statement, but this
1874 should suffice for now
1875 FIXME: Silly syntax like "if 1(==1( (
1877 )" will be parsed wrong */
1878 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1881 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1882 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1885 onlyWhiteSpace = TRUE;
1886 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1887 (*curLen)+=keyw_len;
1891 /* In a for loop, the DO command will follow a close bracket followed by
1892 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1893 is then 0, and all whitespace is skipped */
1895 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1896 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1897 WINE_TRACE("Found 'DO '\n");
1899 onlyWhiteSpace = TRUE;
1900 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1901 (*curLen)+=keyw_len;
1905 } else if (curCopyTo == curString) {
1907 /* Special handling for the 'FOR' command */
1908 if (inFor && lastWasWhiteSpace) {
1909 static const WCHAR forIN[] = {'i','n'};
1911 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1913 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1914 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1915 WINE_TRACE("Found 'IN '\n");
1917 onlyWhiteSpace = TRUE;
1918 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1919 (*curLen)+=keyw_len;
1926 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1927 so just use the default processing ie skip character specific
1929 if (!inRem) thisChar = *curPos;
1930 else thisChar = 'X'; /* Character with no special processing */
1932 lastWasWhiteSpace = FALSE; /* Will be reset below */
1936 case '=': /* drop through - ignore token delimiters at the start of a command */
1937 case ',': /* drop through - ignore token delimiters at the start of a command */
1938 case '\t':/* drop through - ignore token delimiters at the start of a command */
1940 /* If a redirect in place, it ends here */
1941 if (!inQuotes && !lastWasRedirect) {
1943 /* If finishing off a redirect, add a whitespace delimiter */
1944 if (curCopyTo == curRedirs) {
1945 curCopyTo[(*curLen)++] = ' ';
1947 curCopyTo = curString;
1948 curLen = &curStringLen;
1951 curCopyTo[(*curLen)++] = *curPos;
1954 /* Remember just processed whitespace */
1955 lastWasWhiteSpace = TRUE;
1959 case '>': /* drop through - handle redirect chars the same */
1961 /* Make a redirect start here */
1963 curCopyTo = curRedirs;
1964 curLen = &curRedirsLen;
1965 lastWasRedirect = TRUE;
1968 /* See if 1>, 2> etc, in which case we have some patching up
1969 to do (provided there's a preceding whitespace, and enough
1970 chars read so far) */
1971 if (curStringLen > 2
1972 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1973 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1975 curString[curStringLen] = 0x00;
1976 curCopyTo[(*curLen)++] = *(curPos-1);
1979 curCopyTo[(*curLen)++] = *curPos;
1981 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1982 do not process that ampersand as an AND operator */
1983 if (thisChar == '>' && *(curPos+1) == '&') {
1984 curCopyTo[(*curLen)++] = *(curPos+1);
1989 case '|': /* Pipe character only if not || */
1991 lastWasRedirect = FALSE;
1993 /* Add an entry to the command list */
1994 if (curStringLen > 0) {
1996 /* Add the current command */
1997 WCMD_addCommand(curString, &curStringLen,
1998 curRedirs, &curRedirsLen,
1999 &curCopyTo, &curLen,
2000 prevDelim, curDepth,
2001 &lastEntry, output);
2005 if (*(curPos+1) == '|') {
2006 curPos++; /* Skip other | */
2007 prevDelim = CMD_ONFAILURE;
2009 prevDelim = CMD_PIPE;
2012 curCopyTo[(*curLen)++] = *curPos;
2016 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2019 inQuotes++; /* Quotes within quotes are fun! */
2021 curCopyTo[(*curLen)++] = *curPos;
2022 lastWasRedirect = FALSE;
2025 case '(': /* If a '(' is the first non whitespace in a command portion
2026 ie start of line or just after &&, then we read until an
2027 unquoted ) is found */
2028 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2029 ", for(%d, In:%d, Do:%d)"
2030 ", if(%d, else:%d, lwe:%d)\n",
2033 inFor, lastWasIn, lastWasDo,
2034 inIf, inElse, lastWasElse);
2035 lastWasRedirect = FALSE;
2037 /* Ignore open brackets inside the for set */
2038 if (*curLen == 0 && !inIn) {
2041 /* If in quotes, ignore brackets */
2042 } else if (inQuotes) {
2043 curCopyTo[(*curLen)++] = *curPos;
2045 /* In a FOR loop, an unquoted '(' may occur straight after
2047 In an IF statement just handle it regardless as we don't
2049 In an ELSE statement, only allow it straight away after
2050 the ELSE and whitespace
2053 (inElse && lastWasElse && onlyWhiteSpace) ||
2054 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2056 /* If entering into an 'IN', set inIn */
2057 if (inFor && lastWasIn && onlyWhiteSpace) {
2058 WINE_TRACE("Inside an IN\n");
2062 /* Add the current command */
2063 WCMD_addCommand(curString, &curStringLen,
2064 curRedirs, &curRedirsLen,
2065 &curCopyTo, &curLen,
2066 prevDelim, curDepth,
2067 &lastEntry, output);
2071 curCopyTo[(*curLen)++] = *curPos;
2075 case '&': if (!inQuotes) {
2076 lastWasRedirect = FALSE;
2078 /* Add an entry to the command list */
2079 if (curStringLen > 0) {
2081 /* Add the current command */
2082 WCMD_addCommand(curString, &curStringLen,
2083 curRedirs, &curRedirsLen,
2084 &curCopyTo, &curLen,
2085 prevDelim, curDepth,
2086 &lastEntry, output);
2090 if (*(curPos+1) == '&') {
2091 curPos++; /* Skip other & */
2092 prevDelim = CMD_ONSUCCESS;
2094 prevDelim = CMD_NONE;
2097 curCopyTo[(*curLen)++] = *curPos;
2101 case ')': if (!inQuotes && curDepth > 0) {
2102 lastWasRedirect = FALSE;
2104 /* Add the current command if there is one */
2107 /* Add the current command */
2108 WCMD_addCommand(curString, &curStringLen,
2109 curRedirs, &curRedirsLen,
2110 &curCopyTo, &curLen,
2111 prevDelim, curDepth,
2112 &lastEntry, output);
2115 /* Add an empty entry to the command list */
2116 prevDelim = CMD_NONE;
2117 WCMD_addCommand(NULL, &curStringLen,
2118 curRedirs, &curRedirsLen,
2119 &curCopyTo, &curLen,
2120 prevDelim, curDepth,
2121 &lastEntry, output);
2124 /* Leave inIn if necessary */
2125 if (inIn) inIn = FALSE;
2127 curCopyTo[(*curLen)++] = *curPos;
2131 lastWasRedirect = FALSE;
2132 curCopyTo[(*curLen)++] = *curPos;
2137 /* At various times we need to know if we have only skipped whitespace,
2138 so reset this variable and then it will remain true until a non
2139 whitespace is found */
2140 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2141 onlyWhiteSpace = FALSE;
2143 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2144 if (!lastWasWhiteSpace) {
2145 lastWasIn = lastWasDo = FALSE;
2148 /* If we have reached the end, add this command into the list */
2149 if (*curPos == 0x00 && *curLen > 0) {
2151 /* Add an entry to the command list */
2152 WCMD_addCommand(curString, &curStringLen,
2153 curRedirs, &curRedirsLen,
2154 &curCopyTo, &curLen,
2155 prevDelim, curDepth,
2156 &lastEntry, output);
2159 /* If we have reached the end of the string, see if bracketing outstanding */
2160 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2162 prevDelim = CMD_NONE;
2164 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2166 /* Read more, skipping any blank lines */
2167 while (*extraSpace == 0x00) {
2168 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2169 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2171 curPos = extraSpace;
2172 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2173 /* Continue to echo commands IF echo is on and in batch program */
2174 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2175 WCMD_output_asis(extraSpace);
2176 WCMD_output_asis(newline);
2181 /* Dump out the parsed output */
2182 WCMD_DumpCommands(*output);
2187 /***************************************************************************
2188 * WCMD_process_commands
2190 * Process all the commands read in so far
2192 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2193 const WCHAR *var, const WCHAR *val) {
2197 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2199 /* Loop through the commands, processing them one by one */
2202 CMD_LIST *origCmd = thisCmd;
2204 /* If processing one bracket only, and we find the end bracket
2205 entry (or less), return */
2206 if (oneBracket && !thisCmd->command &&
2207 bdepth <= thisCmd->bracketDepth) {
2208 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2209 thisCmd, thisCmd->nextcommand);
2210 return thisCmd->nextcommand;
2213 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2214 about them and it will be handled in there)
2215 Also, skip over any batch labels (eg. :fred) */
2216 if (thisCmd->command && thisCmd->command[0] != ':') {
2217 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2218 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2221 /* Step on unless the command itself already stepped on */
2222 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2227 /***************************************************************************
2228 * WCMD_free_commands
2230 * Frees the storage held for a parsed command line
2231 * - This is not done in the process_commands, as eventually the current
2232 * pointer will be modified within the commands, and hence a single free
2233 * routine is simpler
2235 void WCMD_free_commands(CMD_LIST *cmds) {
2237 /* Loop through the commands, freeing them one by one */
2239 CMD_LIST *thisCmd = cmds;
2240 cmds = cmds->nextcommand;
2241 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2242 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2243 HeapFree(GetProcessHeap(), 0, thisCmd);
2248 /*****************************************************************************
2249 * Main entry point. This is a console application so we have a main() not a
2253 int wmain (int argc, WCHAR *argvW[])
2261 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2262 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2263 char ansiVersion[100];
2264 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2268 /* Pre initialize some messages */
2269 strcpy(ansiVersion, PACKAGE_VERSION);
2270 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2271 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2272 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2275 opt_c=opt_k=opt_q=opt_s=0;
2279 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2280 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2287 if (tolowerW(c)=='c') {
2289 } else if (tolowerW(c)=='q') {
2291 } else if (tolowerW(c)=='k') {
2293 } else if (tolowerW(c)=='s') {
2295 } else if (tolowerW(c)=='a') {
2297 } else if (tolowerW(c)=='u') {
2299 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2300 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2301 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2302 /* Ignored for compatibility with Windows */
2305 if ((*argvW)[2]==0) {
2309 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2314 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2319 static const WCHAR eoff[] = {'O','F','F','\0'};
2323 if (opt_c || opt_k) {
2329 /* opt_s left unflagged if the command starts with and contains exactly
2330 * one quoted string (exactly two quote characters). The quoted string
2331 * must be an executable name that has whitespace and must not have the
2332 * following characters: &<>()@^| */
2334 /* Build the command to execute */
2338 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2340 int has_space,bcount;
2346 if( !*a ) has_space=1;
2351 if (*a==' ' || *a=='\t') {
2353 } else if (*a=='"') {
2354 /* doubling of '\' preceding a '"',
2355 * plus escaping of said '"'
2364 len+=(a-*arg) + 1; /* for the separating space */
2367 len+=2; /* for the quotes */
2375 /* check argvW[0] for a space and invalid characters */
2380 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2381 || *p=='@' || *p=='^' || *p=='|') {
2391 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2397 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2399 int has_space,has_quote;
2402 /* Check for quotes and spaces in this argument */
2403 has_space=has_quote=0;
2405 if( !*a ) has_space=1;
2407 if (*a==' ' || *a=='\t') {
2411 } else if (*a=='"') {
2419 /* Now transfer it to the command line */
2436 /* Double all the '\\' preceding this '"', plus one */
2437 for (i=0;i<=bcount;i++)
2456 p--; /* remove last space */
2459 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2461 /* strip first and last quote characters if opt_s; check for invalid
2462 * executable is done later */
2463 if (opt_s && *cmd=='\"')
2464 WCMD_opt_s_strip_quotes(cmd);
2468 /* If we do a "cmd /c command", we don't want to allocate a new
2469 * console since the command returns immediately. Rather, we use
2470 * the currently allocated input and output handles. This allows
2471 * us to pipe to and read from the command interpreter.
2474 /* Parse the command string, without reading any more input */
2475 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2476 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2477 WCMD_free_commands(toExecute);
2480 HeapFree(GetProcessHeap(), 0, cmd);
2484 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2485 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2486 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2488 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2490 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2491 defaultColor = opt_t & 0xFF;
2496 /* Check HKCU\Software\Microsoft\Command Processor
2497 Then HKLM\Software\Microsoft\Command Processor
2498 for defaultcolour value
2499 Note Can be supplied as DWORD or REG_SZ
2500 Note2 When supplied as REG_SZ it's in decimal!!! */
2503 DWORD value=0, size=4;
2504 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2505 'M','i','c','r','o','s','o','f','t','\\',
2506 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2507 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2509 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2510 0, KEY_READ, &key) == ERROR_SUCCESS) {
2513 /* See if DWORD or REG_SZ */
2514 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2515 NULL, NULL) == ERROR_SUCCESS) {
2516 if (type == REG_DWORD) {
2517 size = sizeof(DWORD);
2518 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2519 (LPBYTE)&value, &size);
2520 } else if (type == REG_SZ) {
2521 size = sizeof(strvalue)/sizeof(WCHAR);
2522 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2523 (LPBYTE)strvalue, &size);
2524 value = strtoulW(strvalue, NULL, 10);
2530 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2531 0, KEY_READ, &key) == ERROR_SUCCESS) {
2534 /* See if DWORD or REG_SZ */
2535 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2536 NULL, NULL) == ERROR_SUCCESS) {
2537 if (type == REG_DWORD) {
2538 size = sizeof(DWORD);
2539 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2540 (LPBYTE)&value, &size);
2541 } else if (type == REG_SZ) {
2542 size = sizeof(strvalue)/sizeof(WCHAR);
2543 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2544 (LPBYTE)strvalue, &size);
2545 value = strtoulW(strvalue, NULL, 10);
2551 /* If one found, set the screen to that colour */
2552 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2553 defaultColor = value & 0xFF;
2560 /* Save cwd into appropriate env var */
2561 GetCurrentDirectoryW(1024, string);
2562 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2563 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2564 wsprintfW(envvar, fmt, string[0]);
2565 SetEnvironmentVariableW(envvar, string);
2566 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2570 /* Parse the command string, without reading any more input */
2571 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2572 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2573 WCMD_free_commands(toExecute);
2575 HeapFree(GetProcessHeap(), 0, cmd);
2579 * Loop forever getting commands and executing them.
2582 SetEnvironmentVariableW(promptW, defaultpromptW);
2586 /* Read until EOF (which for std input is never, but if redirect
2587 in place, may occur */
2588 if (echo_mode) WCMD_show_prompt();
2589 if (WCMD_ReadAndParseLine(NULL, &toExecute,
2590 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2592 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2593 WCMD_free_commands(toExecute);