cmd: Inline some simple extern WCHAR strings.
[wine.git] / programs / cmd / wcmdmain.c
blob3484f05ae73541add5e8ef1b1af89ee130f0f3ae
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 <time.h>
29 #include "wcmd.h"
30 #include "shellapi.h"
31 #include "wine/debug.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
35 extern const WCHAR inbuilt[][10];
36 extern struct env_stack *pushd_directories;
38 BATCH_CONTEXT *context = NULL;
39 DWORD errorlevel;
40 WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING];
41 BOOL interactive;
42 FOR_CONTEXT forloopcontext; /* The 'for' loop context */
43 BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
45 int defaultColor = 7;
46 BOOL echo_mode = TRUE;
48 WCHAR anykey[100], version_string[100];
49 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
50 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
51 '.','c','o','m',';',
52 '.','c','m','d',';',
53 '.','e','x','e','\0'};
55 static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
57 /* Variables pertaining to paging */
58 static BOOL paged_mode;
59 static const WCHAR *pagedMessage = NULL;
60 static int line_count;
61 static int max_height;
62 static int max_width;
63 static int numChars;
65 #define MAX_WRITECONSOLE_SIZE 65535
68 * Returns a buffer for reading from/writing to file
69 * Never freed
71 static char *get_file_buffer(void)
73 static char *output_bufA = NULL;
74 if (!output_bufA)
75 output_bufA = heap_xalloc(MAX_WRITECONSOLE_SIZE);
76 return output_bufA;
79 /*******************************************************************
80 * WCMD_output_asis_len - send output to current standard output
82 * Output a formatted unicode string. Ideally this will go to the console
83 * and hence required WriteConsoleW to output it, however if file i/o is
84 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
86 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
88 DWORD nOut= 0;
89 DWORD res = 0;
91 /* If nothing to write, return (MORE does this sometimes) */
92 if (!len) return;
94 /* Try to write as unicode assuming it is to a console */
95 res = WriteConsoleW(device, message, len, &nOut, NULL);
97 /* If writing to console fails, assume it's file
98 i/o so convert to OEM codepage and output */
99 if (!res) {
100 BOOL usedDefaultChar = FALSE;
101 DWORD convertedChars;
102 char *buffer;
104 if (!unicodeOutput) {
106 if (!(buffer = get_file_buffer()))
107 return;
109 /* Convert to OEM, then output */
110 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
111 len, buffer, MAX_WRITECONSOLE_SIZE,
112 "?", &usedDefaultChar);
113 WriteFile(device, buffer, convertedChars,
114 &nOut, FALSE);
115 } else {
116 WriteFile(device, message, len*sizeof(WCHAR),
117 &nOut, FALSE);
120 return;
123 /*******************************************************************
124 * WCMD_output - send output to current standard output device.
128 void WINAPIV WCMD_output (const WCHAR *format, ...) {
130 __ms_va_list ap;
131 WCHAR* string;
132 DWORD len;
134 __ms_va_start(ap,format);
135 string = NULL;
136 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
137 format, 0, 0, (LPWSTR)&string, 0, &ap);
138 __ms_va_end(ap);
139 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
140 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
141 else
143 WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
144 LocalFree(string);
148 /*******************************************************************
149 * WCMD_output_stderr - send output to current standard error device.
153 void WINAPIV WCMD_output_stderr (const WCHAR *format, ...) {
155 __ms_va_list ap;
156 WCHAR* string;
157 DWORD len;
159 __ms_va_start(ap,format);
160 string = NULL;
161 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
162 format, 0, 0, (LPWSTR)&string, 0, &ap);
163 __ms_va_end(ap);
164 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
165 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
166 else
168 WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
169 LocalFree(string);
173 /*******************************************************************
174 * WCMD_format_string - allocate a buffer and format a string
178 WCHAR* WINAPIV WCMD_format_string (const WCHAR *format, ...)
180 __ms_va_list ap;
181 WCHAR* string;
182 DWORD len;
184 __ms_va_start(ap,format);
185 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
186 format, 0, 0, (LPWSTR)&string, 0, &ap);
187 __ms_va_end(ap);
188 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
189 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
190 string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
191 *string = 0;
193 return string;
196 void WCMD_enter_paged_mode(const WCHAR *msg)
198 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
200 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
201 max_height = consoleInfo.dwSize.Y;
202 max_width = consoleInfo.dwSize.X;
203 } else {
204 max_height = 25;
205 max_width = 80;
207 paged_mode = TRUE;
208 line_count = 0;
209 numChars = 0;
210 pagedMessage = (msg==NULL)? anykey : msg;
213 void WCMD_leave_paged_mode(void)
215 paged_mode = FALSE;
216 pagedMessage = NULL;
219 /***************************************************************************
220 * WCMD_Readfile
222 * Read characters in from a console/file, returning result in Unicode
224 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
226 DWORD numRead;
227 char *buffer;
229 if (WCMD_is_console_handle(hIn))
230 /* Try to read from console as Unicode */
231 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
233 /* We assume it's a file handle and read then convert from assumed OEM codepage */
234 if (!(buffer = get_file_buffer()))
235 return FALSE;
237 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
238 return FALSE;
240 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
242 return TRUE;
245 /*******************************************************************
246 * WCMD_output_asis_handle
248 * Send output to specified handle without formatting e.g. when message contains '%'
250 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
251 DWORD count;
252 const WCHAR* ptr;
253 WCHAR string[1024];
254 HANDLE handle = GetStdHandle(std_handle);
256 if (paged_mode) {
257 do {
258 ptr = message;
259 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
260 numChars++;
261 ptr++;
263 if (*ptr == '\n') ptr++;
264 WCMD_output_asis_len(message, ptr - message, handle);
265 numChars = 0;
266 if (++line_count >= max_height - 1) {
267 line_count = 0;
268 WCMD_output_asis_len(pagedMessage, lstrlenW(pagedMessage), handle);
269 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
271 } while (((message = ptr) != NULL) && (*ptr));
272 } else {
273 WCMD_output_asis_len(message, lstrlenW(message), handle);
277 /*******************************************************************
278 * WCMD_output_asis
280 * Send output to current standard output device, without formatting
281 * e.g. when message contains '%'
283 void WCMD_output_asis (const WCHAR *message) {
284 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
287 /*******************************************************************
288 * WCMD_output_asis_stderr
290 * Send output to current standard error device, without formatting
291 * e.g. when message contains '%'
293 void WCMD_output_asis_stderr (const WCHAR *message) {
294 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
297 /****************************************************************************
298 * WCMD_print_error
300 * Print the message for GetLastError
303 void WCMD_print_error (void) {
304 LPVOID lpMsgBuf;
305 DWORD error_code;
306 int status;
308 error_code = GetLastError ();
309 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
310 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
311 if (!status) {
312 WINE_FIXME ("Cannot display message for error %d, status %d\n",
313 error_code, GetLastError());
314 return;
317 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
318 GetStdHandle(STD_ERROR_HANDLE));
319 LocalFree (lpMsgBuf);
320 WCMD_output_asis_len(L"\r\n", lstrlenW(L"\r\n"), GetStdHandle(STD_ERROR_HANDLE));
321 return;
324 /******************************************************************************
325 * WCMD_show_prompt
327 * Display the prompt on STDout
331 static void WCMD_show_prompt (BOOL newLine) {
333 int status;
334 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
335 WCHAR *p, *q;
336 DWORD len;
337 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
339 len = GetEnvironmentVariableW(envPrompt, prompt_string, ARRAY_SIZE(prompt_string));
340 if ((len == 0) || (len >= ARRAY_SIZE(prompt_string))) {
341 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
342 lstrcpyW (prompt_string, dfltPrompt);
344 p = prompt_string;
345 q = out_string;
346 if (newLine) {
347 *q++ = '\r';
348 *q++ = '\n';
350 *q = '\0';
351 while (*p != '\0') {
352 if (*p != '$') {
353 *q++ = *p++;
354 *q = '\0';
356 else {
357 p++;
358 switch (toupper(*p)) {
359 case '$':
360 *q++ = '$';
361 break;
362 case 'A':
363 *q++ = '&';
364 break;
365 case 'B':
366 *q++ = '|';
367 break;
368 case 'C':
369 *q++ = '(';
370 break;
371 case 'D':
372 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH - (q - out_string));
373 while (*q) q++;
374 break;
375 case 'E':
376 *q++ = '\x1b';
377 break;
378 case 'F':
379 *q++ = ')';
380 break;
381 case 'G':
382 *q++ = '>';
383 break;
384 case 'H':
385 *q++ = '\b';
386 break;
387 case 'L':
388 *q++ = '<';
389 break;
390 case 'N':
391 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
392 if (status) {
393 *q++ = curdir[0];
395 break;
396 case 'P':
397 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
398 if (status) {
399 lstrcatW (q, curdir);
400 while (*q) q++;
402 break;
403 case 'Q':
404 *q++ = '=';
405 break;
406 case 'S':
407 *q++ = ' ';
408 break;
409 case 'T':
410 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
411 while (*q) q++;
412 break;
413 case 'V':
414 lstrcatW (q, version_string);
415 while (*q) q++;
416 break;
417 case '_':
418 *q++ = '\n';
419 break;
420 case '+':
421 if (pushd_directories) {
422 memset(q, '+', pushd_directories->u.stackdepth);
423 q = q + pushd_directories->u.stackdepth;
425 break;
427 p++;
428 *q = '\0';
431 WCMD_output_asis (out_string);
434 void *heap_xalloc(size_t size)
436 void *ret;
438 ret = heap_alloc(size);
439 if(!ret) {
440 ERR("Out of memory\n");
441 ExitProcess(1);
444 return ret;
447 /*************************************************************************
448 * WCMD_strsubstW
449 * Replaces a portion of a Unicode string with the specified string.
450 * It's up to the caller to ensure there is enough space in the
451 * destination buffer.
453 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
455 if (len < 0)
456 len=insert ? lstrlenW(insert) : 0;
457 if (start+len != next)
458 memmove(start+len, next, (lstrlenW(next) + 1) * sizeof(*next));
459 if (insert)
460 memcpy(start, insert, len * sizeof(*insert));
463 /***************************************************************************
464 * WCMD_skip_leading_spaces
466 * Return a pointer to the first non-whitespace character of string.
467 * Does not modify the input string.
469 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
471 WCHAR *ptr;
473 ptr = string;
474 while (*ptr == ' ' || *ptr == '\t') ptr++;
475 return ptr;
478 /***************************************************************************
479 * WCMD_keyword_ws_found
481 * Checks if the string located at ptr matches a keyword (of length len)
482 * followed by a whitespace character (space or tab)
484 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
485 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
486 ptr, len, keyword, len) == CSTR_EQUAL)
487 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
490 /*************************************************************************
491 * WCMD_strip_quotes
493 * Remove first and last quote WCHARacters, preserving all other text
494 * Returns the location of the final quote
496 WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
497 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
498 while((*dest=*src) != '\0') {
499 if (*src=='\"')
500 lastq=dest;
501 dest++; src++;
503 lastquote = lastq;
504 if (lastq) {
505 dest=lastq++;
506 while ((*dest++=*lastq++) != 0)
509 return lastquote;
513 /*************************************************************************
514 * WCMD_is_magic_envvar
515 * Return TRUE if s is '%'magicvar'%'
516 * and is not masked by a real environment variable.
519 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
521 int len;
523 if (s[0] != '%')
524 return FALSE; /* Didn't begin with % */
525 len = lstrlenW(s);
526 if (len < 2 || s[len-1] != '%')
527 return FALSE; /* Didn't end with another % */
529 if (CompareStringW(LOCALE_USER_DEFAULT,
530 NORM_IGNORECASE | SORT_STRINGSORT,
531 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
532 /* Name doesn't match. */
533 return FALSE;
536 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
537 /* Masked by real environment variable. */
538 return FALSE;
541 return TRUE;
544 /*************************************************************************
545 * WCMD_expand_envvar
547 * Expands environment variables, allowing for WCHARacter substitution
549 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
551 WCHAR *endOfVar = NULL, *s;
552 WCHAR *colonpos = NULL;
553 WCHAR thisVar[MAXSTRING];
554 WCHAR thisVarContents[MAXSTRING];
555 WCHAR savedchar = 0x00;
556 int len;
558 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
559 static const WCHAR Date[] = {'D','A','T','E','\0'};
560 static const WCHAR Time[] = {'T','I','M','E','\0'};
561 static const WCHAR Cd[] = {'C','D','\0'};
562 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
563 WCHAR Delims[] = {'%',':','\0'}; /* First char gets replaced appropriately */
565 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
567 /* Find the end of the environment variable, and extract name */
568 Delims[0] = startchar;
569 endOfVar = wcspbrk(start+1, Delims);
571 if (endOfVar == NULL || *endOfVar==' ') {
573 /* In batch program, missing terminator for % and no following
574 ':' just removes the '%' */
575 if (context) {
576 WCMD_strsubstW(start, start + 1, NULL, 0);
577 return start;
578 } else {
580 /* In command processing, just ignore it - allows command line
581 syntax like: for %i in (a.a) do echo %i */
582 return start+1;
586 /* If ':' found, process remaining up until '%' (or stop at ':' if
587 a missing '%' */
588 if (*endOfVar==':') {
589 WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar);
590 if (endOfVar2 != NULL) endOfVar = endOfVar2;
593 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
594 thisVar[(endOfVar - start)+1] = 0x00;
595 colonpos = wcschr(thisVar+1, ':');
597 /* If there's complex substitution, just need %var% for now
598 to get the expanded data to play with */
599 if (colonpos) {
600 *colonpos = startchar;
601 savedchar = *(colonpos+1);
602 *(colonpos+1) = 0x00;
605 /* By now, we know the variable we want to expand but it may be
606 surrounded by '!' if we are in delayed expansion - if so convert
607 to % signs. */
608 if (startchar=='!') {
609 thisVar[0] = '%';
610 thisVar[(endOfVar - start)] = '%';
612 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
614 /* Expand to contents, if unchanged, return */
615 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
616 /* override if existing env var called that name */
617 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
618 static const WCHAR fmt[] = {'%','d','\0'};
619 wsprintfW(thisVarContents, fmt, errorlevel);
620 len = lstrlenW(thisVarContents);
621 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
622 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
623 NULL, thisVarContents, MAXSTRING);
624 len = lstrlenW(thisVarContents);
625 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
626 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
627 NULL, thisVarContents, MAXSTRING);
628 len = lstrlenW(thisVarContents);
629 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
630 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
631 len = lstrlenW(thisVarContents);
632 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
633 static const WCHAR fmt[] = {'%','d','\0'};
634 wsprintfW(thisVarContents, fmt, rand() % 32768);
635 len = lstrlenW(thisVarContents);
636 } else {
638 len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents));
641 if (len == 0)
642 return endOfVar+1;
644 /* In a batch program, unknown env vars are replaced with nothing,
645 note syntax %garbage:1,3% results in anything after the ':'
646 except the %
647 From the command line, you just get back what you entered */
648 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
650 /* Restore the complex part after the compare */
651 if (colonpos) {
652 *colonpos = ':';
653 *(colonpos+1) = savedchar;
656 /* Command line - just ignore this */
657 if (context == NULL) return endOfVar+1;
660 /* Batch - replace unknown env var with nothing */
661 if (colonpos == NULL) {
662 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
663 } else {
664 len = lstrlenW(thisVar);
665 thisVar[len-1] = 0x00;
666 /* If %:...% supplied, : is retained */
667 if (colonpos == thisVar+1) {
668 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
669 } else {
670 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
673 return start;
677 /* See if we need to do complex substitution (any ':'s), if not
678 then our work here is done */
679 if (colonpos == NULL) {
680 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
681 return start;
684 /* Restore complex bit */
685 *colonpos = ':';
686 *(colonpos+1) = savedchar;
689 Handle complex substitutions:
690 xxx=yyy (replace xxx with yyy)
691 *xxx=yyy (replace up to and including xxx with yyy)
692 ~x (from x WCHARs in)
693 ~-x (from x WCHARs from the end)
694 ~x,y (from x WCHARs in for y WCHARacters)
695 ~x,-y (from x WCHARs in until y WCHARacters from the end)
698 /* ~ is substring manipulation */
699 if (savedchar == '~') {
701 int substrposition, substrlength = 0;
702 WCHAR *commapos = wcschr(colonpos+2, ',');
703 WCHAR *startCopy;
705 substrposition = wcstol(colonpos+2, NULL, 10);
706 if (commapos) substrlength = wcstol(commapos+1, NULL, 10);
708 /* Check bounds */
709 if (substrposition >= 0) {
710 startCopy = &thisVarContents[min(substrposition, len)];
711 } else {
712 startCopy = &thisVarContents[max(0, len+substrposition-1)];
715 if (commapos == NULL) {
716 /* Copy the lot */
717 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
718 } else if (substrlength < 0) {
720 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
721 if (copybytes > len) copybytes = len;
722 else if (copybytes < 0) copybytes = 0;
723 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
724 } else {
725 substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
726 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
729 /* search and replace manipulation */
730 } else {
731 WCHAR *equalspos = wcsstr(colonpos, L"=");
732 WCHAR *replacewith = equalspos+1;
733 WCHAR *found = NULL;
734 WCHAR *searchIn;
735 WCHAR *searchFor;
737 if (equalspos == NULL) return start+1;
738 s = heap_strdupW(endOfVar + 1);
740 /* Null terminate both strings */
741 thisVar[lstrlenW(thisVar)-1] = 0x00;
742 *equalspos = 0x00;
744 /* Since we need to be case insensitive, copy the 2 buffers */
745 searchIn = heap_strdupW(thisVarContents);
746 CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
747 searchFor = heap_strdupW(colonpos+1);
748 CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
750 /* Handle wildcard case */
751 if (*(colonpos+1) == '*') {
752 /* Search for string to replace */
753 found = wcsstr(searchIn, searchFor+1);
755 if (found) {
756 /* Do replacement */
757 lstrcpyW(start, replacewith);
758 lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
759 lstrcatW(start, s);
760 } else {
761 /* Copy as is */
762 lstrcpyW(start, thisVarContents);
763 lstrcatW(start, s);
766 } else {
767 /* Loop replacing all instances */
768 WCHAR *lastFound = searchIn;
769 WCHAR *outputposn = start;
771 *start = 0x00;
772 while ((found = wcsstr(lastFound, searchFor))) {
773 lstrcpynW(outputposn,
774 thisVarContents + (lastFound-searchIn),
775 (found - lastFound)+1);
776 outputposn = outputposn + (found - lastFound);
777 lstrcatW(outputposn, replacewith);
778 outputposn = outputposn + lstrlenW(replacewith);
779 lastFound = found + lstrlenW(searchFor);
781 lstrcatW(outputposn,
782 thisVarContents + (lastFound-searchIn));
783 lstrcatW(outputposn, s);
785 heap_free(s);
786 heap_free(searchIn);
787 heap_free(searchFor);
789 return start;
792 /*****************************************************************************
793 * Expand the command. Native expands lines from batch programs as they are
794 * read in and not again, except for 'for' variable substitution.
795 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
796 * atExecute is TRUE when the expansion is occurring as the command is executed
797 * rather than at parse time, i.e. delayed expansion and for loops need to be
798 * processed
800 static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
802 /* For commands in a context (batch program): */
803 /* Expand environment variables in a batch file %{0-9} first */
804 /* including support for any ~ modifiers */
805 /* Additionally: */
806 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
807 /* names allowing environment variable overrides */
808 /* NOTE: To support the %PATH:xxx% syntax, also perform */
809 /* manual expansion of environment variables here */
811 WCHAR *p = cmd;
812 WCHAR *t;
813 int i;
814 WCHAR *delayedp = NULL;
815 WCHAR startchar = '%';
816 WCHAR *normalp;
818 /* Display the FOR variables in effect */
819 for (i=0;i<52;i++) {
820 if (forloopcontext.variable[i]) {
821 WINE_TRACE("FOR variable context: %c = '%s'\n",
822 i<26?i+'a':(i-26)+'A',
823 wine_dbgstr_w(forloopcontext.variable[i]));
827 /* Find the next environment variable delimiter */
828 normalp = wcschr(p, '%');
829 if (delayed) delayedp = wcschr(p, '!');
830 if (!normalp) p = delayedp;
831 else if (!delayedp) p = normalp;
832 else p = min(p,delayedp);
833 if (p) startchar = *p;
835 while (p) {
837 WINE_TRACE("Translate command:%s %d (at: %s)\n",
838 wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
839 i = *(p+1) - '0';
841 /* Don't touch %% unless it's in Batch */
842 if (!atExecute && *(p+1) == startchar) {
843 if (context) {
844 WCMD_strsubstW(p, p+1, NULL, 0);
846 p+=1;
848 /* Replace %~ modifications if in batch program */
849 } else if (*(p+1) == '~') {
850 WCMD_HandleTildeModifiers(&p, atExecute);
851 p++;
853 /* Replace use of %0...%9 if in batch program*/
854 } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
855 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
856 NULL, TRUE, TRUE);
857 WCMD_strsubstW(p, p+2, t, -1);
859 /* Replace use of %* if in batch program*/
860 } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
861 WCHAR *startOfParms = NULL;
862 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
863 if (startOfParms != NULL) {
864 startOfParms += lstrlenW(thisParm);
865 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
866 WCMD_strsubstW(p, p+2, startOfParms, -1);
867 } else
868 WCMD_strsubstW(p, p+2, NULL, 0);
870 } else {
871 int forvaridx = FOR_VAR_IDX(*(p+1));
872 if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
873 /* Replace the 2 characters, % and for variable character */
874 WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
875 } else if (!atExecute || startchar == '!') {
876 p = WCMD_expand_envvar(p, startchar);
878 /* In a FOR loop, see if this is the variable to replace */
879 } else { /* Ignore %'s on second pass of batch program */
880 p++;
884 /* Find the next environment variable delimiter */
885 normalp = wcschr(p, '%');
886 if (delayed) delayedp = wcschr(p, '!');
887 if (!normalp) p = delayedp;
888 else if (!delayedp) p = normalp;
889 else p = min(p,delayedp);
890 if (p) startchar = *p;
893 return;
897 /*******************************************************************
898 * WCMD_parse - parse a command into parameters and qualifiers.
900 * On exit, all qualifiers are concatenated into q, the first string
901 * not beginning with "/" is in p1 and the
902 * second in p2. Any subsequent non-qualifier strings are lost.
903 * Parameters in quotes are handled.
905 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
907 int p = 0;
909 *q = *p1 = *p2 = '\0';
910 while (TRUE) {
911 switch (*s) {
912 case '/':
913 *q++ = *s++;
914 while ((*s != '\0') && (*s != ' ') && *s != '/') {
915 *q++ = towupper (*s++);
917 *q = '\0';
918 break;
919 case ' ':
920 case '\t':
921 s++;
922 break;
923 case '"':
924 s++;
925 while ((*s != '\0') && (*s != '"')) {
926 if (p == 0) *p1++ = *s++;
927 else if (p == 1) *p2++ = *s++;
928 else s++;
930 if (p == 0) *p1 = '\0';
931 if (p == 1) *p2 = '\0';
932 p++;
933 if (*s == '"') s++;
934 break;
935 case '\0':
936 return;
937 default:
938 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
939 && (*s != '=') && (*s != ',') ) {
940 if (p == 0) *p1++ = *s++;
941 else if (p == 1) *p2++ = *s++;
942 else s++;
944 /* Skip concurrent parms */
945 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
947 if (p == 0) *p1 = '\0';
948 if (p == 1) *p2 = '\0';
949 p++;
954 static void init_msvcrt_io_block(STARTUPINFOW* st)
956 STARTUPINFOW st_p;
957 /* fetch the parent MSVCRT info block if any, so that the child can use the
958 * same handles as its grand-father
960 st_p.cb = sizeof(STARTUPINFOW);
961 GetStartupInfoW(&st_p);
962 st->cbReserved2 = st_p.cbReserved2;
963 st->lpReserved2 = st_p.lpReserved2;
964 if (st_p.cbReserved2 && st_p.lpReserved2)
966 unsigned num = *(unsigned*)st_p.lpReserved2;
967 char* flags;
968 HANDLE* handles;
969 BYTE *ptr;
970 size_t sz;
972 /* Override the entries for fd 0,1,2 if we happened
973 * to change those std handles (this depends on the way cmd sets
974 * its new input & output handles)
976 sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
977 ptr = heap_xalloc(sz);
978 flags = (char*)(ptr + sizeof(unsigned));
979 handles = (HANDLE*)(flags + num * sizeof(char));
981 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
982 st->cbReserved2 = sz;
983 st->lpReserved2 = ptr;
985 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
986 if (num <= 0 || (flags[0] & WX_OPEN))
988 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
989 flags[0] |= WX_OPEN;
991 if (num <= 1 || (flags[1] & WX_OPEN))
993 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
994 flags[1] |= WX_OPEN;
996 if (num <= 2 || (flags[2] & WX_OPEN))
998 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
999 flags[2] |= WX_OPEN;
1001 #undef WX_OPEN
1005 /******************************************************************************
1006 * WCMD_run_program
1008 * Execute a command line as an external program. Must allow recursion.
1010 * Precedence:
1011 * Manual testing under windows shows PATHEXT plays a key part in this,
1012 * and the search algorithm and precedence appears to be as follows.
1014 * Search locations:
1015 * If directory supplied on command, just use that directory
1016 * If extension supplied on command, look for that explicit name first
1017 * Otherwise, search in each directory on the path
1018 * Precedence:
1019 * If extension supplied on command, look for that explicit name first
1020 * Then look for supplied name .* (even if extension supplied, so
1021 * 'garbage.exe' will match 'garbage.exe.cmd')
1022 * If any found, cycle through PATHEXT looking for name.exe one by one
1023 * Launching
1024 * Once a match has been found, it is launched - Code currently uses
1025 * findexecutable to achieve this which is left untouched.
1026 * If an executable has not been found, and we were launched through
1027 * a call, we need to check if the command is an internal command,
1028 * so go back through wcmd_execute.
1031 void WCMD_run_program (WCHAR *command, BOOL called)
1033 WCHAR temp[MAX_PATH];
1034 WCHAR pathtosearch[MAXSTRING];
1035 WCHAR *pathposn;
1036 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1037 MAX_PATH, including null character */
1038 WCHAR *lastSlash;
1039 WCHAR pathext[MAXSTRING];
1040 WCHAR *firstParam;
1041 BOOL extensionsupplied = FALSE;
1042 BOOL explicit_path = FALSE;
1043 BOOL status;
1044 DWORD len;
1045 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1046 static const WCHAR delims[] = {'/','\\',':','\0'};
1048 /* Quick way to get the filename is to extract the first argument. */
1049 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1050 firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1051 if (!firstParam) return;
1053 if (!firstParam[0]) {
1054 errorlevel = 0;
1055 return;
1058 /* Calculate the search path and stem to search for */
1059 if (wcspbrk (firstParam, delims) == NULL) { /* No explicit path given, search path */
1060 static const WCHAR curDir[] = {'.',';','\0'};
1061 lstrcpyW(pathtosearch, curDir);
1062 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
1063 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
1064 static const WCHAR curDir[] = {'.','\0'};
1065 lstrcpyW (pathtosearch, curDir);
1067 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
1068 if (lstrlenW(firstParam) >= MAX_PATH)
1070 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1071 return;
1074 lstrcpyW(stemofsearch, firstParam);
1076 } else {
1078 /* Convert eg. ..\fred to include a directory by removing file part */
1079 GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
1080 lastSlash = wcsrchr(pathtosearch, '\\');
1081 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1082 lstrcpyW(stemofsearch, lastSlash+1);
1084 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1085 c:\windows\a.bat syntax */
1086 if (lastSlash) *(lastSlash + 1) = 0x00;
1087 explicit_path = TRUE;
1090 /* Now extract PATHEXT */
1091 len = GetEnvironmentVariableW(envPathExt, pathext, ARRAY_SIZE(pathext));
1092 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
1093 lstrcpyW (pathext, dfltPathExt);
1096 /* Loop through the search path, dir by dir */
1097 pathposn = pathtosearch;
1098 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1099 wine_dbgstr_w(stemofsearch));
1100 while (pathposn) {
1101 WCHAR thisDir[MAX_PATH] = {'\0'};
1102 int length = 0;
1103 WCHAR *pos = NULL;
1104 BOOL found = FALSE;
1105 BOOL inside_quotes = FALSE;
1107 if (explicit_path)
1109 lstrcpyW(thisDir, pathposn);
1110 pathposn = NULL;
1112 else
1114 /* Work on the next directory on the search path */
1115 pos = pathposn;
1116 while ((inside_quotes || *pos != ';') && *pos != 0)
1118 if (*pos == '"')
1119 inside_quotes = !inside_quotes;
1120 pos++;
1123 if (*pos) /* Reached semicolon */
1125 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1126 thisDir[(pos-pathposn)] = 0x00;
1127 pathposn = pos+1;
1129 else /* Reached string end */
1131 lstrcpyW(thisDir, pathposn);
1132 pathposn = NULL;
1135 /* Remove quotes */
1136 length = lstrlenW(thisDir);
1137 if (thisDir[length - 1] == '"')
1138 thisDir[length - 1] = 0;
1140 if (*thisDir != '"')
1141 lstrcpyW(temp, thisDir);
1142 else
1143 lstrcpyW(temp, thisDir + 1);
1145 /* Since you can have eg. ..\.. on the path, need to expand
1146 to full information */
1147 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1150 /* 1. If extension supplied, see if that file exists */
1151 lstrcatW(thisDir, L"\\");
1152 lstrcatW(thisDir, stemofsearch);
1153 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
1155 /* 1. If extension supplied, see if that file exists */
1156 if (extensionsupplied) {
1157 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1158 found = TRUE;
1162 /* 2. Any .* matches? */
1163 if (!found) {
1164 HANDLE h;
1165 WIN32_FIND_DATAW finddata;
1166 static const WCHAR allFiles[] = {'.','*','\0'};
1168 lstrcatW(thisDir,allFiles);
1169 h = FindFirstFileW(thisDir, &finddata);
1170 FindClose(h);
1171 if (h != INVALID_HANDLE_VALUE) {
1173 WCHAR *thisExt = pathext;
1175 /* 3. Yes - Try each path ext */
1176 while (thisExt) {
1177 WCHAR *nextExt = wcschr(thisExt, ';');
1179 if (nextExt) {
1180 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1181 pos[(nextExt-thisExt)] = 0x00;
1182 thisExt = nextExt+1;
1183 } else {
1184 lstrcpyW(pos, thisExt);
1185 thisExt = NULL;
1188 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1189 found = TRUE;
1190 thisExt = NULL;
1196 /* Once found, launch it */
1197 if (found) {
1198 STARTUPINFOW st;
1199 PROCESS_INFORMATION pe;
1200 SHFILEINFOW psfi;
1201 DWORD console;
1202 HINSTANCE hinst;
1203 WCHAR *ext = wcsrchr( thisDir, '.' );
1204 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1205 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1207 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1209 /* Special case BAT and CMD */
1210 if (ext && (!wcsicmp(ext, batExt) || !wcsicmp(ext, cmdExt))) {
1211 BOOL oldinteractive = interactive;
1212 interactive = FALSE;
1213 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1214 interactive = oldinteractive;
1215 return;
1216 } else {
1218 /* thisDir contains the file to be launched, but with what?
1219 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1220 hinst = FindExecutableW (thisDir, NULL, temp);
1221 if ((INT_PTR)hinst < 32)
1222 console = 0;
1223 else
1224 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1226 ZeroMemory (&st, sizeof(STARTUPINFOW));
1227 st.cb = sizeof(STARTUPINFOW);
1228 init_msvcrt_io_block(&st);
1230 /* Launch the process and if a CUI wait on it to complete
1231 Note: Launching internal wine processes cannot specify a full path to exe */
1232 status = CreateProcessW(thisDir,
1233 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1234 heap_free(st.lpReserved2);
1235 if ((opt_c || opt_k) && !opt_s && !status
1236 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1237 /* strip first and last quote WCHARacters and try again */
1238 WCMD_strip_quotes(command);
1239 opt_s = TRUE;
1240 WCMD_run_program(command, called);
1241 return;
1244 if (!status)
1245 break;
1247 /* Always wait when non-interactive (cmd /c or in batch program),
1248 or for console applications */
1249 if (!interactive || (console && !HIWORD(console)))
1250 WaitForSingleObject (pe.hProcess, INFINITE);
1251 GetExitCodeProcess (pe.hProcess, &errorlevel);
1252 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1254 CloseHandle(pe.hProcess);
1255 CloseHandle(pe.hThread);
1256 return;
1261 /* Not found anywhere - were we called? */
1262 if (called) {
1263 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1265 /* Parse the command string, without reading any more input */
1266 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1267 WCMD_process_commands(toExecute, FALSE, called);
1268 WCMD_free_commands(toExecute);
1269 toExecute = NULL;
1270 return;
1273 /* Not found anywhere - give up */
1274 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1276 /* If a command fails to launch, it sets errorlevel 9009 - which
1277 does not seem to have any associated constant definition */
1278 errorlevel = 9009;
1279 return;
1283 /*****************************************************************************
1284 * Process one command. If the command is EXIT this routine does not return.
1285 * We will recurse through here executing batch files.
1286 * Note: If call is used to a non-existing program, we reparse the line and
1287 * try to run it as an internal command. 'retrycall' represents whether
1288 * we are attempting this retry.
1290 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1291 CMD_LIST **cmdList, BOOL retrycall)
1293 WCHAR *cmd, *parms_start, *redir;
1294 WCHAR *pos;
1295 int status, i, cmd_index;
1296 DWORD count, creationDisposition;
1297 HANDLE h;
1298 WCHAR *whichcmd;
1299 SECURITY_ATTRIBUTES sa;
1300 WCHAR *new_cmd = NULL;
1301 WCHAR *new_redir = NULL;
1302 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1303 GetStdHandle (STD_OUTPUT_HANDLE),
1304 GetStdHandle (STD_ERROR_HANDLE)};
1305 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1306 STD_OUTPUT_HANDLE,
1307 STD_ERROR_HANDLE};
1308 BOOL prev_echo_mode, piped = FALSE;
1310 WINE_TRACE("command on entry:%s (%p)\n",
1311 wine_dbgstr_w(command), cmdList);
1313 /* Move copy of the command onto the heap so it can be expanded */
1314 new_cmd = heap_xalloc(MAXSTRING * sizeof(WCHAR));
1315 lstrcpyW(new_cmd, command);
1316 cmd = new_cmd;
1318 /* Move copy of the redirects onto the heap so it can be expanded */
1319 new_redir = heap_xalloc(MAXSTRING * sizeof(WCHAR));
1320 redir = new_redir;
1322 /* Strip leading whitespaces, and a '@' if supplied */
1323 whichcmd = WCMD_skip_leading_spaces(cmd);
1324 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1325 if (whichcmd[0] == '@') whichcmd++;
1327 /* Check if the command entered is internal, and identify which one */
1328 count = 0;
1329 while (IsCharAlphaNumericW(whichcmd[count])) {
1330 count++;
1332 for (i=0; i<=WCMD_EXIT; i++) {
1333 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1334 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1336 cmd_index = i;
1337 parms_start = WCMD_skip_leading_spaces (&whichcmd[count]);
1339 /* If the next command is a pipe then we implement pipes by redirecting
1340 the output from this command to a temp file and input into the
1341 next command from that temp file.
1342 Note: Do not do this for a for or if statement as the pipe is for
1343 the individual statements, not the for or if itself.
1344 FIXME: Use of named pipes would make more sense here as currently this
1345 process has to finish before the next one can start but this requires
1346 a change to not wait for the first app to finish but rather the pipe */
1347 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) &&
1348 cmdList && (*cmdList)->nextcommand &&
1349 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1351 WCHAR temp_path[MAX_PATH];
1352 static const WCHAR cmdW[] = {'C','M','D','\0'};
1354 /* Remember piping is in action */
1355 WINE_TRACE("Output needs to be piped\n");
1356 piped = TRUE;
1358 /* Generate a unique temporary filename */
1359 GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
1360 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1361 WINE_TRACE("Using temporary file of %s\n",
1362 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1365 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1366 if (piped) {
1367 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1368 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1369 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1370 } else {
1371 lstrcpyW(new_redir, redirects);
1374 /* Expand variables in command line mode only (batch mode will
1375 be expanded as the line is read in, except for 'for' loops) */
1376 handleExpansion(new_cmd, (context != NULL), delayedsubst);
1377 handleExpansion(new_redir, (context != NULL), delayedsubst);
1380 * Changing default drive has to be handled as a special case, anything
1381 * else if it exists after whitespace is ignored
1384 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) &&
1385 (!cmd[2] || cmd[2] == ' ' || cmd[2] == '\t')) {
1386 WCHAR envvar[5];
1387 WCHAR dir[MAX_PATH];
1389 /* Ignore potential garbage on the same line */
1390 cmd[2]=0x00;
1392 /* According to MSDN CreateProcess docs, special env vars record
1393 the current directory on each drive, in the form =C:
1394 so see if one specified, and if so go back to it */
1395 lstrcpyW(envvar, L"=");
1396 lstrcatW(envvar, cmd);
1397 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1398 static const WCHAR fmt[] = {'%','s','\\','\0'};
1399 wsprintfW(cmd, fmt, cmd);
1400 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1402 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1403 status = SetCurrentDirectoryW(cmd);
1404 if (!status) WCMD_print_error ();
1405 heap_free(cmd );
1406 heap_free(new_redir);
1407 return;
1410 sa.nLength = sizeof(sa);
1411 sa.lpSecurityDescriptor = NULL;
1412 sa.bInheritHandle = TRUE;
1415 * Redirect stdin, stdout and/or stderr if required.
1416 * Note: Do not do this for a for or if statement as the pipe is for
1417 * the individual statements, not the for or if itself.
1419 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) {
1420 /* STDIN could come from a preceding pipe, so delete on close if it does */
1421 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1422 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1423 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1424 FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
1425 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1426 if (h == INVALID_HANDLE_VALUE) {
1427 WCMD_print_error ();
1428 heap_free(cmd);
1429 heap_free(new_redir);
1430 return;
1432 SetStdHandle (STD_INPUT_HANDLE, h);
1434 /* No need to remember the temporary name any longer once opened */
1435 (*cmdList)->pipeFile[0] = 0x00;
1437 /* Otherwise STDIN could come from a '<' redirect */
1438 } else if ((pos = wcschr(new_redir,'<')) != NULL) {
1439 h = CreateFileW(WCMD_parameter(++pos, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1440 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1441 if (h == INVALID_HANDLE_VALUE) {
1442 WCMD_print_error ();
1443 heap_free(cmd);
1444 heap_free(new_redir);
1445 return;
1447 SetStdHandle (STD_INPUT_HANDLE, h);
1450 /* Scan the whole command looking for > and 2> */
1451 while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) {
1452 int handle = 0;
1454 if (pos > redir && (*(pos-1)=='2'))
1455 handle = 2;
1456 else
1457 handle = 1;
1459 pos++;
1460 if ('>' == *pos) {
1461 creationDisposition = OPEN_ALWAYS;
1462 pos++;
1464 else {
1465 creationDisposition = CREATE_ALWAYS;
1468 /* Add support for 2>&1 */
1469 redir = pos;
1470 if (*pos == '&') {
1471 int idx = *(pos+1) - '0';
1473 if (DuplicateHandle(GetCurrentProcess(),
1474 GetStdHandle(idx_stdhandles[idx]),
1475 GetCurrentProcess(),
1477 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1478 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1480 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1482 } else {
1483 WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE);
1484 h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
1485 &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1486 if (h == INVALID_HANDLE_VALUE) {
1487 WCMD_print_error ();
1488 heap_free(cmd);
1489 heap_free(new_redir);
1490 return;
1492 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1493 INVALID_SET_FILE_POINTER) {
1494 WCMD_print_error ();
1496 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1499 SetStdHandle (idx_stdhandles[handle], h);
1501 } else {
1502 WINE_TRACE("Not touching redirects for a FOR or IF command\n");
1504 WCMD_parse (parms_start, quals, param1, param2);
1505 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1507 if (i <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) {
1508 /* this is a help request for a builtin program */
1509 i = WCMD_HELP;
1510 memcpy(parms_start, whichcmd, count * sizeof(WCHAR));
1511 parms_start[count] = '\0';
1515 switch (i) {
1517 case WCMD_CALL:
1518 WCMD_call (parms_start);
1519 break;
1520 case WCMD_CD:
1521 case WCMD_CHDIR:
1522 WCMD_setshow_default (parms_start);
1523 break;
1524 case WCMD_CLS:
1525 WCMD_clear_screen ();
1526 break;
1527 case WCMD_COPY:
1528 WCMD_copy (parms_start);
1529 break;
1530 case WCMD_CTTY:
1531 WCMD_change_tty ();
1532 break;
1533 case WCMD_DATE:
1534 WCMD_setshow_date ();
1535 break;
1536 case WCMD_DEL:
1537 case WCMD_ERASE:
1538 WCMD_delete (parms_start);
1539 break;
1540 case WCMD_DIR:
1541 WCMD_directory (parms_start);
1542 break;
1543 case WCMD_ECHO:
1544 WCMD_echo(&whichcmd[count]);
1545 break;
1546 case WCMD_GOTO:
1547 WCMD_goto (cmdList);
1548 break;
1549 case WCMD_HELP:
1550 WCMD_give_help (parms_start);
1551 break;
1552 case WCMD_LABEL:
1553 WCMD_volume (TRUE, parms_start);
1554 break;
1555 case WCMD_MD:
1556 case WCMD_MKDIR:
1557 WCMD_create_dir (parms_start);
1558 break;
1559 case WCMD_MOVE:
1560 WCMD_move ();
1561 break;
1562 case WCMD_PATH:
1563 WCMD_setshow_path (parms_start);
1564 break;
1565 case WCMD_PAUSE:
1566 WCMD_pause ();
1567 break;
1568 case WCMD_PROMPT:
1569 WCMD_setshow_prompt ();
1570 break;
1571 case WCMD_REM:
1572 break;
1573 case WCMD_REN:
1574 case WCMD_RENAME:
1575 WCMD_rename ();
1576 break;
1577 case WCMD_RD:
1578 case WCMD_RMDIR:
1579 WCMD_remove_dir (parms_start);
1580 break;
1581 case WCMD_SETLOCAL:
1582 WCMD_setlocal(parms_start);
1583 break;
1584 case WCMD_ENDLOCAL:
1585 WCMD_endlocal();
1586 break;
1587 case WCMD_SET:
1588 WCMD_setshow_env (parms_start);
1589 break;
1590 case WCMD_SHIFT:
1591 WCMD_shift (parms_start);
1592 break;
1593 case WCMD_START:
1594 WCMD_start (parms_start);
1595 break;
1596 case WCMD_TIME:
1597 WCMD_setshow_time ();
1598 break;
1599 case WCMD_TITLE:
1600 if (lstrlenW(&whichcmd[count]) > 0)
1601 WCMD_title(&whichcmd[count+1]);
1602 break;
1603 case WCMD_TYPE:
1604 WCMD_type (parms_start);
1605 break;
1606 case WCMD_VER:
1607 WCMD_output_asis(L"\r\n");
1608 WCMD_version ();
1609 break;
1610 case WCMD_VERIFY:
1611 WCMD_verify (parms_start);
1612 break;
1613 case WCMD_VOL:
1614 WCMD_volume (FALSE, parms_start);
1615 break;
1616 case WCMD_PUSHD:
1617 WCMD_pushd(parms_start);
1618 break;
1619 case WCMD_POPD:
1620 WCMD_popd();
1621 break;
1622 case WCMD_ASSOC:
1623 WCMD_assoc(parms_start, TRUE);
1624 break;
1625 case WCMD_COLOR:
1626 WCMD_color();
1627 break;
1628 case WCMD_FTYPE:
1629 WCMD_assoc(parms_start, FALSE);
1630 break;
1631 case WCMD_MORE:
1632 WCMD_more(parms_start);
1633 break;
1634 case WCMD_CHOICE:
1635 WCMD_choice(parms_start);
1636 break;
1637 case WCMD_MKLINK:
1638 WCMD_mklink(parms_start);
1639 break;
1640 case WCMD_EXIT:
1641 WCMD_exit (cmdList);
1642 break;
1643 case WCMD_FOR:
1644 case WCMD_IF:
1645 /* Very oddly, probably because of all the special parsing required for
1646 these two commands, neither 'for' nor 'if' is supported when called,
1647 i.e. 'call if 1==1...' will fail. */
1648 if (!retrycall) {
1649 if (i==WCMD_FOR) WCMD_for (parms_start, cmdList);
1650 else if (i==WCMD_IF) WCMD_if (parms_start, cmdList);
1651 break;
1653 /* else: drop through */
1654 default:
1655 prev_echo_mode = echo_mode;
1656 WCMD_run_program (whichcmd, FALSE);
1657 echo_mode = prev_echo_mode;
1659 heap_free(cmd);
1660 heap_free(new_redir);
1662 /* Restore old handles */
1663 for (i=0; i<3; i++) {
1664 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1665 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1666 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1671 /*************************************************************************
1672 * WCMD_LoadMessage
1673 * Load a string from the resource file, handling any error
1674 * Returns string retrieved from resource file
1676 WCHAR *WCMD_LoadMessage(UINT id) {
1677 static WCHAR msg[2048];
1678 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1680 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
1681 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1682 lstrcpyW(msg, failedMsg);
1684 return msg;
1687 /***************************************************************************
1688 * WCMD_DumpCommands
1690 * Dumps out the parsed command line to ensure syntax is correct
1692 static void WCMD_DumpCommands(CMD_LIST *commands) {
1693 CMD_LIST *thisCmd = commands;
1695 WINE_TRACE("Parsed line:\n");
1696 while (thisCmd != NULL) {
1697 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1698 thisCmd,
1699 thisCmd->prevDelim,
1700 thisCmd->bracketDepth,
1701 thisCmd->nextcommand,
1702 wine_dbgstr_w(thisCmd->command),
1703 wine_dbgstr_w(thisCmd->redirects));
1704 thisCmd = thisCmd->nextcommand;
1708 /***************************************************************************
1709 * WCMD_addCommand
1711 * Adds a command to the current command list
1713 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1714 WCHAR *redirs, int *redirLen,
1715 WCHAR **copyTo, int **copyToLen,
1716 CMD_DELIMITERS prevDelim, int curDepth,
1717 CMD_LIST **lastEntry, CMD_LIST **output) {
1719 CMD_LIST *thisEntry = NULL;
1721 /* Allocate storage for command */
1722 thisEntry = heap_xalloc(sizeof(CMD_LIST));
1724 /* Copy in the command */
1725 if (command) {
1726 thisEntry->command = heap_xalloc((*commandLen+1) * sizeof(WCHAR));
1727 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1728 thisEntry->command[*commandLen] = 0x00;
1730 /* Copy in the redirects */
1731 thisEntry->redirects = heap_xalloc((*redirLen+1) * sizeof(WCHAR));
1732 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1733 thisEntry->redirects[*redirLen] = 0x00;
1734 thisEntry->pipeFile[0] = 0x00;
1736 /* Reset the lengths */
1737 *commandLen = 0;
1738 *redirLen = 0;
1739 *copyToLen = commandLen;
1740 *copyTo = command;
1742 } else {
1743 thisEntry->command = NULL;
1744 thisEntry->redirects = NULL;
1745 thisEntry->pipeFile[0] = 0x00;
1748 /* Fill in other fields */
1749 thisEntry->nextcommand = NULL;
1750 thisEntry->prevDelim = prevDelim;
1751 thisEntry->bracketDepth = curDepth;
1752 if (*lastEntry) {
1753 (*lastEntry)->nextcommand = thisEntry;
1754 } else {
1755 *output = thisEntry;
1757 *lastEntry = thisEntry;
1761 /***************************************************************************
1762 * WCMD_IsEndQuote
1764 * Checks if the quote pointed to is the end-quote.
1766 * Quotes end if:
1768 * 1) The current parameter ends at EOL or at the beginning
1769 * of a redirection or pipe and not in a quote section.
1771 * 2) If the next character is a space and not in a quote section.
1773 * Returns TRUE if this is an end quote, and FALSE if it is not.
1776 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1778 int quoteCount = quoteIndex;
1779 int i;
1781 /* If we are not in a quoted section, then we are not an end-quote */
1782 if(quoteIndex == 0)
1784 return FALSE;
1787 /* Check how many quotes are left for this parameter */
1788 for(i=0;quote[i];i++)
1790 if(quote[i] == '"')
1792 quoteCount++;
1795 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1796 else if(((quoteCount % 2) == 0)
1797 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ') ||
1798 (quote[i] == '&')))
1800 break;
1804 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1805 be an end-quote */
1806 if(quoteIndex >= (quoteCount / 2))
1808 return TRUE;
1811 /* No cigar */
1812 return FALSE;
1815 /***************************************************************************
1816 * WCMD_ReadAndParseLine
1818 * Either uses supplied input or
1819 * Reads a file from the handle, and then...
1820 * Parse the text buffer, splitting into separate commands
1821 * - unquoted && strings split 2 commands but the 2nd is flagged as
1822 * following an &&
1823 * - ( as the first character just ups the bracket depth
1824 * - unquoted ) when bracket depth > 0 terminates a bracket and
1825 * adds a CMD_LIST structure with null command
1826 * - Anything else gets put into the command string (including
1827 * redirects)
1829 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1831 WCHAR *curPos;
1832 int inQuotes = 0;
1833 WCHAR curString[MAXSTRING];
1834 int curStringLen = 0;
1835 WCHAR curRedirs[MAXSTRING];
1836 int curRedirsLen = 0;
1837 WCHAR *curCopyTo;
1838 int *curLen;
1839 int curDepth = 0;
1840 CMD_LIST *lastEntry = NULL;
1841 CMD_DELIMITERS prevDelim = CMD_NONE;
1842 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1843 static const WCHAR remCmd[] = {'r','e','m'};
1844 static const WCHAR forCmd[] = {'f','o','r'};
1845 static const WCHAR ifCmd[] = {'i','f'};
1846 static const WCHAR ifElse[] = {'e','l','s','e'};
1847 BOOL inOneLine = FALSE;
1848 BOOL inFor = FALSE;
1849 BOOL inIn = FALSE;
1850 BOOL inIf = FALSE;
1851 BOOL inElse= FALSE;
1852 BOOL onlyWhiteSpace = FALSE;
1853 BOOL lastWasWhiteSpace = FALSE;
1854 BOOL lastWasDo = FALSE;
1855 BOOL lastWasIn = FALSE;
1856 BOOL lastWasElse = FALSE;
1857 BOOL lastWasRedirect = TRUE;
1858 BOOL lastWasCaret = FALSE;
1859 int lineCurDepth; /* Bracket depth when line was read in */
1860 BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
1862 /* Allocate working space for a command read from keyboard, file etc */
1863 if (!extraSpace)
1864 extraSpace = heap_xalloc((MAXSTRING+1) * sizeof(WCHAR));
1865 if (!extraSpace)
1867 WINE_ERR("Could not allocate memory for extraSpace\n");
1868 return NULL;
1871 /* If initial command read in, use that, otherwise get input from handle */
1872 if (optionalcmd != NULL) {
1873 lstrcpyW(extraSpace, optionalcmd);
1874 } else if (readFrom == INVALID_HANDLE_VALUE) {
1875 WINE_FIXME("No command nor handle supplied\n");
1876 } else {
1877 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1878 return NULL;
1880 curPos = extraSpace;
1882 /* Handle truncated input - issue warning */
1883 if (lstrlenW(extraSpace) == MAXSTRING -1) {
1884 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1885 WCMD_output_asis_stderr(extraSpace);
1886 WCMD_output_asis_stderr(L"\r\n");
1889 /* Replace env vars if in a batch context */
1890 if (context) handleExpansion(extraSpace, FALSE, FALSE);
1892 /* Skip preceding whitespace */
1893 while (*curPos == ' ' || *curPos == '\t') curPos++;
1895 /* Show prompt before batch line IF echo is on and in batch program */
1896 if (context && echo_mode && *curPos && (*curPos != '@')) {
1897 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1898 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1899 static const WCHAR echoSlash[] = {'e','c','h','o','/'};
1900 const DWORD len = ARRAY_SIZE(echoDot);
1901 DWORD curr_size = lstrlenW(curPos);
1902 DWORD min_len = (curr_size < len ? curr_size : len);
1903 WCMD_show_prompt(TRUE);
1904 WCMD_output_asis(curPos);
1905 /* I don't know why Windows puts a space here but it does */
1906 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1907 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1908 curPos, min_len, echoDot, len) != CSTR_EQUAL
1909 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1910 curPos, min_len, echoCol, len) != CSTR_EQUAL
1911 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1912 curPos, min_len, echoSlash, len) != CSTR_EQUAL)
1914 WCMD_output_asis(L" ");
1916 WCMD_output_asis(L"\r\n");
1919 /* Skip repeated 'no echo' characters */
1920 while (*curPos == '@') curPos++;
1922 /* Start with an empty string, copying to the command string */
1923 curStringLen = 0;
1924 curRedirsLen = 0;
1925 curCopyTo = curString;
1926 curLen = &curStringLen;
1927 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1928 lineCurDepth = curDepth; /* What was the curdepth at the beginning of the line */
1930 /* Parse every character on the line being processed */
1931 while (*curPos != 0x00) {
1933 WCHAR thisChar;
1935 /* Debugging AID:
1936 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1937 lastWasWhiteSpace, onlyWhiteSpace);
1940 /* Prevent overflow caused by the caret escape char */
1941 if (*curLen >= MAXSTRING) {
1942 WINE_ERR("Overflow detected in command\n");
1943 return NULL;
1946 /* Certain commands need special handling */
1947 if (curStringLen == 0 && curCopyTo == curString) {
1948 static const WCHAR forDO[] = {'d','o'};
1950 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
1951 if (WCMD_keyword_ws_found(remCmd, ARRAY_SIZE(remCmd), curPos) || *curPos == ':') {
1952 inOneLine = TRUE;
1954 } else if (WCMD_keyword_ws_found(forCmd, ARRAY_SIZE(forCmd), curPos)) {
1955 inFor = TRUE;
1957 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1958 is only true in the command portion of the IF statement, but this
1959 should suffice for now.
1960 To be able to handle ('s in the condition part take as much as evaluate_if_condition
1961 would take and skip parsing it here. */
1962 } else if (WCMD_keyword_ws_found(ifCmd, ARRAY_SIZE(ifCmd), curPos)) {
1963 int negate; /* Negate condition */
1964 int test; /* Condition evaluation result */
1965 WCHAR *p, *command;
1967 inIf = TRUE;
1969 p = curPos+(ARRAY_SIZE(ifCmd));
1970 while (*p == ' ' || *p == '\t')
1971 p++;
1972 WCMD_parse (p, quals, param1, param2);
1974 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
1975 set in a call to WCMD_parse before */
1976 if (evaluate_if_condition(p, &command, &test, &negate) != -1)
1978 int if_condition_len = command - curPos;
1979 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
1980 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
1981 wine_dbgstr_w(param2), wine_dbgstr_w(command), if_condition_len);
1982 memcpy(&curCopyTo[*curLen], curPos, if_condition_len*sizeof(WCHAR));
1983 (*curLen)+=if_condition_len;
1984 curPos+=if_condition_len;
1987 } else if (WCMD_keyword_ws_found(ifElse, ARRAY_SIZE(ifElse), curPos)) {
1988 const int keyw_len = ARRAY_SIZE(ifElse) + 1;
1989 inElse = TRUE;
1990 lastWasElse = TRUE;
1991 onlyWhiteSpace = TRUE;
1992 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1993 (*curLen)+=keyw_len;
1994 curPos+=keyw_len;
1996 /* If we had a single line if XXX which reaches an else (needs odd
1997 syntax like if 1=1 command && (command) else command we pretended
1998 to add brackets for the if, so they are now over */
1999 if (resetAtEndOfLine) {
2000 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
2001 resetAtEndOfLine = FALSE;
2002 curDepth = lineCurDepth;
2004 continue;
2006 /* In a for loop, the DO command will follow a close bracket followed by
2007 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2008 is then 0, and all whitespace is skipped */
2009 } else if (inFor && WCMD_keyword_ws_found(forDO, ARRAY_SIZE(forDO), curPos)) {
2010 const int keyw_len = ARRAY_SIZE(forDO) + 1;
2011 WINE_TRACE("Found 'DO '\n");
2012 lastWasDo = TRUE;
2013 onlyWhiteSpace = TRUE;
2014 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
2015 (*curLen)+=keyw_len;
2016 curPos+=keyw_len;
2017 continue;
2019 } else if (curCopyTo == curString) {
2021 /* Special handling for the 'FOR' command */
2022 if (inFor && lastWasWhiteSpace) {
2023 static const WCHAR forIN[] = {'i','n'};
2025 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2027 if (WCMD_keyword_ws_found(forIN, ARRAY_SIZE(forIN), curPos)) {
2028 const int keyw_len = ARRAY_SIZE(forIN) + 1;
2029 WINE_TRACE("Found 'IN '\n");
2030 lastWasIn = TRUE;
2031 onlyWhiteSpace = TRUE;
2032 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
2033 (*curLen)+=keyw_len;
2034 curPos+=keyw_len;
2035 continue;
2040 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2041 the &&, quotes and redirection etc are ineffective, so just force
2042 the use of the default processing by skipping character specific
2043 matching below) */
2044 if (!inOneLine) thisChar = *curPos;
2045 else thisChar = 'X'; /* Character with no special processing */
2047 lastWasWhiteSpace = FALSE; /* Will be reset below */
2048 lastWasCaret = FALSE;
2050 switch (thisChar) {
2052 case '=': /* drop through - ignore token delimiters at the start of a command */
2053 case ',': /* drop through - ignore token delimiters at the start of a command */
2054 case '\t':/* drop through - ignore token delimiters at the start of a command */
2055 case ' ':
2056 /* If a redirect in place, it ends here */
2057 if (!inQuotes && !lastWasRedirect) {
2059 /* If finishing off a redirect, add a whitespace delimiter */
2060 if (curCopyTo == curRedirs) {
2061 curCopyTo[(*curLen)++] = ' ';
2063 curCopyTo = curString;
2064 curLen = &curStringLen;
2066 if (*curLen > 0) {
2067 curCopyTo[(*curLen)++] = *curPos;
2070 /* Remember just processed whitespace */
2071 lastWasWhiteSpace = TRUE;
2073 break;
2075 case '>': /* drop through - handle redirect chars the same */
2076 case '<':
2077 /* Make a redirect start here */
2078 if (!inQuotes) {
2079 curCopyTo = curRedirs;
2080 curLen = &curRedirsLen;
2081 lastWasRedirect = TRUE;
2084 /* See if 1>, 2> etc, in which case we have some patching up
2085 to do (provided there's a preceding whitespace, and enough
2086 chars read so far) */
2087 if (curStringLen > 2
2088 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
2089 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2090 curStringLen--;
2091 curString[curStringLen] = 0x00;
2092 curCopyTo[(*curLen)++] = *(curPos-1);
2095 curCopyTo[(*curLen)++] = *curPos;
2097 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2098 do not process that ampersand as an AND operator */
2099 if (thisChar == '>' && *(curPos+1) == '&') {
2100 curCopyTo[(*curLen)++] = *(curPos+1);
2101 curPos++;
2103 break;
2105 case '|': /* Pipe character only if not || */
2106 if (!inQuotes) {
2107 lastWasRedirect = FALSE;
2109 /* Add an entry to the command list */
2110 if (curStringLen > 0) {
2112 /* Add the current command */
2113 WCMD_addCommand(curString, &curStringLen,
2114 curRedirs, &curRedirsLen,
2115 &curCopyTo, &curLen,
2116 prevDelim, curDepth,
2117 &lastEntry, output);
2121 if (*(curPos+1) == '|') {
2122 curPos++; /* Skip other | */
2123 prevDelim = CMD_ONFAILURE;
2124 } else {
2125 prevDelim = CMD_PIPE;
2128 /* If in an IF or ELSE statement, put subsequent chained
2129 commands at a higher depth as if brackets were supplied
2130 but remember to reset to the original depth at EOL */
2131 if ((inIf || inElse) && curDepth == lineCurDepth) {
2132 curDepth++;
2133 resetAtEndOfLine = TRUE;
2135 } else {
2136 curCopyTo[(*curLen)++] = *curPos;
2138 break;
2140 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2141 inQuotes--;
2142 } else {
2143 inQuotes++; /* Quotes within quotes are fun! */
2145 curCopyTo[(*curLen)++] = *curPos;
2146 lastWasRedirect = FALSE;
2147 break;
2149 case '(': /* If a '(' is the first non whitespace in a command portion
2150 ie start of line or just after &&, then we read until an
2151 unquoted ) is found */
2152 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2153 ", for(%d, In:%d, Do:%d)"
2154 ", if(%d, else:%d, lwe:%d)\n",
2155 *curLen, inQuotes,
2156 onlyWhiteSpace,
2157 inFor, lastWasIn, lastWasDo,
2158 inIf, inElse, lastWasElse);
2159 lastWasRedirect = FALSE;
2161 /* Ignore open brackets inside the for set */
2162 if (*curLen == 0 && !inIn) {
2163 curDepth++;
2165 /* If in quotes, ignore brackets */
2166 } else if (inQuotes) {
2167 curCopyTo[(*curLen)++] = *curPos;
2169 /* In a FOR loop, an unquoted '(' may occur straight after
2170 IN or DO
2171 In an IF statement just handle it regardless as we don't
2172 parse the operands
2173 In an ELSE statement, only allow it straight away after
2174 the ELSE and whitespace
2176 } else if (inIf ||
2177 (inElse && lastWasElse && onlyWhiteSpace) ||
2178 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2180 /* If entering into an 'IN', set inIn */
2181 if (inFor && lastWasIn && onlyWhiteSpace) {
2182 WINE_TRACE("Inside an IN\n");
2183 inIn = TRUE;
2186 /* Add the current command */
2187 WCMD_addCommand(curString, &curStringLen,
2188 curRedirs, &curRedirsLen,
2189 &curCopyTo, &curLen,
2190 prevDelim, curDepth,
2191 &lastEntry, output);
2193 curDepth++;
2194 } else {
2195 curCopyTo[(*curLen)++] = *curPos;
2197 break;
2199 case '^': if (!inQuotes) {
2200 /* If we reach the end of the input, we need to wait for more */
2201 if (*(curPos+1) == 0x00) {
2202 lastWasCaret = TRUE;
2203 WINE_TRACE("Caret found at end of line\n");
2204 break;
2206 curPos++;
2208 curCopyTo[(*curLen)++] = *curPos;
2209 break;
2211 case '&': if (!inQuotes) {
2212 lastWasRedirect = FALSE;
2214 /* Add an entry to the command list */
2215 if (curStringLen > 0) {
2217 /* Add the current command */
2218 WCMD_addCommand(curString, &curStringLen,
2219 curRedirs, &curRedirsLen,
2220 &curCopyTo, &curLen,
2221 prevDelim, curDepth,
2222 &lastEntry, output);
2226 if (*(curPos+1) == '&') {
2227 curPos++; /* Skip other & */
2228 prevDelim = CMD_ONSUCCESS;
2229 } else {
2230 prevDelim = CMD_NONE;
2232 /* If in an IF or ELSE statement, put subsequent chained
2233 commands at a higher depth as if brackets were supplied
2234 but remember to reset to the original depth at EOL */
2235 if ((inIf || inElse) && curDepth == lineCurDepth) {
2236 curDepth++;
2237 resetAtEndOfLine = TRUE;
2239 } else {
2240 curCopyTo[(*curLen)++] = *curPos;
2242 break;
2244 case ')': if (!inQuotes && curDepth > 0) {
2245 lastWasRedirect = FALSE;
2247 /* Add the current command if there is one */
2248 if (curStringLen) {
2250 /* Add the current command */
2251 WCMD_addCommand(curString, &curStringLen,
2252 curRedirs, &curRedirsLen,
2253 &curCopyTo, &curLen,
2254 prevDelim, curDepth,
2255 &lastEntry, output);
2258 /* Add an empty entry to the command list */
2259 prevDelim = CMD_NONE;
2260 WCMD_addCommand(NULL, &curStringLen,
2261 curRedirs, &curRedirsLen,
2262 &curCopyTo, &curLen,
2263 prevDelim, curDepth,
2264 &lastEntry, output);
2265 curDepth--;
2267 /* Leave inIn if necessary */
2268 if (inIn) inIn = FALSE;
2269 } else {
2270 curCopyTo[(*curLen)++] = *curPos;
2272 break;
2273 default:
2274 lastWasRedirect = FALSE;
2275 curCopyTo[(*curLen)++] = *curPos;
2278 curPos++;
2280 /* At various times we need to know if we have only skipped whitespace,
2281 so reset this variable and then it will remain true until a non
2282 whitespace is found */
2283 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2284 onlyWhiteSpace = FALSE;
2286 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2287 if (!lastWasWhiteSpace) {
2288 lastWasIn = lastWasDo = FALSE;
2291 /* If we have reached the end, add this command into the list
2292 Do not add command to list if escape char ^ was last */
2293 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2295 /* Add an entry to the command list */
2296 WCMD_addCommand(curString, &curStringLen,
2297 curRedirs, &curRedirsLen,
2298 &curCopyTo, &curLen,
2299 prevDelim, curDepth,
2300 &lastEntry, output);
2302 /* If we had a single line if or else, and we pretended to add
2303 brackets, end them now */
2304 if (resetAtEndOfLine) {
2305 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
2306 resetAtEndOfLine = FALSE;
2307 curDepth = lineCurDepth;
2311 /* If we have reached the end of the string, see if bracketing or
2312 final caret is outstanding */
2313 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2314 readFrom != INVALID_HANDLE_VALUE) {
2315 WCHAR *extraData;
2317 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2318 inOneLine = FALSE;
2319 prevDelim = CMD_NONE;
2320 inQuotes = 0;
2321 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2322 extraData = extraSpace;
2324 /* Read more, skipping any blank lines */
2325 do {
2326 WINE_TRACE("Read more input\n");
2327 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2328 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2329 break;
2331 /* Edge case for carets - a completely blank line (i.e. was just
2332 CRLF) is oddly added as an LF but then more data is received (but
2333 only once more!) */
2334 if (lastWasCaret) {
2335 if (*extraSpace == 0x00) {
2336 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2337 *extraData++ = '\r';
2338 *extraData = 0x00;
2339 } else break;
2342 } while (*extraData == 0x00);
2343 curPos = extraSpace;
2345 /* Skip preceding whitespace */
2346 while (*curPos == ' ' || *curPos == '\t') curPos++;
2348 /* Replace env vars if in a batch context */
2349 if (context) handleExpansion(curPos, FALSE, FALSE);
2351 /* Continue to echo commands IF echo is on and in batch program */
2352 if (context && echo_mode && *curPos && *curPos != '@') {
2353 WCMD_output_asis(extraSpace);
2354 WCMD_output_asis(L"\r\n");
2357 /* Skip repeated 'no echo' characters and whitespace */
2358 while (*curPos == '@' || *curPos == ' ' || *curPos == '\t') curPos++;
2362 /* Dump out the parsed output */
2363 WCMD_DumpCommands(*output);
2365 return extraSpace;
2368 /***************************************************************************
2369 * WCMD_process_commands
2371 * Process all the commands read in so far
2373 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2374 BOOL retrycall) {
2376 int bdepth = -1;
2378 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2380 /* Loop through the commands, processing them one by one */
2381 while (thisCmd) {
2383 CMD_LIST *origCmd = thisCmd;
2385 /* If processing one bracket only, and we find the end bracket
2386 entry (or less), return */
2387 if (oneBracket && !thisCmd->command &&
2388 bdepth <= thisCmd->bracketDepth) {
2389 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2390 thisCmd, thisCmd->nextcommand);
2391 return thisCmd->nextcommand;
2394 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2395 about them and it will be handled in there)
2396 Also, skip over any batch labels (eg. :fred) */
2397 if (thisCmd->command && thisCmd->command[0] != ':') {
2398 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2399 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2402 /* Step on unless the command itself already stepped on */
2403 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2405 return NULL;
2408 /***************************************************************************
2409 * WCMD_free_commands
2411 * Frees the storage held for a parsed command line
2412 * - This is not done in the process_commands, as eventually the current
2413 * pointer will be modified within the commands, and hence a single free
2414 * routine is simpler
2416 void WCMD_free_commands(CMD_LIST *cmds) {
2418 /* Loop through the commands, freeing them one by one */
2419 while (cmds) {
2420 CMD_LIST *thisCmd = cmds;
2421 cmds = cmds->nextcommand;
2422 heap_free(thisCmd->command);
2423 heap_free(thisCmd->redirects);
2424 heap_free(thisCmd);
2429 /*****************************************************************************
2430 * Main entry point. This is a console application so we have a main() not a
2431 * winmain().
2434 int __cdecl wmain (int argc, WCHAR *argvW[])
2436 WCHAR *cmdLine = NULL;
2437 WCHAR *cmd = NULL;
2438 WCHAR string[1024];
2439 WCHAR envvar[4];
2440 BOOL promptNewLine = TRUE;
2441 BOOL opt_q;
2442 int opt_t = 0;
2443 static const WCHAR offW[] = {'O','F','F','\0'};
2444 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2445 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2446 static const WCHAR comspecW[] = {'C','O','M','S','P','E','C',0};
2447 static const WCHAR cmdW[] = {'\\','c','m','d','.','e','x','e',0};
2448 WCHAR comspec[MAX_PATH];
2449 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2450 RTL_OSVERSIONINFOEXW osv;
2451 char osver[50];
2452 STARTUPINFOW startupInfo;
2453 const WCHAR *arg;
2455 if (!GetEnvironmentVariableW(comspecW, comspec, ARRAY_SIZE(comspec)))
2457 GetSystemDirectoryW(comspec, ARRAY_SIZE(comspec) - ARRAY_SIZE(cmdW));
2458 lstrcatW(comspec, cmdW);
2459 SetEnvironmentVariableW(comspecW, comspec);
2462 srand(time(NULL));
2464 /* Get the windows version being emulated */
2465 osv.dwOSVersionInfoSize = sizeof(osv);
2466 RtlGetVersion(&osv);
2468 /* Pre initialize some messages */
2469 lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2470 sprintf(osver, "%d.%d.%d", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
2471 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2472 lstrcpyW(version_string, cmd);
2473 LocalFree(cmd);
2474 cmd = NULL;
2476 /* Can't use argc/argv as it will have stripped quotes from parameters
2477 * meaning cmd.exe /C echo "quoted string" is impossible
2479 cmdLine = GetCommandLineW();
2480 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2482 while (*cmdLine && *cmdLine != '/') ++cmdLine;
2484 opt_c = opt_k = opt_q = opt_s = FALSE;
2486 for (arg = cmdLine; *arg; ++arg)
2488 if (arg[0] != '/')
2489 continue;
2491 switch (towlower(arg[1]))
2493 case 'a':
2494 unicodeOutput = FALSE;
2495 break;
2496 case 'c':
2497 opt_c = TRUE;
2498 break;
2499 case 'k':
2500 opt_k = TRUE;
2501 break;
2502 case 'q':
2503 opt_q = TRUE;
2504 break;
2505 case 's':
2506 opt_s = TRUE;
2507 break;
2508 case 't':
2509 if (arg[2] == ':')
2510 opt_t = wcstoul(&arg[3], NULL, 16);
2511 break;
2512 case 'u':
2513 unicodeOutput = TRUE;
2514 break;
2515 case 'v':
2516 if (arg[2] == ':')
2517 delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
2518 break;
2521 if (opt_c || opt_k)
2523 arg += 2;
2524 break;
2528 while (*arg && wcschr(L" \t,=;", *arg)) arg++;
2530 if (opt_q) {
2531 WCMD_echo(offW);
2534 /* Until we start to read from the keyboard, stay as non-interactive */
2535 interactive = FALSE;
2537 SetEnvironmentVariableW(promptW, defaultpromptW);
2539 if (opt_c || opt_k) {
2540 int len;
2541 WCHAR *q1 = NULL,*q2 = NULL,*p;
2543 /* Take a copy */
2544 cmd = heap_strdupW(arg);
2546 /* opt_s left unflagged if the command starts with and contains exactly
2547 * one quoted string (exactly two quote characters). The quoted string
2548 * must be an executable name that has whitespace and must not have the
2549 * following characters: &<>()@^| */
2551 if (!opt_s) {
2552 /* 1. Confirm there is at least one quote */
2553 q1 = wcschr(arg, '"');
2554 if (!q1) opt_s=1;
2557 if (!opt_s) {
2558 /* 2. Confirm there is a second quote */
2559 q2 = wcschr(q1+1, '"');
2560 if (!q2) opt_s=1;
2563 if (!opt_s) {
2564 /* 3. Ensure there are no more quotes */
2565 if (wcschr(q2+1, '"')) opt_s=1;
2568 /* check first parameter for a space and invalid characters. There must not be any
2569 * invalid characters, but there must be one or more whitespace */
2570 if (!opt_s) {
2571 opt_s = TRUE;
2572 p=q1;
2573 while (p!=q2) {
2574 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2575 || *p=='@' || *p=='^' || *p=='|') {
2576 opt_s = TRUE;
2577 break;
2579 if (*p==' ' || *p=='\t')
2580 opt_s = FALSE;
2581 p++;
2585 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2587 /* Finally, we only stay in new mode IF the first parameter is quoted and
2588 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2589 if (!opt_s) {
2590 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2591 WCHAR pathext[MAXSTRING];
2592 BOOL found = FALSE;
2594 /* Now extract PATHEXT */
2595 len = GetEnvironmentVariableW(envPathExt, pathext, ARRAY_SIZE(pathext));
2596 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
2597 lstrcpyW (pathext, dfltPathExt);
2600 /* If the supplied parameter has any directory information, look there */
2601 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2602 if (wcschr(thisArg, '\\') != NULL) {
2604 GetFullPathNameW(thisArg, ARRAY_SIZE(string), string, NULL);
2605 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2606 p = string + lstrlenW(string);
2608 /* Does file exist with this name? */
2609 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2610 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2611 found = TRUE;
2612 } else {
2613 WCHAR *thisExt = pathext;
2615 /* No - try with each of the PATHEXT extensions */
2616 while (!found && thisExt) {
2617 WCHAR *nextExt = wcschr(thisExt, ';');
2619 if (nextExt) {
2620 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2621 p[(nextExt-thisExt)] = 0x00;
2622 thisExt = nextExt+1;
2623 } else {
2624 lstrcpyW(p, thisExt);
2625 thisExt = NULL;
2628 /* Does file exist with this extension appended? */
2629 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2630 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2631 found = TRUE;
2636 /* Otherwise we now need to look in the path to see if we can find it */
2637 } else {
2638 /* Does file exist with this name? */
2639 if (SearchPathW(NULL, thisArg, NULL, ARRAY_SIZE(string), string, NULL) != 0) {
2640 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2641 found = TRUE;
2642 } else {
2643 WCHAR *thisExt = pathext;
2645 /* No - try with each of the PATHEXT extensions */
2646 while (!found && thisExt) {
2647 WCHAR *nextExt = wcschr(thisExt, ';');
2649 if (nextExt) {
2650 *nextExt = 0;
2651 nextExt = nextExt+1;
2652 } else {
2653 nextExt = NULL;
2656 /* Does file exist with this extension? */
2657 if (SearchPathW(NULL, thisArg, thisExt, ARRAY_SIZE(string), string, NULL) != 0) {
2658 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2659 wine_dbgstr_w(thisExt));
2660 found = TRUE;
2662 thisExt = nextExt;
2667 /* If not found, drop back to old behaviour */
2668 if (!found) {
2669 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2670 opt_s = TRUE;
2675 /* strip first and last quote characters if opt_s; check for invalid
2676 * executable is done later */
2677 if (opt_s && *cmd=='\"')
2678 WCMD_strip_quotes(cmd);
2681 /* Save cwd into appropriate env var (Must be before the /c processing */
2682 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
2683 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2684 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2685 wsprintfW(envvar, fmt, string[0]);
2686 SetEnvironmentVariableW(envvar, string);
2687 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2690 if (opt_c) {
2691 /* If we do a "cmd /c command", we don't want to allocate a new
2692 * console since the command returns immediately. Rather, we use
2693 * the currently allocated input and output handles. This allows
2694 * us to pipe to and read from the command interpreter.
2697 /* Parse the command string, without reading any more input */
2698 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2699 WCMD_process_commands(toExecute, FALSE, FALSE);
2700 WCMD_free_commands(toExecute);
2701 toExecute = NULL;
2703 heap_free(cmd);
2704 return errorlevel;
2707 GetStartupInfoW(&startupInfo);
2708 if (startupInfo.lpTitle != NULL)
2709 SetConsoleTitleW(startupInfo.lpTitle);
2710 else
2711 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2713 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2714 if (opt_t) {
2715 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2716 defaultColor = opt_t & 0xFF;
2717 param1[0] = 0x00;
2718 WCMD_color();
2720 } else {
2721 /* Check HKCU\Software\Microsoft\Command Processor
2722 Then HKLM\Software\Microsoft\Command Processor
2723 for defaultcolour value
2724 Note Can be supplied as DWORD or REG_SZ
2725 Note2 When supplied as REG_SZ it's in decimal!!! */
2726 HKEY key;
2727 DWORD type;
2728 DWORD value=0, size=4;
2729 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2730 'M','i','c','r','o','s','o','f','t','\\',
2731 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2732 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2734 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2735 0, KEY_READ, &key) == ERROR_SUCCESS) {
2736 WCHAR strvalue[4];
2738 /* See if DWORD or REG_SZ */
2739 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2740 NULL, NULL) == ERROR_SUCCESS) {
2741 if (type == REG_DWORD) {
2742 size = sizeof(DWORD);
2743 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2744 (LPBYTE)&value, &size);
2745 } else if (type == REG_SZ) {
2746 size = ARRAY_SIZE(strvalue);
2747 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2748 (LPBYTE)strvalue, &size);
2749 value = wcstoul(strvalue, NULL, 10);
2752 RegCloseKey(key);
2755 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2756 0, KEY_READ, &key) == ERROR_SUCCESS) {
2757 WCHAR strvalue[4];
2759 /* See if DWORD or REG_SZ */
2760 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2761 NULL, NULL) == ERROR_SUCCESS) {
2762 if (type == REG_DWORD) {
2763 size = sizeof(DWORD);
2764 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2765 (LPBYTE)&value, &size);
2766 } else if (type == REG_SZ) {
2767 size = ARRAY_SIZE(strvalue);
2768 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2769 (LPBYTE)strvalue, &size);
2770 value = wcstoul(strvalue, NULL, 10);
2773 RegCloseKey(key);
2776 /* If one found, set the screen to that colour */
2777 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2778 defaultColor = value & 0xFF;
2779 param1[0] = 0x00;
2780 WCMD_color();
2785 if (opt_k) {
2786 /* Parse the command string, without reading any more input */
2787 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2788 WCMD_process_commands(toExecute, FALSE, FALSE);
2789 WCMD_free_commands(toExecute);
2790 toExecute = NULL;
2791 heap_free(cmd);
2795 * Loop forever getting commands and executing them.
2798 interactive = TRUE;
2799 if (!opt_k) WCMD_version ();
2800 while (TRUE) {
2802 /* Read until EOF (which for std input is never, but if redirect
2803 in place, may occur */
2804 if (echo_mode) WCMD_show_prompt(promptNewLine);
2805 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2806 break;
2807 WCMD_process_commands(toExecute, FALSE, FALSE);
2808 WCMD_free_commands(toExecute);
2809 promptNewLine = !!toExecute;
2810 toExecute = NULL;
2812 return 0;