cmd: Add support for calling a built in command.
[wine.git] / programs / cmd / wcmdmain.c
blob6931755152786473b4e6ff65326b14d24fd78139
1 /*
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
23 * FIXME:
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
28 #include "config.h"
29 #include <time.h>
30 #include "wcmd.h"
31 #include "shellapi.h"
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;
40 DWORD errorlevel;
41 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
42 BOOL interactive;
44 int defaultColor = 7;
45 BOOL echo_mode = TRUE;
47 WCHAR anykey[100], version_string[100];
48 const WCHAR newlineW[] = {'\r','\n','\0'};
49 const WCHAR spaceW[] = {' ','\0'};
50 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
51 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
52 '.','c','o','m',';',
53 '.','c','m','d',';',
54 '.','e','x','e','\0'};
56 static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
58 /* Variables pertaining to paging */
59 static BOOL paged_mode;
60 static const WCHAR *pagedMessage = NULL;
61 static int line_count;
62 static int max_height;
63 static int max_width;
64 static int numChars;
66 #define MAX_WRITECONSOLE_SIZE 65535
69 * Returns a buffer for reading from/writing to file
70 * Never freed
72 static char *get_file_buffer(void)
74 static char *output_bufA = NULL;
75 if (!output_bufA) {
76 output_bufA = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
77 if (!output_bufA)
78 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
80 return output_bufA;
83 /*******************************************************************
84 * WCMD_output_asis_len - send output to current standard output
86 * Output a formatted unicode string. Ideally this will go to the console
87 * and hence required WriteConsoleW to output it, however if file i/o is
88 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
90 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
92 DWORD nOut= 0;
93 DWORD res = 0;
95 /* If nothing to write, return (MORE does this sometimes) */
96 if (!len) return;
98 /* Try to write as unicode assuming it is to a console */
99 res = WriteConsoleW(device, message, len, &nOut, NULL);
101 /* If writing to console fails, assume its file
102 i/o so convert to OEM codepage and output */
103 if (!res) {
104 BOOL usedDefaultChar = FALSE;
105 DWORD convertedChars;
106 char *buffer;
108 if (!unicodeOutput) {
110 if (!(buffer = get_file_buffer()))
111 return;
113 /* Convert to OEM, then output */
114 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
115 len, buffer, MAX_WRITECONSOLE_SIZE,
116 "?", &usedDefaultChar);
117 WriteFile(device, buffer, convertedChars,
118 &nOut, FALSE);
119 } else {
120 WriteFile(device, message, len*sizeof(WCHAR),
121 &nOut, FALSE);
124 return;
127 /*******************************************************************
128 * WCMD_output - send output to current standard output device.
132 void CDECL WCMD_output (const WCHAR *format, ...) {
134 __ms_va_list ap;
135 WCHAR* string;
136 DWORD len;
138 __ms_va_start(ap,format);
139 SetLastError(NO_ERROR);
140 string = NULL;
141 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
142 format, 0, 0, (LPWSTR)&string, 0, &ap);
143 __ms_va_end(ap);
144 if (len == 0 && GetLastError() != NO_ERROR)
145 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
146 else
148 WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
149 LocalFree(string);
153 /*******************************************************************
154 * WCMD_output_stderr - send output to current standard error device.
158 void CDECL WCMD_output_stderr (const WCHAR *format, ...) {
160 __ms_va_list ap;
161 WCHAR* string;
162 DWORD len;
164 __ms_va_start(ap,format);
165 SetLastError(NO_ERROR);
166 string = NULL;
167 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
168 format, 0, 0, (LPWSTR)&string, 0, &ap);
169 __ms_va_end(ap);
170 if (len == 0 && GetLastError() != NO_ERROR)
171 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
172 else
174 WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
175 LocalFree(string);
179 /*******************************************************************
180 * WCMD_format_string - allocate a buffer and format a string
184 WCHAR* CDECL WCMD_format_string (const WCHAR *format, ...) {
186 __ms_va_list ap;
187 WCHAR* string;
188 DWORD len;
190 __ms_va_start(ap,format);
191 SetLastError(NO_ERROR);
192 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
193 format, 0, 0, (LPWSTR)&string, 0, &ap);
194 __ms_va_end(ap);
195 if (len == 0 && GetLastError() != NO_ERROR) {
196 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
197 string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
198 *string = 0;
200 return string;
203 void WCMD_enter_paged_mode(const WCHAR *msg)
205 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
207 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
208 max_height = consoleInfo.dwSize.Y;
209 max_width = consoleInfo.dwSize.X;
210 } else {
211 max_height = 25;
212 max_width = 80;
214 paged_mode = TRUE;
215 line_count = 0;
216 numChars = 0;
217 pagedMessage = (msg==NULL)? anykey : msg;
220 void WCMD_leave_paged_mode(void)
222 paged_mode = FALSE;
223 pagedMessage = NULL;
226 /***************************************************************************
227 * WCMD_Readfile
229 * Read characters in from a console/file, returning result in Unicode
231 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
233 DWORD numRead;
234 char *buffer;
236 if (WCMD_is_console_handle(hIn))
237 /* Try to read from console as Unicode */
238 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
240 /* We assume it's a file handle and read then convert from assumed OEM codepage */
241 if (!(buffer = get_file_buffer()))
242 return FALSE;
244 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
245 return FALSE;
247 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
249 return TRUE;
252 /*******************************************************************
253 * WCMD_output_asis_handle
255 * Send output to specified handle without formatting e.g. when message contains '%'
257 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
258 DWORD count;
259 const WCHAR* ptr;
260 WCHAR string[1024];
261 HANDLE handle = GetStdHandle(std_handle);
263 if (paged_mode) {
264 do {
265 ptr = message;
266 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
267 numChars++;
268 ptr++;
270 if (*ptr == '\n') ptr++;
271 WCMD_output_asis_len(message, ptr - message, handle);
272 numChars = 0;
273 if (++line_count >= max_height - 1) {
274 line_count = 0;
275 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
276 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
278 } while (((message = ptr) != NULL) && (*ptr));
279 } else {
280 WCMD_output_asis_len(message, lstrlenW(message), handle);
284 /*******************************************************************
285 * WCMD_output_asis
287 * Send output to current standard output device, without formatting
288 * e.g. when message contains '%'
290 void WCMD_output_asis (const WCHAR *message) {
291 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
294 /*******************************************************************
295 * WCMD_output_asis_stderr
297 * Send output to current standard error device, without formatting
298 * e.g. when message contains '%'
300 void WCMD_output_asis_stderr (const WCHAR *message) {
301 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
304 /****************************************************************************
305 * WCMD_print_error
307 * Print the message for GetLastError
310 void WCMD_print_error (void) {
311 LPVOID lpMsgBuf;
312 DWORD error_code;
313 int status;
315 error_code = GetLastError ();
316 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
317 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
318 if (!status) {
319 WINE_FIXME ("Cannot display message for error %d, status %d\n",
320 error_code, GetLastError());
321 return;
324 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
325 GetStdHandle(STD_ERROR_HANDLE));
326 LocalFree (lpMsgBuf);
327 WCMD_output_asis_len (newlineW, lstrlenW(newlineW),
328 GetStdHandle(STD_ERROR_HANDLE));
329 return;
332 /******************************************************************************
333 * WCMD_show_prompt
335 * Display the prompt on STDout
339 static void WCMD_show_prompt (void) {
341 int status;
342 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
343 WCHAR *p, *q;
344 DWORD len;
345 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
347 len = GetEnvironmentVariableW(envPrompt, prompt_string,
348 sizeof(prompt_string)/sizeof(WCHAR));
349 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
350 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
351 strcpyW (prompt_string, dfltPrompt);
353 p = prompt_string;
354 q = out_string;
355 *q++ = '\r';
356 *q++ = '\n';
357 *q = '\0';
358 while (*p != '\0') {
359 if (*p != '$') {
360 *q++ = *p++;
361 *q = '\0';
363 else {
364 p++;
365 switch (toupper(*p)) {
366 case '$':
367 *q++ = '$';
368 break;
369 case 'A':
370 *q++ = '&';
371 break;
372 case 'B':
373 *q++ = '|';
374 break;
375 case 'C':
376 *q++ = '(';
377 break;
378 case 'D':
379 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
380 while (*q) q++;
381 break;
382 case 'E':
383 *q++ = '\E';
384 break;
385 case 'F':
386 *q++ = ')';
387 break;
388 case 'G':
389 *q++ = '>';
390 break;
391 case 'H':
392 *q++ = '\b';
393 break;
394 case 'L':
395 *q++ = '<';
396 break;
397 case 'N':
398 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
399 if (status) {
400 *q++ = curdir[0];
402 break;
403 case 'P':
404 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
405 if (status) {
406 strcatW (q, curdir);
407 while (*q) q++;
409 break;
410 case 'Q':
411 *q++ = '=';
412 break;
413 case 'S':
414 *q++ = ' ';
415 break;
416 case 'T':
417 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
418 while (*q) q++;
419 break;
420 case 'V':
421 strcatW (q, version_string);
422 while (*q) q++;
423 break;
424 case '_':
425 *q++ = '\n';
426 break;
427 case '+':
428 if (pushd_directories) {
429 memset(q, '+', pushd_directories->u.stackdepth);
430 q = q + pushd_directories->u.stackdepth;
432 break;
434 p++;
435 *q = '\0';
438 WCMD_output_asis (out_string);
442 /*************************************************************************
443 * WCMD_strdupW
444 * A wide version of strdup as its missing from unicode.h
446 WCHAR *WCMD_strdupW(const WCHAR *input) {
447 int len=strlenW(input)+1;
448 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
449 memcpy(result, input, len * sizeof(WCHAR));
450 return result;
453 /*************************************************************************
454 * WCMD_strsubstW
455 * Replaces a portion of a Unicode string with the specified string.
456 * It's up to the caller to ensure there is enough space in the
457 * destination buffer.
459 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
461 if (len < 0)
462 len=insert ? lstrlenW(insert) : 0;
463 if (start+len != next)
464 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
465 if (insert)
466 memcpy(start, insert, len * sizeof(*insert));
469 /***************************************************************************
470 * WCMD_skip_leading_spaces
472 * Return a pointer to the first non-whitespace character of string.
473 * Does not modify the input string.
475 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
477 WCHAR *ptr;
479 ptr = string;
480 while (*ptr == ' ' || *ptr == '\t') ptr++;
481 return ptr;
484 /***************************************************************************
485 * WCMD_keyword_ws_found
487 * Checks if the string located at ptr matches a keyword (of length len)
488 * followed by a whitespace character (space or tab)
490 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
491 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
492 ptr, len, keyword, len) == CSTR_EQUAL)
493 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
496 /*************************************************************************
497 * WCMD_strip_quotes
499 * Remove first and last quote WCHARacters, preserving all other text
501 void WCMD_strip_quotes(WCHAR *cmd) {
502 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
503 while((*dest=*src) != '\0') {
504 if (*src=='\"')
505 lastq=dest;
506 dest++, src++;
508 if (lastq) {
509 dest=lastq++;
510 while ((*dest++=*lastq++) != 0)
516 /*************************************************************************
517 * WCMD_is_magic_envvar
518 * Return TRUE if s is '%'magicvar'%'
519 * and is not masked by a real environment variable.
522 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
524 int len;
526 if (s[0] != '%')
527 return FALSE; /* Didn't begin with % */
528 len = strlenW(s);
529 if (len < 2 || s[len-1] != '%')
530 return FALSE; /* Didn't end with another % */
532 if (CompareStringW(LOCALE_USER_DEFAULT,
533 NORM_IGNORECASE | SORT_STRINGSORT,
534 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
535 /* Name doesn't match. */
536 return FALSE;
539 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
540 /* Masked by real environment variable. */
541 return FALSE;
544 return TRUE;
547 /*************************************************************************
548 * WCMD_expand_envvar
550 * Expands environment variables, allowing for WCHARacter substitution
552 static WCHAR *WCMD_expand_envvar(WCHAR *start,
553 const WCHAR *forVar, const WCHAR *forVal) {
554 WCHAR *endOfVar = NULL, *s;
555 WCHAR *colonpos = NULL;
556 WCHAR thisVar[MAXSTRING];
557 WCHAR thisVarContents[MAXSTRING];
558 WCHAR savedchar = 0x00;
559 int len;
561 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
562 static const WCHAR Date[] = {'D','A','T','E','\0'};
563 static const WCHAR Time[] = {'T','I','M','E','\0'};
564 static const WCHAR Cd[] = {'C','D','\0'};
565 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
566 static const WCHAR Delims[] = {'%',':','\0'};
568 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
569 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
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 '%' */
578 if (context) {
579 WCMD_strsubstW(start, start + 1, NULL, 0);
580 return start;
581 } else {
583 /* In command processing, just ignore it - allows command line
584 syntax like: for %i in (a.a) do echo %i */
585 return start+1;
589 /* If ':' found, process remaining up until '%' (or stop at ':' if
590 a missing '%' */
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 */
602 if (colonpos) {
603 *colonpos = '%';
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);
633 /* Look for a matching 'for' variable */
634 } else if (forVar &&
635 (CompareStringW(LOCALE_USER_DEFAULT,
636 SORT_STRINGSORT,
637 thisVar,
638 (colonpos - thisVar) - 1,
639 forVar, -1) == CSTR_EQUAL)) {
640 strcpyW(thisVarContents, forVal);
641 len = strlenW(thisVarContents);
643 } else {
645 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
646 sizeof(thisVarContents)/sizeof(WCHAR));
649 if (len == 0)
650 return endOfVar+1;
652 /* In a batch program, unknown env vars are replaced with nothing,
653 note syntax %garbage:1,3% results in anything after the ':'
654 except the %
655 From the command line, you just get back what you entered */
656 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
658 /* Restore the complex part after the compare */
659 if (colonpos) {
660 *colonpos = ':';
661 *(colonpos+1) = savedchar;
664 /* Command line - just ignore this */
665 if (context == NULL) return endOfVar+1;
668 /* Batch - replace unknown env var with nothing */
669 if (colonpos == NULL) {
670 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
671 } else {
672 len = strlenW(thisVar);
673 thisVar[len-1] = 0x00;
674 /* If %:...% supplied, : is retained */
675 if (colonpos == thisVar+1) {
676 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
677 } else {
678 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
681 return start;
685 /* See if we need to do complex substitution (any ':'s), if not
686 then our work here is done */
687 if (colonpos == NULL) {
688 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
689 return start;
692 /* Restore complex bit */
693 *colonpos = ':';
694 *(colonpos+1) = savedchar;
697 Handle complex substitutions:
698 xxx=yyy (replace xxx with yyy)
699 *xxx=yyy (replace up to and including xxx with yyy)
700 ~x (from x WCHARs in)
701 ~-x (from x WCHARs from the end)
702 ~x,y (from x WCHARs in for y WCHARacters)
703 ~x,-y (from x WCHARs in until y WCHARacters from the end)
706 /* ~ is substring manipulation */
707 if (savedchar == '~') {
709 int substrposition, substrlength = 0;
710 WCHAR *commapos = strchrW(colonpos+2, ',');
711 WCHAR *startCopy;
713 substrposition = atolW(colonpos+2);
714 if (commapos) substrlength = atolW(commapos+1);
716 /* Check bounds */
717 if (substrposition >= 0) {
718 startCopy = &thisVarContents[min(substrposition, len)];
719 } else {
720 startCopy = &thisVarContents[max(0, len+substrposition-1)];
723 if (commapos == NULL) {
724 /* Copy the lot */
725 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
726 } else if (substrlength < 0) {
728 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
729 if (copybytes > len) copybytes = len;
730 else if (copybytes < 0) copybytes = 0;
731 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
732 } else {
733 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
736 /* search and replace manipulation */
737 } else {
738 WCHAR *equalspos = strstrW(colonpos, equalW);
739 WCHAR *replacewith = equalspos+1;
740 WCHAR *found = NULL;
741 WCHAR *searchIn;
742 WCHAR *searchFor;
744 if (equalspos == NULL) return start+1;
745 s = WCMD_strdupW(endOfVar + 1);
747 /* Null terminate both strings */
748 thisVar[strlenW(thisVar)-1] = 0x00;
749 *equalspos = 0x00;
751 /* Since we need to be case insensitive, copy the 2 buffers */
752 searchIn = WCMD_strdupW(thisVarContents);
753 CharUpperBuffW(searchIn, strlenW(thisVarContents));
754 searchFor = WCMD_strdupW(colonpos+1);
755 CharUpperBuffW(searchFor, strlenW(colonpos+1));
757 /* Handle wildcard case */
758 if (*(colonpos+1) == '*') {
759 /* Search for string to replace */
760 found = strstrW(searchIn, searchFor+1);
762 if (found) {
763 /* Do replacement */
764 strcpyW(start, replacewith);
765 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
766 strcatW(start, s);
767 } else {
768 /* Copy as is */
769 strcpyW(start, thisVarContents);
770 strcatW(start, s);
773 } else {
774 /* Loop replacing all instances */
775 WCHAR *lastFound = searchIn;
776 WCHAR *outputposn = start;
778 *start = 0x00;
779 while ((found = strstrW(lastFound, searchFor))) {
780 lstrcpynW(outputposn,
781 thisVarContents + (lastFound-searchIn),
782 (found - lastFound)+1);
783 outputposn = outputposn + (found - lastFound);
784 strcatW(outputposn, replacewith);
785 outputposn = outputposn + strlenW(replacewith);
786 lastFound = found + strlenW(searchFor);
788 strcatW(outputposn,
789 thisVarContents + (lastFound-searchIn));
790 strcatW(outputposn, s);
792 HeapFree(GetProcessHeap(), 0, s);
793 HeapFree(GetProcessHeap(), 0, searchIn);
794 HeapFree(GetProcessHeap(), 0, searchFor);
796 return start;
799 /*****************************************************************************
800 * Expand the command. Native expands lines from batch programs as they are
801 * read in and not again, except for 'for' variable substitution.
802 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
804 static void handleExpansion(WCHAR *cmd, BOOL justFors,
805 const WCHAR *forVariable, const WCHAR *forValue) {
807 /* For commands in a context (batch program): */
808 /* Expand environment variables in a batch file %{0-9} first */
809 /* including support for any ~ modifiers */
810 /* Additionally: */
811 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
812 /* names allowing environment variable overrides */
813 /* NOTE: To support the %PATH:xxx% syntax, also perform */
814 /* manual expansion of environment variables here */
816 WCHAR *p = cmd;
817 WCHAR *t;
818 int i;
820 while ((p = strchrW(p, '%'))) {
822 WINE_TRACE("Translate command:%s %d (at: %s)\n",
823 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
824 i = *(p+1) - '0';
826 /* Don't touch %% unless its in Batch */
827 if (!justFors && *(p+1) == '%') {
828 if (context) {
829 WCMD_strsubstW(p, p+1, NULL, 0);
831 p+=1;
833 /* Replace %~ modifications if in batch program */
834 } else if (*(p+1) == '~') {
835 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
836 p++;
838 /* Replace use of %0...%9 if in batch program*/
839 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
840 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
841 NULL, NULL, TRUE, TRUE);
842 WCMD_strsubstW(p, p+2, t, -1);
844 /* Replace use of %* if in batch program*/
845 } else if (!justFors && context && *(p+1)=='*') {
846 WCHAR *startOfParms = NULL;
847 WCMD_parameter(context -> command, 0, NULL, &startOfParms, TRUE, TRUE);
848 if (startOfParms != NULL) {
849 startOfParms++; /* Skip to first delimiter then skip whitespace */
850 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
851 WCMD_strsubstW(p, p+2, startOfParms, -1);
852 } else
853 WCMD_strsubstW(p, p+2, NULL, 0);
855 } else if (forVariable &&
856 (CompareStringW(LOCALE_USER_DEFAULT,
857 SORT_STRINGSORT,
859 strlenW(forVariable),
860 forVariable, -1) == CSTR_EQUAL)) {
861 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
863 } else if (!justFors) {
864 p = WCMD_expand_envvar(p, forVariable, forValue);
866 /* In a FOR loop, see if this is the variable to replace */
867 } else { /* Ignore %'s on second pass of batch program */
868 p++;
872 return;
876 /*******************************************************************
877 * WCMD_parse - parse a command into parameters and qualifiers.
879 * On exit, all qualifiers are concatenated into q, the first string
880 * not beginning with "/" is in p1 and the
881 * second in p2. Any subsequent non-qualifier strings are lost.
882 * Parameters in quotes are handled.
884 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
886 int p = 0;
888 *q = *p1 = *p2 = '\0';
889 while (TRUE) {
890 switch (*s) {
891 case '/':
892 *q++ = *s++;
893 while ((*s != '\0') && (*s != ' ') && *s != '/') {
894 *q++ = toupperW (*s++);
896 *q = '\0';
897 break;
898 case ' ':
899 case '\t':
900 s++;
901 break;
902 case '"':
903 s++;
904 while ((*s != '\0') && (*s != '"')) {
905 if (p == 0) *p1++ = *s++;
906 else if (p == 1) *p2++ = *s++;
907 else s++;
909 if (p == 0) *p1 = '\0';
910 if (p == 1) *p2 = '\0';
911 p++;
912 if (*s == '"') s++;
913 break;
914 case '\0':
915 return;
916 default:
917 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
918 && (*s != '=') && (*s != ',') ) {
919 if (p == 0) *p1++ = *s++;
920 else if (p == 1) *p2++ = *s++;
921 else s++;
923 /* Skip concurrent parms */
924 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
926 if (p == 0) *p1 = '\0';
927 if (p == 1) *p2 = '\0';
928 p++;
933 static void init_msvcrt_io_block(STARTUPINFOW* st)
935 STARTUPINFOW st_p;
936 /* fetch the parent MSVCRT info block if any, so that the child can use the
937 * same handles as its grand-father
939 st_p.cb = sizeof(STARTUPINFOW);
940 GetStartupInfoW(&st_p);
941 st->cbReserved2 = st_p.cbReserved2;
942 st->lpReserved2 = st_p.lpReserved2;
943 if (st_p.cbReserved2 && 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 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
950 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
951 if (ptr)
953 unsigned num = *(unsigned*)st_p.lpReserved2;
954 char* flags = (char*)(ptr + sizeof(unsigned));
955 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
957 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
958 st->cbReserved2 = sz;
959 st->lpReserved2 = ptr;
961 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
962 if (num <= 0 || (flags[0] & WX_OPEN))
964 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
965 flags[0] |= WX_OPEN;
967 if (num <= 1 || (flags[1] & WX_OPEN))
969 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
970 flags[1] |= WX_OPEN;
972 if (num <= 2 || (flags[2] & WX_OPEN))
974 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
975 flags[2] |= WX_OPEN;
977 #undef WX_OPEN
982 /******************************************************************************
983 * WCMD_run_program
985 * Execute a command line as an external program. Must allow recursion.
987 * Precedence:
988 * Manual testing under windows shows PATHEXT plays a key part in this,
989 * and the search algorithm and precedence appears to be as follows.
991 * Search locations:
992 * If directory supplied on command, just use that directory
993 * If extension supplied on command, look for that explicit name first
994 * Otherwise, search in each directory on the path
995 * Precedence:
996 * If extension supplied on command, look for that explicit name first
997 * Then look for supplied name .* (even if extension supplied, so
998 * 'garbage.exe' will match 'garbage.exe.cmd')
999 * If any found, cycle through PATHEXT looking for name.exe one by one
1000 * Launching
1001 * Once a match has been found, it is launched - Code currently uses
1002 * findexecutable to achieve this which is left untouched.
1003 * If an executable has not been found, and we were launched through
1004 * a call, we need to check if the command is an internal command,
1005 * so go back through wcmd_execute.
1008 void WCMD_run_program (WCHAR *command, BOOL called)
1010 WCHAR temp[MAX_PATH];
1011 WCHAR pathtosearch[MAXSTRING];
1012 WCHAR *pathposn;
1013 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1014 MAX_PATH, including null character */
1015 WCHAR *lastSlash;
1016 WCHAR pathext[MAXSTRING];
1017 WCHAR *firstParam;
1018 BOOL extensionsupplied = FALSE;
1019 BOOL launched = FALSE;
1020 BOOL status;
1021 BOOL assumeInternal = FALSE;
1022 DWORD len;
1023 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1024 static const WCHAR delims[] = {'/','\\',':','\0'};
1026 /* Quick way to get the filename is to extract the first argument. */
1027 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1028 firstParam = WCMD_parameter(command, 0, NULL, NULL, FALSE, TRUE);
1029 if (!firstParam) return;
1031 /* Calculate the search path and stem to search for */
1032 if (strpbrkW (firstParam, delims) == NULL) { /* No explicit path given, search path */
1033 static const WCHAR curDir[] = {'.',';','\0'};
1034 strcpyW(pathtosearch, curDir);
1035 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1036 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1037 static const WCHAR curDir[] = {'.','\0'};
1038 strcpyW (pathtosearch, curDir);
1040 if (strchrW(firstParam, '.') != NULL) extensionsupplied = TRUE;
1041 if (strlenW(firstParam) >= MAX_PATH)
1043 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1044 return;
1047 strcpyW(stemofsearch, firstParam);
1049 } else {
1051 /* Convert eg. ..\fred to include a directory by removing file part */
1052 GetFullPathNameW(firstParam, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1053 lastSlash = strrchrW(pathtosearch, '\\');
1054 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1055 strcpyW(stemofsearch, lastSlash+1);
1057 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1058 c:\windows\a.bat syntax */
1059 if (lastSlash) *(lastSlash + 1) = 0x00;
1062 /* Now extract PATHEXT */
1063 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1064 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1065 strcpyW (pathext, dfltPathExt);
1068 /* Loop through the search path, dir by dir */
1069 pathposn = pathtosearch;
1070 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1071 wine_dbgstr_w(stemofsearch));
1072 while (!launched && pathposn) {
1074 WCHAR thisDir[MAX_PATH] = {'\0'};
1075 WCHAR *pos = NULL;
1076 BOOL found = FALSE;
1078 /* Work on the first directory on the search path */
1079 pos = strchrW(pathposn, ';');
1080 if (pos) {
1081 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1082 thisDir[(pos-pathposn)] = 0x00;
1083 pathposn = pos+1;
1085 } else {
1086 strcpyW(thisDir, pathposn);
1087 pathposn = NULL;
1090 /* Since you can have eg. ..\.. on the path, need to expand
1091 to full information */
1092 strcpyW(temp, thisDir);
1093 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1095 /* 1. If extension supplied, see if that file exists */
1096 strcatW(thisDir, slashW);
1097 strcatW(thisDir, stemofsearch);
1098 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1100 /* 1. If extension supplied, see if that file exists */
1101 if (extensionsupplied) {
1102 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1103 found = TRUE;
1107 /* 2. Any .* matches? */
1108 if (!found) {
1109 HANDLE h;
1110 WIN32_FIND_DATAW finddata;
1111 static const WCHAR allFiles[] = {'.','*','\0'};
1113 strcatW(thisDir,allFiles);
1114 h = FindFirstFileW(thisDir, &finddata);
1115 FindClose(h);
1116 if (h != INVALID_HANDLE_VALUE) {
1118 WCHAR *thisExt = pathext;
1120 /* 3. Yes - Try each path ext */
1121 while (thisExt) {
1122 WCHAR *nextExt = strchrW(thisExt, ';');
1124 if (nextExt) {
1125 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1126 pos[(nextExt-thisExt)] = 0x00;
1127 thisExt = nextExt+1;
1128 } else {
1129 strcpyW(pos, thisExt);
1130 thisExt = NULL;
1133 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1134 found = TRUE;
1135 thisExt = NULL;
1141 /* Internal programs won't be picked up by this search, so even
1142 though not found, try one last createprocess and wait for it
1143 to complete.
1144 Note: Ideally we could tell between a console app (wait) and a
1145 windows app, but the API's for it fail in this case */
1146 if (!found && pathposn == NULL) {
1147 WINE_TRACE("ASSUMING INTERNAL\n");
1148 assumeInternal = TRUE;
1149 } else {
1150 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1153 /* Once found, launch it */
1154 if (found || assumeInternal) {
1155 STARTUPINFOW st;
1156 PROCESS_INFORMATION pe;
1157 SHFILEINFOW psfi;
1158 DWORD console;
1159 HINSTANCE hinst;
1160 WCHAR *ext = strrchrW( thisDir, '.' );
1161 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1162 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1164 launched = TRUE;
1166 /* Special case BAT and CMD */
1167 if (ext && (!strcmpiW(ext, batExt) || !strcmpiW(ext, cmdExt))) {
1168 BOOL oldinteractive = interactive;
1169 interactive = FALSE;
1170 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1171 interactive = oldinteractive;
1172 return;
1173 } else {
1175 /* thisDir contains the file to be launched, but with what?
1176 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1177 hinst = FindExecutableW (thisDir, NULL, temp);
1178 if ((INT_PTR)hinst < 32)
1179 console = 0;
1180 else
1181 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1183 ZeroMemory (&st, sizeof(STARTUPINFOW));
1184 st.cb = sizeof(STARTUPINFOW);
1185 init_msvcrt_io_block(&st);
1187 /* Launch the process and if a CUI wait on it to complete
1188 Note: Launching internal wine processes cannot specify a full path to exe */
1189 status = CreateProcessW(assumeInternal?NULL : thisDir,
1190 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1191 if ((opt_c || opt_k) && !opt_s && !status
1192 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1193 /* strip first and last quote WCHARacters and try again */
1194 WCMD_strip_quotes(command);
1195 opt_s = TRUE;
1196 WCMD_run_program(command, called);
1197 return;
1200 if (!status)
1201 break;
1203 called = FALSE; /* No need to retry as we launched something */
1205 if (!assumeInternal && !console) errorlevel = 0;
1206 else
1208 /* Always wait when called in a batch program context */
1209 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1210 GetExitCodeProcess (pe.hProcess, &errorlevel);
1211 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1213 CloseHandle(pe.hProcess);
1214 CloseHandle(pe.hThread);
1215 return;
1220 /* Not found anywhere - were we called? */
1221 if (called) {
1222 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1224 /* Parse the command string, without reading any more input */
1225 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1226 WCMD_process_commands(toExecute, FALSE, NULL, NULL, called);
1227 WCMD_free_commands(toExecute);
1228 toExecute = NULL;
1229 return;
1232 /* Not found anywhere - give up */
1233 SetLastError(ERROR_FILE_NOT_FOUND);
1234 WCMD_print_error ();
1236 /* If a command fails to launch, it sets errorlevel 9009 - which
1237 does not seem to have any associated constant definition */
1238 errorlevel = 9009;
1239 return;
1243 /*****************************************************************************
1244 * Process one command. If the command is EXIT this routine does not return.
1245 * We will recurse through here executing batch files.
1246 * Note: If call is used to a non-existing program, we reparse the line and
1247 * try to run it as an internal command. 'retrycall' represents whether
1248 * we are attempting this retry.
1250 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1251 const WCHAR *forVariable, const WCHAR *forValue,
1252 CMD_LIST **cmdList, BOOL retrycall)
1254 WCHAR *cmd, *p, *redir;
1255 int status, i;
1256 DWORD count, creationDisposition;
1257 HANDLE h;
1258 WCHAR *whichcmd;
1259 SECURITY_ATTRIBUTES sa;
1260 WCHAR *new_cmd = NULL;
1261 WCHAR *new_redir = NULL;
1262 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1263 GetStdHandle (STD_OUTPUT_HANDLE),
1264 GetStdHandle (STD_ERROR_HANDLE)};
1265 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1266 STD_OUTPUT_HANDLE,
1267 STD_ERROR_HANDLE};
1268 BOOL prev_echo_mode, piped = FALSE;
1270 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1271 wine_dbgstr_w(command), cmdList,
1272 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1274 /* If the next command is a pipe then we implement pipes by redirecting
1275 the output from this command to a temp file and input into the
1276 next command from that temp file.
1277 FIXME: Use of named pipes would make more sense here as currently this
1278 process has to finish before the next one can start but this requires
1279 a change to not wait for the first app to finish but rather the pipe */
1280 if (cmdList && (*cmdList)->nextcommand &&
1281 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1283 WCHAR temp_path[MAX_PATH];
1284 static const WCHAR cmdW[] = {'C','M','D','\0'};
1286 /* Remember piping is in action */
1287 WINE_TRACE("Output needs to be piped\n");
1288 piped = TRUE;
1290 /* Generate a unique temporary filename */
1291 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1292 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1293 WINE_TRACE("Using temporary file of %s\n",
1294 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1297 /* Move copy of the command onto the heap so it can be expanded */
1298 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1299 if (!new_cmd)
1301 WINE_ERR("Could not allocate memory for new_cmd\n");
1302 return;
1304 strcpyW(new_cmd, command);
1306 /* Move copy of the redirects onto the heap so it can be expanded */
1307 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1308 if (!new_redir)
1310 WINE_ERR("Could not allocate memory for new_redir\n");
1311 HeapFree( GetProcessHeap(), 0, new_cmd );
1312 return;
1315 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1316 if (piped) {
1317 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1318 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1319 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1320 } else {
1321 strcpyW(new_redir, redirects);
1324 /* Expand variables in command line mode only (batch mode will
1325 be expanded as the line is read in, except for 'for' loops) */
1326 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1327 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1328 cmd = new_cmd;
1331 * Changing default drive has to be handled as a special case.
1334 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1335 WCHAR envvar[5];
1336 WCHAR dir[MAX_PATH];
1338 /* According to MSDN CreateProcess docs, special env vars record
1339 the current directory on each drive, in the form =C:
1340 so see if one specified, and if so go back to it */
1341 strcpyW(envvar, equalW);
1342 strcatW(envvar, cmd);
1343 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1344 static const WCHAR fmt[] = {'%','s','\\','\0'};
1345 wsprintfW(cmd, fmt, cmd);
1346 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1348 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1349 status = SetCurrentDirectoryW(cmd);
1350 if (!status) WCMD_print_error ();
1351 HeapFree( GetProcessHeap(), 0, cmd );
1352 HeapFree( GetProcessHeap(), 0, new_redir );
1353 return;
1356 sa.nLength = sizeof(sa);
1357 sa.lpSecurityDescriptor = NULL;
1358 sa.bInheritHandle = TRUE;
1361 * Redirect stdin, stdout and/or stderr if required.
1364 /* STDIN could come from a preceding pipe, so delete on close if it does */
1365 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1366 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1367 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1368 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1369 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1370 if (h == INVALID_HANDLE_VALUE) {
1371 WCMD_print_error ();
1372 HeapFree( GetProcessHeap(), 0, cmd );
1373 HeapFree( GetProcessHeap(), 0, new_redir );
1374 return;
1376 SetStdHandle (STD_INPUT_HANDLE, h);
1378 /* No need to remember the temporary name any longer once opened */
1379 (*cmdList)->pipeFile[0] = 0x00;
1381 /* Otherwise STDIN could come from a '<' redirect */
1382 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1383 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1384 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1385 if (h == INVALID_HANDLE_VALUE) {
1386 WCMD_print_error ();
1387 HeapFree( GetProcessHeap(), 0, cmd );
1388 HeapFree( GetProcessHeap(), 0, new_redir );
1389 return;
1391 SetStdHandle (STD_INPUT_HANDLE, h);
1394 /* Scan the whole command looking for > and 2> */
1395 redir = new_redir;
1396 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1397 int handle = 0;
1399 if (p > redir && (*(p-1)=='2'))
1400 handle = 2;
1401 else
1402 handle = 1;
1404 p++;
1405 if ('>' == *p) {
1406 creationDisposition = OPEN_ALWAYS;
1407 p++;
1409 else {
1410 creationDisposition = CREATE_ALWAYS;
1413 /* Add support for 2>&1 */
1414 redir = p;
1415 if (*p == '&') {
1416 int idx = *(p+1) - '0';
1418 if (DuplicateHandle(GetCurrentProcess(),
1419 GetStdHandle(idx_stdhandles[idx]),
1420 GetCurrentProcess(),
1422 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1423 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1425 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1427 } else {
1428 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL, FALSE, FALSE);
1429 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1430 FILE_ATTRIBUTE_NORMAL, NULL);
1431 if (h == INVALID_HANDLE_VALUE) {
1432 WCMD_print_error ();
1433 HeapFree( GetProcessHeap(), 0, cmd );
1434 HeapFree( GetProcessHeap(), 0, new_redir );
1435 return;
1437 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1438 INVALID_SET_FILE_POINTER) {
1439 WCMD_print_error ();
1441 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1444 SetStdHandle (idx_stdhandles[handle], h);
1448 * Strip leading whitespaces, and a '@' if supplied
1450 whichcmd = WCMD_skip_leading_spaces(cmd);
1451 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1452 if (whichcmd[0] == '@') whichcmd++;
1455 * Check if the command entered is internal. If it is, pass the rest of the
1456 * line down to the command. If not try to run a program.
1459 count = 0;
1460 while (IsCharAlphaNumericW(whichcmd[count])) {
1461 count++;
1463 for (i=0; i<=WCMD_EXIT; i++) {
1464 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1465 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1467 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1468 WCMD_parse (p, quals, param1, param2);
1469 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1471 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1472 /* this is a help request for a builtin program */
1473 i = WCMD_HELP;
1474 memcpy(p, whichcmd, count * sizeof(WCHAR));
1475 p[count] = '\0';
1479 switch (i) {
1481 case WCMD_CALL:
1482 WCMD_call (p);
1483 break;
1484 case WCMD_CD:
1485 case WCMD_CHDIR:
1486 WCMD_setshow_default (p);
1487 break;
1488 case WCMD_CLS:
1489 WCMD_clear_screen ();
1490 break;
1491 case WCMD_COPY:
1492 WCMD_copy (p);
1493 break;
1494 case WCMD_CTTY:
1495 WCMD_change_tty ();
1496 break;
1497 case WCMD_DATE:
1498 WCMD_setshow_date ();
1499 break;
1500 case WCMD_DEL:
1501 case WCMD_ERASE:
1502 WCMD_delete (p);
1503 break;
1504 case WCMD_DIR:
1505 WCMD_directory (p);
1506 break;
1507 case WCMD_ECHO:
1508 WCMD_echo(&whichcmd[count]);
1509 break;
1510 case WCMD_GOTO:
1511 WCMD_goto (cmdList);
1512 break;
1513 case WCMD_HELP:
1514 WCMD_give_help (p);
1515 break;
1516 case WCMD_LABEL:
1517 WCMD_volume (TRUE, p);
1518 break;
1519 case WCMD_MD:
1520 case WCMD_MKDIR:
1521 WCMD_create_dir (p);
1522 break;
1523 case WCMD_MOVE:
1524 WCMD_move ();
1525 break;
1526 case WCMD_PATH:
1527 WCMD_setshow_path (p);
1528 break;
1529 case WCMD_PAUSE:
1530 WCMD_pause ();
1531 break;
1532 case WCMD_PROMPT:
1533 WCMD_setshow_prompt ();
1534 break;
1535 case WCMD_REM:
1536 break;
1537 case WCMD_REN:
1538 case WCMD_RENAME:
1539 WCMD_rename ();
1540 break;
1541 case WCMD_RD:
1542 case WCMD_RMDIR:
1543 WCMD_remove_dir (p);
1544 break;
1545 case WCMD_SETLOCAL:
1546 WCMD_setlocal(p);
1547 break;
1548 case WCMD_ENDLOCAL:
1549 WCMD_endlocal();
1550 break;
1551 case WCMD_SET:
1552 WCMD_setshow_env (p);
1553 break;
1554 case WCMD_SHIFT:
1555 WCMD_shift (p);
1556 break;
1557 case WCMD_START:
1558 WCMD_start (p);
1559 break;
1560 case WCMD_TIME:
1561 WCMD_setshow_time ();
1562 break;
1563 case WCMD_TITLE:
1564 if (strlenW(&whichcmd[count]) > 0)
1565 WCMD_title(&whichcmd[count+1]);
1566 break;
1567 case WCMD_TYPE:
1568 WCMD_type (p);
1569 break;
1570 case WCMD_VER:
1571 WCMD_output_asis(newlineW);
1572 WCMD_version ();
1573 break;
1574 case WCMD_VERIFY:
1575 WCMD_verify (p);
1576 break;
1577 case WCMD_VOL:
1578 WCMD_volume (FALSE, p);
1579 break;
1580 case WCMD_PUSHD:
1581 WCMD_pushd(p);
1582 break;
1583 case WCMD_POPD:
1584 WCMD_popd();
1585 break;
1586 case WCMD_ASSOC:
1587 WCMD_assoc(p, TRUE);
1588 break;
1589 case WCMD_COLOR:
1590 WCMD_color();
1591 break;
1592 case WCMD_FTYPE:
1593 WCMD_assoc(p, FALSE);
1594 break;
1595 case WCMD_MORE:
1596 WCMD_more(p);
1597 break;
1598 case WCMD_CHOICE:
1599 WCMD_choice(p);
1600 break;
1601 case WCMD_EXIT:
1602 WCMD_exit (cmdList);
1603 break;
1604 case WCMD_FOR:
1605 case WCMD_IF:
1606 /* Very oddly, probably because of all the special parsing required for
1607 these two commands, neither for nor if are supported when called,
1608 ie call if 1==1... will fail. */
1609 if (!retrycall) {
1610 if (i==WCMD_FOR) WCMD_for (p, cmdList);
1611 else if (i==WCMD_IF) WCMD_if (p, cmdList);
1612 break;
1614 /* else: drop through */
1615 default:
1616 prev_echo_mode = echo_mode;
1617 WCMD_run_program (whichcmd, FALSE);
1618 echo_mode = prev_echo_mode;
1620 HeapFree( GetProcessHeap(), 0, cmd );
1621 HeapFree( GetProcessHeap(), 0, new_redir );
1623 /* Restore old handles */
1624 for (i=0; i<3; i++) {
1625 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1626 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1627 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1632 /*************************************************************************
1633 * WCMD_LoadMessage
1634 * Load a string from the resource file, handling any error
1635 * Returns string retrieved from resource file
1637 WCHAR *WCMD_LoadMessage(UINT id) {
1638 static WCHAR msg[2048];
1639 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1641 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1642 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1643 strcpyW(msg, failedMsg);
1645 return msg;
1648 /***************************************************************************
1649 * WCMD_DumpCommands
1651 * Dumps out the parsed command line to ensure syntax is correct
1653 static void WCMD_DumpCommands(CMD_LIST *commands) {
1654 CMD_LIST *thisCmd = commands;
1656 WINE_TRACE("Parsed line:\n");
1657 while (thisCmd != NULL) {
1658 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1659 thisCmd,
1660 thisCmd->prevDelim,
1661 thisCmd->bracketDepth,
1662 thisCmd->nextcommand,
1663 wine_dbgstr_w(thisCmd->command),
1664 wine_dbgstr_w(thisCmd->redirects));
1665 thisCmd = thisCmd->nextcommand;
1669 /***************************************************************************
1670 * WCMD_addCommand
1672 * Adds a command to the current command list
1674 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1675 WCHAR *redirs, int *redirLen,
1676 WCHAR **copyTo, int **copyToLen,
1677 CMD_DELIMITERS prevDelim, int curDepth,
1678 CMD_LIST **lastEntry, CMD_LIST **output) {
1680 CMD_LIST *thisEntry = NULL;
1682 /* Allocate storage for command */
1683 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1685 /* Copy in the command */
1686 if (command) {
1687 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1688 (*commandLen+1) * sizeof(WCHAR));
1689 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1690 thisEntry->command[*commandLen] = 0x00;
1692 /* Copy in the redirects */
1693 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1694 (*redirLen+1) * sizeof(WCHAR));
1695 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1696 thisEntry->redirects[*redirLen] = 0x00;
1697 thisEntry->pipeFile[0] = 0x00;
1699 /* Reset the lengths */
1700 *commandLen = 0;
1701 *redirLen = 0;
1702 *copyToLen = commandLen;
1703 *copyTo = command;
1705 } else {
1706 thisEntry->command = NULL;
1707 thisEntry->redirects = NULL;
1708 thisEntry->pipeFile[0] = 0x00;
1711 /* Fill in other fields */
1712 thisEntry->nextcommand = NULL;
1713 thisEntry->prevDelim = prevDelim;
1714 thisEntry->bracketDepth = curDepth;
1715 if (*lastEntry) {
1716 (*lastEntry)->nextcommand = thisEntry;
1717 } else {
1718 *output = thisEntry;
1720 *lastEntry = thisEntry;
1724 /***************************************************************************
1725 * WCMD_IsEndQuote
1727 * Checks if the quote pointed to is the end-quote.
1729 * Quotes end if:
1731 * 1) The current parameter ends at EOL or at the beginning
1732 * of a redirection or pipe and not in a quote section.
1734 * 2) If the next character is a space and not in a quote section.
1736 * Returns TRUE if this is an end quote, and FALSE if it is not.
1739 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1741 int quoteCount = quoteIndex;
1742 int i;
1744 /* If we are not in a quoted section, then we are not an end-quote */
1745 if(quoteIndex == 0)
1747 return FALSE;
1750 /* Check how many quotes are left for this parameter */
1751 for(i=0;quote[i];i++)
1753 if(quote[i] == '"')
1755 quoteCount++;
1758 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1759 else if(((quoteCount % 2) == 0)
1760 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1762 break;
1766 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1767 be an end-quote */
1768 if(quoteIndex >= (quoteCount / 2))
1770 return TRUE;
1773 /* No cigar */
1774 return FALSE;
1777 /***************************************************************************
1778 * WCMD_ReadAndParseLine
1780 * Either uses supplied input or
1781 * Reads a file from the handle, and then...
1782 * Parse the text buffer, splitting into separate commands
1783 * - unquoted && strings split 2 commands but the 2nd is flagged as
1784 * following an &&
1785 * - ( as the first character just ups the bracket depth
1786 * - unquoted ) when bracket depth > 0 terminates a bracket and
1787 * adds a CMD_LIST structure with null command
1788 * - Anything else gets put into the command string (including
1789 * redirects)
1791 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1793 WCHAR *curPos;
1794 int inQuotes = 0;
1795 WCHAR curString[MAXSTRING];
1796 int curStringLen = 0;
1797 WCHAR curRedirs[MAXSTRING];
1798 int curRedirsLen = 0;
1799 WCHAR *curCopyTo;
1800 int *curLen;
1801 int curDepth = 0;
1802 CMD_LIST *lastEntry = NULL;
1803 CMD_DELIMITERS prevDelim = CMD_NONE;
1804 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1805 static const WCHAR remCmd[] = {'r','e','m'};
1806 static const WCHAR forCmd[] = {'f','o','r'};
1807 static const WCHAR ifCmd[] = {'i','f'};
1808 static const WCHAR ifElse[] = {'e','l','s','e'};
1809 BOOL inRem = FALSE;
1810 BOOL inFor = FALSE;
1811 BOOL inIn = FALSE;
1812 BOOL inIf = FALSE;
1813 BOOL inElse= FALSE;
1814 BOOL onlyWhiteSpace = FALSE;
1815 BOOL lastWasWhiteSpace = FALSE;
1816 BOOL lastWasDo = FALSE;
1817 BOOL lastWasIn = FALSE;
1818 BOOL lastWasElse = FALSE;
1819 BOOL lastWasRedirect = TRUE;
1820 BOOL lastWasCaret = FALSE;
1822 /* Allocate working space for a command read from keyboard, file etc */
1823 if (!extraSpace)
1824 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1825 if (!extraSpace)
1827 WINE_ERR("Could not allocate memory for extraSpace\n");
1828 return NULL;
1831 /* If initial command read in, use that, otherwise get input from handle */
1832 if (optionalcmd != NULL) {
1833 strcpyW(extraSpace, optionalcmd);
1834 } else if (readFrom == INVALID_HANDLE_VALUE) {
1835 WINE_FIXME("No command nor handle supplied\n");
1836 } else {
1837 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1838 return NULL;
1840 curPos = extraSpace;
1842 /* Handle truncated input - issue warning */
1843 if (strlenW(extraSpace) == MAXSTRING -1) {
1844 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1845 WCMD_output_asis_stderr(extraSpace);
1846 WCMD_output_asis_stderr(newlineW);
1849 /* Replace env vars if in a batch context */
1850 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1852 /* Skip preceding whitespace */
1853 while (*curPos == ' ' || *curPos == '\t') curPos++;
1855 /* Show prompt before batch line IF echo is on and in batch program */
1856 if (context && echo_mode && *curPos && (*curPos != '@')) {
1857 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1858 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1859 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1860 DWORD curr_size = strlenW(curPos);
1861 DWORD min_len = (curr_size < len ? curr_size : len);
1862 WCMD_show_prompt();
1863 WCMD_output_asis(curPos);
1864 /* I don't know why Windows puts a space here but it does */
1865 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1866 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1867 curPos, min_len, echoDot, len) != CSTR_EQUAL
1868 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1869 curPos, min_len, echoCol, len) != CSTR_EQUAL)
1871 WCMD_output_asis(spaceW);
1873 WCMD_output_asis(newlineW);
1876 /* Skip repeated 'no echo' characters */
1877 while (*curPos == '@') curPos++;
1879 /* Start with an empty string, copying to the command string */
1880 curStringLen = 0;
1881 curRedirsLen = 0;
1882 curCopyTo = curString;
1883 curLen = &curStringLen;
1884 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1886 /* Parse every character on the line being processed */
1887 while (*curPos != 0x00) {
1889 WCHAR thisChar;
1891 /* Debugging AID:
1892 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1893 lastWasWhiteSpace, onlyWhiteSpace);
1896 /* Prevent overflow caused by the caret escape char */
1897 if (*curLen >= MAXSTRING) {
1898 WINE_ERR("Overflow detected in command\n");
1899 return NULL;
1902 /* Certain commands need special handling */
1903 if (curStringLen == 0 && curCopyTo == curString) {
1904 static const WCHAR forDO[] = {'d','o'};
1906 /* If command starts with 'rem ', ignore any &&, ( etc. */
1907 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1908 inRem = TRUE;
1910 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1911 inFor = TRUE;
1913 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1914 is only true in the command portion of the IF statement, but this
1915 should suffice for now
1916 FIXME: Silly syntax like "if 1(==1( (
1917 echo they equal
1918 )" will be parsed wrong */
1919 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1920 inIf = TRUE;
1922 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1923 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1924 inElse = TRUE;
1925 lastWasElse = TRUE;
1926 onlyWhiteSpace = TRUE;
1927 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1928 (*curLen)+=keyw_len;
1929 curPos+=keyw_len;
1930 continue;
1932 /* In a for loop, the DO command will follow a close bracket followed by
1933 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1934 is then 0, and all whitespace is skipped */
1935 } else if (inFor &&
1936 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1937 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1938 WINE_TRACE("Found 'DO '\n");
1939 lastWasDo = TRUE;
1940 onlyWhiteSpace = TRUE;
1941 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1942 (*curLen)+=keyw_len;
1943 curPos+=keyw_len;
1944 continue;
1946 } else if (curCopyTo == curString) {
1948 /* Special handling for the 'FOR' command */
1949 if (inFor && lastWasWhiteSpace) {
1950 static const WCHAR forIN[] = {'i','n'};
1952 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1954 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1955 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1956 WINE_TRACE("Found 'IN '\n");
1957 lastWasIn = TRUE;
1958 onlyWhiteSpace = TRUE;
1959 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1960 (*curLen)+=keyw_len;
1961 curPos+=keyw_len;
1962 continue;
1967 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1968 so just use the default processing ie skip character specific
1969 matching below */
1970 if (!inRem) thisChar = *curPos;
1971 else thisChar = 'X'; /* Character with no special processing */
1973 lastWasWhiteSpace = FALSE; /* Will be reset below */
1974 lastWasCaret = FALSE;
1976 switch (thisChar) {
1978 case '=': /* drop through - ignore token delimiters at the start of a command */
1979 case ',': /* drop through - ignore token delimiters at the start of a command */
1980 case '\t':/* drop through - ignore token delimiters at the start of a command */
1981 case ' ':
1982 /* If a redirect in place, it ends here */
1983 if (!inQuotes && !lastWasRedirect) {
1985 /* If finishing off a redirect, add a whitespace delimiter */
1986 if (curCopyTo == curRedirs) {
1987 curCopyTo[(*curLen)++] = ' ';
1989 curCopyTo = curString;
1990 curLen = &curStringLen;
1992 if (*curLen > 0) {
1993 curCopyTo[(*curLen)++] = *curPos;
1996 /* Remember just processed whitespace */
1997 lastWasWhiteSpace = TRUE;
1999 break;
2001 case '>': /* drop through - handle redirect chars the same */
2002 case '<':
2003 /* Make a redirect start here */
2004 if (!inQuotes) {
2005 curCopyTo = curRedirs;
2006 curLen = &curRedirsLen;
2007 lastWasRedirect = TRUE;
2010 /* See if 1>, 2> etc, in which case we have some patching up
2011 to do (provided there's a preceding whitespace, and enough
2012 chars read so far) */
2013 if (curStringLen > 2
2014 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
2015 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2016 curStringLen--;
2017 curString[curStringLen] = 0x00;
2018 curCopyTo[(*curLen)++] = *(curPos-1);
2021 curCopyTo[(*curLen)++] = *curPos;
2023 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2024 do not process that ampersand as an AND operator */
2025 if (thisChar == '>' && *(curPos+1) == '&') {
2026 curCopyTo[(*curLen)++] = *(curPos+1);
2027 curPos++;
2029 break;
2031 case '|': /* Pipe character only if not || */
2032 if (!inQuotes) {
2033 lastWasRedirect = FALSE;
2035 /* Add an entry to the command list */
2036 if (curStringLen > 0) {
2038 /* Add the current command */
2039 WCMD_addCommand(curString, &curStringLen,
2040 curRedirs, &curRedirsLen,
2041 &curCopyTo, &curLen,
2042 prevDelim, curDepth,
2043 &lastEntry, output);
2047 if (*(curPos+1) == '|') {
2048 curPos++; /* Skip other | */
2049 prevDelim = CMD_ONFAILURE;
2050 } else {
2051 prevDelim = CMD_PIPE;
2053 } else {
2054 curCopyTo[(*curLen)++] = *curPos;
2056 break;
2058 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2059 inQuotes--;
2060 } else {
2061 inQuotes++; /* Quotes within quotes are fun! */
2063 curCopyTo[(*curLen)++] = *curPos;
2064 lastWasRedirect = FALSE;
2065 break;
2067 case '(': /* If a '(' is the first non whitespace in a command portion
2068 ie start of line or just after &&, then we read until an
2069 unquoted ) is found */
2070 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2071 ", for(%d, In:%d, Do:%d)"
2072 ", if(%d, else:%d, lwe:%d)\n",
2073 *curLen, inQuotes,
2074 onlyWhiteSpace,
2075 inFor, lastWasIn, lastWasDo,
2076 inIf, inElse, lastWasElse);
2077 lastWasRedirect = FALSE;
2079 /* Ignore open brackets inside the for set */
2080 if (*curLen == 0 && !inIn) {
2081 curDepth++;
2083 /* If in quotes, ignore brackets */
2084 } else if (inQuotes) {
2085 curCopyTo[(*curLen)++] = *curPos;
2087 /* In a FOR loop, an unquoted '(' may occur straight after
2088 IN or DO
2089 In an IF statement just handle it regardless as we don't
2090 parse the operands
2091 In an ELSE statement, only allow it straight away after
2092 the ELSE and whitespace
2094 } else if (inIf ||
2095 (inElse && lastWasElse && onlyWhiteSpace) ||
2096 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2098 /* If entering into an 'IN', set inIn */
2099 if (inFor && lastWasIn && onlyWhiteSpace) {
2100 WINE_TRACE("Inside an IN\n");
2101 inIn = TRUE;
2104 /* Add the current command */
2105 WCMD_addCommand(curString, &curStringLen,
2106 curRedirs, &curRedirsLen,
2107 &curCopyTo, &curLen,
2108 prevDelim, curDepth,
2109 &lastEntry, output);
2111 curDepth++;
2112 } else {
2113 curCopyTo[(*curLen)++] = *curPos;
2115 break;
2117 case '^': if (!inQuotes) {
2118 /* If we reach the end of the input, we need to wait for more */
2119 if (*(curPos+1) == 0x00) {
2120 lastWasCaret = TRUE;
2121 WINE_TRACE("Caret found at end of line\n");
2122 break;
2124 curPos++;
2126 curCopyTo[(*curLen)++] = *curPos;
2127 break;
2129 case '&': if (!inQuotes) {
2130 lastWasRedirect = FALSE;
2132 /* Add an entry to the command list */
2133 if (curStringLen > 0) {
2135 /* Add the current command */
2136 WCMD_addCommand(curString, &curStringLen,
2137 curRedirs, &curRedirsLen,
2138 &curCopyTo, &curLen,
2139 prevDelim, curDepth,
2140 &lastEntry, output);
2144 if (*(curPos+1) == '&') {
2145 curPos++; /* Skip other & */
2146 prevDelim = CMD_ONSUCCESS;
2147 } else {
2148 prevDelim = CMD_NONE;
2150 } else {
2151 curCopyTo[(*curLen)++] = *curPos;
2153 break;
2155 case ')': if (!inQuotes && curDepth > 0) {
2156 lastWasRedirect = FALSE;
2158 /* Add the current command if there is one */
2159 if (curStringLen) {
2161 /* Add the current command */
2162 WCMD_addCommand(curString, &curStringLen,
2163 curRedirs, &curRedirsLen,
2164 &curCopyTo, &curLen,
2165 prevDelim, curDepth,
2166 &lastEntry, output);
2169 /* Add an empty entry to the command list */
2170 prevDelim = CMD_NONE;
2171 WCMD_addCommand(NULL, &curStringLen,
2172 curRedirs, &curRedirsLen,
2173 &curCopyTo, &curLen,
2174 prevDelim, curDepth,
2175 &lastEntry, output);
2176 curDepth--;
2178 /* Leave inIn if necessary */
2179 if (inIn) inIn = FALSE;
2180 } else {
2181 curCopyTo[(*curLen)++] = *curPos;
2183 break;
2184 default:
2185 lastWasRedirect = FALSE;
2186 curCopyTo[(*curLen)++] = *curPos;
2189 curPos++;
2191 /* At various times we need to know if we have only skipped whitespace,
2192 so reset this variable and then it will remain true until a non
2193 whitespace is found */
2194 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2195 onlyWhiteSpace = FALSE;
2197 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2198 if (!lastWasWhiteSpace) {
2199 lastWasIn = lastWasDo = FALSE;
2202 /* If we have reached the end, add this command into the list
2203 Do not add command to list if escape char ^ was last */
2204 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2206 /* Add an entry to the command list */
2207 WCMD_addCommand(curString, &curStringLen,
2208 curRedirs, &curRedirsLen,
2209 &curCopyTo, &curLen,
2210 prevDelim, curDepth,
2211 &lastEntry, output);
2214 /* If we have reached the end of the string, see if bracketing or
2215 final caret is outstanding */
2216 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2217 readFrom != INVALID_HANDLE_VALUE) {
2218 WCHAR *extraData;
2220 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2221 inRem = FALSE;
2222 prevDelim = CMD_NONE;
2223 inQuotes = 0;
2224 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2225 extraData = extraSpace;
2227 /* Read more, skipping any blank lines */
2228 do {
2229 WINE_TRACE("Read more input\n");
2230 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2231 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2232 break;
2234 /* Edge case for carets - a completely blank line (i.e. was just
2235 CRLF) is oddly added as an LF but then more data is received (but
2236 only once more!) */
2237 if (lastWasCaret) {
2238 if (*extraSpace == 0x00) {
2239 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2240 *extraData++ = '\r';
2241 *extraData = 0x00;
2242 } else break;
2245 } while (*extraData == 0x00);
2246 curPos = extraSpace;
2247 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2248 /* Continue to echo commands IF echo is on and in batch program */
2249 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2250 WCMD_output_asis(extraSpace);
2251 WCMD_output_asis(newlineW);
2256 /* Dump out the parsed output */
2257 WCMD_DumpCommands(*output);
2259 return extraSpace;
2262 /***************************************************************************
2263 * WCMD_process_commands
2265 * Process all the commands read in so far
2267 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2268 const WCHAR *var, const WCHAR *val,
2269 BOOL retrycall) {
2271 int bdepth = -1;
2273 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2275 /* Loop through the commands, processing them one by one */
2276 while (thisCmd) {
2278 CMD_LIST *origCmd = thisCmd;
2280 /* If processing one bracket only, and we find the end bracket
2281 entry (or less), return */
2282 if (oneBracket && !thisCmd->command &&
2283 bdepth <= thisCmd->bracketDepth) {
2284 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2285 thisCmd, thisCmd->nextcommand);
2286 return thisCmd->nextcommand;
2289 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2290 about them and it will be handled in there)
2291 Also, skip over any batch labels (eg. :fred) */
2292 if (thisCmd->command && thisCmd->command[0] != ':') {
2293 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2294 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd, retrycall);
2297 /* Step on unless the command itself already stepped on */
2298 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2300 return NULL;
2303 /***************************************************************************
2304 * WCMD_free_commands
2306 * Frees the storage held for a parsed command line
2307 * - This is not done in the process_commands, as eventually the current
2308 * pointer will be modified within the commands, and hence a single free
2309 * routine is simpler
2311 void WCMD_free_commands(CMD_LIST *cmds) {
2313 /* Loop through the commands, freeing them one by one */
2314 while (cmds) {
2315 CMD_LIST *thisCmd = cmds;
2316 cmds = cmds->nextcommand;
2317 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2318 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2319 HeapFree(GetProcessHeap(), 0, thisCmd);
2324 /*****************************************************************************
2325 * Main entry point. This is a console application so we have a main() not a
2326 * winmain().
2329 int wmain (int argc, WCHAR *argvW[])
2331 int args;
2332 WCHAR *cmdLine = NULL;
2333 WCHAR *cmd = NULL;
2334 WCHAR *argPos = NULL;
2335 WCHAR string[1024];
2336 WCHAR envvar[4];
2337 BOOL opt_q;
2338 int opt_t = 0;
2339 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2340 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2341 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2343 srand(time(NULL));
2345 /* Pre initialize some messages */
2346 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2347 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), PACKAGE_VERSION);
2348 strcpyW(version_string, cmd);
2349 LocalFree(cmd);
2350 cmd = NULL;
2352 /* Can't use argc/argv as it will have stripped quotes from parameters
2353 * meaning cmd.exe /C echo "quoted string" is impossible
2355 cmdLine = GetCommandLineW();
2356 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2357 args = 1; /* start at first arg, skipping cmd.exe itself */
2359 opt_c = opt_k = opt_q = opt_s = FALSE;
2360 WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE, TRUE);
2361 while (argPos && argPos[0] != 0x00)
2363 WCHAR c;
2364 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(argPos));
2365 if (argPos[0]!='/' || argPos[1]=='\0') {
2366 args++;
2367 WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE, TRUE);
2368 continue;
2371 c=argPos[1];
2372 if (tolowerW(c)=='c') {
2373 opt_c = TRUE;
2374 } else if (tolowerW(c)=='q') {
2375 opt_q = TRUE;
2376 } else if (tolowerW(c)=='k') {
2377 opt_k = TRUE;
2378 } else if (tolowerW(c)=='s') {
2379 opt_s = TRUE;
2380 } else if (tolowerW(c)=='a') {
2381 unicodeOutput = FALSE;
2382 } else if (tolowerW(c)=='u') {
2383 unicodeOutput = TRUE;
2384 } else if (tolowerW(c)=='t' && argPos[2]==':') {
2385 opt_t=strtoulW(&argPos[3], NULL, 16);
2386 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2387 /* Ignored for compatibility with Windows */
2390 if (argPos[2]==0 || argPos[2]==' ' || argPos[2]=='\t') {
2391 args++;
2392 WCMD_parameter(cmdLine, args, &argPos, NULL, TRUE, TRUE);
2394 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2396 /* Do not step to next paramater, instead carry on parsing this one */
2397 argPos+=2;
2400 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2401 break;
2404 if (opt_q) {
2405 static const WCHAR eoff[] = {'O','F','F','\0'};
2406 WCMD_echo(eoff);
2409 /* Until we start to read from the keyboard, stay as non-interactive */
2410 interactive = FALSE;
2412 if (opt_c || opt_k) {
2413 int len;
2414 WCHAR *q1 = NULL,*q2 = NULL,*p;
2416 /* Handle very edge case error scenarion, "cmd.exe /c" ie when there are no
2417 * parameters after the /C or /K by pretending there was a single space */
2418 if (argPos == NULL) argPos = (WCHAR *)spaceW;
2420 /* Build the command to execute - It is what is left in argPos */
2421 len = strlenW(argPos);
2423 /* Take a copy */
2424 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2425 if (!cmd)
2426 exit(1);
2427 strcpyW(cmd, argPos);
2429 /* opt_s left unflagged if the command starts with and contains exactly
2430 * one quoted string (exactly two quote characters). The quoted string
2431 * must be an executable name that has whitespace and must not have the
2432 * following characters: &<>()@^| */
2434 if (!opt_s) {
2435 /* 1. Confirm there is at least one quote */
2436 q1 = strchrW(argPos, '"');
2437 if (!q1) opt_s=1;
2440 if (!opt_s) {
2441 /* 2. Confirm there is a second quote */
2442 q2 = strchrW(q1+1, '"');
2443 if (!q2) opt_s=1;
2446 if (!opt_s) {
2447 /* 3. Ensure there are no more quotes */
2448 if (strchrW(q2+1, '"')) opt_s=1;
2451 /* check first parameter for a space and invalid characters. There must not be any
2452 * invalid characters, but there must be one or more whitespace */
2453 if (!opt_s) {
2454 opt_s = TRUE;
2455 p=q1;
2456 while (p!=q2) {
2457 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2458 || *p=='@' || *p=='^' || *p=='|') {
2459 opt_s = TRUE;
2460 break;
2462 if (*p==' ' || *p=='\t')
2463 opt_s = FALSE;
2464 p++;
2468 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2470 /* Finally, we only stay in new mode IF the first parameter is quoted and
2471 is a valid executable, ie must exist, otherwise drop back to old mode */
2472 if (!opt_s) {
2473 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, NULL, FALSE, TRUE);
2474 WCHAR pathext[MAXSTRING];
2475 BOOL found = FALSE;
2477 /* Now extract PATHEXT */
2478 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
2479 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
2480 strcpyW (pathext, dfltPathExt);
2483 /* If the supplied parameter has any directory information, look there */
2484 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2485 if (strchrW(thisArg, '\\') != NULL) {
2487 GetFullPathNameW(thisArg, sizeof(string)/sizeof(WCHAR), string, NULL);
2488 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2489 p = string + strlenW(string);
2491 /* Does file exist with this name? */
2492 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2493 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2494 found = TRUE;
2495 } else {
2496 WCHAR *thisExt = pathext;
2498 /* No - try with each of the PATHEXT extensions */
2499 while (!found && thisExt) {
2500 WCHAR *nextExt = strchrW(thisExt, ';');
2502 if (nextExt) {
2503 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2504 p[(nextExt-thisExt)] = 0x00;
2505 thisExt = nextExt+1;
2506 } else {
2507 strcpyW(p, thisExt);
2508 thisExt = NULL;
2511 /* Does file exist with this extension appended? */
2512 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2513 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2514 found = TRUE;
2519 /* Otherwise we now need to look in the path to see if we can find it */
2520 } else {
2521 p = thisArg + strlenW(thisArg);
2523 /* Does file exist with this name? */
2524 if (SearchPathW(NULL, thisArg, NULL, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2525 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2526 found = TRUE;
2527 } else {
2528 WCHAR *thisExt = pathext;
2530 /* No - try with each of the PATHEXT extensions */
2531 while (!found && thisExt) {
2532 WCHAR *nextExt = strchrW(thisExt, ';');
2534 if (nextExt) {
2535 *nextExt = 0;
2536 nextExt = nextExt+1;
2537 } else {
2538 nextExt = NULL;
2541 /* Does file exist with this extension? */
2542 if (SearchPathW(NULL, thisArg, thisExt, sizeof(string)/sizeof(WCHAR), string, NULL) != 0) {
2543 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2544 wine_dbgstr_w(thisExt));
2545 found = TRUE;
2547 thisExt = nextExt;
2552 /* If not found, drop back to old behaviour */
2553 if (!found) {
2554 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2555 opt_s = TRUE;
2560 /* strip first and last quote characters if opt_s; check for invalid
2561 * executable is done later */
2562 if (opt_s && *cmd=='\"')
2563 WCMD_strip_quotes(cmd);
2566 /* Save cwd into appropriate env var (Must be before the /c processing */
2567 GetCurrentDirectoryW(sizeof(string)/sizeof(WCHAR), string);
2568 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2569 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2570 wsprintfW(envvar, fmt, string[0]);
2571 SetEnvironmentVariableW(envvar, string);
2572 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2575 if (opt_c) {
2576 /* If we do a "cmd /c command", we don't want to allocate a new
2577 * console since the command returns immediately. Rather, we use
2578 * the currently allocated input and output handles. This allows
2579 * us to pipe to and read from the command interpreter.
2582 /* Parse the command string, without reading any more input */
2583 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2584 WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
2585 WCMD_free_commands(toExecute);
2586 toExecute = NULL;
2588 HeapFree(GetProcessHeap(), 0, cmd);
2589 return errorlevel;
2592 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2593 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2594 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2596 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2597 if (opt_t) {
2598 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2599 defaultColor = opt_t & 0xFF;
2600 param1[0] = 0x00;
2601 WCMD_color();
2603 } else {
2604 /* Check HKCU\Software\Microsoft\Command Processor
2605 Then HKLM\Software\Microsoft\Command Processor
2606 for defaultcolour value
2607 Note Can be supplied as DWORD or REG_SZ
2608 Note2 When supplied as REG_SZ it's in decimal!!! */
2609 HKEY key;
2610 DWORD type;
2611 DWORD value=0, size=4;
2612 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2613 'M','i','c','r','o','s','o','f','t','\\',
2614 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2615 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2617 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2618 0, KEY_READ, &key) == ERROR_SUCCESS) {
2619 WCHAR strvalue[4];
2621 /* See if DWORD or REG_SZ */
2622 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2623 NULL, NULL) == ERROR_SUCCESS) {
2624 if (type == REG_DWORD) {
2625 size = sizeof(DWORD);
2626 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2627 (LPBYTE)&value, &size);
2628 } else if (type == REG_SZ) {
2629 size = sizeof(strvalue)/sizeof(WCHAR);
2630 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2631 (LPBYTE)strvalue, &size);
2632 value = strtoulW(strvalue, NULL, 10);
2635 RegCloseKey(key);
2638 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2639 0, KEY_READ, &key) == ERROR_SUCCESS) {
2640 WCHAR strvalue[4];
2642 /* See if DWORD or REG_SZ */
2643 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2644 NULL, NULL) == ERROR_SUCCESS) {
2645 if (type == REG_DWORD) {
2646 size = sizeof(DWORD);
2647 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2648 (LPBYTE)&value, &size);
2649 } else if (type == REG_SZ) {
2650 size = sizeof(strvalue)/sizeof(WCHAR);
2651 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2652 (LPBYTE)strvalue, &size);
2653 value = strtoulW(strvalue, NULL, 10);
2656 RegCloseKey(key);
2659 /* If one found, set the screen to that colour */
2660 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2661 defaultColor = value & 0xFF;
2662 param1[0] = 0x00;
2663 WCMD_color();
2668 if (opt_k) {
2669 /* Parse the command string, without reading any more input */
2670 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2671 WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
2672 WCMD_free_commands(toExecute);
2673 toExecute = NULL;
2674 HeapFree(GetProcessHeap(), 0, cmd);
2678 * Loop forever getting commands and executing them.
2681 SetEnvironmentVariableW(promptW, defaultpromptW);
2682 interactive = TRUE;
2683 if (!opt_k) WCMD_version ();
2684 while (TRUE) {
2686 /* Read until EOF (which for std input is never, but if redirect
2687 in place, may occur */
2688 if (echo_mode) WCMD_show_prompt();
2689 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2690 break;
2691 WCMD_process_commands(toExecute, FALSE, NULL, NULL, FALSE);
2692 WCMD_free_commands(toExecute);
2693 toExecute = NULL;
2695 return 0;