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'};
1162 /* Special case BAT and CMD */
1163 if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) {
1164 BOOL oldinteractive = interactive;
1165 interactive = FALSE;
1166 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1167 interactive = oldinteractive;
1171 /* thisDir contains the file to be launched, but with what?
1172 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1173 hinst = FindExecutableW (thisDir, NULL, temp);
1174 if ((INT_PTR)hinst < 32)
1177 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1179 ZeroMemory (&st, sizeof(STARTUPINFOW));
1180 st.cb = sizeof(STARTUPINFOW);
1181 init_msvcrt_io_block(&st);
1183 /* Launch the process and if a CUI wait on it to complete
1184 Note: Launching internal wine processes cannot specify a full path to exe */
1185 status = CreateProcessW(assumeInternal?NULL : thisDir,
1186 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1187 if ((opt_c || opt_k) && !opt_s && !status
1188 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1189 /* strip first and last quote WCHARacters and try again */
1190 WCMD_strip_quotes(command);
1192 WCMD_run_program(command, called);
1199 called = FALSE; /* No need to retry as we launched something */
1201 if (!assumeInternal && !console) errorlevel = 0;
1204 /* Always wait when non-interactive (cmd /c or in batch program),
1205 or for console applications */
1206 if (assumeInternal || !interactive || !HIWORD(console))
1207 WaitForSingleObject (pe.hProcess, INFINITE);
1208 GetExitCodeProcess (pe.hProcess, &errorlevel);
1209 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1211 CloseHandle(pe.hProcess);
1212 CloseHandle(pe.hThread);
1218 /* Not found anywhere - were we called? */
1220 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1222 /* Parse the command string, without reading any more input */
1223 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1224 WCMD_process_commands(toExecute, FALSE, called);
1225 WCMD_free_commands(toExecute);
1230 /* Not found anywhere - give up */
1231 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1233 /* If a command fails to launch, it sets errorlevel 9009 - which
1234 does not seem to have any associated constant definition */
1240 /*****************************************************************************
1241 * Process one command. If the command is EXIT this routine does not return.
1242 * We will recurse through here executing batch files.
1243 * Note: If call is used to a non-existing program, we reparse the line and
1244 * try to run it as an internal command. 'retrycall' represents whether
1245 * we are attempting this retry.
1247 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1248 CMD_LIST **cmdList, BOOL retrycall)
1250 WCHAR *cmd, *p, *redir;
1252 DWORD count, creationDisposition;
1255 SECURITY_ATTRIBUTES sa;
1256 WCHAR *new_cmd = NULL;
1257 WCHAR *new_redir = NULL;
1258 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1259 GetStdHandle (STD_OUTPUT_HANDLE),
1260 GetStdHandle (STD_ERROR_HANDLE)};
1261 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1264 BOOL prev_echo_mode, piped = FALSE;
1266 WINE_TRACE("command on entry:%s (%p)\n",
1267 wine_dbgstr_w(command), cmdList);
1269 /* If the next command is a pipe then we implement pipes by redirecting
1270 the output from this command to a temp file and input into the
1271 next command from that temp file.
1272 FIXME: Use of named pipes would make more sense here as currently this
1273 process has to finish before the next one can start but this requires
1274 a change to not wait for the first app to finish but rather the pipe */
1275 if (cmdList && (*cmdList)->nextcommand &&
1276 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1278 WCHAR temp_path[MAX_PATH];
1279 static const WCHAR cmdW[] = {'C','M','D','\0'};
1281 /* Remember piping is in action */
1282 WINE_TRACE("Output needs to be piped\n");
1285 /* Generate a unique temporary filename */
1286 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1287 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1288 WINE_TRACE("Using temporary file of %s\n",
1289 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1292 /* Move copy of the command onto the heap so it can be expanded */
1293 new_cmd = heap_alloc(MAXSTRING * sizeof(WCHAR));
1294 strcpyW(new_cmd, command);
1296 /* Move copy of the redirects onto the heap so it can be expanded */
1297 new_redir = heap_alloc(MAXSTRING * sizeof(WCHAR));
1299 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1301 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1302 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1303 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1305 strcpyW(new_redir, redirects);
1308 /* Expand variables in command line mode only (batch mode will
1309 be expanded as the line is read in, except for 'for' loops) */
1310 handleExpansion(new_cmd, (context != NULL));
1311 handleExpansion(new_redir, (context != NULL));
1315 * Changing default drive has to be handled as a special case.
1318 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1320 WCHAR dir[MAX_PATH];
1322 /* According to MSDN CreateProcess docs, special env vars record
1323 the current directory on each drive, in the form =C:
1324 so see if one specified, and if so go back to it */
1325 strcpyW(envvar, equalW);
1326 strcatW(envvar, cmd);
1327 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1328 static const WCHAR fmt[] = {'%','s','\\','\0'};
1329 wsprintfW(cmd, fmt, cmd);
1330 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1332 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1333 status = SetCurrentDirectoryW(cmd);
1334 if (!status) WCMD_print_error ();
1336 heap_free(new_redir);
1340 sa.nLength = sizeof(sa);
1341 sa.lpSecurityDescriptor = NULL;
1342 sa.bInheritHandle = TRUE;
1345 * Redirect stdin, stdout and/or stderr if required.
1348 /* STDIN could come from a preceding pipe, so delete on close if it does */
1349 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1350 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1351 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1352 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1353 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1354 if (h == INVALID_HANDLE_VALUE) {
1355 WCMD_print_error ();
1357 heap_free(new_redir);
1360 SetStdHandle (STD_INPUT_HANDLE, h);
1362 /* No need to remember the temporary name any longer once opened */
1363 (*cmdList)->pipeFile[0] = 0x00;
1365 /* Otherwise STDIN could come from a '<' redirect */
1366 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1367 h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1368 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1369 if (h == INVALID_HANDLE_VALUE) {
1370 WCMD_print_error ();
1372 heap_free(new_redir);
1375 SetStdHandle (STD_INPUT_HANDLE, h);
1378 /* Scan the whole command looking for > and 2> */
1380 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1383 if (p > redir && (*(p-1)=='2'))
1390 creationDisposition = OPEN_ALWAYS;
1394 creationDisposition = CREATE_ALWAYS;
1397 /* Add support for 2>&1 */
1400 int idx = *(p+1) - '0';
1402 if (DuplicateHandle(GetCurrentProcess(),
1403 GetStdHandle(idx_stdhandles[idx]),
1404 GetCurrentProcess(),
1406 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1407 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1409 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1412 WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
1413 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1414 FILE_ATTRIBUTE_NORMAL, NULL);
1415 if (h == INVALID_HANDLE_VALUE) {
1416 WCMD_print_error ();
1418 heap_free(new_redir);
1421 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1422 INVALID_SET_FILE_POINTER) {
1423 WCMD_print_error ();
1425 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1428 SetStdHandle (idx_stdhandles[handle], h);
1432 * Strip leading whitespaces, and a '@' if supplied
1434 whichcmd = WCMD_skip_leading_spaces(cmd);
1435 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1436 if (whichcmd[0] == '@') whichcmd++;
1439 * Check if the command entered is internal. If it is, pass the rest of the
1440 * line down to the command. If not try to run a program.
1444 while (IsCharAlphaNumericW(whichcmd[count])) {
1447 for (i=0; i<=WCMD_EXIT; i++) {
1448 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1449 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1451 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1452 WCMD_parse (p, quals, param1, param2);
1453 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1455 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1456 /* this is a help request for a builtin program */
1458 memcpy(p, whichcmd, count * sizeof(WCHAR));
1470 WCMD_setshow_default (p);
1473 WCMD_clear_screen ();
1482 WCMD_setshow_date ();
1492 WCMD_echo(&whichcmd[count]);
1495 WCMD_goto (cmdList);
1501 WCMD_volume (TRUE, p);
1505 WCMD_create_dir (p);
1511 WCMD_setshow_path (p);
1517 WCMD_setshow_prompt ();
1527 WCMD_remove_dir (p);
1536 WCMD_setshow_env (p);
1545 WCMD_setshow_time ();
1548 if (strlenW(&whichcmd[count]) > 0)
1549 WCMD_title(&whichcmd[count+1]);
1555 WCMD_output_asis(newlineW);
1562 WCMD_volume (FALSE, p);
1571 WCMD_assoc(p, TRUE);
1577 WCMD_assoc(p, FALSE);
1586 WCMD_exit (cmdList);
1590 /* Very oddly, probably because of all the special parsing required for
1591 these two commands, neither for nor if are supported when called,
1592 ie call if 1==1... will fail. */
1594 if (i==WCMD_FOR) WCMD_for (p, cmdList);
1595 else if (i==WCMD_IF) WCMD_if (p, cmdList);
1598 /* else: drop through */
1600 prev_echo_mode = echo_mode;
1601 WCMD_run_program (whichcmd, FALSE);
1602 echo_mode = prev_echo_mode;
1605 heap_free(new_redir);
1607 /* Restore old handles */
1608 for (i=0; i<3; i++) {
1609 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1610 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1611 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1616 /*************************************************************************
1618 * Load a string from the resource file, handling any error
1619 * Returns string retrieved from resource file
1621 WCHAR *WCMD_LoadMessage(UINT id) {
1622 static WCHAR msg[2048];
1623 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1625 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1626 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1627 strcpyW(msg, failedMsg);
1632 /***************************************************************************
1635 * Dumps out the parsed command line to ensure syntax is correct
1637 static void WCMD_DumpCommands(CMD_LIST *commands) {
1638 CMD_LIST *thisCmd = commands;
1640 WINE_TRACE("Parsed line:\n");
1641 while (thisCmd != NULL) {
1642 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1645 thisCmd->bracketDepth,
1646 thisCmd->nextcommand,
1647 wine_dbgstr_w(thisCmd->command),
1648 wine_dbgstr_w(thisCmd->redirects));
1649 thisCmd = thisCmd->nextcommand;
1653 /***************************************************************************
1656 * Adds a command to the current command list
1658 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1659 WCHAR *redirs, int *redirLen,
1660 WCHAR **copyTo, int **copyToLen,
1661 CMD_DELIMITERS prevDelim, int curDepth,
1662 CMD_LIST **lastEntry, CMD_LIST **output) {
1664 CMD_LIST *thisEntry = NULL;
1666 /* Allocate storage for command */
1667 thisEntry = heap_alloc(sizeof(CMD_LIST));
1669 /* Copy in the command */
1671 thisEntry->command = heap_alloc((*commandLen+1) * sizeof(WCHAR));
1672 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1673 thisEntry->command[*commandLen] = 0x00;
1675 /* Copy in the redirects */
1676 thisEntry->redirects = heap_alloc((*redirLen+1) * sizeof(WCHAR));
1677 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1678 thisEntry->redirects[*redirLen] = 0x00;
1679 thisEntry->pipeFile[0] = 0x00;
1681 /* Reset the lengths */
1684 *copyToLen = commandLen;
1688 thisEntry->command = NULL;
1689 thisEntry->redirects = NULL;
1690 thisEntry->pipeFile[0] = 0x00;
1693 /* Fill in other fields */
1694 thisEntry->nextcommand = NULL;
1695 thisEntry->prevDelim = prevDelim;
1696 thisEntry->bracketDepth = curDepth;
1698 (*lastEntry)->nextcommand = thisEntry;
1700 *output = thisEntry;
1702 *lastEntry = thisEntry;
1706 /***************************************************************************
1709 * Checks if the quote pointed to is the end-quote.
1713 * 1) The current parameter ends at EOL or at the beginning
1714 * of a redirection or pipe and not in a quote section.
1716 * 2) If the next character is a space and not in a quote section.
1718 * Returns TRUE if this is an end quote, and FALSE if it is not.
1721 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1723 int quoteCount = quoteIndex;
1726 /* If we are not in a quoted section, then we are not an end-quote */
1732 /* Check how many quotes are left for this parameter */
1733 for(i=0;quote[i];i++)
1740 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1741 else if(((quoteCount % 2) == 0)
1742 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1748 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1750 if(quoteIndex >= (quoteCount / 2))
1759 /***************************************************************************
1760 * WCMD_ReadAndParseLine
1762 * Either uses supplied input or
1763 * Reads a file from the handle, and then...
1764 * Parse the text buffer, splitting into separate commands
1765 * - unquoted && strings split 2 commands but the 2nd is flagged as
1767 * - ( as the first character just ups the bracket depth
1768 * - unquoted ) when bracket depth > 0 terminates a bracket and
1769 * adds a CMD_LIST structure with null command
1770 * - Anything else gets put into the command string (including
1773 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1777 WCHAR curString[MAXSTRING];
1778 int curStringLen = 0;
1779 WCHAR curRedirs[MAXSTRING];
1780 int curRedirsLen = 0;
1784 CMD_LIST *lastEntry = NULL;
1785 CMD_DELIMITERS prevDelim = CMD_NONE;
1786 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1787 static const WCHAR remCmd[] = {'r','e','m'};
1788 static const WCHAR forCmd[] = {'f','o','r'};
1789 static const WCHAR ifCmd[] = {'i','f'};
1790 static const WCHAR ifElse[] = {'e','l','s','e'};
1796 BOOL onlyWhiteSpace = FALSE;
1797 BOOL lastWasWhiteSpace = FALSE;
1798 BOOL lastWasDo = FALSE;
1799 BOOL lastWasIn = FALSE;
1800 BOOL lastWasElse = FALSE;
1801 BOOL lastWasRedirect = TRUE;
1802 BOOL lastWasCaret = FALSE;
1804 /* Allocate working space for a command read from keyboard, file etc */
1806 extraSpace = heap_alloc((MAXSTRING+1) * sizeof(WCHAR));
1809 WINE_ERR("Could not allocate memory for extraSpace\n");
1813 /* If initial command read in, use that, otherwise get input from handle */
1814 if (optionalcmd != NULL) {
1815 strcpyW(extraSpace, optionalcmd);
1816 } else if (readFrom == INVALID_HANDLE_VALUE) {
1817 WINE_FIXME("No command nor handle supplied\n");
1819 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1822 curPos = extraSpace;
1824 /* Handle truncated input - issue warning */
1825 if (strlenW(extraSpace) == MAXSTRING -1) {
1826 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1827 WCMD_output_asis_stderr(extraSpace);
1828 WCMD_output_asis_stderr(newlineW);
1831 /* Replace env vars if in a batch context */
1832 if (context) handleExpansion(extraSpace, FALSE);
1834 /* Skip preceding whitespace */
1835 while (*curPos == ' ' || *curPos == '\t') curPos++;
1837 /* Show prompt before batch line IF echo is on and in batch program */
1838 if (context && echo_mode && *curPos && (*curPos != '@')) {
1839 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1840 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1841 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1842 DWORD curr_size = strlenW(curPos);
1843 DWORD min_len = (curr_size < len ? curr_size : len);
1845 WCMD_output_asis(curPos);
1846 /* I don't know why Windows puts a space here but it does */
1847 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1848 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1849 curPos, min_len, echoDot, len) != CSTR_EQUAL
1850 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1851 curPos, min_len, echoCol, len) != CSTR_EQUAL)
1853 WCMD_output_asis(spaceW);
1855 WCMD_output_asis(newlineW);
1858 /* Skip repeated 'no echo' characters */
1859 while (*curPos == '@') curPos++;
1861 /* Start with an empty string, copying to the command string */
1864 curCopyTo = curString;
1865 curLen = &curStringLen;
1866 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1868 /* Parse every character on the line being processed */
1869 while (*curPos != 0x00) {
1874 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1875 lastWasWhiteSpace, onlyWhiteSpace);
1878 /* Prevent overflow caused by the caret escape char */
1879 if (*curLen >= MAXSTRING) {
1880 WINE_ERR("Overflow detected in command\n");
1884 /* Certain commands need special handling */
1885 if (curStringLen == 0 && curCopyTo == curString) {
1886 static const WCHAR forDO[] = {'d','o'};
1888 /* If command starts with 'rem ', ignore any &&, ( etc. */
1889 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1892 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1895 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1896 is only true in the command portion of the IF statement, but this
1897 should suffice for now
1898 FIXME: Silly syntax like "if 1(==1( (
1900 )" will be parsed wrong */
1901 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1904 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1905 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1908 onlyWhiteSpace = TRUE;
1909 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1910 (*curLen)+=keyw_len;
1914 /* In a for loop, the DO command will follow a close bracket followed by
1915 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1916 is then 0, and all whitespace is skipped */
1918 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1919 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1920 WINE_TRACE("Found 'DO '\n");
1922 onlyWhiteSpace = TRUE;
1923 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1924 (*curLen)+=keyw_len;
1928 } else if (curCopyTo == curString) {
1930 /* Special handling for the 'FOR' command */
1931 if (inFor && lastWasWhiteSpace) {
1932 static const WCHAR forIN[] = {'i','n'};
1934 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1936 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1937 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1938 WINE_TRACE("Found 'IN '\n");
1940 onlyWhiteSpace = TRUE;
1941 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1942 (*curLen)+=keyw_len;
1949 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1950 so just use the default processing ie skip character specific
1952 if (!inRem) thisChar = *curPos;
1953 else thisChar = 'X'; /* Character with no special processing */
1955 lastWasWhiteSpace = FALSE; /* Will be reset below */
1956 lastWasCaret = FALSE;
1960 case '=': /* drop through - ignore token delimiters at the start of a command */
1961 case ',': /* drop through - ignore token delimiters at the start of a command */
1962 case '\t':/* drop through - ignore token delimiters at the start of a command */
1964 /* If a redirect in place, it ends here */
1965 if (!inQuotes && !lastWasRedirect) {
1967 /* If finishing off a redirect, add a whitespace delimiter */
1968 if (curCopyTo == curRedirs) {
1969 curCopyTo[(*curLen)++] = ' ';
1971 curCopyTo = curString;
1972 curLen = &curStringLen;
1975 curCopyTo[(*curLen)++] = *curPos;
1978 /* Remember just processed whitespace */
1979 lastWasWhiteSpace = TRUE;
1983 case '>': /* drop through - handle redirect chars the same */
1985 /* Make a redirect start here */
1987 curCopyTo = curRedirs;
1988 curLen = &curRedirsLen;
1989 lastWasRedirect = TRUE;
1992 /* See if 1>, 2> etc, in which case we have some patching up
1993 to do (provided there's a preceding whitespace, and enough
1994 chars read so far) */
1995 if (curStringLen > 2
1996 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1997 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1999 curString[curStringLen] = 0x00;
2000 curCopyTo[(*curLen)++] = *(curPos-1);
2003 curCopyTo[(*curLen)++] = *curPos;
2005 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2006 do not process that ampersand as an AND operator */
2007 if (thisChar == '>' && *(curPos+1) == '&') {
2008 curCopyTo[(*curLen)++] = *(curPos+1);
2013 case '|': /* Pipe character only if not || */
2015 lastWasRedirect = FALSE;
2017 /* Add an entry to the command list */
2018 if (curStringLen > 0) {
2020 /* Add the current command */
2021 WCMD_addCommand(curString, &curStringLen,
2022 curRedirs, &curRedirsLen,
2023 &curCopyTo, &curLen,
2024 prevDelim, curDepth,
2025 &lastEntry, output);
2029 if (*(curPos+1) == '|') {
2030 curPos++; /* Skip other | */
2031 prevDelim = CMD_ONFAILURE;
2033 prevDelim = CMD_PIPE;
2036 curCopyTo[(*curLen)++] = *curPos;
2040 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2043 inQuotes++; /* Quotes within quotes are fun! */
2045 curCopyTo[(*curLen)++] = *curPos;
2046 lastWasRedirect = FALSE;
2049 case '(': /* If a '(' is the first non whitespace in a command portion
2050 ie start of line or just after &&, then we read until an
2051 unquoted ) is found */
2052 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2053 ", for(%d, In:%d, Do:%d)"
2054 ", if(%d, else:%d, lwe:%d)\n",
2057 inFor, lastWasIn, lastWasDo,
2058 inIf, inElse, lastWasElse);
2059 lastWasRedirect = FALSE;
2061 /* Ignore open brackets inside the for set */
2062 if (*curLen == 0 && !inIn) {
2065 /* If in quotes, ignore brackets */
2066 } else if (inQuotes) {
2067 curCopyTo[(*curLen)++] = *curPos;
2069 /* In a FOR loop, an unquoted '(' may occur straight after
2071 In an IF statement just handle it regardless as we don't
2073 In an ELSE statement, only allow it straight away after
2074 the ELSE and whitespace
2077 (inElse && lastWasElse && onlyWhiteSpace) ||
2078 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2080 /* If entering into an 'IN', set inIn */
2081 if (inFor && lastWasIn && onlyWhiteSpace) {
2082 WINE_TRACE("Inside an IN\n");
2086 /* Add the current command */
2087 WCMD_addCommand(curString, &curStringLen,
2088 curRedirs, &curRedirsLen,
2089 &curCopyTo, &curLen,
2090 prevDelim, curDepth,
2091 &lastEntry, output);
2095 curCopyTo[(*curLen)++] = *curPos;
2099 case '^': if (!inQuotes) {
2100 /* If we reach the end of the input, we need to wait for more */
2101 if (*(curPos+1) == 0x00) {
2102 lastWasCaret = TRUE;
2103 WINE_TRACE("Caret found at end of line\n");
2108 curCopyTo[(*curLen)++] = *curPos;
2111 case '&': if (!inQuotes) {
2112 lastWasRedirect = FALSE;
2114 /* Add an entry to the command list */
2115 if (curStringLen > 0) {
2117 /* Add the current command */
2118 WCMD_addCommand(curString, &curStringLen,
2119 curRedirs, &curRedirsLen,
2120 &curCopyTo, &curLen,
2121 prevDelim, curDepth,
2122 &lastEntry, output);
2126 if (*(curPos+1) == '&') {
2127 curPos++; /* Skip other & */
2128 prevDelim = CMD_ONSUCCESS;
2130 prevDelim = CMD_NONE;
2133 curCopyTo[(*curLen)++] = *curPos;
2137 case ')': if (!inQuotes && curDepth > 0) {
2138 lastWasRedirect = FALSE;
2140 /* Add the current command if there is one */
2143 /* Add the current command */
2144 WCMD_addCommand(curString, &curStringLen,
2145 curRedirs, &curRedirsLen,
2146 &curCopyTo, &curLen,
2147 prevDelim, curDepth,
2148 &lastEntry, output);
2151 /* Add an empty entry to the command list */
2152 prevDelim = CMD_NONE;
2153 WCMD_addCommand(NULL, &curStringLen,
2154 curRedirs, &curRedirsLen,
2155 &curCopyTo, &curLen,
2156 prevDelim, curDepth,
2157 &lastEntry, output);
2160 /* Leave inIn if necessary */
2161 if (inIn) inIn = FALSE;
2163 curCopyTo[(*curLen)++] = *curPos;
2167 lastWasRedirect = FALSE;
2168 curCopyTo[(*curLen)++] = *curPos;
2173 /* At various times we need to know if we have only skipped whitespace,
2174 so reset this variable and then it will remain true until a non
2175 whitespace is found */
2176 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2177 onlyWhiteSpace = FALSE;
2179 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2180 if (!lastWasWhiteSpace) {
2181 lastWasIn = lastWasDo = FALSE;
2184 /* If we have reached the end, add this command into the list
2185 Do not add command to list if escape char ^ was last */
2186 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2188 /* Add an entry to the command list */
2189 WCMD_addCommand(curString, &curStringLen,
2190 curRedirs, &curRedirsLen,
2191 &curCopyTo, &curLen,
2192 prevDelim, curDepth,
2193 &lastEntry, output);
2196 /* If we have reached the end of the string, see if bracketing or
2197 final caret is outstanding */
2198 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2199 readFrom != INVALID_HANDLE_VALUE) {
2202 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2204 prevDelim = CMD_NONE;
2206 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2207 extraData = extraSpace;
2209 /* Read more, skipping any blank lines */
2211 WINE_TRACE("Read more input\n");
2212 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2213 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2216 /* Edge case for carets - a completely blank line (i.e. was just
2217 CRLF) is oddly added as an LF but then more data is received (but
2220 if (*extraSpace == 0x00) {
2221 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2222 *extraData++ = '\r';
2227 } while (*extraData == 0x00);
2228 curPos = extraSpace;
2229 if (context) handleExpansion(extraSpace, FALSE);
2230 /* Continue to echo commands IF echo is on and in batch program */
2231 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2232 WCMD_output_asis(extraSpace);
2233 WCMD_output_asis(newlineW);
2238 /* Dump out the parsed output */
2239 WCMD_DumpCommands(*output);
2244 /***************************************************************************
2245 * WCMD_process_commands
2247 * Process all the commands read in so far
2249 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2254 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2256 /* Loop through the commands, processing them one by one */
2259 CMD_LIST *origCmd = thisCmd;
2261 /* If processing one bracket only, and we find the end bracket
2262 entry (or less), return */
2263 if (oneBracket && !thisCmd->command &&
2264 bdepth <= thisCmd->bracketDepth) {
2265 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2266 thisCmd, thisCmd->nextcommand);
2267 return thisCmd->nextcommand;
2270 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2271 about them and it will be handled in there)
2272 Also, skip over any batch labels (eg. :fred) */
2273 if (thisCmd->command && thisCmd->command[0] != ':') {
2274 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2275 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2278 /* Step on unless the command itself already stepped on */
2279 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2284 /***************************************************************************
2285 * WCMD_free_commands
2287 * Frees the storage held for a parsed command line
2288 * - This is not done in the process_commands, as eventually the current
2289 * pointer will be modified within the commands, and hence a single free
2290 * routine is simpler
2292 void WCMD_free_commands(CMD_LIST *cmds) {
2294 /* Loop through the commands, freeing them one by one */
2296 CMD_LIST *thisCmd = cmds;
2297 cmds = cmds->nextcommand;
2298 heap_free(thisCmd->command);
2299 heap_free(thisCmd->redirects);
2305 /*****************************************************************************
2306 * Main entry point. This is a console application so we have a main() not a
2310 int wmain (int argc, WCHAR *argvW[])
2313 WCHAR *cmdLine = NULL;
2315 WCHAR *argPos = NULL;
2320 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2321 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2322 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2328 /* Get the windows version being emulated */
2329 osv.dwOSVersionInfoSize = sizeof(osv);
2330 GetVersionExW(&osv);
2332 /* Pre initialize some messages */
2333 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2334 sprintf(osver, "%d.%d.%d (%s)", osv.dwMajorVersion, osv.dwMinorVersion,
2335 osv.dwBuildNumber, PACKAGE_VERSION);
2336 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2337 strcpyW(version_string, cmd);
2341 /* Can't use argc/argv as it will have stripped quotes from parameters
2342 * meaning cmd.exe /C echo "quoted string" is impossible
2344 cmdLine = GetCommandLineW();
2345 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2346 args = 1; /* start at first arg, skipping cmd.exe itself */
2348 opt_c = opt_k = opt_q = opt_s = FALSE;
2349 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2350 while (argPos && argPos[0] != 0x00)
2353 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
2354 if (argPos[0]!='/' || argPos[1]=='\0') {
2356 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2361 if (tolowerW(c)=='c') {
2363 } else if (tolowerW(c)=='q') {
2365 } else if (tolowerW(c)=='k') {
2367 } else if (tolowerW(c)=='s') {
2369 } else if (tolowerW(c)=='a') {
2370 unicodeOutput = FALSE;
2371 } else if (tolowerW(c)=='u') {
2372 unicodeOutput = TRUE;
2373 } else if (tolowerW(c)=='t' && argPos[2]==':') {
2374 opt_t=strtoulW(&argPos[3], NULL, 16);
2375 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2376 /* Ignored for compatibility with Windows */
2379 if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') {
2381 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2383 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2385 /* Do not step to next parameter, instead carry on parsing this one */
2389 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2394 static const WCHAR eoff[] = {'O','F','F','\0'};
2398 /* Until we start to read from the keyboard, stay as non-interactive */
2399 interactive = FALSE;
2401 if (opt_c || opt_k) {
2403 WCHAR *q1 = NULL,*q2 = NULL,*p;
2405 /* Handle very edge case error scenario, "cmd.exe /c" ie when there are no
2406 * parameters after the /C or /K by pretending there was a single space */
2407 if (argPos == NULL) argPos = (WCHAR *)spaceW;
2409 /* Build the command to execute - It is what is left in argPos */
2410 len = strlenW(argPos);
2413 cmd = heap_strdupW(argPos);
2415 /* opt_s left unflagged if the command starts with and contains exactly
2416 * one quoted string (exactly two quote characters). The quoted string
2417 * must be an executable name that has whitespace and must not have the
2418 * following characters: &<>()@^| */
2421 /* 1. Confirm there is at least one quote */
2422 q1 = strchrW(argPos, '"');
2427 /* 2. Confirm there is a second quote */
2428 q2 = strchrW(q1+1, '"');
2433 /* 3. Ensure there are no more quotes */
2434 if (strchrW(q2+1, '"')) opt_s=1;
2437 /* check first parameter for a space and invalid characters. There must not be any
2438 * invalid characters, but there must be one or more whitespace */
2443 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2444 || *p=='@' || *p=='^' || *p=='|') {
2448 if (*p==' ' || *p=='\t')
2454 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2456 /* Finally, we only stay in new mode IF the first parameter is quoted and
2457 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2459 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2460 WCHAR pathext[MAXSTRING];
2463 /* Now extract PATHEXT */
2464 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
2465 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
2466 strcpyW (pathext, dfltPathExt);
2469 /* If the supplied parameter has any directory information, look there */
2470 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2471 if (strchrW(thisArg, '\\') != NULL) {
2473 GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
2474 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2475 p = string + strlenW(string);
2477 /* Does file exist with this name? */
2478 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2479 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2482 WCHAR *thisExt = pathext;
2484 /* No - try with each of the PATHEXT extensions */
2485 while (!found && thisExt) {
2486 WCHAR *nextExt = strchrW(thisExt, ';');
2489 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2490 p[(nextExt-thisExt)] = 0x00;
2491 thisExt = nextExt+1;
2493 strcpyW(p, thisExt);
2497 /* Does file exist with this extension appended? */
2498 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2499 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2505 /* Otherwise we now need to look in the path to see if we can find it */
2507 p = thisArg + strlenW(thisArg);
2509 /* Does file exist with this name? */
2510 if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2511 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2514 WCHAR *thisExt = pathext;
2516 /* No - try with each of the PATHEXT extensions */
2517 while (!found && thisExt) {
2518 WCHAR *nextExt = strchrW(thisExt, ';');
2522 nextExt = nextExt+1;
2527 /* Does file exist with this extension? */
2528 if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2529 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2530 wine_dbgstr_w(thisExt));
2538 /* If not found, drop back to old behaviour */
2540 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2546 /* strip first and last quote characters if opt_s; check for invalid
2547 * executable is done later */
2548 if (opt_s && *cmd=='\"')
2549 WCMD_strip_quotes(cmd);
2552 /* Save cwd into appropriate env var (Must be before the /c processing */
2553 GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
2554 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2555 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2556 wsprintfW(envvar, fmt, string[0]);
2557 SetEnvironmentVariableW(envvar, string);
2558 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2562 /* If we do a "cmd /c command", we don't want to allocate a new
2563 * console since the command returns immediately. Rather, we use
2564 * the currently allocated input and output handles. This allows
2565 * us to pipe to and read from the command interpreter.
2568 /* Parse the command string, without reading any more input */
2569 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2570 WCMD_process_commands(toExecute, FALSE, FALSE);
2571 WCMD_free_commands(toExecute);
2578 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2579 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2580 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2582 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2584 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2585 defaultColor = opt_t & 0xFF;
2590 /* Check HKCU\Software\Microsoft\Command Processor
2591 Then HKLM\Software\Microsoft\Command Processor
2592 for defaultcolour value
2593 Note Can be supplied as DWORD or REG_SZ
2594 Note2 When supplied as REG_SZ it's in decimal!!! */
2597 DWORD value=0, size=4;
2598 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2599 'M','i','c','r','o','s','o','f','t','\\',
2600 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2601 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2603 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2604 0, KEY_READ, &key) == ERROR_SUCCESS) {
2607 /* See if DWORD or REG_SZ */
2608 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2609 NULL, NULL) == ERROR_SUCCESS) {
2610 if (type == REG_DWORD) {
2611 size = sizeof(DWORD);
2612 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2613 (LPBYTE)&value, &size);
2614 } else if (type == REG_SZ) {
2615 size = sizeof(strvalue)/sizeof(WCHAR);
2616 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2617 (LPBYTE)strvalue, &size);
2618 value = strtoulW(strvalue, NULL, 10);
2624 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2625 0, KEY_READ, &key) == ERROR_SUCCESS) {
2628 /* See if DWORD or REG_SZ */
2629 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2630 NULL, NULL) == ERROR_SUCCESS) {
2631 if (type == REG_DWORD) {
2632 size = sizeof(DWORD);
2633 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2634 (LPBYTE)&value, &size);
2635 } else if (type == REG_SZ) {
2636 size = sizeof(strvalue)/sizeof(WCHAR);
2637 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2638 (LPBYTE)strvalue, &size);
2639 value = strtoulW(strvalue, NULL, 10);
2645 /* If one found, set the screen to that colour */
2646 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2647 defaultColor = value & 0xFF;
2655 /* Parse the command string, without reading any more input */
2656 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2657 WCMD_process_commands(toExecute, FALSE, FALSE);
2658 WCMD_free_commands(toExecute);
2664 * Loop forever getting commands and executing them.
2667 SetEnvironmentVariableW(promptW, defaultpromptW);
2669 if (!opt_k) WCMD_version ();
2672 /* Read until EOF (which for std input is never, but if redirect
2673 in place, may occur */
2674 if (echo_mode) WCMD_show_prompt();
2675 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2677 WCMD_process_commands(toExecute, FALSE, FALSE);
2678 WCMD_free_commands(toExecute);