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
32 #include "wine/debug.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
36 extern const WCHAR inbuilt[][10];
37 extern struct env_stack *pushd_directories;
39 BATCH_CONTEXT *context = NULL;
41 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
43 FOR_CONTEXT forloopcontext; /* The 'for' loop context */
46 BOOL echo_mode = TRUE;
48 WCHAR anykey[100], version_string[100];
49 const WCHAR newlineW[] = {'\r','\n','\0'};
50 const WCHAR spaceW[] = {' ','\0'};
51 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
52 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
55 '.','e','x','e','\0'};
57 static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
59 /* Variables pertaining to paging */
60 static BOOL paged_mode;
61 static const WCHAR *pagedMessage = NULL;
62 static int line_count;
63 static int max_height;
67 #define MAX_WRITECONSOLE_SIZE 65535
70 * Returns a buffer for reading from/writing to file
73 static char *get_file_buffer(void)
75 static char *output_bufA = NULL;
77 output_bufA = heap_alloc(MAX_WRITECONSOLE_SIZE);
81 /*******************************************************************
82 * WCMD_output_asis_len - send output to current standard output
84 * Output a formatted unicode string. Ideally this will go to the console
85 * and hence required WriteConsoleW to output it, however if file i/o is
86 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
88 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
93 /* If nothing to write, return (MORE does this sometimes) */
96 /* Try to write as unicode assuming it is to a console */
97 res = WriteConsoleW(device, message, len, &nOut, NULL);
99 /* If writing to console fails, assume its file
100 i/o so convert to OEM codepage and output */
102 BOOL usedDefaultChar = FALSE;
103 DWORD convertedChars;
106 if (!unicodeOutput) {
108 if (!(buffer = get_file_buffer()))
111 /* Convert to OEM, then output */
112 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
113 len, buffer, MAX_WRITECONSOLE_SIZE,
114 "?", &usedDefaultChar);
115 WriteFile(device, buffer, convertedChars,
118 WriteFile(device, message, len*sizeof(WCHAR),
125 /*******************************************************************
126 * WCMD_output - send output to current standard output device.
130 void CDECL WCMD_output (const WCHAR *format, ...) {
136 __ms_va_start(ap,format);
137 SetLastError(NO_ERROR);
139 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
140 format, 0, 0, (LPWSTR)&string, 0, &ap);
142 if (len == 0 && GetLastError() != NO_ERROR)
143 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
146 WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
151 /*******************************************************************
152 * WCMD_output_stderr - send output to current standard error device.
156 void CDECL WCMD_output_stderr (const WCHAR *format, ...) {
162 __ms_va_start(ap,format);
163 SetLastError(NO_ERROR);
165 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
166 format, 0, 0, (LPWSTR)&string, 0, &ap);
168 if (len == 0 && GetLastError() != NO_ERROR)
169 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
172 WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
177 /*******************************************************************
178 * WCMD_format_string - allocate a buffer and format a string
182 WCHAR* CDECL WCMD_format_string (const WCHAR *format, ...) {
188 __ms_va_start(ap,format);
189 SetLastError(NO_ERROR);
190 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
191 format, 0, 0, (LPWSTR)&string, 0, &ap);
193 if (len == 0 && GetLastError() != NO_ERROR) {
194 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
195 string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
201 void WCMD_enter_paged_mode(const WCHAR *msg)
203 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
205 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
206 max_height = consoleInfo.dwSize.Y;
207 max_width = consoleInfo.dwSize.X;
215 pagedMessage = (msg==NULL)? anykey : msg;
218 void WCMD_leave_paged_mode(void)
224 /***************************************************************************
227 * Read characters in from a console/file, returning result in Unicode
229 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
234 if (WCMD_is_console_handle(hIn))
235 /* Try to read from console as Unicode */
236 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
238 /* We assume it's a file handle and read then convert from assumed OEM codepage */
239 if (!(buffer = get_file_buffer()))
242 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
245 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
250 /*******************************************************************
251 * WCMD_output_asis_handle
253 * Send output to specified handle without formatting e.g. when message contains '%'
255 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
259 HANDLE handle = GetStdHandle(std_handle);
264 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
268 if (*ptr == '\n') ptr++;
269 WCMD_output_asis_len(message, ptr - message, handle);
271 if (++line_count >= max_height - 1) {
273 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
274 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
276 } while (((message = ptr) != NULL) && (*ptr));
278 WCMD_output_asis_len(message, lstrlenW(message), handle);
282 /*******************************************************************
285 * Send output to current standard output device, without formatting
286 * e.g. when message contains '%'
288 void WCMD_output_asis (const WCHAR *message) {
289 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
292 /*******************************************************************
293 * WCMD_output_asis_stderr
295 * Send output to current standard error device, without formatting
296 * e.g. when message contains '%'
298 void WCMD_output_asis_stderr (const WCHAR *message) {
299 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
302 /****************************************************************************
305 * Print the message for GetLastError
308 void WCMD_print_error (void) {
313 error_code = GetLastError ();
314 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
315 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
317 WINE_FIXME ("Cannot display message for error %d, status %d\n",
318 error_code, GetLastError());
322 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
323 GetStdHandle(STD_ERROR_HANDLE));
324 LocalFree (lpMsgBuf);
325 WCMD_output_asis_len (newlineW, lstrlenW(newlineW),
326 GetStdHandle(STD_ERROR_HANDLE));
330 /******************************************************************************
333 * Display the prompt on STDout
337 static void WCMD_show_prompt (void) {
340 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
343 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
345 len = GetEnvironmentVariableW(envPrompt, prompt_string,
346 sizeof(prompt_string)/sizeof(WCHAR));
347 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
348 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
349 strcpyW (prompt_string, dfltPrompt);
363 switch (toupper(*p)) {
377 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
396 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
402 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
415 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
419 strcatW (q, version_string);
426 if (pushd_directories) {
427 memset(q, '+', pushd_directories->u.stackdepth);
428 q = q + pushd_directories->u.stackdepth;
436 WCMD_output_asis (out_string);
439 void *heap_alloc(size_t size)
443 ret = HeapAlloc(GetProcessHeap(), 0, size);
445 ERR("Out of memory\n");
452 /*************************************************************************
454 * Replaces a portion of a Unicode string with the specified string.
455 * It's up to the caller to ensure there is enough space in the
456 * destination buffer.
458 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
461 len=insert ? lstrlenW(insert) : 0;
462 if (start+len != next)
463 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
465 memcpy(start, insert, len * sizeof(*insert));
468 /***************************************************************************
469 * WCMD_skip_leading_spaces
471 * Return a pointer to the first non-whitespace character of string.
472 * Does not modify the input string.
474 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
479 while (*ptr == ' ' || *ptr == '\t') ptr++;
483 /***************************************************************************
484 * WCMD_keyword_ws_found
486 * Checks if the string located at ptr matches a keyword (of length len)
487 * followed by a whitespace character (space or tab)
489 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
490 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
491 ptr, len, keyword, len) == CSTR_EQUAL)
492 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
495 /*************************************************************************
498 * Remove first and last quote WCHARacters, preserving all other text
500 void WCMD_strip_quotes(WCHAR *cmd) {
501 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
502 while((*dest=*src) != '\0') {
509 while ((*dest++=*lastq++) != 0)
515 /*************************************************************************
516 * WCMD_is_magic_envvar
517 * Return TRUE if s is '%'magicvar'%'
518 * and is not masked by a real environment variable.
521 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
526 return FALSE; /* Didn't begin with % */
528 if (len < 2 || s[len-1] != '%')
529 return FALSE; /* Didn't end with another % */
531 if (CompareStringW(LOCALE_USER_DEFAULT,
532 NORM_IGNORECASE | SORT_STRINGSORT,
533 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
534 /* Name doesn't match. */
538 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
539 /* Masked by real environment variable. */
546 /*************************************************************************
549 * Expands environment variables, allowing for WCHARacter substitution
551 static WCHAR *WCMD_expand_envvar(WCHAR *start)
553 WCHAR *endOfVar = NULL, *s;
554 WCHAR *colonpos = NULL;
555 WCHAR thisVar[MAXSTRING];
556 WCHAR thisVarContents[MAXSTRING];
557 WCHAR savedchar = 0x00;
560 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
561 static const WCHAR Date[] = {'D','A','T','E','\0'};
562 static const WCHAR Time[] = {'T','I','M','E','\0'};
563 static const WCHAR Cd[] = {'C','D','\0'};
564 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
565 static const WCHAR Delims[] = {'%',':','\0'};
567 WINE_TRACE("Expanding: %s\n", wine_dbgstr_w(start));
569 /* Find the end of the environment variable, and extract name */
570 endOfVar = strpbrkW(start+1, Delims);
572 if (endOfVar == NULL || *endOfVar==' ') {
574 /* In batch program, missing terminator for % and no following
575 ':' just removes the '%' */
577 WCMD_strsubstW(start, start + 1, NULL, 0);
581 /* In command processing, just ignore it - allows command line
582 syntax like: for %i in (a.a) do echo %i */
587 /* If ':' found, process remaining up until '%' (or stop at ':' if
589 if (*endOfVar==':') {
590 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
591 if (endOfVar2 != NULL) endOfVar = endOfVar2;
594 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
595 thisVar[(endOfVar - start)+1] = 0x00;
596 colonpos = strchrW(thisVar+1, ':');
598 /* If there's complex substitution, just need %var% for now
599 to get the expanded data to play with */
602 savedchar = *(colonpos+1);
603 *(colonpos+1) = 0x00;
606 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
608 /* Expand to contents, if unchanged, return */
609 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
610 /* override if existing env var called that name */
611 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
612 static const WCHAR fmt[] = {'%','d','\0'};
613 wsprintfW(thisVarContents, fmt, errorlevel);
614 len = strlenW(thisVarContents);
615 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
616 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
617 NULL, thisVarContents, MAXSTRING);
618 len = strlenW(thisVarContents);
619 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
620 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
621 NULL, thisVarContents, MAXSTRING);
622 len = strlenW(thisVarContents);
623 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
624 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
625 len = strlenW(thisVarContents);
626 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
627 static const WCHAR fmt[] = {'%','d','\0'};
628 wsprintfW(thisVarContents, fmt, rand() % 32768);
629 len = strlenW(thisVarContents);
632 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
633 sizeof(thisVarContents)/sizeof(WCHAR));
639 /* In a batch program, unknown env vars are replaced with nothing,
640 note syntax %garbage:1,3% results in anything after the ':'
642 From the command line, you just get back what you entered */
643 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
645 /* Restore the complex part after the compare */
648 *(colonpos+1) = savedchar;
651 /* Command line - just ignore this */
652 if (context == NULL) return endOfVar+1;
655 /* Batch - replace unknown env var with nothing */
656 if (colonpos == NULL) {
657 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
659 len = strlenW(thisVar);
660 thisVar[len-1] = 0x00;
661 /* If %:...% supplied, : is retained */
662 if (colonpos == thisVar+1) {
663 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
665 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
672 /* See if we need to do complex substitution (any ':'s), if not
673 then our work here is done */
674 if (colonpos == NULL) {
675 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
679 /* Restore complex bit */
681 *(colonpos+1) = savedchar;
684 Handle complex substitutions:
685 xxx=yyy (replace xxx with yyy)
686 *xxx=yyy (replace up to and including xxx with yyy)
687 ~x (from x WCHARs in)
688 ~-x (from x WCHARs from the end)
689 ~x,y (from x WCHARs in for y WCHARacters)
690 ~x,-y (from x WCHARs in until y WCHARacters from the end)
693 /* ~ is substring manipulation */
694 if (savedchar == '~') {
696 int substrposition, substrlength = 0;
697 WCHAR *commapos = strchrW(colonpos+2, ',');
700 substrposition = atolW(colonpos+2);
701 if (commapos) substrlength = atolW(commapos+1);
704 if (substrposition >= 0) {
705 startCopy = &thisVarContents[min(substrposition, len)];
707 startCopy = &thisVarContents[max(0, len+substrposition-1)];
710 if (commapos == NULL) {
712 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
713 } else if (substrlength < 0) {
715 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
716 if (copybytes > len) copybytes = len;
717 else if (copybytes < 0) copybytes = 0;
718 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
720 substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
721 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
724 /* search and replace manipulation */
726 WCHAR *equalspos = strstrW(colonpos, equalW);
727 WCHAR *replacewith = equalspos+1;
732 if (equalspos == NULL) return start+1;
733 s = heap_strdupW(endOfVar + 1);
735 /* Null terminate both strings */
736 thisVar[strlenW(thisVar)-1] = 0x00;
739 /* Since we need to be case insensitive, copy the 2 buffers */
740 searchIn = heap_strdupW(thisVarContents);
741 CharUpperBuffW(searchIn, strlenW(thisVarContents));
742 searchFor = heap_strdupW(colonpos+1);
743 CharUpperBuffW(searchFor, strlenW(colonpos+1));
745 /* Handle wildcard case */
746 if (*(colonpos+1) == '*') {
747 /* Search for string to replace */
748 found = strstrW(searchIn, searchFor+1);
752 strcpyW(start, replacewith);
753 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
757 strcpyW(start, thisVarContents);
762 /* Loop replacing all instances */
763 WCHAR *lastFound = searchIn;
764 WCHAR *outputposn = start;
767 while ((found = strstrW(lastFound, searchFor))) {
768 lstrcpynW(outputposn,
769 thisVarContents + (lastFound-searchIn),
770 (found - lastFound)+1);
771 outputposn = outputposn + (found - lastFound);
772 strcatW(outputposn, replacewith);
773 outputposn = outputposn + strlenW(replacewith);
774 lastFound = found + strlenW(searchFor);
777 thisVarContents + (lastFound-searchIn));
778 strcatW(outputposn, s);
782 heap_free(searchFor);
787 /*****************************************************************************
788 * Expand the command. Native expands lines from batch programs as they are
789 * read in and not again, except for 'for' variable substitution.
790 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
792 static void handleExpansion(WCHAR *cmd, BOOL justFors) {
794 /* For commands in a context (batch program): */
795 /* Expand environment variables in a batch file %{0-9} first */
796 /* including support for any ~ modifiers */
798 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
799 /* names allowing environment variable overrides */
800 /* NOTE: To support the %PATH:xxx% syntax, also perform */
801 /* manual expansion of environment variables here */
807 /* Display the FOR variables in effect */
809 if (forloopcontext.variable[i]) {
810 WINE_TRACE("FOR variable context: %c = '%s'\n",
811 i<26?i+'a':(i-26)+'A',
812 wine_dbgstr_w(forloopcontext.variable[i]));
816 while ((p = strchrW(p, '%'))) {
818 WINE_TRACE("Translate command:%s %d (at: %s)\n",
819 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
822 /* Don't touch %% unless its in Batch */
823 if (!justFors && *(p+1) == '%') {
825 WCMD_strsubstW(p, p+1, NULL, 0);
829 /* Replace %~ modifications if in batch program */
830 } else if (*(p+1) == '~') {
831 WCMD_HandleTildaModifiers(&p, justFors);
834 /* Replace use of %0...%9 if in batch program*/
835 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
836 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
838 WCMD_strsubstW(p, p+2, t, -1);
840 /* Replace use of %* if in batch program*/
841 } else if (!justFors && context && *(p+1)=='*') {
842 WCHAR *startOfParms = NULL;
843 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
844 if (startOfParms != NULL) {
845 startOfParms += strlenW(thisParm);
846 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
847 WCMD_strsubstW(p, p+2, startOfParms, -1);
849 WCMD_strsubstW(p, p+2, NULL, 0);
852 int forvaridx = FOR_VAR_IDX(*(p+1));
853 if (forvaridx != -1 && forloopcontext.variable[forvaridx]) {
854 /* Replace the 2 characters, % and for variable character */
855 WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
856 } else if (!justFors) {
857 p = WCMD_expand_envvar(p);
859 /* In a FOR loop, see if this is the variable to replace */
860 } else { /* Ignore %'s on second pass of batch program */
870 /*******************************************************************
871 * WCMD_parse - parse a command into parameters and qualifiers.
873 * On exit, all qualifiers are concatenated into q, the first string
874 * not beginning with "/" is in p1 and the
875 * second in p2. Any subsequent non-qualifier strings are lost.
876 * Parameters in quotes are handled.
878 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
882 *q = *p1 = *p2 = '\0';
887 while ((*s != '\0') && (*s != ' ') && *s != '/') {
888 *q++ = toupperW (*s++);
898 while ((*s != '\0') && (*s != '"')) {
899 if (p == 0) *p1++ = *s++;
900 else if (p == 1) *p2++ = *s++;
903 if (p == 0) *p1 = '\0';
904 if (p == 1) *p2 = '\0';
911 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
912 && (*s != '=') && (*s != ',') ) {
913 if (p == 0) *p1++ = *s++;
914 else if (p == 1) *p2++ = *s++;
917 /* Skip concurrent parms */
918 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
920 if (p == 0) *p1 = '\0';
921 if (p == 1) *p2 = '\0';
927 static void init_msvcrt_io_block(STARTUPINFOW* st)
930 /* fetch the parent MSVCRT info block if any, so that the child can use the
931 * same handles as its grand-father
933 st_p.cb = sizeof(STARTUPINFOW);
934 GetStartupInfoW(&st_p);
935 st->cbReserved2 = st_p.cbReserved2;
936 st->lpReserved2 = st_p.lpReserved2;
937 if (st_p.cbReserved2 && st_p.lpReserved2)
939 unsigned num = *(unsigned*)st_p.lpReserved2;
945 /* Override the entries for fd 0,1,2 if we happened
946 * to change those std handles (this depends on the way cmd sets
947 * its new input & output handles)
949 sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
950 ptr = heap_alloc(sz);
951 flags = (char*)(ptr + sizeof(unsigned));
952 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);
978 /******************************************************************************
981 * Execute a command line as an external program. Must allow recursion.
984 * Manual testing under windows shows PATHEXT plays a key part in this,
985 * and the search algorithm and precedence appears to be as follows.
988 * If directory supplied on command, just use that directory
989 * If extension supplied on command, look for that explicit name first
990 * Otherwise, search in each directory on the path
992 * If extension supplied on command, look for that explicit name first
993 * Then look for supplied name .* (even if extension supplied, so
994 * 'garbage.exe' will match 'garbage.exe.cmd')
995 * If any found, cycle through PATHEXT looking for name.exe one by one
997 * Once a match has been found, it is launched - Code currently uses
998 * findexecutable to achieve this which is left untouched.
999 * If an executable has not been found, and we were launched through
1000 * a call, we need to check if the command is an internal command,
1001 * so go back through wcmd_execute.
1004 void WCMD_run_program (WCHAR *command, BOOL called)
1006 WCHAR temp[MAX_PATH];
1007 WCHAR pathtosearch[MAXSTRING];
1009 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1010 MAX_PATH, including null character */
1012 WCHAR pathext[MAXSTRING];
1014 BOOL extensionsupplied = FALSE;
1015 BOOL launched = FALSE;
1017 BOOL assumeInternal = FALSE;
1019 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1020 static const WCHAR delims[] = {'/','\\',':','\0'};
1022 /* Quick way to get the filename is to extract the first argument. */
1023 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1024 firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1025 if (!firstParam) return;
1027 /* Calculate the search path and stem to search for */
1028 if (strpbrkW (firstParam, delims) == NULL) { /* No explicit path given, search path */
1029 static const WCHAR curDir[] = {'.',';','\0'};
1030 strcpyW(pathtosearch, curDir);
1031 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1032 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1033 static const WCHAR curDir[] = {'.','\0'};
1034 strcpyW (pathtosearch, curDir);
1036 if (strchrW(firstParam, '.') != NULL) extensionsupplied = TRUE;
1037 if (strlenW(firstParam) >= MAX_PATH)
1039 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1043 strcpyW(stemofsearch, firstParam);
1047 /* Convert eg. ..\fred to include a directory by removing file part */
1048 GetFullPathNameW(firstParam, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1049 lastSlash = strrchrW(pathtosearch, '\\');
1050 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1051 strcpyW(stemofsearch, lastSlash+1);
1053 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1054 c:\windows\a.bat syntax */
1055 if (lastSlash) *(lastSlash + 1) = 0x00;
1058 /* Now extract PATHEXT */
1059 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1060 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1061 strcpyW (pathext, dfltPathExt);
1064 /* Loop through the search path, dir by dir */
1065 pathposn = pathtosearch;
1066 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1067 wine_dbgstr_w(stemofsearch));
1068 while (!launched && pathposn) {
1070 WCHAR thisDir[MAX_PATH] = {'\0'};
1074 /* Work on the first directory on the search path */
1075 pos = strchrW(pathposn, ';');
1077 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1078 thisDir[(pos-pathposn)] = 0x00;
1082 strcpyW(thisDir, pathposn);
1086 /* Since you can have eg. ..\.. on the path, need to expand
1087 to full information */
1088 strcpyW(temp, thisDir);
1089 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1091 /* 1. If extension supplied, see if that file exists */
1092 strcatW(thisDir, slashW);
1093 strcatW(thisDir, stemofsearch);
1094 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1096 /* 1. If extension supplied, see if that file exists */
1097 if (extensionsupplied) {
1098 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1103 /* 2. Any .* matches? */
1106 WIN32_FIND_DATAW finddata;
1107 static const WCHAR allFiles[] = {'.','*','\0'};
1109 strcatW(thisDir,allFiles);
1110 h = FindFirstFileW(thisDir, &finddata);
1112 if (h != INVALID_HANDLE_VALUE) {
1114 WCHAR *thisExt = pathext;
1116 /* 3. Yes - Try each path ext */
1118 WCHAR *nextExt = strchrW(thisExt, ';');
1121 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1122 pos[(nextExt-thisExt)] = 0x00;
1123 thisExt = nextExt+1;
1125 strcpyW(pos, thisExt);
1129 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1137 /* Internal programs won't be picked up by this search, so even
1138 though not found, try one last createprocess and wait for it
1140 Note: Ideally we could tell between a console app (wait) and a
1141 windows app, but the API's for it fail in this case */
1142 if (!found && pathposn == NULL) {
1143 WINE_TRACE("ASSUMING INTERNAL\n");
1144 assumeInternal = TRUE;
1146 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1149 /* Once found, launch it */
1150 if (found || assumeInternal) {
1152 PROCESS_INFORMATION pe;
1156 WCHAR *ext = strrchrW( thisDir, '.' );
1157 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1158 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1160 /* Special case BAT and CMD */
1161 if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) {
1162 BOOL oldinteractive = interactive;
1163 interactive = FALSE;
1164 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1165 interactive = oldinteractive;
1169 /* thisDir contains the file to be launched, but with what?
1170 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1171 hinst = FindExecutableW (thisDir, NULL, temp);
1172 if ((INT_PTR)hinst < 32)
1175 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1177 ZeroMemory (&st, sizeof(STARTUPINFOW));
1178 st.cb = sizeof(STARTUPINFOW);
1179 init_msvcrt_io_block(&st);
1181 /* Launch the process and if a CUI wait on it to complete
1182 Note: Launching internal wine processes cannot specify a full path to exe */
1183 status = CreateProcessW(assumeInternal?NULL : thisDir,
1184 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1185 if ((opt_c || opt_k) && !opt_s && !status
1186 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1187 /* strip first and last quote WCHARacters and try again */
1188 WCMD_strip_quotes(command);
1190 WCMD_run_program(command, called);
1197 if (!assumeInternal && !console) errorlevel = 0;
1200 /* Always wait when non-interactive (cmd /c or in batch program),
1201 or for console applications */
1202 if (assumeInternal || !interactive || !HIWORD(console))
1203 WaitForSingleObject (pe.hProcess, INFINITE);
1204 GetExitCodeProcess (pe.hProcess, &errorlevel);
1205 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1207 CloseHandle(pe.hProcess);
1208 CloseHandle(pe.hThread);
1214 /* Not found anywhere - were we called? */
1216 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1218 /* Parse the command string, without reading any more input */
1219 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1220 WCMD_process_commands(toExecute, FALSE, called);
1221 WCMD_free_commands(toExecute);
1226 /* Not found anywhere - give up */
1227 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1229 /* If a command fails to launch, it sets errorlevel 9009 - which
1230 does not seem to have any associated constant definition */
1236 /*****************************************************************************
1237 * Process one command. If the command is EXIT this routine does not return.
1238 * We will recurse through here executing batch files.
1239 * Note: If call is used to a non-existing program, we reparse the line and
1240 * try to run it as an internal command. 'retrycall' represents whether
1241 * we are attempting this retry.
1243 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1244 CMD_LIST **cmdList, BOOL retrycall)
1246 WCHAR *cmd, *p, *redir;
1248 DWORD count, creationDisposition;
1251 SECURITY_ATTRIBUTES sa;
1252 WCHAR *new_cmd = NULL;
1253 WCHAR *new_redir = NULL;
1254 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1255 GetStdHandle (STD_OUTPUT_HANDLE),
1256 GetStdHandle (STD_ERROR_HANDLE)};
1257 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1260 BOOL prev_echo_mode, piped = FALSE;
1262 WINE_TRACE("command on entry:%s (%p)\n",
1263 wine_dbgstr_w(command), cmdList);
1265 /* If the next command is a pipe then we implement pipes by redirecting
1266 the output from this command to a temp file and input into the
1267 next command from that temp file.
1268 FIXME: Use of named pipes would make more sense here as currently this
1269 process has to finish before the next one can start but this requires
1270 a change to not wait for the first app to finish but rather the pipe */
1271 if (cmdList && (*cmdList)->nextcommand &&
1272 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1274 WCHAR temp_path[MAX_PATH];
1275 static const WCHAR cmdW[] = {'C','M','D','\0'};
1277 /* Remember piping is in action */
1278 WINE_TRACE("Output needs to be piped\n");
1281 /* Generate a unique temporary filename */
1282 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1283 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1284 WINE_TRACE("Using temporary file of %s\n",
1285 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1288 /* Move copy of the command onto the heap so it can be expanded */
1289 new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR));
1290 strcpyW(new_cmd, command);
1292 /* Move copy of the redirects onto the heap so it can be expanded */
1293 new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR));
1295 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1297 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1298 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1299 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1301 strcpyW(new_redir, redirects);
1304 /* Expand variables in command line mode only (batch mode will
1305 be expanded as the line is read in, except for 'for' loops) */
1306 handleExpansion(new_cmd, (context != NULL));
1307 handleExpansion(new_redir, (context != NULL));
1311 * Changing default drive has to be handled as a special case.
1314 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1316 WCHAR dir[MAX_PATH];
1318 /* According to MSDN CreateProcess docs, special env vars record
1319 the current directory on each drive, in the form =C:
1320 so see if one specified, and if so go back to it */
1321 strcpyW(envvar, equalW);
1322 strcatW(envvar, cmd);
1323 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1324 static const WCHAR fmt[] = {'%','s','\\','\0'};
1325 wsprintfW(cmd, fmt, cmd);
1326 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1328 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1329 status = SetCurrentDirectoryW(cmd);
1330 if (!status) WCMD_print_error ();
1332 heap_free(new_redir);
1336 sa.nLength = sizeof(sa);
1337 sa.lpSecurityDescriptor = NULL;
1338 sa.bInheritHandle = TRUE;
1341 * Redirect stdin, stdout and/or stderr if required.
1344 /* STDIN could come from a preceding pipe, so delete on close if it does */
1345 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1346 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1347 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1348 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1349 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1350 if (h == INVALID_HANDLE_VALUE) {
1351 WCMD_print_error ();
1353 heap_free(new_redir);
1356 SetStdHandle (STD_INPUT_HANDLE, h);
1358 /* No need to remember the temporary name any longer once opened */
1359 (*cmdList)->pipeFile[0] = 0x00;
1361 /* Otherwise STDIN could come from a '<' redirect */
1362 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1363 h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1364 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1365 if (h == INVALID_HANDLE_VALUE) {
1366 WCMD_print_error ();
1368 heap_free(new_redir);
1371 SetStdHandle (STD_INPUT_HANDLE, h);
1374 /* Scan the whole command looking for > and 2> */
1376 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1379 if (p > redir && (*(p-1)=='2'))
1386 creationDisposition = OPEN_ALWAYS;
1390 creationDisposition = CREATE_ALWAYS;
1393 /* Add support for 2>&1 */
1396 int idx = *(p+1) - '0';
1398 if (DuplicateHandle(GetCurrentProcess(),
1399 GetStdHandle(idx_stdhandles[idx]),
1400 GetCurrentProcess(),
1402 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1403 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1405 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1408 WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
1409 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1410 FILE_ATTRIBUTE_NORMAL, NULL);
1411 if (h == INVALID_HANDLE_VALUE) {
1412 WCMD_print_error ();
1414 heap_free(new_redir);
1417 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1418 INVALID_SET_FILE_POINTER) {
1419 WCMD_print_error ();
1421 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1424 SetStdHandle (idx_stdhandles[handle], h);
1428 * Strip leading whitespaces, and a '@' if supplied
1430 whichcmd = WCMD_skip_leading_spaces(cmd);
1431 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1432 if (whichcmd[0] == '@') whichcmd++;
1435 * Check if the command entered is internal. If it is, pass the rest of the
1436 * line down to the command. If not try to run a program.
1440 while (IsCharAlphaNumericW(whichcmd[count])) {
1443 for (i=0; i<=WCMD_EXIT; i++) {
1444 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1445 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1447 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1448 WCMD_parse (p, quals, param1, param2);
1449 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1451 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1452 /* this is a help request for a builtin program */
1454 memcpy(p, whichcmd, count * sizeof(WCHAR));
1466 WCMD_setshow_default (p);
1469 WCMD_clear_screen ();
1478 WCMD_setshow_date ();
1488 WCMD_echo(&whichcmd[count]);
1491 WCMD_goto (cmdList);
1497 WCMD_volume (TRUE, p);
1501 WCMD_create_dir (p);
1507 WCMD_setshow_path (p);
1513 WCMD_setshow_prompt ();
1523 WCMD_remove_dir (p);
1532 WCMD_setshow_env (p);
1541 WCMD_setshow_time ();
1544 if (strlenW(&whichcmd[count]) > 0)
1545 WCMD_title(&whichcmd[count+1]);
1551 WCMD_output_asis(newlineW);
1558 WCMD_volume (FALSE, p);
1567 WCMD_assoc(p, TRUE);
1573 WCMD_assoc(p, FALSE);
1582 WCMD_exit (cmdList);
1586 /* Very oddly, probably because of all the special parsing required for
1587 these two commands, neither for nor if are supported when called,
1588 ie call if 1==1... will fail. */
1590 if (i==WCMD_FOR) WCMD_for (p, cmdList);
1591 else if (i==WCMD_IF) WCMD_if (p, cmdList);
1594 /* else: drop through */
1596 prev_echo_mode = echo_mode;
1597 WCMD_run_program (whichcmd, FALSE);
1598 echo_mode = prev_echo_mode;
1601 heap_free(new_redir);
1603 /* Restore old handles */
1604 for (i=0; i<3; i++) {
1605 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1606 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1607 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1612 /*************************************************************************
1614 * Load a string from the resource file, handling any error
1615 * Returns string retrieved from resource file
1617 WCHAR *WCMD_LoadMessage(UINT id) {
1618 static WCHAR msg[2048];
1619 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1621 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1622 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1623 strcpyW(msg, failedMsg);
1628 /***************************************************************************
1631 * Dumps out the parsed command line to ensure syntax is correct
1633 static void WCMD_DumpCommands(CMD_LIST *commands) {
1634 CMD_LIST *thisCmd = commands;
1636 WINE_TRACE("Parsed line:\n");
1637 while (thisCmd != NULL) {
1638 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1641 thisCmd->bracketDepth,
1642 thisCmd->nextcommand,
1643 wine_dbgstr_w(thisCmd->command),
1644 wine_dbgstr_w(thisCmd->redirects));
1645 thisCmd = thisCmd->nextcommand;
1649 /***************************************************************************
1652 * Adds a command to the current command list
1654 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1655 WCHAR *redirs, int *redirLen,
1656 WCHAR **copyTo, int **copyToLen,
1657 CMD_DELIMITERS prevDelim, int curDepth,
1658 CMD_LIST **lastEntry, CMD_LIST **output) {
1660 CMD_LIST *thisEntry = NULL;
1662 /* Allocate storage for command */
1663 thisEntry = heap_alloc(sizeof(CMD_LIST));
1665 /* Copy in the command */
1667 thisEntry->command = heap_alloc((*commandLen+1) * sizeof(WCHAR));
1668 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1669 thisEntry->command[*commandLen] = 0x00;
1671 /* Copy in the redirects */
1672 thisEntry->redirects = heap_alloc((*redirLen+1) * sizeof(WCHAR));
1673 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1674 thisEntry->redirects[*redirLen] = 0x00;
1675 thisEntry->pipeFile[0] = 0x00;
1677 /* Reset the lengths */
1680 *copyToLen = commandLen;
1684 thisEntry->command = NULL;
1685 thisEntry->redirects = NULL;
1686 thisEntry->pipeFile[0] = 0x00;
1689 /* Fill in other fields */
1690 thisEntry->nextcommand = NULL;
1691 thisEntry->prevDelim = prevDelim;
1692 thisEntry->bracketDepth = curDepth;
1694 (*lastEntry)->nextcommand = thisEntry;
1696 *output = thisEntry;
1698 *lastEntry = thisEntry;
1702 /***************************************************************************
1705 * Checks if the quote pointed to is the end-quote.
1709 * 1) The current parameter ends at EOL or at the beginning
1710 * of a redirection or pipe and not in a quote section.
1712 * 2) If the next character is a space and not in a quote section.
1714 * Returns TRUE if this is an end quote, and FALSE if it is not.
1717 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1719 int quoteCount = quoteIndex;
1722 /* If we are not in a quoted section, then we are not an end-quote */
1728 /* Check how many quotes are left for this parameter */
1729 for(i=0;quote[i];i++)
1736 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1737 else if(((quoteCount % 2) == 0)
1738 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1744 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1746 if(quoteIndex >= (quoteCount / 2))
1755 /***************************************************************************
1756 * WCMD_ReadAndParseLine
1758 * Either uses supplied input or
1759 * Reads a file from the handle, and then...
1760 * Parse the text buffer, splitting into separate commands
1761 * - unquoted && strings split 2 commands but the 2nd is flagged as
1763 * - ( as the first character just ups the bracket depth
1764 * - unquoted ) when bracket depth > 0 terminates a bracket and
1765 * adds a CMD_LIST structure with null command
1766 * - Anything else gets put into the command string (including
1769 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1773 WCHAR curString[MAXSTRING];
1774 int curStringLen = 0;
1775 WCHAR curRedirs[MAXSTRING];
1776 int curRedirsLen = 0;
1780 CMD_LIST *lastEntry = NULL;
1781 CMD_DELIMITERS prevDelim = CMD_NONE;
1782 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1783 static const WCHAR remCmd[] = {'r','e','m'};
1784 static const WCHAR forCmd[] = {'f','o','r'};
1785 static const WCHAR ifCmd[] = {'i','f'};
1786 static const WCHAR ifElse[] = {'e','l','s','e'};
1792 BOOL onlyWhiteSpace = FALSE;
1793 BOOL lastWasWhiteSpace = FALSE;
1794 BOOL lastWasDo = FALSE;
1795 BOOL lastWasIn = FALSE;
1796 BOOL lastWasElse = FALSE;
1797 BOOL lastWasRedirect = TRUE;
1798 BOOL lastWasCaret = FALSE;
1800 /* Allocate working space for a command read from keyboard, file etc */
1802 extraSpace = heap_alloc((MAXSTRING+1) * sizeof(WCHAR));
1805 WINE_ERR("Could not allocate memory for extraSpace\n");
1809 /* If initial command read in, use that, otherwise get input from handle */
1810 if (optionalcmd != NULL) {
1811 strcpyW(extraSpace, optionalcmd);
1812 } else if (readFrom == INVALID_HANDLE_VALUE) {
1813 WINE_FIXME("No command nor handle supplied\n");
1815 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1818 curPos = extraSpace;
1820 /* Handle truncated input - issue warning */
1821 if (strlenW(extraSpace) == MAXSTRING -1) {
1822 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1823 WCMD_output_asis_stderr(extraSpace);
1824 WCMD_output_asis_stderr(newlineW);
1827 /* Replace env vars if in a batch context */
1828 if (context) handleExpansion(extraSpace, FALSE);
1830 /* Skip preceding whitespace */
1831 while (*curPos == ' ' || *curPos == '\t') curPos++;
1833 /* Show prompt before batch line IF echo is on and in batch program */
1834 if (context && echo_mode && *curPos && (*curPos != '@')) {
1835 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1836 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1837 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1838 DWORD curr_size = strlenW(curPos);
1839 DWORD min_len = (curr_size < len ? curr_size : len);
1841 WCMD_output_asis(curPos);
1842 /* I don't know why Windows puts a space here but it does */
1843 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1844 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1845 curPos, min_len, echoDot, len) != CSTR_EQUAL
1846 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1847 curPos, min_len, echoCol, len) != CSTR_EQUAL)
1849 WCMD_output_asis(spaceW);
1851 WCMD_output_asis(newlineW);
1854 /* Skip repeated 'no echo' characters */
1855 while (*curPos == '@') curPos++;
1857 /* Start with an empty string, copying to the command string */
1860 curCopyTo = curString;
1861 curLen = &curStringLen;
1862 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1864 /* Parse every character on the line being processed */
1865 while (*curPos != 0x00) {
1870 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1871 lastWasWhiteSpace, onlyWhiteSpace);
1874 /* Prevent overflow caused by the caret escape char */
1875 if (*curLen >= MAXSTRING) {
1876 WINE_ERR("Overflow detected in command\n");
1880 /* Certain commands need special handling */
1881 if (curStringLen == 0 && curCopyTo == curString) {
1882 static const WCHAR forDO[] = {'d','o'};
1884 /* If command starts with 'rem ', ignore any &&, ( etc. */
1885 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1888 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1891 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1892 is only true in the command portion of the IF statement, but this
1893 should suffice for now
1894 FIXME: Silly syntax like "if 1(==1( (
1896 )" will be parsed wrong */
1897 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1900 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1901 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1904 onlyWhiteSpace = TRUE;
1905 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1906 (*curLen)+=keyw_len;
1910 /* In a for loop, the DO command will follow a close bracket followed by
1911 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1912 is then 0, and all whitespace is skipped */
1914 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1915 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1916 WINE_TRACE("Found 'DO '\n");
1918 onlyWhiteSpace = TRUE;
1919 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1920 (*curLen)+=keyw_len;
1924 } else if (curCopyTo == curString) {
1926 /* Special handling for the 'FOR' command */
1927 if (inFor && lastWasWhiteSpace) {
1928 static const WCHAR forIN[] = {'i','n'};
1930 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1932 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1933 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1934 WINE_TRACE("Found 'IN '\n");
1936 onlyWhiteSpace = TRUE;
1937 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1938 (*curLen)+=keyw_len;
1945 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1946 so just use the default processing ie skip character specific
1948 if (!inRem) thisChar = *curPos;
1949 else thisChar = 'X'; /* Character with no special processing */
1951 lastWasWhiteSpace = FALSE; /* Will be reset below */
1952 lastWasCaret = FALSE;
1956 case '=': /* drop through - ignore token delimiters at the start of a command */
1957 case ',': /* drop through - ignore token delimiters at the start of a command */
1958 case '\t':/* drop through - ignore token delimiters at the start of a command */
1960 /* If a redirect in place, it ends here */
1961 if (!inQuotes && !lastWasRedirect) {
1963 /* If finishing off a redirect, add a whitespace delimiter */
1964 if (curCopyTo == curRedirs) {
1965 curCopyTo[(*curLen)++] = ' ';
1967 curCopyTo = curString;
1968 curLen = &curStringLen;
1971 curCopyTo[(*curLen)++] = *curPos;
1974 /* Remember just processed whitespace */
1975 lastWasWhiteSpace = TRUE;
1979 case '>': /* drop through - handle redirect chars the same */
1981 /* Make a redirect start here */
1983 curCopyTo = curRedirs;
1984 curLen = &curRedirsLen;
1985 lastWasRedirect = TRUE;
1988 /* See if 1>, 2> etc, in which case we have some patching up
1989 to do (provided there's a preceding whitespace, and enough
1990 chars read so far) */
1991 if (curStringLen > 2
1992 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1993 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1995 curString[curStringLen] = 0x00;
1996 curCopyTo[(*curLen)++] = *(curPos-1);
1999 curCopyTo[(*curLen)++] = *curPos;
2001 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2002 do not process that ampersand as an AND operator */
2003 if (thisChar == '>' && *(curPos+1) == '&') {
2004 curCopyTo[(*curLen)++] = *(curPos+1);
2009 case '|': /* Pipe character only if not || */
2011 lastWasRedirect = FALSE;
2013 /* Add an entry to the command list */
2014 if (curStringLen > 0) {
2016 /* Add the current command */
2017 WCMD_addCommand(curString, &curStringLen,
2018 curRedirs, &curRedirsLen,
2019 &curCopyTo, &curLen,
2020 prevDelim, curDepth,
2021 &lastEntry, output);
2025 if (*(curPos+1) == '|') {
2026 curPos++; /* Skip other | */
2027 prevDelim = CMD_ONFAILURE;
2029 prevDelim = CMD_PIPE;
2032 curCopyTo[(*curLen)++] = *curPos;
2036 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2039 inQuotes++; /* Quotes within quotes are fun! */
2041 curCopyTo[(*curLen)++] = *curPos;
2042 lastWasRedirect = FALSE;
2045 case '(': /* If a '(' is the first non whitespace in a command portion
2046 ie start of line or just after &&, then we read until an
2047 unquoted ) is found */
2048 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2049 ", for(%d, In:%d, Do:%d)"
2050 ", if(%d, else:%d, lwe:%d)\n",
2053 inFor, lastWasIn, lastWasDo,
2054 inIf, inElse, lastWasElse);
2055 lastWasRedirect = FALSE;
2057 /* Ignore open brackets inside the for set */
2058 if (*curLen == 0 && !inIn) {
2061 /* If in quotes, ignore brackets */
2062 } else if (inQuotes) {
2063 curCopyTo[(*curLen)++] = *curPos;
2065 /* In a FOR loop, an unquoted '(' may occur straight after
2067 In an IF statement just handle it regardless as we don't
2069 In an ELSE statement, only allow it straight away after
2070 the ELSE and whitespace
2073 (inElse && lastWasElse && onlyWhiteSpace) ||
2074 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2076 /* If entering into an 'IN', set inIn */
2077 if (inFor && lastWasIn && onlyWhiteSpace) {
2078 WINE_TRACE("Inside an IN\n");
2082 /* Add the current command */
2083 WCMD_addCommand(curString, &curStringLen,
2084 curRedirs, &curRedirsLen,
2085 &curCopyTo, &curLen,
2086 prevDelim, curDepth,
2087 &lastEntry, output);
2091 curCopyTo[(*curLen)++] = *curPos;
2095 case '^': if (!inQuotes) {
2096 /* If we reach the end of the input, we need to wait for more */
2097 if (*(curPos+1) == 0x00) {
2098 lastWasCaret = TRUE;
2099 WINE_TRACE("Caret found at end of line\n");
2104 curCopyTo[(*curLen)++] = *curPos;
2107 case '&': if (!inQuotes) {
2108 lastWasRedirect = FALSE;
2110 /* Add an entry to the command list */
2111 if (curStringLen > 0) {
2113 /* Add the current command */
2114 WCMD_addCommand(curString, &curStringLen,
2115 curRedirs, &curRedirsLen,
2116 &curCopyTo, &curLen,
2117 prevDelim, curDepth,
2118 &lastEntry, output);
2122 if (*(curPos+1) == '&') {
2123 curPos++; /* Skip other & */
2124 prevDelim = CMD_ONSUCCESS;
2126 prevDelim = CMD_NONE;
2129 curCopyTo[(*curLen)++] = *curPos;
2133 case ')': if (!inQuotes && curDepth > 0) {
2134 lastWasRedirect = FALSE;
2136 /* Add the current command if there is one */
2139 /* Add the current command */
2140 WCMD_addCommand(curString, &curStringLen,
2141 curRedirs, &curRedirsLen,
2142 &curCopyTo, &curLen,
2143 prevDelim, curDepth,
2144 &lastEntry, output);
2147 /* Add an empty entry to the command list */
2148 prevDelim = CMD_NONE;
2149 WCMD_addCommand(NULL, &curStringLen,
2150 curRedirs, &curRedirsLen,
2151 &curCopyTo, &curLen,
2152 prevDelim, curDepth,
2153 &lastEntry, output);
2156 /* Leave inIn if necessary */
2157 if (inIn) inIn = FALSE;
2159 curCopyTo[(*curLen)++] = *curPos;
2163 lastWasRedirect = FALSE;
2164 curCopyTo[(*curLen)++] = *curPos;
2169 /* At various times we need to know if we have only skipped whitespace,
2170 so reset this variable and then it will remain true until a non
2171 whitespace is found */
2172 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2173 onlyWhiteSpace = FALSE;
2175 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2176 if (!lastWasWhiteSpace) {
2177 lastWasIn = lastWasDo = FALSE;
2180 /* If we have reached the end, add this command into the list
2181 Do not add command to list if escape char ^ was last */
2182 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2184 /* Add an entry to the command list */
2185 WCMD_addCommand(curString, &curStringLen,
2186 curRedirs, &curRedirsLen,
2187 &curCopyTo, &curLen,
2188 prevDelim, curDepth,
2189 &lastEntry, output);
2192 /* If we have reached the end of the string, see if bracketing or
2193 final caret is outstanding */
2194 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2195 readFrom != INVALID_HANDLE_VALUE) {
2198 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2200 prevDelim = CMD_NONE;
2202 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2203 extraData = extraSpace;
2205 /* Read more, skipping any blank lines */
2207 WINE_TRACE("Read more input\n");
2208 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2209 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2212 /* Edge case for carets - a completely blank line (i.e. was just
2213 CRLF) is oddly added as an LF but then more data is received (but
2216 if (*extraSpace == 0x00) {
2217 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2218 *extraData++ = '\r';
2223 } while (*extraData == 0x00);
2224 curPos = extraSpace;
2225 if (context) handleExpansion(extraSpace, FALSE);
2226 /* Continue to echo commands IF echo is on and in batch program */
2227 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2228 WCMD_output_asis(extraSpace);
2229 WCMD_output_asis(newlineW);
2234 /* Dump out the parsed output */
2235 WCMD_DumpCommands(*output);
2240 /***************************************************************************
2241 * WCMD_process_commands
2243 * Process all the commands read in so far
2245 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2250 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2252 /* Loop through the commands, processing them one by one */
2255 CMD_LIST *origCmd = thisCmd;
2257 /* If processing one bracket only, and we find the end bracket
2258 entry (or less), return */
2259 if (oneBracket && !thisCmd->command &&
2260 bdepth <= thisCmd->bracketDepth) {
2261 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2262 thisCmd, thisCmd->nextcommand);
2263 return thisCmd->nextcommand;
2266 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2267 about them and it will be handled in there)
2268 Also, skip over any batch labels (eg. :fred) */
2269 if (thisCmd->command && thisCmd->command[0] != ':') {
2270 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2271 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2274 /* Step on unless the command itself already stepped on */
2275 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2280 /***************************************************************************
2281 * WCMD_free_commands
2283 * Frees the storage held for a parsed command line
2284 * - This is not done in the process_commands, as eventually the current
2285 * pointer will be modified within the commands, and hence a single free
2286 * routine is simpler
2288 void WCMD_free_commands(CMD_LIST *cmds) {
2290 /* Loop through the commands, freeing them one by one */
2292 CMD_LIST *thisCmd = cmds;
2293 cmds = cmds->nextcommand;
2294 heap_free(thisCmd->command);
2295 heap_free(thisCmd->redirects);
2301 /*****************************************************************************
2302 * Main entry point. This is a console application so we have a main() not a
2306 int wmain (int argc, WCHAR *argvW[])
2309 WCHAR *cmdLine = NULL;
2311 WCHAR *argPos = NULL;
2316 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2317 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2318 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2324 /* Get the windows version being emulated */
2325 osv.dwOSVersionInfoSize = sizeof(osv);
2326 GetVersionExW(&osv);
2328 /* Pre initialize some messages */
2329 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2330 sprintf(osver, "%d.%d.%d (%s)", osv.dwMajorVersion, osv.dwMinorVersion,
2331 osv.dwBuildNumber, PACKAGE_VERSION);
2332 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2333 strcpyW(version_string, cmd);
2337 /* Can't use argc/argv as it will have stripped quotes from parameters
2338 * meaning cmd.exe /C echo "quoted string" is impossible
2340 cmdLine = GetCommandLineW();
2341 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2342 args = 1; /* start at first arg, skipping cmd.exe itself */
2344 opt_c = opt_k = opt_q = opt_s = FALSE;
2345 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2346 while (argPos && argPos[0] != 0x00)
2349 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
2350 if (argPos[0]!='/' || argPos[1]=='\0') {
2352 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2357 if (tolowerW(c)=='c') {
2359 } else if (tolowerW(c)=='q') {
2361 } else if (tolowerW(c)=='k') {
2363 } else if (tolowerW(c)=='s') {
2365 } else if (tolowerW(c)=='a') {
2366 unicodeOutput = FALSE;
2367 } else if (tolowerW(c)=='u') {
2368 unicodeOutput = TRUE;
2369 } else if (tolowerW(c)=='t' && argPos[2]==':') {
2370 opt_t=strtoulW(&argPos[3], NULL, 16);
2371 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2372 /* Ignored for compatibility with Windows */
2375 if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') {
2377 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2379 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2381 /* Do not step to next parameter, instead carry on parsing this one */
2385 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2390 static const WCHAR eoff[] = {'O','F','F','\0'};
2394 /* Until we start to read from the keyboard, stay as non-interactive */
2395 interactive = FALSE;
2397 if (opt_c || opt_k) {
2399 WCHAR *q1 = NULL,*q2 = NULL,*p;
2401 /* Handle very edge case error scenario, "cmd.exe /c" ie when there are no
2402 * parameters after the /C or /K by pretending there was a single space */
2403 if (argPos == NULL) argPos = (WCHAR *)spaceW;
2406 cmd = heap_strdupW(argPos);
2408 /* opt_s left unflagged if the command starts with and contains exactly
2409 * one quoted string (exactly two quote characters). The quoted string
2410 * must be an executable name that has whitespace and must not have the
2411 * following characters: &<>()@^| */
2414 /* 1. Confirm there is at least one quote */
2415 q1 = strchrW(argPos, '"');
2420 /* 2. Confirm there is a second quote */
2421 q2 = strchrW(q1+1, '"');
2426 /* 3. Ensure there are no more quotes */
2427 if (strchrW(q2+1, '"')) opt_s=1;
2430 /* check first parameter for a space and invalid characters. There must not be any
2431 * invalid characters, but there must be one or more whitespace */
2436 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2437 || *p=='@' || *p=='^' || *p=='|') {
2441 if (*p==' ' || *p=='\t')
2447 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2449 /* Finally, we only stay in new mode IF the first parameter is quoted and
2450 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2452 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2453 WCHAR pathext[MAXSTRING];
2456 /* Now extract PATHEXT */
2457 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
2458 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
2459 strcpyW (pathext, dfltPathExt);
2462 /* If the supplied parameter has any directory information, look there */
2463 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2464 if (strchrW(thisArg, '\\') != NULL) {
2466 GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
2467 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2468 p = string + strlenW(string);
2470 /* Does file exist with this name? */
2471 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2472 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2475 WCHAR *thisExt = pathext;
2477 /* No - try with each of the PATHEXT extensions */
2478 while (!found && thisExt) {
2479 WCHAR *nextExt = strchrW(thisExt, ';');
2482 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2483 p[(nextExt-thisExt)] = 0x00;
2484 thisExt = nextExt+1;
2486 strcpyW(p, thisExt);
2490 /* Does file exist with this extension appended? */
2491 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2492 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2498 /* Otherwise we now need to look in the path to see if we can find it */
2500 p = thisArg + strlenW(thisArg);
2502 /* Does file exist with this name? */
2503 if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2504 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2507 WCHAR *thisExt = pathext;
2509 /* No - try with each of the PATHEXT extensions */
2510 while (!found && thisExt) {
2511 WCHAR *nextExt = strchrW(thisExt, ';');
2515 nextExt = nextExt+1;
2520 /* Does file exist with this extension? */
2521 if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2522 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2523 wine_dbgstr_w(thisExt));
2531 /* If not found, drop back to old behaviour */
2533 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2539 /* strip first and last quote characters if opt_s; check for invalid
2540 * executable is done later */
2541 if (opt_s && *cmd=='\"')
2542 WCMD_strip_quotes(cmd);
2545 /* Save cwd into appropriate env var (Must be before the /c processing */
2546 GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
2547 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2548 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2549 wsprintfW(envvar, fmt, string[0]);
2550 SetEnvironmentVariableW(envvar, string);
2551 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2555 /* If we do a "cmd /c command", we don't want to allocate a new
2556 * console since the command returns immediately. Rather, we use
2557 * the currently allocated input and output handles. This allows
2558 * us to pipe to and read from the command interpreter.
2561 /* Parse the command string, without reading any more input */
2562 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2563 WCMD_process_commands(toExecute, FALSE, FALSE);
2564 WCMD_free_commands(toExecute);
2571 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2572 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2573 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2575 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2577 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2578 defaultColor = opt_t & 0xFF;
2583 /* Check HKCU\Software\Microsoft\Command Processor
2584 Then HKLM\Software\Microsoft\Command Processor
2585 for defaultcolour value
2586 Note Can be supplied as DWORD or REG_SZ
2587 Note2 When supplied as REG_SZ it's in decimal!!! */
2590 DWORD value=0, size=4;
2591 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2592 'M','i','c','r','o','s','o','f','t','\\',
2593 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2594 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2596 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2597 0, KEY_READ, &key) == ERROR_SUCCESS) {
2600 /* See if DWORD or REG_SZ */
2601 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2602 NULL, NULL) == ERROR_SUCCESS) {
2603 if (type == REG_DWORD) {
2604 size = sizeof(DWORD);
2605 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2606 (LPBYTE)&value, &size);
2607 } else if (type == REG_SZ) {
2608 size = sizeof(strvalue)/sizeof(WCHAR);
2609 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2610 (LPBYTE)strvalue, &size);
2611 value = strtoulW(strvalue, NULL, 10);
2617 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2618 0, KEY_READ, &key) == ERROR_SUCCESS) {
2621 /* See if DWORD or REG_SZ */
2622 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2623 NULL, NULL) == ERROR_SUCCESS) {
2624 if (type == REG_DWORD) {
2625 size = sizeof(DWORD);
2626 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2627 (LPBYTE)&value, &size);
2628 } else if (type == REG_SZ) {
2629 size = sizeof(strvalue)/sizeof(WCHAR);
2630 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2631 (LPBYTE)strvalue, &size);
2632 value = strtoulW(strvalue, NULL, 10);
2638 /* If one found, set the screen to that colour */
2639 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2640 defaultColor = value & 0xFF;
2648 /* Parse the command string, without reading any more input */
2649 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2650 WCMD_process_commands(toExecute, FALSE, FALSE);
2651 WCMD_free_commands(toExecute);
2657 * Loop forever getting commands and executing them.
2660 SetEnvironmentVariableW(promptW, defaultpromptW);
2662 if (!opt_k) WCMD_version ();
2665 /* Read until EOF (which for std input is never, but if redirect
2666 in place, may occur */
2667 if (echo_mode) WCMD_show_prompt();
2668 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2670 WCMD_process_commands(toExecute, FALSE, FALSE);
2671 WCMD_free_commands(toExecute);