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 extern const WCHAR inbuilt[][10];
35 extern struct env_stack *pushd_directories;
37 BATCH_CONTEXT *context = NULL;
39 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
42 BOOL echo_mode = TRUE;
44 WCHAR anykey[100], version_string[100];
45 const WCHAR newline[] = {'\r','\n','\0'};
46 const WCHAR space[] = {' ','\0'};
48 static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
50 /* Variables pertaining to paging */
51 static BOOL paged_mode;
52 static const WCHAR *pagedMessage = NULL;
53 static int line_count;
54 static int max_height;
58 #define MAX_WRITECONSOLE_SIZE 65535
61 * Returns a buffer for reading from/writing to file
64 static char *get_file_buffer(void)
66 static char *output_bufA = NULL;
68 output_bufA = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
70 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
75 /*******************************************************************
76 * WCMD_output_asis_len - send output to current standard output
78 * Output a formatted unicode string. Ideally this will go to the console
79 * and hence required WriteConsoleW to output it, however if file i/o is
80 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
82 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
87 /* If nothing to write, return (MORE does this sometimes) */
90 /* Try to write as unicode assuming it is to a console */
91 res = WriteConsoleW(device, message, len, &nOut, NULL);
93 /* If writing to console fails, assume its file
94 i/o so convert to OEM codepage and output */
96 BOOL usedDefaultChar = FALSE;
100 if (!unicodeOutput) {
102 if (!(buffer = get_file_buffer()))
105 /* Convert to OEM, then output */
106 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
107 len, buffer, MAX_WRITECONSOLE_SIZE,
108 "?", &usedDefaultChar);
109 WriteFile(device, buffer, convertedChars,
112 WriteFile(device, message, len*sizeof(WCHAR),
119 /*******************************************************************
120 * WCMD_output - send output to current standard output device.
124 void WCMD_output (const WCHAR *format, ...) {
131 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
132 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
133 WINE_ERR("Output truncated\n" );
134 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
138 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
141 /*******************************************************************
142 * WCMD_output_stderr - send output to current standard error device.
146 void WCMD_output_stderr (const WCHAR *format, ...) {
153 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
154 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
155 WINE_ERR("Output truncated\n" );
156 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
160 WCMD_output_asis_len(string, ret, GetStdHandle(STD_ERROR_HANDLE));
163 void WCMD_enter_paged_mode(const WCHAR *msg)
165 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
167 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
168 max_height = consoleInfo.dwSize.Y;
169 max_width = consoleInfo.dwSize.X;
177 pagedMessage = (msg==NULL)? anykey : msg;
180 void WCMD_leave_paged_mode(void)
186 /***************************************************************************
189 * Read characters in from a console/file, returning result in Unicode
191 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
196 if (WCMD_is_console_handle(hIn))
197 /* Try to read from console as Unicode */
198 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
200 /* We assume it's a file handle and read then convert from assumed OEM codepage */
201 if (!(buffer = get_file_buffer()))
204 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
207 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
212 /*******************************************************************
213 * WCMD_output_asis_handle
215 * Send output to specified handle without formatting e.g. when message contains '%'
217 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
221 HANDLE handle = GetStdHandle(std_handle);
226 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
230 if (*ptr == '\n') ptr++;
231 WCMD_output_asis_len(message, ptr - message, handle);
233 if (++line_count >= max_height - 1) {
235 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
236 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
238 } while (((message = ptr) != NULL) && (*ptr));
240 WCMD_output_asis_len(message, lstrlenW(message), handle);
244 /*******************************************************************
247 * Send output to current standard output device, without formatting
248 * e.g. when message contains '%'
250 void WCMD_output_asis (const WCHAR *message) {
251 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
254 /*******************************************************************
255 * WCMD_output_asis_stderr
257 * Send output to current standard error device, without formatting
258 * e.g. when message contains '%'
260 void WCMD_output_asis_stderr (const WCHAR *message) {
261 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
264 /****************************************************************************
267 * Print the message for GetLastError
270 void WCMD_print_error (void) {
275 error_code = GetLastError ();
276 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
277 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
279 WINE_FIXME ("Cannot display message for error %d, status %d\n",
280 error_code, GetLastError());
284 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
285 GetStdHandle(STD_ERROR_HANDLE));
286 LocalFree (lpMsgBuf);
287 WCMD_output_asis_len (newline, lstrlenW(newline),
288 GetStdHandle(STD_ERROR_HANDLE));
292 /******************************************************************************
295 * Display the prompt on STDout
299 static void WCMD_show_prompt (void) {
302 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
305 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
307 len = GetEnvironmentVariableW(envPrompt, prompt_string,
308 sizeof(prompt_string)/sizeof(WCHAR));
309 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
310 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
311 strcpyW (prompt_string, dfltPrompt);
325 switch (toupper(*p)) {
339 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
358 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
364 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
377 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
381 strcatW (q, version_string);
388 if (pushd_directories) {
389 memset(q, '+', pushd_directories->u.stackdepth);
390 q = q + pushd_directories->u.stackdepth;
398 WCMD_output_asis (out_string);
402 /*************************************************************************
404 * A wide version of strdup as its missing from unicode.h
406 WCHAR *WCMD_strdupW(const WCHAR *input) {
407 int len=strlenW(input)+1;
408 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
409 memcpy(result, input, len * sizeof(WCHAR));
413 /*************************************************************************
415 * Replaces a portion of a Unicode string with the specified string.
416 * It's up to the caller to ensure there is enough space in the
417 * destination buffer.
419 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
422 len=insert ? lstrlenW(insert) : 0;
423 if (start+len != next)
424 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
426 memcpy(start, insert, len * sizeof(*insert));
429 /***************************************************************************
430 * WCMD_skip_leading_spaces
432 * Return a pointer to the first non-whitespace character of string.
433 * Does not modify the input string.
435 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
440 while (*ptr == ' ' || *ptr == '\t') ptr++;
444 /***************************************************************************
445 * WCMD_keyword_ws_found
447 * Checks if the string located at ptr matches a keyword (of length len)
448 * followed by a whitespace character (space or tab)
450 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
451 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
452 ptr, len, keyword, len) == CSTR_EQUAL)
453 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
456 /*************************************************************************
459 * Remove first and last quote WCHARacters, preserving all other text
461 void WCMD_strip_quotes(WCHAR *cmd) {
462 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
463 while((*dest=*src) != '\0') {
470 while ((*dest++=*lastq++) != 0)
476 /*************************************************************************
477 * WCMD_is_magic_envvar
478 * Return TRUE if s is '%'magicvar'%'
479 * and is not masked by a real environment variable.
482 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
487 return FALSE; /* Didn't begin with % */
489 if (len < 2 || s[len-1] != '%')
490 return FALSE; /* Didn't end with another % */
492 if (CompareStringW(LOCALE_USER_DEFAULT,
493 NORM_IGNORECASE | SORT_STRINGSORT,
494 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
495 /* Name doesn't match. */
499 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
500 /* Masked by real environment variable. */
507 /*************************************************************************
510 * Expands environment variables, allowing for WCHARacter substitution
512 static WCHAR *WCMD_expand_envvar(WCHAR *start,
513 const WCHAR *forVar, const WCHAR *forVal) {
514 WCHAR *endOfVar = NULL, *s;
515 WCHAR *colonpos = NULL;
516 WCHAR thisVar[MAXSTRING];
517 WCHAR thisVarContents[MAXSTRING];
518 WCHAR savedchar = 0x00;
521 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
522 static const WCHAR Date[] = {'D','A','T','E','\0'};
523 static const WCHAR Time[] = {'T','I','M','E','\0'};
524 static const WCHAR Cd[] = {'C','D','\0'};
525 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
526 static const WCHAR Delims[] = {'%',' ',':','\0'};
528 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
529 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
531 /* Find the end of the environment variable, and extract name */
532 endOfVar = strpbrkW(start+1, Delims);
534 if (endOfVar == NULL || *endOfVar==' ') {
536 /* In batch program, missing terminator for % and no following
537 ':' just removes the '%' */
539 WCMD_strsubstW(start, start + 1, NULL, 0);
543 /* In command processing, just ignore it - allows command line
544 syntax like: for %i in (a.a) do echo %i */
549 /* If ':' found, process remaining up until '%' (or stop at ':' if
551 if (*endOfVar==':') {
552 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
553 if (endOfVar2 != NULL) endOfVar = endOfVar2;
556 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
557 thisVar[(endOfVar - start)+1] = 0x00;
558 colonpos = strchrW(thisVar+1, ':');
560 /* If there's complex substitution, just need %var% for now
561 to get the expanded data to play with */
564 savedchar = *(colonpos+1);
565 *(colonpos+1) = 0x00;
568 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
570 /* Expand to contents, if unchanged, return */
571 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
572 /* override if existing env var called that name */
573 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
574 static const WCHAR fmt[] = {'%','d','\0'};
575 wsprintfW(thisVarContents, fmt, errorlevel);
576 len = strlenW(thisVarContents);
577 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
578 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
579 NULL, thisVarContents, MAXSTRING);
580 len = strlenW(thisVarContents);
581 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
582 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
583 NULL, thisVarContents, MAXSTRING);
584 len = strlenW(thisVarContents);
585 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
586 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
587 len = strlenW(thisVarContents);
588 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
589 static const WCHAR fmt[] = {'%','d','\0'};
590 wsprintfW(thisVarContents, fmt, rand() % 32768);
591 len = strlenW(thisVarContents);
593 /* Look for a matching 'for' variable */
595 (CompareStringW(LOCALE_USER_DEFAULT,
598 (colonpos - thisVar) - 1,
599 forVar, -1) == CSTR_EQUAL)) {
600 strcpyW(thisVarContents, forVal);
601 len = strlenW(thisVarContents);
605 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
606 sizeof(thisVarContents)/sizeof(WCHAR));
612 /* In a batch program, unknown env vars are replaced with nothing,
613 note syntax %garbage:1,3% results in anything after the ':'
615 From the command line, you just get back what you entered */
616 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
618 /* Restore the complex part after the compare */
621 *(colonpos+1) = savedchar;
624 /* Command line - just ignore this */
625 if (context == NULL) return endOfVar+1;
628 /* Batch - replace unknown env var with nothing */
629 if (colonpos == NULL) {
630 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
632 len = strlenW(thisVar);
633 thisVar[len-1] = 0x00;
634 /* If %:...% supplied, : is retained */
635 if (colonpos == thisVar+1) {
636 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
638 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
645 /* See if we need to do complex substitution (any ':'s), if not
646 then our work here is done */
647 if (colonpos == NULL) {
648 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
652 /* Restore complex bit */
654 *(colonpos+1) = savedchar;
657 Handle complex substitutions:
658 xxx=yyy (replace xxx with yyy)
659 *xxx=yyy (replace up to and including xxx with yyy)
660 ~x (from x WCHARs in)
661 ~-x (from x WCHARs from the end)
662 ~x,y (from x WCHARs in for y WCHARacters)
663 ~x,-y (from x WCHARs in until y WCHARacters from the end)
666 /* ~ is substring manipulation */
667 if (savedchar == '~') {
669 int substrposition, substrlength = 0;
670 WCHAR *commapos = strchrW(colonpos+2, ',');
673 substrposition = atolW(colonpos+2);
674 if (commapos) substrlength = atolW(commapos+1);
677 if (substrposition >= 0) {
678 startCopy = &thisVarContents[min(substrposition, len)];
680 startCopy = &thisVarContents[max(0, len+substrposition-1)];
683 if (commapos == NULL) {
685 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
686 } else if (substrlength < 0) {
688 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
689 if (copybytes > len) copybytes = len;
690 else if (copybytes < 0) copybytes = 0;
691 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
693 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
698 /* search and replace manipulation */
700 WCHAR *equalspos = strstrW(colonpos, equalW);
701 WCHAR *replacewith = equalspos+1;
706 if (equalspos == NULL) return start+1;
707 s = WCMD_strdupW(endOfVar + 1);
709 /* Null terminate both strings */
710 thisVar[strlenW(thisVar)-1] = 0x00;
713 /* Since we need to be case insensitive, copy the 2 buffers */
714 searchIn = WCMD_strdupW(thisVarContents);
715 CharUpperBuffW(searchIn, strlenW(thisVarContents));
716 searchFor = WCMD_strdupW(colonpos+1);
717 CharUpperBuffW(searchFor, strlenW(colonpos+1));
719 /* Handle wildcard case */
720 if (*(colonpos+1) == '*') {
721 /* Search for string to replace */
722 found = strstrW(searchIn, searchFor+1);
726 strcpyW(start, replacewith);
727 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
731 strcpyW(start, thisVarContents);
736 /* Loop replacing all instances */
737 WCHAR *lastFound = searchIn;
738 WCHAR *outputposn = start;
741 while ((found = strstrW(lastFound, searchFor))) {
742 lstrcpynW(outputposn,
743 thisVarContents + (lastFound-searchIn),
744 (found - lastFound)+1);
745 outputposn = outputposn + (found - lastFound);
746 strcatW(outputposn, replacewith);
747 outputposn = outputposn + strlenW(replacewith);
748 lastFound = found + strlenW(searchFor);
751 thisVarContents + (lastFound-searchIn));
752 strcatW(outputposn, s);
754 HeapFree(GetProcessHeap(), 0, s);
755 HeapFree(GetProcessHeap(), 0, searchIn);
756 HeapFree(GetProcessHeap(), 0, searchFor);
762 /*****************************************************************************
763 * Expand the command. Native expands lines from batch programs as they are
764 * read in and not again, except for 'for' variable substitution.
765 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
767 static void handleExpansion(WCHAR *cmd, BOOL justFors,
768 const WCHAR *forVariable, const WCHAR *forValue) {
770 /* For commands in a context (batch program): */
771 /* Expand environment variables in a batch file %{0-9} first */
772 /* including support for any ~ modifiers */
774 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
775 /* names allowing environment variable overrides */
776 /* NOTE: To support the %PATH:xxx% syntax, also perform */
777 /* manual expansion of environment variables here */
783 while ((p = strchrW(p, '%'))) {
785 WINE_TRACE("Translate command:%s %d (at: %s)\n",
786 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
789 /* Don't touch %% unless its in Batch */
790 if (!justFors && *(p+1) == '%') {
792 WCMD_strsubstW(p, p+1, NULL, 0);
796 /* Replace %~ modifications if in batch program */
797 } else if (*(p+1) == '~') {
798 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
801 /* Replace use of %0...%9 if in batch program*/
802 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
803 t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
804 WCMD_strsubstW(p, p+2, t, -1);
806 /* Replace use of %* if in batch program*/
807 } else if (!justFors && context && *(p+1)=='*') {
808 WCHAR *startOfParms = NULL;
809 t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
810 if (startOfParms != NULL)
811 WCMD_strsubstW(p, p+2, startOfParms, -1);
813 WCMD_strsubstW(p, p+2, NULL, 0);
815 } else if (forVariable &&
816 (CompareStringW(LOCALE_USER_DEFAULT,
819 strlenW(forVariable),
820 forVariable, -1) == CSTR_EQUAL)) {
821 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
823 } else if (!justFors) {
824 p = WCMD_expand_envvar(p, forVariable, forValue);
826 /* In a FOR loop, see if this is the variable to replace */
827 } else { /* Ignore %'s on second pass of batch program */
836 /*******************************************************************
837 * WCMD_parse - parse a command into parameters and qualifiers.
839 * On exit, all qualifiers are concatenated into q, the first string
840 * not beginning with "/" is in p1 and the
841 * second in p2. Any subsequent non-qualifier strings are lost.
842 * Parameters in quotes are handled.
844 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
848 *q = *p1 = *p2 = '\0';
853 while ((*s != '\0') && (*s != ' ') && *s != '/') {
854 *q++ = toupperW (*s++);
864 while ((*s != '\0') && (*s != '"')) {
865 if (p == 0) *p1++ = *s++;
866 else if (p == 1) *p2++ = *s++;
869 if (p == 0) *p1 = '\0';
870 if (p == 1) *p2 = '\0';
877 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
878 && (*s != '=') && (*s != ',') ) {
879 if (p == 0) *p1++ = *s++;
880 else if (p == 1) *p2++ = *s++;
883 /* Skip concurrent parms */
884 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
886 if (p == 0) *p1 = '\0';
887 if (p == 1) *p2 = '\0';
893 static void init_msvcrt_io_block(STARTUPINFOW* st)
896 /* fetch the parent MSVCRT info block if any, so that the child can use the
897 * same handles as its grand-father
899 st_p.cb = sizeof(STARTUPINFOW);
900 GetStartupInfoW(&st_p);
901 st->cbReserved2 = st_p.cbReserved2;
902 st->lpReserved2 = st_p.lpReserved2;
903 if (st_p.cbReserved2 && st_p.lpReserved2)
905 /* Override the entries for fd 0,1,2 if we happened
906 * to change those std handles (this depends on the way cmd sets
907 * its new input & output handles)
909 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
910 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
913 unsigned num = *(unsigned*)st_p.lpReserved2;
914 char* flags = (char*)(ptr + sizeof(unsigned));
915 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
917 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
918 st->cbReserved2 = sz;
919 st->lpReserved2 = ptr;
921 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
922 if (num <= 0 || (flags[0] & WX_OPEN))
924 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
927 if (num <= 1 || (flags[1] & WX_OPEN))
929 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
932 if (num <= 2 || (flags[2] & WX_OPEN))
934 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
942 /******************************************************************************
945 * Execute a command line as an external program. Must allow recursion.
948 * Manual testing under windows shows PATHEXT plays a key part in this,
949 * and the search algorithm and precedence appears to be as follows.
952 * If directory supplied on command, just use that directory
953 * If extension supplied on command, look for that explicit name first
954 * Otherwise, search in each directory on the path
956 * If extension supplied on command, look for that explicit name first
957 * Then look for supplied name .* (even if extension supplied, so
958 * 'garbage.exe' will match 'garbage.exe.cmd')
959 * If any found, cycle through PATHEXT looking for name.exe one by one
961 * Once a match has been found, it is launched - Code currently uses
962 * findexecutable to achieve this which is left untouched.
965 void WCMD_run_program (WCHAR *command, int called) {
967 WCHAR temp[MAX_PATH];
968 WCHAR pathtosearch[MAXSTRING];
970 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
971 MAX_PATH, including null character */
973 WCHAR pathext[MAXSTRING];
974 BOOL extensionsupplied = FALSE;
975 BOOL launched = FALSE;
977 BOOL assumeInternal = FALSE;
979 static const WCHAR envPath[] = {'P','A','T','H','\0'};
980 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
981 static const WCHAR delims[] = {'/','\\',':','\0'};
983 /* Quick way to get the filename
984 * (but handle leading / as part of program name, not qualifier)
986 for (len = 0; command[len] == '/'; len++) param1[len] = '/';
987 WCMD_parse (command + len, quals, param1 + len, param2);
989 if (!(*param1) && !(*param2))
992 /* Calculate the search path and stem to search for */
993 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
994 static const WCHAR curDir[] = {'.',';','\0'};
995 strcpyW(pathtosearch, curDir);
996 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
997 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
998 static const WCHAR curDir[] = {'.','\0'};
999 strcpyW (pathtosearch, curDir);
1001 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1002 if (strlenW(param1) >= MAX_PATH)
1004 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1008 strcpyW(stemofsearch, param1);
1012 /* Convert eg. ..\fred to include a directory by removing file part */
1013 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1014 lastSlash = strrchrW(pathtosearch, '\\');
1015 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1016 strcpyW(stemofsearch, lastSlash+1);
1018 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1019 c:\windows\a.bat syntax */
1020 if (lastSlash) *(lastSlash + 1) = 0x00;
1023 /* Now extract PATHEXT */
1024 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1025 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1026 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1027 '.','c','o','m',';',
1028 '.','c','m','d',';',
1029 '.','e','x','e','\0'};
1030 strcpyW (pathext, dfltPathExt);
1033 /* Loop through the search path, dir by dir */
1034 pathposn = pathtosearch;
1035 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1036 wine_dbgstr_w(stemofsearch));
1037 while (!launched && pathposn) {
1039 WCHAR thisDir[MAX_PATH] = {'\0'};
1043 /* Work on the first directory on the search path */
1044 pos = strchrW(pathposn, ';');
1046 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1047 thisDir[(pos-pathposn)] = 0x00;
1051 strcpyW(thisDir, pathposn);
1055 /* Since you can have eg. ..\.. on the path, need to expand
1056 to full information */
1057 strcpyW(temp, thisDir);
1058 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1060 /* 1. If extension supplied, see if that file exists */
1061 strcatW(thisDir, slashW);
1062 strcatW(thisDir, stemofsearch);
1063 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1065 /* 1. If extension supplied, see if that file exists */
1066 if (extensionsupplied) {
1067 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1072 /* 2. Any .* matches? */
1075 WIN32_FIND_DATAW finddata;
1076 static const WCHAR allFiles[] = {'.','*','\0'};
1078 strcatW(thisDir,allFiles);
1079 h = FindFirstFileW(thisDir, &finddata);
1081 if (h != INVALID_HANDLE_VALUE) {
1083 WCHAR *thisExt = pathext;
1085 /* 3. Yes - Try each path ext */
1087 WCHAR *nextExt = strchrW(thisExt, ';');
1090 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1091 pos[(nextExt-thisExt)] = 0x00;
1092 thisExt = nextExt+1;
1094 strcpyW(pos, thisExt);
1098 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1106 /* Internal programs won't be picked up by this search, so even
1107 though not found, try one last createprocess and wait for it
1109 Note: Ideally we could tell between a console app (wait) and a
1110 windows app, but the API's for it fail in this case */
1111 if (!found && pathposn == NULL) {
1112 WINE_TRACE("ASSUMING INTERNAL\n");
1113 assumeInternal = TRUE;
1115 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1118 /* Once found, launch it */
1119 if (found || assumeInternal) {
1121 PROCESS_INFORMATION pe;
1125 WCHAR *ext = strrchrW( thisDir, '.' );
1126 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1127 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1131 /* Special case BAT and CMD */
1132 if (ext && !strcmpiW(ext, batExt)) {
1133 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1135 } else if (ext && !strcmpiW(ext, cmdExt)) {
1136 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1140 /* thisDir contains the file to be launched, but with what?
1141 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1142 hinst = FindExecutableW (thisDir, NULL, temp);
1143 if ((INT_PTR)hinst < 32)
1146 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1148 ZeroMemory (&st, sizeof(STARTUPINFOW));
1149 st.cb = sizeof(STARTUPINFOW);
1150 init_msvcrt_io_block(&st);
1152 /* Launch the process and if a CUI wait on it to complete
1153 Note: Launching internal wine processes cannot specify a full path to exe */
1154 status = CreateProcessW(assumeInternal?NULL : thisDir,
1155 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1156 if ((opt_c || opt_k) && !opt_s && !status
1157 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1158 /* strip first and last quote WCHARacters and try again */
1159 WCMD_strip_quotes(command);
1161 WCMD_run_program(command, called);
1168 if (!assumeInternal && !console) errorlevel = 0;
1171 /* Always wait when called in a batch program context */
1172 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1173 GetExitCodeProcess (pe.hProcess, &errorlevel);
1174 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1176 CloseHandle(pe.hProcess);
1177 CloseHandle(pe.hThread);
1183 /* Not found anywhere - give up */
1184 SetLastError(ERROR_FILE_NOT_FOUND);
1185 WCMD_print_error ();
1187 /* If a command fails to launch, it sets errorlevel 9009 - which
1188 does not seem to have any associated constant definition */
1194 /*****************************************************************************
1195 * Process one command. If the command is EXIT this routine does not return.
1196 * We will recurse through here executing batch files.
1198 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1199 const WCHAR *forVariable, const WCHAR *forValue,
1202 WCHAR *cmd, *p, *redir;
1204 DWORD count, creationDisposition;
1207 SECURITY_ATTRIBUTES sa;
1208 WCHAR *new_cmd = NULL;
1209 WCHAR *new_redir = NULL;
1210 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1211 GetStdHandle (STD_OUTPUT_HANDLE),
1212 GetStdHandle (STD_ERROR_HANDLE)};
1213 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1216 BOOL prev_echo_mode, piped = FALSE;
1218 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1219 wine_dbgstr_w(command), cmdList,
1220 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1222 /* If the next command is a pipe then we implement pipes by redirecting
1223 the output from this command to a temp file and input into the
1224 next command from that temp file.
1225 FIXME: Use of named pipes would make more sense here as currently this
1226 process has to finish before the next one can start but this requires
1227 a change to not wait for the first app to finish but rather the pipe */
1228 if (cmdList && (*cmdList)->nextcommand &&
1229 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1231 WCHAR temp_path[MAX_PATH];
1232 static const WCHAR cmdW[] = {'C','M','D','\0'};
1234 /* Remember piping is in action */
1235 WINE_TRACE("Output needs to be piped\n");
1238 /* Generate a unique temporary filename */
1239 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1240 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1241 WINE_TRACE("Using temporary file of %s\n",
1242 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1245 /* Move copy of the command onto the heap so it can be expanded */
1246 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1249 WINE_ERR("Could not allocate memory for new_cmd\n");
1252 strcpyW(new_cmd, command);
1254 /* Move copy of the redirects onto the heap so it can be expanded */
1255 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1258 WINE_ERR("Could not allocate memory for new_redir\n");
1259 HeapFree( GetProcessHeap(), 0, new_cmd );
1263 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1265 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1266 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1267 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1269 strcpyW(new_redir, redirects);
1272 /* Expand variables in command line mode only (batch mode will
1273 be expanded as the line is read in, except for 'for' loops) */
1274 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1275 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1279 * Changing default drive has to be handled as a special case.
1282 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1284 WCHAR dir[MAX_PATH];
1286 /* According to MSDN CreateProcess docs, special env vars record
1287 the current directory on each drive, in the form =C:
1288 so see if one specified, and if so go back to it */
1289 strcpyW(envvar, equalW);
1290 strcatW(envvar, cmd);
1291 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1292 static const WCHAR fmt[] = {'%','s','\\','\0'};
1293 wsprintfW(cmd, fmt, cmd);
1294 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1296 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1297 status = SetCurrentDirectoryW(cmd);
1298 if (!status) WCMD_print_error ();
1299 HeapFree( GetProcessHeap(), 0, cmd );
1300 HeapFree( GetProcessHeap(), 0, new_redir );
1304 sa.nLength = sizeof(sa);
1305 sa.lpSecurityDescriptor = NULL;
1306 sa.bInheritHandle = TRUE;
1309 * Redirect stdin, stdout and/or stderr if required.
1312 /* STDIN could come from a preceding pipe, so delete on close if it does */
1313 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1314 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1315 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1316 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1317 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1318 if (h == INVALID_HANDLE_VALUE) {
1319 WCMD_print_error ();
1320 HeapFree( GetProcessHeap(), 0, cmd );
1321 HeapFree( GetProcessHeap(), 0, new_redir );
1324 SetStdHandle (STD_INPUT_HANDLE, h);
1326 /* No need to remember the temporary name any longer once opened */
1327 (*cmdList)->pipeFile[0] = 0x00;
1329 /* Otherwise STDIN could come from a '<' redirect */
1330 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1331 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1332 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1333 if (h == INVALID_HANDLE_VALUE) {
1334 WCMD_print_error ();
1335 HeapFree( GetProcessHeap(), 0, cmd );
1336 HeapFree( GetProcessHeap(), 0, new_redir );
1339 SetStdHandle (STD_INPUT_HANDLE, h);
1342 /* Scan the whole command looking for > and 2> */
1344 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1347 if (p > redir && (*(p-1)=='2'))
1354 creationDisposition = OPEN_ALWAYS;
1358 creationDisposition = CREATE_ALWAYS;
1361 /* Add support for 2>&1 */
1364 int idx = *(p+1) - '0';
1366 if (DuplicateHandle(GetCurrentProcess(),
1367 GetStdHandle(idx_stdhandles[idx]),
1368 GetCurrentProcess(),
1370 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1371 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1373 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1376 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1377 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1378 FILE_ATTRIBUTE_NORMAL, NULL);
1379 if (h == INVALID_HANDLE_VALUE) {
1380 WCMD_print_error ();
1381 HeapFree( GetProcessHeap(), 0, cmd );
1382 HeapFree( GetProcessHeap(), 0, new_redir );
1385 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1386 INVALID_SET_FILE_POINTER) {
1387 WCMD_print_error ();
1389 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1392 SetStdHandle (idx_stdhandles[handle], h);
1396 * Strip leading whitespaces, and a '@' if supplied
1398 whichcmd = WCMD_skip_leading_spaces(cmd);
1399 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1400 if (whichcmd[0] == '@') whichcmd++;
1403 * Check if the command entered is internal. If it is, pass the rest of the
1404 * line down to the command. If not try to run a program.
1408 while (IsCharAlphaNumericW(whichcmd[count])) {
1411 for (i=0; i<=WCMD_EXIT; i++) {
1412 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1413 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1415 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1416 WCMD_parse (p, quals, param1, param2);
1417 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1419 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1420 /* this is a help request for a builtin program */
1422 memcpy(p, whichcmd, count * sizeof(WCHAR));
1434 WCMD_setshow_default (p);
1437 WCMD_clear_screen ();
1446 WCMD_setshow_date ();
1456 WCMD_echo(&whichcmd[count]);
1459 WCMD_for (p, cmdList);
1462 WCMD_goto (cmdList);
1468 WCMD_if (p, cmdList);
1471 WCMD_volume (TRUE, p);
1475 WCMD_create_dir (p);
1481 WCMD_setshow_path (p);
1487 WCMD_setshow_prompt ();
1497 WCMD_remove_dir (p);
1506 WCMD_setshow_env (p);
1512 WCMD_setshow_time ();
1515 if (strlenW(&whichcmd[count]) > 0)
1516 WCMD_title(&whichcmd[count+1]);
1522 WCMD_output_asis(newline);
1529 WCMD_volume (FALSE, p);
1538 WCMD_assoc(p, TRUE);
1544 WCMD_assoc(p, FALSE);
1553 WCMD_exit (cmdList);
1556 prev_echo_mode = echo_mode;
1557 WCMD_run_program (whichcmd, 0);
1558 echo_mode = prev_echo_mode;
1560 HeapFree( GetProcessHeap(), 0, cmd );
1561 HeapFree( GetProcessHeap(), 0, new_redir );
1563 /* Restore old handles */
1564 for (i=0; i<3; i++) {
1565 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1566 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1567 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1572 /*************************************************************************
1574 * Load a string from the resource file, handling any error
1575 * Returns string retrieved from resource file
1577 WCHAR *WCMD_LoadMessage(UINT id) {
1578 static WCHAR msg[2048];
1579 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1581 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1582 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1583 strcpyW(msg, failedMsg);
1588 /***************************************************************************
1591 * Dumps out the parsed command line to ensure syntax is correct
1593 static void WCMD_DumpCommands(CMD_LIST *commands) {
1594 CMD_LIST *thisCmd = commands;
1596 WINE_TRACE("Parsed line:\n");
1597 while (thisCmd != NULL) {
1598 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1601 thisCmd->bracketDepth,
1602 thisCmd->nextcommand,
1603 wine_dbgstr_w(thisCmd->command),
1604 wine_dbgstr_w(thisCmd->redirects));
1605 thisCmd = thisCmd->nextcommand;
1609 /***************************************************************************
1612 * Adds a command to the current command list
1614 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1615 WCHAR *redirs, int *redirLen,
1616 WCHAR **copyTo, int **copyToLen,
1617 CMD_DELIMITERS prevDelim, int curDepth,
1618 CMD_LIST **lastEntry, CMD_LIST **output) {
1620 CMD_LIST *thisEntry = NULL;
1622 /* Allocate storage for command */
1623 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1625 /* Copy in the command */
1627 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1628 (*commandLen+1) * sizeof(WCHAR));
1629 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1630 thisEntry->command[*commandLen] = 0x00;
1632 /* Copy in the redirects */
1633 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1634 (*redirLen+1) * sizeof(WCHAR));
1635 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1636 thisEntry->redirects[*redirLen] = 0x00;
1637 thisEntry->pipeFile[0] = 0x00;
1639 /* Reset the lengths */
1642 *copyToLen = commandLen;
1646 thisEntry->command = NULL;
1647 thisEntry->redirects = NULL;
1648 thisEntry->pipeFile[0] = 0x00;
1651 /* Fill in other fields */
1652 thisEntry->nextcommand = NULL;
1653 thisEntry->prevDelim = prevDelim;
1654 thisEntry->bracketDepth = curDepth;
1656 (*lastEntry)->nextcommand = thisEntry;
1658 *output = thisEntry;
1660 *lastEntry = thisEntry;
1664 /***************************************************************************
1667 * Checks if the quote pointed to is the end-quote.
1671 * 1) The current parameter ends at EOL or at the beginning
1672 * of a redirection or pipe and not in a quote section.
1674 * 2) If the next character is a space and not in a quote section.
1676 * Returns TRUE if this is an end quote, and FALSE if it is not.
1679 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1681 int quoteCount = quoteIndex;
1684 /* If we are not in a quoted section, then we are not an end-quote */
1690 /* Check how many quotes are left for this parameter */
1691 for(i=0;quote[i];i++)
1698 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1699 else if(((quoteCount % 2) == 0)
1700 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1706 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1708 if(quoteIndex >= (quoteCount / 2))
1717 /***************************************************************************
1718 * WCMD_ReadAndParseLine
1720 * Either uses supplied input or
1721 * Reads a file from the handle, and then...
1722 * Parse the text buffer, splitting into separate commands
1723 * - unquoted && strings split 2 commands but the 2nd is flagged as
1725 * - ( as the first character just ups the bracket depth
1726 * - unquoted ) when bracket depth > 0 terminates a bracket and
1727 * adds a CMD_LIST structure with null command
1728 * - Anything else gets put into the command string (including
1731 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1735 WCHAR curString[MAXSTRING];
1736 int curStringLen = 0;
1737 WCHAR curRedirs[MAXSTRING];
1738 int curRedirsLen = 0;
1742 CMD_LIST *lastEntry = NULL;
1743 CMD_DELIMITERS prevDelim = CMD_NONE;
1744 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1745 static const WCHAR remCmd[] = {'r','e','m'};
1746 static const WCHAR forCmd[] = {'f','o','r'};
1747 static const WCHAR ifCmd[] = {'i','f'};
1748 static const WCHAR ifElse[] = {'e','l','s','e'};
1754 BOOL onlyWhiteSpace = FALSE;
1755 BOOL lastWasWhiteSpace = FALSE;
1756 BOOL lastWasDo = FALSE;
1757 BOOL lastWasIn = FALSE;
1758 BOOL lastWasElse = FALSE;
1759 BOOL lastWasRedirect = TRUE;
1761 /* Allocate working space for a command read from keyboard, file etc */
1763 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1766 WINE_ERR("Could not allocate memory for extraSpace\n");
1770 /* If initial command read in, use that, otherwise get input from handle */
1771 if (optionalcmd != NULL) {
1772 strcpyW(extraSpace, optionalcmd);
1773 } else if (readFrom == INVALID_HANDLE_VALUE) {
1774 WINE_FIXME("No command nor handle supplied\n");
1776 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1779 curPos = extraSpace;
1781 /* Handle truncated input - issue warning */
1782 if (strlenW(extraSpace) == MAXSTRING -1) {
1783 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1784 WCMD_output_asis_stderr(extraSpace);
1785 WCMD_output_asis_stderr(newline);
1788 /* Replace env vars if in a batch context */
1789 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1790 /* Show prompt before batch line IF echo is on and in batch program */
1791 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1792 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1793 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1794 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1795 DWORD curr_size = strlenW(extraSpace);
1796 DWORD min_len = (curr_size < len ? curr_size : len);
1798 WCMD_output_asis(extraSpace);
1799 /* I don't know why Windows puts a space here but it does */
1800 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1801 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1802 extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1803 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1804 extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1806 WCMD_output_asis(space);
1808 WCMD_output_asis(newline);
1811 /* Start with an empty string, copying to the command string */
1814 curCopyTo = curString;
1815 curLen = &curStringLen;
1816 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1818 /* Parse every character on the line being processed */
1819 while (*curPos != 0x00) {
1824 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1825 lastWasWhiteSpace, onlyWhiteSpace);
1828 /* Certain commands need special handling */
1829 if (curStringLen == 0 && curCopyTo == curString) {
1830 static const WCHAR forDO[] = {'d','o'};
1832 /* If command starts with 'rem ', ignore any &&, ( etc. */
1833 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1836 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1839 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1840 is only true in the command portion of the IF statement, but this
1841 should suffice for now
1842 FIXME: Silly syntax like "if 1(==1( (
1844 )" will be parsed wrong */
1845 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1848 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1849 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1852 onlyWhiteSpace = TRUE;
1853 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1854 (*curLen)+=keyw_len;
1858 /* In a for loop, the DO command will follow a close bracket followed by
1859 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1860 is then 0, and all whitespace is skipped */
1862 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1863 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1864 WINE_TRACE("Found 'DO '\n");
1866 onlyWhiteSpace = TRUE;
1867 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1868 (*curLen)+=keyw_len;
1872 } else if (curCopyTo == curString) {
1874 /* Special handling for the 'FOR' command */
1875 if (inFor && lastWasWhiteSpace) {
1876 static const WCHAR forIN[] = {'i','n'};
1878 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1880 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1881 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1882 WINE_TRACE("Found 'IN '\n");
1884 onlyWhiteSpace = TRUE;
1885 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1886 (*curLen)+=keyw_len;
1893 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1894 so just use the default processing ie skip character specific
1896 if (!inRem) thisChar = *curPos;
1897 else thisChar = 'X'; /* Character with no special processing */
1899 lastWasWhiteSpace = FALSE; /* Will be reset below */
1903 case '=': /* drop through - ignore token delimiters at the start of a command */
1904 case ',': /* drop through - ignore token delimiters at the start of a command */
1905 case '\t':/* drop through - ignore token delimiters at the start of a command */
1907 /* If a redirect in place, it ends here */
1908 if (!inQuotes && !lastWasRedirect) {
1910 /* If finishing off a redirect, add a whitespace delimiter */
1911 if (curCopyTo == curRedirs) {
1912 curCopyTo[(*curLen)++] = ' ';
1914 curCopyTo = curString;
1915 curLen = &curStringLen;
1918 curCopyTo[(*curLen)++] = *curPos;
1921 /* Remember just processed whitespace */
1922 lastWasWhiteSpace = TRUE;
1926 case '>': /* drop through - handle redirect chars the same */
1928 /* Make a redirect start here */
1930 curCopyTo = curRedirs;
1931 curLen = &curRedirsLen;
1932 lastWasRedirect = TRUE;
1935 /* See if 1>, 2> etc, in which case we have some patching up
1936 to do (provided there's a preceding whitespace, and enough
1937 chars read so far) */
1938 if (curStringLen > 2
1939 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1940 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1942 curString[curStringLen] = 0x00;
1943 curCopyTo[(*curLen)++] = *(curPos-1);
1946 curCopyTo[(*curLen)++] = *curPos;
1948 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1949 do not process that ampersand as an AND operator */
1950 if (thisChar == '>' && *(curPos+1) == '&') {
1951 curCopyTo[(*curLen)++] = *(curPos+1);
1956 case '|': /* Pipe character only if not || */
1958 lastWasRedirect = FALSE;
1960 /* Add an entry to the command list */
1961 if (curStringLen > 0) {
1963 /* Add the current command */
1964 WCMD_addCommand(curString, &curStringLen,
1965 curRedirs, &curRedirsLen,
1966 &curCopyTo, &curLen,
1967 prevDelim, curDepth,
1968 &lastEntry, output);
1972 if (*(curPos+1) == '|') {
1973 curPos++; /* Skip other | */
1974 prevDelim = CMD_ONFAILURE;
1976 prevDelim = CMD_PIPE;
1979 curCopyTo[(*curLen)++] = *curPos;
1983 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1986 inQuotes++; /* Quotes within quotes are fun! */
1988 curCopyTo[(*curLen)++] = *curPos;
1989 lastWasRedirect = FALSE;
1992 case '(': /* If a '(' is the first non whitespace in a command portion
1993 ie start of line or just after &&, then we read until an
1994 unquoted ) is found */
1995 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1996 ", for(%d, In:%d, Do:%d)"
1997 ", if(%d, else:%d, lwe:%d)\n",
2000 inFor, lastWasIn, lastWasDo,
2001 inIf, inElse, lastWasElse);
2002 lastWasRedirect = FALSE;
2004 /* Ignore open brackets inside the for set */
2005 if (*curLen == 0 && !inIn) {
2008 /* If in quotes, ignore brackets */
2009 } else if (inQuotes) {
2010 curCopyTo[(*curLen)++] = *curPos;
2012 /* In a FOR loop, an unquoted '(' may occur straight after
2014 In an IF statement just handle it regardless as we don't
2016 In an ELSE statement, only allow it straight away after
2017 the ELSE and whitespace
2020 (inElse && lastWasElse && onlyWhiteSpace) ||
2021 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2023 /* If entering into an 'IN', set inIn */
2024 if (inFor && lastWasIn && onlyWhiteSpace) {
2025 WINE_TRACE("Inside an IN\n");
2029 /* Add the current command */
2030 WCMD_addCommand(curString, &curStringLen,
2031 curRedirs, &curRedirsLen,
2032 &curCopyTo, &curLen,
2033 prevDelim, curDepth,
2034 &lastEntry, output);
2038 curCopyTo[(*curLen)++] = *curPos;
2042 case '&': if (!inQuotes) {
2043 lastWasRedirect = FALSE;
2045 /* Add an entry to the command list */
2046 if (curStringLen > 0) {
2048 /* Add the current command */
2049 WCMD_addCommand(curString, &curStringLen,
2050 curRedirs, &curRedirsLen,
2051 &curCopyTo, &curLen,
2052 prevDelim, curDepth,
2053 &lastEntry, output);
2057 if (*(curPos+1) == '&') {
2058 curPos++; /* Skip other & */
2059 prevDelim = CMD_ONSUCCESS;
2061 prevDelim = CMD_NONE;
2064 curCopyTo[(*curLen)++] = *curPos;
2068 case ')': if (!inQuotes && curDepth > 0) {
2069 lastWasRedirect = FALSE;
2071 /* Add the current command if there is one */
2074 /* Add the current command */
2075 WCMD_addCommand(curString, &curStringLen,
2076 curRedirs, &curRedirsLen,
2077 &curCopyTo, &curLen,
2078 prevDelim, curDepth,
2079 &lastEntry, output);
2082 /* Add an empty entry to the command list */
2083 prevDelim = CMD_NONE;
2084 WCMD_addCommand(NULL, &curStringLen,
2085 curRedirs, &curRedirsLen,
2086 &curCopyTo, &curLen,
2087 prevDelim, curDepth,
2088 &lastEntry, output);
2091 /* Leave inIn if necessary */
2092 if (inIn) inIn = FALSE;
2094 curCopyTo[(*curLen)++] = *curPos;
2098 lastWasRedirect = FALSE;
2099 curCopyTo[(*curLen)++] = *curPos;
2104 /* At various times we need to know if we have only skipped whitespace,
2105 so reset this variable and then it will remain true until a non
2106 whitespace is found */
2107 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2108 onlyWhiteSpace = FALSE;
2110 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2111 if (!lastWasWhiteSpace) {
2112 lastWasIn = lastWasDo = FALSE;
2115 /* If we have reached the end, add this command into the list */
2116 if (*curPos == 0x00 && *curLen > 0) {
2118 /* Add an entry to the command list */
2119 WCMD_addCommand(curString, &curStringLen,
2120 curRedirs, &curRedirsLen,
2121 &curCopyTo, &curLen,
2122 prevDelim, curDepth,
2123 &lastEntry, output);
2126 /* If we have reached the end of the string, see if bracketing outstanding */
2127 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2129 prevDelim = CMD_NONE;
2131 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2133 /* Read more, skipping any blank lines */
2134 while (*extraSpace == 0x00) {
2135 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2136 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
2139 curPos = extraSpace;
2140 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2141 /* Continue to echo commands IF echo is on and in batch program */
2142 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2143 WCMD_output_asis(extraSpace);
2144 WCMD_output_asis(newline);
2149 /* Dump out the parsed output */
2150 WCMD_DumpCommands(*output);
2155 /***************************************************************************
2156 * WCMD_process_commands
2158 * Process all the commands read in so far
2160 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2161 const WCHAR *var, const WCHAR *val) {
2165 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2167 /* Loop through the commands, processing them one by one */
2170 CMD_LIST *origCmd = thisCmd;
2172 /* If processing one bracket only, and we find the end bracket
2173 entry (or less), return */
2174 if (oneBracket && !thisCmd->command &&
2175 bdepth <= thisCmd->bracketDepth) {
2176 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2177 thisCmd, thisCmd->nextcommand);
2178 return thisCmd->nextcommand;
2181 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2182 about them and it will be handled in there)
2183 Also, skip over any batch labels (eg. :fred) */
2184 if (thisCmd->command && thisCmd->command[0] != ':') {
2185 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2186 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2189 /* Step on unless the command itself already stepped on */
2190 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2195 /***************************************************************************
2196 * WCMD_free_commands
2198 * Frees the storage held for a parsed command line
2199 * - This is not done in the process_commands, as eventually the current
2200 * pointer will be modified within the commands, and hence a single free
2201 * routine is simpler
2203 void WCMD_free_commands(CMD_LIST *cmds) {
2205 /* Loop through the commands, freeing them one by one */
2207 CMD_LIST *thisCmd = cmds;
2208 cmds = cmds->nextcommand;
2209 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2210 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2211 HeapFree(GetProcessHeap(), 0, thisCmd);
2216 /*****************************************************************************
2217 * Main entry point. This is a console application so we have a main() not a
2221 int wmain (int argc, WCHAR *argvW[])
2229 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2230 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2231 char ansiVersion[100];
2232 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2236 /* Pre initialize some messages */
2237 strcpy(ansiVersion, PACKAGE_VERSION);
2238 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2239 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2240 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2243 opt_c = opt_k = opt_q = opt_s = FALSE;
2247 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2248 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2255 if (tolowerW(c)=='c') {
2257 } else if (tolowerW(c)=='q') {
2259 } else if (tolowerW(c)=='k') {
2261 } else if (tolowerW(c)=='s') {
2263 } else if (tolowerW(c)=='a') {
2264 unicodeOutput = FALSE;
2265 } else if (tolowerW(c)=='u') {
2266 unicodeOutput = TRUE;
2267 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2268 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2269 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2270 /* Ignored for compatibility with Windows */
2273 if ((*argvW)[2]==0) {
2277 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2282 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2287 static const WCHAR eoff[] = {'O','F','F','\0'};
2291 if (opt_c || opt_k) {
2297 /* opt_s left unflagged if the command starts with and contains exactly
2298 * one quoted string (exactly two quote characters). The quoted string
2299 * must be an executable name that has whitespace and must not have the
2300 * following characters: &<>()@^| */
2302 /* Build the command to execute */
2306 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2308 int has_space,bcount;
2314 if( !*a ) has_space=1;
2319 if (*a==' ' || *a=='\t') {
2321 } else if (*a=='"') {
2322 /* doubling of '\' preceding a '"',
2323 * plus escaping of said '"'
2332 len+=(a-*arg) + 1; /* for the separating space */
2335 len+=2; /* for the quotes */
2343 /* check argvW[0] for a space and invalid characters */
2348 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2349 || *p=='@' || *p=='^' || *p=='|') {
2359 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2365 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2367 int has_space,has_quote;
2370 /* Check for quotes and spaces in this argument */
2371 has_space=has_quote=0;
2373 if( !*a ) has_space=1;
2375 if (*a==' ' || *a=='\t') {
2379 } else if (*a=='"') {
2387 /* Now transfer it to the command line */
2404 /* Double all the '\\' preceding this '"', plus one */
2405 for (i=0;i<=bcount;i++)
2424 p--; /* remove last space */
2427 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2429 /* strip first and last quote characters if opt_s; check for invalid
2430 * executable is done later */
2431 if (opt_s && *cmd=='\"')
2432 WCMD_strip_quotes(cmd);
2436 /* If we do a "cmd /c command", we don't want to allocate a new
2437 * console since the command returns immediately. Rather, we use
2438 * the currently allocated input and output handles. This allows
2439 * us to pipe to and read from the command interpreter.
2442 /* Parse the command string, without reading any more input */
2443 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2444 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2445 WCMD_free_commands(toExecute);
2448 HeapFree(GetProcessHeap(), 0, cmd);
2452 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2453 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2454 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2456 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2458 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2459 defaultColor = opt_t & 0xFF;
2464 /* Check HKCU\Software\Microsoft\Command Processor
2465 Then HKLM\Software\Microsoft\Command Processor
2466 for defaultcolour value
2467 Note Can be supplied as DWORD or REG_SZ
2468 Note2 When supplied as REG_SZ it's in decimal!!! */
2471 DWORD value=0, size=4;
2472 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2473 'M','i','c','r','o','s','o','f','t','\\',
2474 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2475 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2477 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2478 0, KEY_READ, &key) == ERROR_SUCCESS) {
2481 /* See if DWORD or REG_SZ */
2482 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2483 NULL, NULL) == ERROR_SUCCESS) {
2484 if (type == REG_DWORD) {
2485 size = sizeof(DWORD);
2486 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2487 (LPBYTE)&value, &size);
2488 } else if (type == REG_SZ) {
2489 size = sizeof(strvalue)/sizeof(WCHAR);
2490 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2491 (LPBYTE)strvalue, &size);
2492 value = strtoulW(strvalue, NULL, 10);
2498 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2499 0, KEY_READ, &key) == ERROR_SUCCESS) {
2502 /* See if DWORD or REG_SZ */
2503 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2504 NULL, NULL) == ERROR_SUCCESS) {
2505 if (type == REG_DWORD) {
2506 size = sizeof(DWORD);
2507 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2508 (LPBYTE)&value, &size);
2509 } else if (type == REG_SZ) {
2510 size = sizeof(strvalue)/sizeof(WCHAR);
2511 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2512 (LPBYTE)strvalue, &size);
2513 value = strtoulW(strvalue, NULL, 10);
2519 /* If one found, set the screen to that colour */
2520 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2521 defaultColor = value & 0xFF;
2528 /* Save cwd into appropriate env var */
2529 GetCurrentDirectoryW(1024, string);
2530 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2531 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2532 wsprintfW(envvar, fmt, string[0]);
2533 SetEnvironmentVariableW(envvar, string);
2534 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2538 /* Parse the command string, without reading any more input */
2539 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2540 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2541 WCMD_free_commands(toExecute);
2543 HeapFree(GetProcessHeap(), 0, cmd);
2547 * Loop forever getting commands and executing them.
2550 SetEnvironmentVariableW(promptW, defaultpromptW);
2554 /* Read until EOF (which for std input is never, but if redirect
2555 in place, may occur */
2556 if (echo_mode) WCMD_show_prompt();
2557 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2559 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2560 WCMD_free_commands(toExecute);