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 = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
79 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
84 /*******************************************************************
85 * WCMD_output_asis_len - send output to current standard output
87 * Output a formatted unicode string. Ideally this will go to the console
88 * and hence required WriteConsoleW to output it, however if file i/o is
89 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
91 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
96 /* If nothing to write, return (MORE does this sometimes) */
99 /* Try to write as unicode assuming it is to a console */
100 res = WriteConsoleW(device, message, len, &nOut, NULL);
102 /* If writing to console fails, assume its file
103 i/o so convert to OEM codepage and output */
105 BOOL usedDefaultChar = FALSE;
106 DWORD convertedChars;
109 if (!unicodeOutput) {
111 if (!(buffer = get_file_buffer()))
114 /* Convert to OEM, then output */
115 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
116 len, buffer, MAX_WRITECONSOLE_SIZE,
117 "?", &usedDefaultChar);
118 WriteFile(device, buffer, convertedChars,
121 WriteFile(device, message, len*sizeof(WCHAR),
128 /*******************************************************************
129 * WCMD_output - send output to current standard output device.
133 void CDECL WCMD_output (const WCHAR *format, ...) {
139 __ms_va_start(ap,format);
140 SetLastError(NO_ERROR);
142 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
143 format, 0, 0, (LPWSTR)&string, 0, &ap);
145 if (len == 0 && GetLastError() != NO_ERROR)
146 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
149 WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
154 /*******************************************************************
155 * WCMD_output_stderr - send output to current standard error device.
159 void CDECL WCMD_output_stderr (const WCHAR *format, ...) {
165 __ms_va_start(ap,format);
166 SetLastError(NO_ERROR);
168 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
169 format, 0, 0, (LPWSTR)&string, 0, &ap);
171 if (len == 0 && GetLastError() != NO_ERROR)
172 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
175 WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
180 /*******************************************************************
181 * WCMD_format_string - allocate a buffer and format a string
185 WCHAR* CDECL WCMD_format_string (const WCHAR *format, ...) {
191 __ms_va_start(ap,format);
192 SetLastError(NO_ERROR);
193 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
194 format, 0, 0, (LPWSTR)&string, 0, &ap);
196 if (len == 0 && GetLastError() != NO_ERROR) {
197 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
198 string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
204 void WCMD_enter_paged_mode(const WCHAR *msg)
206 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
208 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
209 max_height = consoleInfo.dwSize.Y;
210 max_width = consoleInfo.dwSize.X;
218 pagedMessage = (msg==NULL)? anykey : msg;
221 void WCMD_leave_paged_mode(void)
227 /***************************************************************************
230 * Read characters in from a console/file, returning result in Unicode
232 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
237 if (WCMD_is_console_handle(hIn))
238 /* Try to read from console as Unicode */
239 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
241 /* We assume it's a file handle and read then convert from assumed OEM codepage */
242 if (!(buffer = get_file_buffer()))
245 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
248 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
253 /*******************************************************************
254 * WCMD_output_asis_handle
256 * Send output to specified handle without formatting e.g. when message contains '%'
258 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
262 HANDLE handle = GetStdHandle(std_handle);
267 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
271 if (*ptr == '\n') ptr++;
272 WCMD_output_asis_len(message, ptr - message, handle);
274 if (++line_count >= max_height - 1) {
276 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
277 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
279 } while (((message = ptr) != NULL) && (*ptr));
281 WCMD_output_asis_len(message, lstrlenW(message), handle);
285 /*******************************************************************
288 * Send output to current standard output device, without formatting
289 * e.g. when message contains '%'
291 void WCMD_output_asis (const WCHAR *message) {
292 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
295 /*******************************************************************
296 * WCMD_output_asis_stderr
298 * Send output to current standard error device, without formatting
299 * e.g. when message contains '%'
301 void WCMD_output_asis_stderr (const WCHAR *message) {
302 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
305 /****************************************************************************
308 * Print the message for GetLastError
311 void WCMD_print_error (void) {
316 error_code = GetLastError ();
317 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
318 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
320 WINE_FIXME ("Cannot display message for error %d, status %d\n",
321 error_code, GetLastError());
325 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
326 GetStdHandle(STD_ERROR_HANDLE));
327 LocalFree (lpMsgBuf);
328 WCMD_output_asis_len (newlineW, lstrlenW(newlineW),
329 GetStdHandle(STD_ERROR_HANDLE));
333 /******************************************************************************
336 * Display the prompt on STDout
340 static void WCMD_show_prompt (void) {
343 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
346 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
348 len = GetEnvironmentVariableW(envPrompt, prompt_string,
349 sizeof(prompt_string)/sizeof(WCHAR));
350 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
351 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
352 strcpyW (prompt_string, dfltPrompt);
366 switch (toupper(*p)) {
380 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
399 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
405 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
418 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
422 strcatW (q, version_string);
429 if (pushd_directories) {
430 memset(q, '+', pushd_directories->u.stackdepth);
431 q = q + pushd_directories->u.stackdepth;
439 WCMD_output_asis (out_string);
443 /*************************************************************************
445 * A wide version of strdup as its missing from unicode.h
447 WCHAR *WCMD_strdupW(const WCHAR *input) {
448 int len=strlenW(input)+1;
449 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
450 memcpy(result, input, len * sizeof(WCHAR));
454 /*************************************************************************
456 * Replaces a portion of a Unicode string with the specified string.
457 * It's up to the caller to ensure there is enough space in the
458 * destination buffer.
460 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
463 len=insert ? lstrlenW(insert) : 0;
464 if (start+len != next)
465 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
467 memcpy(start, insert, len * sizeof(*insert));
470 /***************************************************************************
471 * WCMD_skip_leading_spaces
473 * Return a pointer to the first non-whitespace character of string.
474 * Does not modify the input string.
476 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
481 while (*ptr == ' ' || *ptr == '\t') ptr++;
485 /***************************************************************************
486 * WCMD_keyword_ws_found
488 * Checks if the string located at ptr matches a keyword (of length len)
489 * followed by a whitespace character (space or tab)
491 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
492 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
493 ptr, len, keyword, len) == CSTR_EQUAL)
494 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
497 /*************************************************************************
500 * Remove first and last quote WCHARacters, preserving all other text
502 void WCMD_strip_quotes(WCHAR *cmd) {
503 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
504 while((*dest=*src) != '\0') {
511 while ((*dest++=*lastq++) != 0)
517 /*************************************************************************
518 * WCMD_is_magic_envvar
519 * Return TRUE if s is '%'magicvar'%'
520 * and is not masked by a real environment variable.
523 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
528 return FALSE; /* Didn't begin with % */
530 if (len < 2 || s[len-1] != '%')
531 return FALSE; /* Didn't end with another % */
533 if (CompareStringW(LOCALE_USER_DEFAULT,
534 NORM_IGNORECASE | SORT_STRINGSORT,
535 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
536 /* Name doesn't match. */
540 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
541 /* Masked by real environment variable. */
548 /*************************************************************************
551 * Expands environment variables, allowing for WCHARacter substitution
553 static WCHAR *WCMD_expand_envvar(WCHAR *start)
555 WCHAR *endOfVar = NULL, *s;
556 WCHAR *colonpos = NULL;
557 WCHAR thisVar[MAXSTRING];
558 WCHAR thisVarContents[MAXSTRING];
559 WCHAR savedchar = 0x00;
562 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
563 static const WCHAR Date[] = {'D','A','T','E','\0'};
564 static const WCHAR Time[] = {'T','I','M','E','\0'};
565 static const WCHAR Cd[] = {'C','D','\0'};
566 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
567 static const WCHAR Delims[] = {'%',':','\0'};
569 WINE_TRACE("Expanding: %s\n", wine_dbgstr_w(start));
571 /* Find the end of the environment variable, and extract name */
572 endOfVar = strpbrkW(start+1, Delims);
574 if (endOfVar == NULL || *endOfVar==' ') {
576 /* In batch program, missing terminator for % and no following
577 ':' just removes the '%' */
579 WCMD_strsubstW(start, start + 1, NULL, 0);
583 /* In command processing, just ignore it - allows command line
584 syntax like: for %i in (a.a) do echo %i */
589 /* If ':' found, process remaining up until '%' (or stop at ':' if
591 if (*endOfVar==':') {
592 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
593 if (endOfVar2 != NULL) endOfVar = endOfVar2;
596 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
597 thisVar[(endOfVar - start)+1] = 0x00;
598 colonpos = strchrW(thisVar+1, ':');
600 /* If there's complex substitution, just need %var% for now
601 to get the expanded data to play with */
604 savedchar = *(colonpos+1);
605 *(colonpos+1) = 0x00;
608 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
610 /* Expand to contents, if unchanged, return */
611 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
612 /* override if existing env var called that name */
613 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
614 static const WCHAR fmt[] = {'%','d','\0'};
615 wsprintfW(thisVarContents, fmt, errorlevel);
616 len = strlenW(thisVarContents);
617 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
618 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
619 NULL, thisVarContents, MAXSTRING);
620 len = strlenW(thisVarContents);
621 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
622 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
623 NULL, thisVarContents, MAXSTRING);
624 len = strlenW(thisVarContents);
625 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
626 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
627 len = strlenW(thisVarContents);
628 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
629 static const WCHAR fmt[] = {'%','d','\0'};
630 wsprintfW(thisVarContents, fmt, rand() % 32768);
631 len = strlenW(thisVarContents);
634 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
635 sizeof(thisVarContents)/sizeof(WCHAR));
641 /* In a batch program, unknown env vars are replaced with nothing,
642 note syntax %garbage:1,3% results in anything after the ':'
644 From the command line, you just get back what you entered */
645 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
647 /* Restore the complex part after the compare */
650 *(colonpos+1) = savedchar;
653 /* Command line - just ignore this */
654 if (context == NULL) return endOfVar+1;
657 /* Batch - replace unknown env var with nothing */
658 if (colonpos == NULL) {
659 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
661 len = strlenW(thisVar);
662 thisVar[len-1] = 0x00;
663 /* If %:...% supplied, : is retained */
664 if (colonpos == thisVar+1) {
665 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
667 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
674 /* See if we need to do complex substitution (any ':'s), if not
675 then our work here is done */
676 if (colonpos == NULL) {
677 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
681 /* Restore complex bit */
683 *(colonpos+1) = savedchar;
686 Handle complex substitutions:
687 xxx=yyy (replace xxx with yyy)
688 *xxx=yyy (replace up to and including xxx with yyy)
689 ~x (from x WCHARs in)
690 ~-x (from x WCHARs from the end)
691 ~x,y (from x WCHARs in for y WCHARacters)
692 ~x,-y (from x WCHARs in until y WCHARacters from the end)
695 /* ~ is substring manipulation */
696 if (savedchar == '~') {
698 int substrposition, substrlength = 0;
699 WCHAR *commapos = strchrW(colonpos+2, ',');
702 substrposition = atolW(colonpos+2);
703 if (commapos) substrlength = atolW(commapos+1);
706 if (substrposition >= 0) {
707 startCopy = &thisVarContents[min(substrposition, len)];
709 startCopy = &thisVarContents[max(0, len+substrposition-1)];
712 if (commapos == NULL) {
714 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
715 } else if (substrlength < 0) {
717 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
718 if (copybytes > len) copybytes = len;
719 else if (copybytes < 0) copybytes = 0;
720 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
722 substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
723 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
726 /* search and replace manipulation */
728 WCHAR *equalspos = strstrW(colonpos, equalW);
729 WCHAR *replacewith = equalspos+1;
734 if (equalspos == NULL) return start+1;
735 s = WCMD_strdupW(endOfVar + 1);
737 /* Null terminate both strings */
738 thisVar[strlenW(thisVar)-1] = 0x00;
741 /* Since we need to be case insensitive, copy the 2 buffers */
742 searchIn = WCMD_strdupW(thisVarContents);
743 CharUpperBuffW(searchIn, strlenW(thisVarContents));
744 searchFor = WCMD_strdupW(colonpos+1);
745 CharUpperBuffW(searchFor, strlenW(colonpos+1));
747 /* Handle wildcard case */
748 if (*(colonpos+1) == '*') {
749 /* Search for string to replace */
750 found = strstrW(searchIn, searchFor+1);
754 strcpyW(start, replacewith);
755 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
759 strcpyW(start, thisVarContents);
764 /* Loop replacing all instances */
765 WCHAR *lastFound = searchIn;
766 WCHAR *outputposn = start;
769 while ((found = strstrW(lastFound, searchFor))) {
770 lstrcpynW(outputposn,
771 thisVarContents + (lastFound-searchIn),
772 (found - lastFound)+1);
773 outputposn = outputposn + (found - lastFound);
774 strcatW(outputposn, replacewith);
775 outputposn = outputposn + strlenW(replacewith);
776 lastFound = found + strlenW(searchFor);
779 thisVarContents + (lastFound-searchIn));
780 strcatW(outputposn, s);
782 HeapFree(GetProcessHeap(), 0, s);
783 HeapFree(GetProcessHeap(), 0, searchIn);
784 HeapFree(GetProcessHeap(), 0, searchFor);
789 /*****************************************************************************
790 * Expand the command. Native expands lines from batch programs as they are
791 * read in and not again, except for 'for' variable substitution.
792 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
794 static void handleExpansion(WCHAR *cmd, BOOL justFors) {
796 /* For commands in a context (batch program): */
797 /* Expand environment variables in a batch file %{0-9} first */
798 /* including support for any ~ modifiers */
800 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
801 /* names allowing environment variable overrides */
802 /* NOTE: To support the %PATH:xxx% syntax, also perform */
803 /* manual expansion of environment variables here */
809 /* Display the FOR variables in effect */
811 if (forloopcontext.variable[i]) {
812 WINE_TRACE("FOR variable context: %c = '%s'\n",
813 i<26?i+'a':(i-26)+'A',
814 wine_dbgstr_w(forloopcontext.variable[i]));
818 while ((p = strchrW(p, '%'))) {
820 WINE_TRACE("Translate command:%s %d (at: %s)\n",
821 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
824 /* Don't touch %% unless its in Batch */
825 if (!justFors && *(p+1) == '%') {
827 WCMD_strsubstW(p, p+1, NULL, 0);
831 /* Replace %~ modifications if in batch program */
832 } else if (*(p+1) == '~') {
833 WCMD_HandleTildaModifiers(&p, justFors);
836 /* Replace use of %0...%9 if in batch program*/
837 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
838 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
840 WCMD_strsubstW(p, p+2, t, -1);
842 /* Replace use of %* if in batch program*/
843 } else if (!justFors && context && *(p+1)=='*') {
844 WCHAR *startOfParms = NULL;
845 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
846 if (startOfParms != NULL) {
847 startOfParms += strlenW(thisParm);
848 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
849 WCMD_strsubstW(p, p+2, startOfParms, -1);
851 WCMD_strsubstW(p, p+2, NULL, 0);
854 int forvaridx = FOR_VAR_IDX(*(p+1));
855 if (forvaridx != -1 && forloopcontext.variable[forvaridx]) {
856 /* Replace the 2 characters, % and for variable character */
857 WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
858 } else if (!justFors) {
859 p = WCMD_expand_envvar(p);
861 /* In a FOR loop, see if this is the variable to replace */
862 } else { /* Ignore %'s on second pass of batch program */
872 /*******************************************************************
873 * WCMD_parse - parse a command into parameters and qualifiers.
875 * On exit, all qualifiers are concatenated into q, the first string
876 * not beginning with "/" is in p1 and the
877 * second in p2. Any subsequent non-qualifier strings are lost.
878 * Parameters in quotes are handled.
880 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
884 *q = *p1 = *p2 = '\0';
889 while ((*s != '\0') && (*s != ' ') && *s != '/') {
890 *q++ = toupperW (*s++);
900 while ((*s != '\0') && (*s != '"')) {
901 if (p == 0) *p1++ = *s++;
902 else if (p == 1) *p2++ = *s++;
905 if (p == 0) *p1 = '\0';
906 if (p == 1) *p2 = '\0';
913 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
914 && (*s != '=') && (*s != ',') ) {
915 if (p == 0) *p1++ = *s++;
916 else if (p == 1) *p2++ = *s++;
919 /* Skip concurrent parms */
920 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
922 if (p == 0) *p1 = '\0';
923 if (p == 1) *p2 = '\0';
929 static void init_msvcrt_io_block(STARTUPINFOW* st)
932 /* fetch the parent MSVCRT info block if any, so that the child can use the
933 * same handles as its grand-father
935 st_p.cb = sizeof(STARTUPINFOW);
936 GetStartupInfoW(&st_p);
937 st->cbReserved2 = st_p.cbReserved2;
938 st->lpReserved2 = st_p.lpReserved2;
939 if (st_p.cbReserved2 && st_p.lpReserved2)
941 /* Override the entries for fd 0,1,2 if we happened
942 * to change those std handles (this depends on the way cmd sets
943 * its new input & output handles)
945 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
946 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
949 unsigned num = *(unsigned*)st_p.lpReserved2;
950 char* flags = (char*)(ptr + sizeof(unsigned));
951 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
953 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
954 st->cbReserved2 = sz;
955 st->lpReserved2 = ptr;
957 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
958 if (num <= 0 || (flags[0] & WX_OPEN))
960 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
963 if (num <= 1 || (flags[1] & WX_OPEN))
965 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
968 if (num <= 2 || (flags[2] & WX_OPEN))
970 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 called in a batch program context */
1205 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1206 GetExitCodeProcess (pe.hProcess, &errorlevel);
1207 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1209 CloseHandle(pe.hProcess);
1210 CloseHandle(pe.hThread);
1216 /* Not found anywhere - were we called? */
1218 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1220 /* Parse the command string, without reading any more input */
1221 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1222 WCMD_process_commands(toExecute, FALSE, called);
1223 WCMD_free_commands(toExecute);
1228 /* Not found anywhere - give up */
1229 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1231 /* If a command fails to launch, it sets errorlevel 9009 - which
1232 does not seem to have any associated constant definition */
1238 /*****************************************************************************
1239 * Process one command. If the command is EXIT this routine does not return.
1240 * We will recurse through here executing batch files.
1241 * Note: If call is used to a non-existing program, we reparse the line and
1242 * try to run it as an internal command. 'retrycall' represents whether
1243 * we are attempting this retry.
1245 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1246 CMD_LIST **cmdList, BOOL retrycall)
1248 WCHAR *cmd, *p, *redir;
1250 DWORD count, creationDisposition;
1253 SECURITY_ATTRIBUTES sa;
1254 WCHAR *new_cmd = NULL;
1255 WCHAR *new_redir = NULL;
1256 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1257 GetStdHandle (STD_OUTPUT_HANDLE),
1258 GetStdHandle (STD_ERROR_HANDLE)};
1259 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1262 BOOL prev_echo_mode, piped = FALSE;
1264 WINE_TRACE("command on entry:%s (%p)\n",
1265 wine_dbgstr_w(command), cmdList);
1267 /* If the next command is a pipe then we implement pipes by redirecting
1268 the output from this command to a temp file and input into the
1269 next command from that temp file.
1270 FIXME: Use of named pipes would make more sense here as currently this
1271 process has to finish before the next one can start but this requires
1272 a change to not wait for the first app to finish but rather the pipe */
1273 if (cmdList && (*cmdList)->nextcommand &&
1274 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1276 WCHAR temp_path[MAX_PATH];
1277 static const WCHAR cmdW[] = {'C','M','D','\0'};
1279 /* Remember piping is in action */
1280 WINE_TRACE("Output needs to be piped\n");
1283 /* Generate a unique temporary filename */
1284 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1285 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1286 WINE_TRACE("Using temporary file of %s\n",
1287 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1290 /* Move copy of the command onto the heap so it can be expanded */
1291 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1294 WINE_ERR("Could not allocate memory for new_cmd\n");
1297 strcpyW(new_cmd, command);
1299 /* Move copy of the redirects onto the heap so it can be expanded */
1300 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1303 WINE_ERR("Could not allocate memory for new_redir\n");
1304 HeapFree( GetProcessHeap(), 0, new_cmd );
1308 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1310 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1311 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1312 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1314 strcpyW(new_redir, redirects);
1317 /* Expand variables in command line mode only (batch mode will
1318 be expanded as the line is read in, except for 'for' loops) */
1319 handleExpansion(new_cmd, (context != NULL));
1320 handleExpansion(new_redir, (context != NULL));
1324 * Changing default drive has to be handled as a special case.
1327 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1329 WCHAR dir[MAX_PATH];
1331 /* According to MSDN CreateProcess docs, special env vars record
1332 the current directory on each drive, in the form =C:
1333 so see if one specified, and if so go back to it */
1334 strcpyW(envvar, equalW);
1335 strcatW(envvar, cmd);
1336 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1337 static const WCHAR fmt[] = {'%','s','\\','\0'};
1338 wsprintfW(cmd, fmt, cmd);
1339 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1341 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1342 status = SetCurrentDirectoryW(cmd);
1343 if (!status) WCMD_print_error ();
1344 HeapFree( GetProcessHeap(), 0, cmd );
1345 HeapFree( GetProcessHeap(), 0, new_redir );
1349 sa.nLength = sizeof(sa);
1350 sa.lpSecurityDescriptor = NULL;
1351 sa.bInheritHandle = TRUE;
1354 * Redirect stdin, stdout and/or stderr if required.
1357 /* STDIN could come from a preceding pipe, so delete on close if it does */
1358 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1359 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1360 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1361 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1362 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1363 if (h == INVALID_HANDLE_VALUE) {
1364 WCMD_print_error ();
1365 HeapFree( GetProcessHeap(), 0, cmd );
1366 HeapFree( GetProcessHeap(), 0, new_redir );
1369 SetStdHandle (STD_INPUT_HANDLE, h);
1371 /* No need to remember the temporary name any longer once opened */
1372 (*cmdList)->pipeFile[0] = 0x00;
1374 /* Otherwise STDIN could come from a '<' redirect */
1375 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1376 h = CreateFileW(WCMD_parameter(++p, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1377 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1378 if (h == INVALID_HANDLE_VALUE) {
1379 WCMD_print_error ();
1380 HeapFree( GetProcessHeap(), 0, cmd );
1381 HeapFree( GetProcessHeap(), 0, new_redir );
1384 SetStdHandle (STD_INPUT_HANDLE, h);
1387 /* Scan the whole command looking for > and 2> */
1389 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1392 if (p > redir && (*(p-1)=='2'))
1399 creationDisposition = OPEN_ALWAYS;
1403 creationDisposition = CREATE_ALWAYS;
1406 /* Add support for 2>&1 */
1409 int idx = *(p+1) - '0';
1411 if (DuplicateHandle(GetCurrentProcess(),
1412 GetStdHandle(idx_stdhandles[idx]),
1413 GetCurrentProcess(),
1415 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1416 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1418 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1421 WCHAR *param = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
1422 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1423 FILE_ATTRIBUTE_NORMAL, NULL);
1424 if (h == INVALID_HANDLE_VALUE) {
1425 WCMD_print_error ();
1426 HeapFree( GetProcessHeap(), 0, cmd );
1427 HeapFree( GetProcessHeap(), 0, new_redir );
1430 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1431 INVALID_SET_FILE_POINTER) {
1432 WCMD_print_error ();
1434 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1437 SetStdHandle (idx_stdhandles[handle], h);
1441 * Strip leading whitespaces, and a '@' if supplied
1443 whichcmd = WCMD_skip_leading_spaces(cmd);
1444 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1445 if (whichcmd[0] == '@') whichcmd++;
1448 * Check if the command entered is internal. If it is, pass the rest of the
1449 * line down to the command. If not try to run a program.
1453 while (IsCharAlphaNumericW(whichcmd[count])) {
1456 for (i=0; i<=WCMD_EXIT; i++) {
1457 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1458 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1460 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1461 WCMD_parse (p, quals, param1, param2);
1462 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1464 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1465 /* this is a help request for a builtin program */
1467 memcpy(p, whichcmd, count * sizeof(WCHAR));
1479 WCMD_setshow_default (p);
1482 WCMD_clear_screen ();
1491 WCMD_setshow_date ();
1501 WCMD_echo(&whichcmd[count]);
1504 WCMD_goto (cmdList);
1510 WCMD_volume (TRUE, p);
1514 WCMD_create_dir (p);
1520 WCMD_setshow_path (p);
1526 WCMD_setshow_prompt ();
1536 WCMD_remove_dir (p);
1545 WCMD_setshow_env (p);
1554 WCMD_setshow_time ();
1557 if (strlenW(&whichcmd[count]) > 0)
1558 WCMD_title(&whichcmd[count+1]);
1564 WCMD_output_asis(newlineW);
1571 WCMD_volume (FALSE, p);
1580 WCMD_assoc(p, TRUE);
1586 WCMD_assoc(p, FALSE);
1595 WCMD_exit (cmdList);
1599 /* Very oddly, probably because of all the special parsing required for
1600 these two commands, neither for nor if are supported when called,
1601 ie call if 1==1... will fail. */
1603 if (i==WCMD_FOR) WCMD_for (p, cmdList);
1604 else if (i==WCMD_IF) WCMD_if (p, cmdList);
1607 /* else: drop through */
1609 prev_echo_mode = echo_mode;
1610 WCMD_run_program (whichcmd, FALSE);
1611 echo_mode = prev_echo_mode;
1613 HeapFree( GetProcessHeap(), 0, cmd );
1614 HeapFree( GetProcessHeap(), 0, new_redir );
1616 /* Restore old handles */
1617 for (i=0; i<3; i++) {
1618 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1619 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1620 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1625 /*************************************************************************
1627 * Load a string from the resource file, handling any error
1628 * Returns string retrieved from resource file
1630 WCHAR *WCMD_LoadMessage(UINT id) {
1631 static WCHAR msg[2048];
1632 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1634 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1635 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1636 strcpyW(msg, failedMsg);
1641 /***************************************************************************
1644 * Dumps out the parsed command line to ensure syntax is correct
1646 static void WCMD_DumpCommands(CMD_LIST *commands) {
1647 CMD_LIST *thisCmd = commands;
1649 WINE_TRACE("Parsed line:\n");
1650 while (thisCmd != NULL) {
1651 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1654 thisCmd->bracketDepth,
1655 thisCmd->nextcommand,
1656 wine_dbgstr_w(thisCmd->command),
1657 wine_dbgstr_w(thisCmd->redirects));
1658 thisCmd = thisCmd->nextcommand;
1662 /***************************************************************************
1665 * Adds a command to the current command list
1667 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1668 WCHAR *redirs, int *redirLen,
1669 WCHAR **copyTo, int **copyToLen,
1670 CMD_DELIMITERS prevDelim, int curDepth,
1671 CMD_LIST **lastEntry, CMD_LIST **output) {
1673 CMD_LIST *thisEntry = NULL;
1675 /* Allocate storage for command */
1676 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1678 /* Copy in the command */
1680 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1681 (*commandLen+1) * sizeof(WCHAR));
1682 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1683 thisEntry->command[*commandLen] = 0x00;
1685 /* Copy in the redirects */
1686 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1687 (*redirLen+1) * sizeof(WCHAR));
1688 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1689 thisEntry->redirects[*redirLen] = 0x00;
1690 thisEntry->pipeFile[0] = 0x00;
1692 /* Reset the lengths */
1695 *copyToLen = commandLen;
1699 thisEntry->command = NULL;
1700 thisEntry->redirects = NULL;
1701 thisEntry->pipeFile[0] = 0x00;
1704 /* Fill in other fields */
1705 thisEntry->nextcommand = NULL;
1706 thisEntry->prevDelim = prevDelim;
1707 thisEntry->bracketDepth = curDepth;
1709 (*lastEntry)->nextcommand = thisEntry;
1711 *output = thisEntry;
1713 *lastEntry = thisEntry;
1717 /***************************************************************************
1720 * Checks if the quote pointed to is the end-quote.
1724 * 1) The current parameter ends at EOL or at the beginning
1725 * of a redirection or pipe and not in a quote section.
1727 * 2) If the next character is a space and not in a quote section.
1729 * Returns TRUE if this is an end quote, and FALSE if it is not.
1732 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1734 int quoteCount = quoteIndex;
1737 /* If we are not in a quoted section, then we are not an end-quote */
1743 /* Check how many quotes are left for this parameter */
1744 for(i=0;quote[i];i++)
1751 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1752 else if(((quoteCount % 2) == 0)
1753 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1759 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1761 if(quoteIndex >= (quoteCount / 2))
1770 /***************************************************************************
1771 * WCMD_ReadAndParseLine
1773 * Either uses supplied input or
1774 * Reads a file from the handle, and then...
1775 * Parse the text buffer, splitting into separate commands
1776 * - unquoted && strings split 2 commands but the 2nd is flagged as
1778 * - ( as the first character just ups the bracket depth
1779 * - unquoted ) when bracket depth > 0 terminates a bracket and
1780 * adds a CMD_LIST structure with null command
1781 * - Anything else gets put into the command string (including
1784 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1788 WCHAR curString[MAXSTRING];
1789 int curStringLen = 0;
1790 WCHAR curRedirs[MAXSTRING];
1791 int curRedirsLen = 0;
1795 CMD_LIST *lastEntry = NULL;
1796 CMD_DELIMITERS prevDelim = CMD_NONE;
1797 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1798 static const WCHAR remCmd[] = {'r','e','m'};
1799 static const WCHAR forCmd[] = {'f','o','r'};
1800 static const WCHAR ifCmd[] = {'i','f'};
1801 static const WCHAR ifElse[] = {'e','l','s','e'};
1807 BOOL onlyWhiteSpace = FALSE;
1808 BOOL lastWasWhiteSpace = FALSE;
1809 BOOL lastWasDo = FALSE;
1810 BOOL lastWasIn = FALSE;
1811 BOOL lastWasElse = FALSE;
1812 BOOL lastWasRedirect = TRUE;
1813 BOOL lastWasCaret = FALSE;
1815 /* Allocate working space for a command read from keyboard, file etc */
1817 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1820 WINE_ERR("Could not allocate memory for extraSpace\n");
1824 /* If initial command read in, use that, otherwise get input from handle */
1825 if (optionalcmd != NULL) {
1826 strcpyW(extraSpace, optionalcmd);
1827 } else if (readFrom == INVALID_HANDLE_VALUE) {
1828 WINE_FIXME("No command nor handle supplied\n");
1830 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1833 curPos = extraSpace;
1835 /* Handle truncated input - issue warning */
1836 if (strlenW(extraSpace) == MAXSTRING -1) {
1837 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1838 WCMD_output_asis_stderr(extraSpace);
1839 WCMD_output_asis_stderr(newlineW);
1842 /* Replace env vars if in a batch context */
1843 if (context) handleExpansion(extraSpace, FALSE);
1845 /* Skip preceding whitespace */
1846 while (*curPos == ' ' || *curPos == '\t') curPos++;
1848 /* Show prompt before batch line IF echo is on and in batch program */
1849 if (context && echo_mode && *curPos && (*curPos != '@')) {
1850 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1851 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1852 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1853 DWORD curr_size = strlenW(curPos);
1854 DWORD min_len = (curr_size < len ? curr_size : len);
1856 WCMD_output_asis(curPos);
1857 /* I don't know why Windows puts a space here but it does */
1858 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1859 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1860 curPos, min_len, echoDot, len) != CSTR_EQUAL
1861 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1862 curPos, min_len, echoCol, len) != CSTR_EQUAL)
1864 WCMD_output_asis(spaceW);
1866 WCMD_output_asis(newlineW);
1869 /* Skip repeated 'no echo' characters */
1870 while (*curPos == '@') curPos++;
1872 /* Start with an empty string, copying to the command string */
1875 curCopyTo = curString;
1876 curLen = &curStringLen;
1877 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1879 /* Parse every character on the line being processed */
1880 while (*curPos != 0x00) {
1885 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1886 lastWasWhiteSpace, onlyWhiteSpace);
1889 /* Prevent overflow caused by the caret escape char */
1890 if (*curLen >= MAXSTRING) {
1891 WINE_ERR("Overflow detected in command\n");
1895 /* Certain commands need special handling */
1896 if (curStringLen == 0 && curCopyTo == curString) {
1897 static const WCHAR forDO[] = {'d','o'};
1899 /* If command starts with 'rem ', ignore any &&, ( etc. */
1900 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1903 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1906 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1907 is only true in the command portion of the IF statement, but this
1908 should suffice for now
1909 FIXME: Silly syntax like "if 1(==1( (
1911 )" will be parsed wrong */
1912 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1915 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1916 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1919 onlyWhiteSpace = TRUE;
1920 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1921 (*curLen)+=keyw_len;
1925 /* In a for loop, the DO command will follow a close bracket followed by
1926 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1927 is then 0, and all whitespace is skipped */
1929 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1930 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1931 WINE_TRACE("Found 'DO '\n");
1933 onlyWhiteSpace = TRUE;
1934 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1935 (*curLen)+=keyw_len;
1939 } else if (curCopyTo == curString) {
1941 /* Special handling for the 'FOR' command */
1942 if (inFor && lastWasWhiteSpace) {
1943 static const WCHAR forIN[] = {'i','n'};
1945 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1947 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1948 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1949 WINE_TRACE("Found 'IN '\n");
1951 onlyWhiteSpace = TRUE;
1952 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1953 (*curLen)+=keyw_len;
1960 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1961 so just use the default processing ie skip character specific
1963 if (!inRem) thisChar = *curPos;
1964 else thisChar = 'X'; /* Character with no special processing */
1966 lastWasWhiteSpace = FALSE; /* Will be reset below */
1967 lastWasCaret = FALSE;
1971 case '=': /* drop through - ignore token delimiters at the start of a command */
1972 case ',': /* drop through - ignore token delimiters at the start of a command */
1973 case '\t':/* drop through - ignore token delimiters at the start of a command */
1975 /* If a redirect in place, it ends here */
1976 if (!inQuotes && !lastWasRedirect) {
1978 /* If finishing off a redirect, add a whitespace delimiter */
1979 if (curCopyTo == curRedirs) {
1980 curCopyTo[(*curLen)++] = ' ';
1982 curCopyTo = curString;
1983 curLen = &curStringLen;
1986 curCopyTo[(*curLen)++] = *curPos;
1989 /* Remember just processed whitespace */
1990 lastWasWhiteSpace = TRUE;
1994 case '>': /* drop through - handle redirect chars the same */
1996 /* Make a redirect start here */
1998 curCopyTo = curRedirs;
1999 curLen = &curRedirsLen;
2000 lastWasRedirect = TRUE;
2003 /* See if 1>, 2> etc, in which case we have some patching up
2004 to do (provided there's a preceding whitespace, and enough
2005 chars read so far) */
2006 if (curStringLen > 2
2007 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
2008 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2010 curString[curStringLen] = 0x00;
2011 curCopyTo[(*curLen)++] = *(curPos-1);
2014 curCopyTo[(*curLen)++] = *curPos;
2016 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2017 do not process that ampersand as an AND operator */
2018 if (thisChar == '>' && *(curPos+1) == '&') {
2019 curCopyTo[(*curLen)++] = *(curPos+1);
2024 case '|': /* Pipe character only if not || */
2026 lastWasRedirect = FALSE;
2028 /* Add an entry to the command list */
2029 if (curStringLen > 0) {
2031 /* Add the current command */
2032 WCMD_addCommand(curString, &curStringLen,
2033 curRedirs, &curRedirsLen,
2034 &curCopyTo, &curLen,
2035 prevDelim, curDepth,
2036 &lastEntry, output);
2040 if (*(curPos+1) == '|') {
2041 curPos++; /* Skip other | */
2042 prevDelim = CMD_ONFAILURE;
2044 prevDelim = CMD_PIPE;
2047 curCopyTo[(*curLen)++] = *curPos;
2051 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2054 inQuotes++; /* Quotes within quotes are fun! */
2056 curCopyTo[(*curLen)++] = *curPos;
2057 lastWasRedirect = FALSE;
2060 case '(': /* If a '(' is the first non whitespace in a command portion
2061 ie start of line or just after &&, then we read until an
2062 unquoted ) is found */
2063 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2064 ", for(%d, In:%d, Do:%d)"
2065 ", if(%d, else:%d, lwe:%d)\n",
2068 inFor, lastWasIn, lastWasDo,
2069 inIf, inElse, lastWasElse);
2070 lastWasRedirect = FALSE;
2072 /* Ignore open brackets inside the for set */
2073 if (*curLen == 0 && !inIn) {
2076 /* If in quotes, ignore brackets */
2077 } else if (inQuotes) {
2078 curCopyTo[(*curLen)++] = *curPos;
2080 /* In a FOR loop, an unquoted '(' may occur straight after
2082 In an IF statement just handle it regardless as we don't
2084 In an ELSE statement, only allow it straight away after
2085 the ELSE and whitespace
2088 (inElse && lastWasElse && onlyWhiteSpace) ||
2089 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2091 /* If entering into an 'IN', set inIn */
2092 if (inFor && lastWasIn && onlyWhiteSpace) {
2093 WINE_TRACE("Inside an IN\n");
2097 /* Add the current command */
2098 WCMD_addCommand(curString, &curStringLen,
2099 curRedirs, &curRedirsLen,
2100 &curCopyTo, &curLen,
2101 prevDelim, curDepth,
2102 &lastEntry, output);
2106 curCopyTo[(*curLen)++] = *curPos;
2110 case '^': if (!inQuotes) {
2111 /* If we reach the end of the input, we need to wait for more */
2112 if (*(curPos+1) == 0x00) {
2113 lastWasCaret = TRUE;
2114 WINE_TRACE("Caret found at end of line\n");
2119 curCopyTo[(*curLen)++] = *curPos;
2122 case '&': if (!inQuotes) {
2123 lastWasRedirect = FALSE;
2125 /* Add an entry to the command list */
2126 if (curStringLen > 0) {
2128 /* Add the current command */
2129 WCMD_addCommand(curString, &curStringLen,
2130 curRedirs, &curRedirsLen,
2131 &curCopyTo, &curLen,
2132 prevDelim, curDepth,
2133 &lastEntry, output);
2137 if (*(curPos+1) == '&') {
2138 curPos++; /* Skip other & */
2139 prevDelim = CMD_ONSUCCESS;
2141 prevDelim = CMD_NONE;
2144 curCopyTo[(*curLen)++] = *curPos;
2148 case ')': if (!inQuotes && curDepth > 0) {
2149 lastWasRedirect = FALSE;
2151 /* Add the current command if there is one */
2154 /* Add the current command */
2155 WCMD_addCommand(curString, &curStringLen,
2156 curRedirs, &curRedirsLen,
2157 &curCopyTo, &curLen,
2158 prevDelim, curDepth,
2159 &lastEntry, output);
2162 /* Add an empty entry to the command list */
2163 prevDelim = CMD_NONE;
2164 WCMD_addCommand(NULL, &curStringLen,
2165 curRedirs, &curRedirsLen,
2166 &curCopyTo, &curLen,
2167 prevDelim, curDepth,
2168 &lastEntry, output);
2171 /* Leave inIn if necessary */
2172 if (inIn) inIn = FALSE;
2174 curCopyTo[(*curLen)++] = *curPos;
2178 lastWasRedirect = FALSE;
2179 curCopyTo[(*curLen)++] = *curPos;
2184 /* At various times we need to know if we have only skipped whitespace,
2185 so reset this variable and then it will remain true until a non
2186 whitespace is found */
2187 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2188 onlyWhiteSpace = FALSE;
2190 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2191 if (!lastWasWhiteSpace) {
2192 lastWasIn = lastWasDo = FALSE;
2195 /* If we have reached the end, add this command into the list
2196 Do not add command to list if escape char ^ was last */
2197 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2199 /* Add an entry to the command list */
2200 WCMD_addCommand(curString, &curStringLen,
2201 curRedirs, &curRedirsLen,
2202 &curCopyTo, &curLen,
2203 prevDelim, curDepth,
2204 &lastEntry, output);
2207 /* If we have reached the end of the string, see if bracketing or
2208 final caret is outstanding */
2209 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2210 readFrom != INVALID_HANDLE_VALUE) {
2213 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2215 prevDelim = CMD_NONE;
2217 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2218 extraData = extraSpace;
2220 /* Read more, skipping any blank lines */
2222 WINE_TRACE("Read more input\n");
2223 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2224 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2227 /* Edge case for carets - a completely blank line (i.e. was just
2228 CRLF) is oddly added as an LF but then more data is received (but
2231 if (*extraSpace == 0x00) {
2232 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2233 *extraData++ = '\r';
2238 } while (*extraData == 0x00);
2239 curPos = extraSpace;
2240 if (context) handleExpansion(extraSpace, FALSE);
2241 /* Continue to echo commands IF echo is on and in batch program */
2242 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2243 WCMD_output_asis(extraSpace);
2244 WCMD_output_asis(newlineW);
2249 /* Dump out the parsed output */
2250 WCMD_DumpCommands(*output);
2255 /***************************************************************************
2256 * WCMD_process_commands
2258 * Process all the commands read in so far
2260 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2265 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2267 /* Loop through the commands, processing them one by one */
2270 CMD_LIST *origCmd = thisCmd;
2272 /* If processing one bracket only, and we find the end bracket
2273 entry (or less), return */
2274 if (oneBracket && !thisCmd->command &&
2275 bdepth <= thisCmd->bracketDepth) {
2276 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2277 thisCmd, thisCmd->nextcommand);
2278 return thisCmd->nextcommand;
2281 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2282 about them and it will be handled in there)
2283 Also, skip over any batch labels (eg. :fred) */
2284 if (thisCmd->command && thisCmd->command[0] != ':') {
2285 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2286 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2289 /* Step on unless the command itself already stepped on */
2290 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2295 /***************************************************************************
2296 * WCMD_free_commands
2298 * Frees the storage held for a parsed command line
2299 * - This is not done in the process_commands, as eventually the current
2300 * pointer will be modified within the commands, and hence a single free
2301 * routine is simpler
2303 void WCMD_free_commands(CMD_LIST *cmds) {
2305 /* Loop through the commands, freeing them one by one */
2307 CMD_LIST *thisCmd = cmds;
2308 cmds = cmds->nextcommand;
2309 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2310 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2311 HeapFree(GetProcessHeap(), 0, thisCmd);
2316 /*****************************************************************************
2317 * Main entry point. This is a console application so we have a main() not a
2321 int wmain (int argc, WCHAR *argvW[])
2324 WCHAR *cmdLine = NULL;
2326 WCHAR *argPos = NULL;
2331 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2332 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2333 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2337 /* Pre initialize some messages */
2338 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2339 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), PACKAGE_VERSION);
2340 strcpyW(version_string, cmd);
2344 /* Can't use argc/argv as it will have stripped quotes from parameters
2345 * meaning cmd.exe /C echo "quoted string" is impossible
2347 cmdLine = GetCommandLineW();
2348 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2349 args = 1; /* start at first arg, skipping cmd.exe itself */
2351 opt_c = opt_k = opt_q = opt_s = FALSE;
2352 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2353 while (argPos && argPos[0] != 0x00)
2356 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
2357 if (argPos[0]!='/' || argPos[1]=='\0') {
2359 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2364 if (tolowerW(c)=='c') {
2366 } else if (tolowerW(c)=='q') {
2368 } else if (tolowerW(c)=='k') {
2370 } else if (tolowerW(c)=='s') {
2372 } else if (tolowerW(c)=='a') {
2373 unicodeOutput = FALSE;
2374 } else if (tolowerW(c)=='u') {
2375 unicodeOutput = TRUE;
2376 } else if (tolowerW(c)=='t' && argPos[2]==':') {
2377 opt_t=strtoulW(&argPos[3], NULL, 16);
2378 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2379 /* Ignored for compatibility with Windows */
2382 if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') {
2384 WCMD_parameter(cmdLine, args, &argPos, TRUE, TRUE);
2386 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2388 /* Do not step to next parameter, instead carry on parsing this one */
2392 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2397 static const WCHAR eoff[] = {'O','F','F','\0'};
2401 /* Until we start to read from the keyboard, stay as non-interactive */
2402 interactive = FALSE;
2404 if (opt_c || opt_k) {
2406 WCHAR *q1 = NULL,*q2 = NULL,*p;
2408 /* Handle very edge case error scenario, "cmd.exe /c" ie when there are no
2409 * parameters after the /C or /K by pretending there was a single space */
2410 if (argPos == NULL) argPos = (WCHAR *)spaceW;
2412 /* Build the command to execute - It is what is left in argPos */
2413 len = strlenW(argPos);
2416 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2419 strcpyW(cmd, argPos);
2421 /* opt_s left unflagged if the command starts with and contains exactly
2422 * one quoted string (exactly two quote characters). The quoted string
2423 * must be an executable name that has whitespace and must not have the
2424 * following characters: &<>()@^| */
2427 /* 1. Confirm there is at least one quote */
2428 q1 = strchrW(argPos, '"');
2433 /* 2. Confirm there is a second quote */
2434 q2 = strchrW(q1+1, '"');
2439 /* 3. Ensure there are no more quotes */
2440 if (strchrW(q2+1, '"')) opt_s=1;
2443 /* check first parameter for a space and invalid characters. There must not be any
2444 * invalid characters, but there must be one or more whitespace */
2449 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2450 || *p=='@' || *p=='^' || *p=='|') {
2454 if (*p==' ' || *p=='\t')
2460 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2462 /* Finally, we only stay in new mode IF the first parameter is quoted and
2463 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2465 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2466 WCHAR pathext[MAXSTRING];
2469 /* Now extract PATHEXT */
2470 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
2471 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
2472 strcpyW (pathext, dfltPathExt);
2475 /* If the supplied parameter has any directory information, look there */
2476 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2477 if (strchrW(thisArg, '\\') != NULL) {
2479 GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
2480 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2481 p = string + strlenW(string);
2483 /* Does file exist with this name? */
2484 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2485 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2488 WCHAR *thisExt = pathext;
2490 /* No - try with each of the PATHEXT extensions */
2491 while (!found && thisExt) {
2492 WCHAR *nextExt = strchrW(thisExt, ';');
2495 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2496 p[(nextExt-thisExt)] = 0x00;
2497 thisExt = nextExt+1;
2499 strcpyW(p, thisExt);
2503 /* Does file exist with this extension appended? */
2504 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2505 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2511 /* Otherwise we now need to look in the path to see if we can find it */
2513 p = thisArg + strlenW(thisArg);
2515 /* Does file exist with this name? */
2516 if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2517 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2520 WCHAR *thisExt = pathext;
2522 /* No - try with each of the PATHEXT extensions */
2523 while (!found && thisExt) {
2524 WCHAR *nextExt = strchrW(thisExt, ';');
2528 nextExt = nextExt+1;
2533 /* Does file exist with this extension? */
2534 if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2535 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2536 wine_dbgstr_w(thisExt));
2544 /* If not found, drop back to old behaviour */
2546 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2552 /* strip first and last quote characters if opt_s; check for invalid
2553 * executable is done later */
2554 if (opt_s && *cmd=='\"')
2555 WCMD_strip_quotes(cmd);
2558 /* Save cwd into appropriate env var (Must be before the /c processing */
2559 GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
2560 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2561 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2562 wsprintfW(envvar, fmt, string[0]);
2563 SetEnvironmentVariableW(envvar, string);
2564 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2568 /* If we do a "cmd /c command", we don't want to allocate a new
2569 * console since the command returns immediately. Rather, we use
2570 * the currently allocated input and output handles. This allows
2571 * us to pipe to and read from the command interpreter.
2574 /* Parse the command string, without reading any more input */
2575 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2576 WCMD_process_commands(toExecute, FALSE, FALSE);
2577 WCMD_free_commands(toExecute);
2580 HeapFree(GetProcessHeap(), 0, cmd);
2584 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2585 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2586 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2588 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2590 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2591 defaultColor = opt_t & 0xFF;
2596 /* Check HKCU\Software\Microsoft\Command Processor
2597 Then HKLM\Software\Microsoft\Command Processor
2598 for defaultcolour value
2599 Note Can be supplied as DWORD or REG_SZ
2600 Note2 When supplied as REG_SZ it's in decimal!!! */
2603 DWORD value=0, size=4;
2604 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2605 'M','i','c','r','o','s','o','f','t','\\',
2606 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2607 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2609 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2610 0, KEY_READ, &key) == ERROR_SUCCESS) {
2613 /* See if DWORD or REG_SZ */
2614 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2615 NULL, NULL) == ERROR_SUCCESS) {
2616 if (type == REG_DWORD) {
2617 size = sizeof(DWORD);
2618 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2619 (LPBYTE)&value, &size);
2620 } else if (type == REG_SZ) {
2621 size = sizeof(strvalue)/sizeof(WCHAR);
2622 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2623 (LPBYTE)strvalue, &size);
2624 value = strtoulW(strvalue, NULL, 10);
2630 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2631 0, KEY_READ, &key) == ERROR_SUCCESS) {
2634 /* See if DWORD or REG_SZ */
2635 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2636 NULL, NULL) == ERROR_SUCCESS) {
2637 if (type == REG_DWORD) {
2638 size = sizeof(DWORD);
2639 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2640 (LPBYTE)&value, &size);
2641 } else if (type == REG_SZ) {
2642 size = sizeof(strvalue)/sizeof(WCHAR);
2643 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2644 (LPBYTE)strvalue, &size);
2645 value = strtoulW(strvalue, NULL, 10);
2651 /* If one found, set the screen to that colour */
2652 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2653 defaultColor = value & 0xFF;
2661 /* Parse the command string, without reading any more input */
2662 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2663 WCMD_process_commands(toExecute, FALSE, FALSE);
2664 WCMD_free_commands(toExecute);
2666 HeapFree(GetProcessHeap(), 0, cmd);
2670 * Loop forever getting commands and executing them.
2673 SetEnvironmentVariableW(promptW, defaultpromptW);
2675 if (!opt_k) WCMD_version ();
2678 /* Read until EOF (which for std input is never, but if redirect
2679 in place, may occur */
2680 if (echo_mode) WCMD_show_prompt();
2681 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2683 WCMD_process_commands(toExecute, FALSE, FALSE);
2684 WCMD_free_commands(toExecute);