wined3d: Use the "bo user" mechanism to invalidate existing bindings in wined3d_buffe...
[wine.git] / programs / cmd / wcmdmain.c
blobfbe8475d8a9053f8e7d45121a77d8f20834bbd76
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];
50 static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
52 /* Variables pertaining to paging */
53 static BOOL paged_mode;
54 static const WCHAR *pagedMessage = NULL;
55 static int line_count;
56 static int max_height;
57 static int max_width;
58 static int numChars;
60 #define MAX_WRITECONSOLE_SIZE 65535
63 * Returns a buffer for reading from/writing to file
64 * Never freed
66 static char *get_file_buffer(void)
68 static char *output_bufA = NULL;
69 if (!output_bufA)
70 output_bufA = heap_xalloc(MAX_WRITECONSOLE_SIZE);
71 return output_bufA;
74 /*******************************************************************
75 * WCMD_output_asis_len - send output to current standard output
77 * Output a formatted unicode string. Ideally this will go to the console
78 * and hence required WriteConsoleW to output it, however if file i/o is
79 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
81 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
83 DWORD nOut= 0;
84 DWORD res = 0;
86 /* If nothing to write, return (MORE does this sometimes) */
87 if (!len) return;
89 /* Try to write as unicode assuming it is to a console */
90 res = WriteConsoleW(device, message, len, &nOut, NULL);
92 /* If writing to console fails, assume it's file
93 i/o so convert to OEM codepage and output */
94 if (!res) {
95 BOOL usedDefaultChar = FALSE;
96 DWORD convertedChars;
97 char *buffer;
99 if (!unicodeOutput) {
101 if (!(buffer = get_file_buffer()))
102 return;
104 /* Convert to OEM, then output */
105 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
106 len, buffer, MAX_WRITECONSOLE_SIZE,
107 "?", &usedDefaultChar);
108 WriteFile(device, buffer, convertedChars,
109 &nOut, FALSE);
110 } else {
111 WriteFile(device, message, len*sizeof(WCHAR),
112 &nOut, FALSE);
115 return;
118 /*******************************************************************
119 * WCMD_output - send output to current standard output device.
123 void WINAPIV WCMD_output (const WCHAR *format, ...) {
125 __ms_va_list ap;
126 WCHAR* string;
127 DWORD len;
129 __ms_va_start(ap,format);
130 string = NULL;
131 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
132 format, 0, 0, (LPWSTR)&string, 0, &ap);
133 __ms_va_end(ap);
134 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
135 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
136 else
138 WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
139 LocalFree(string);
143 /*******************************************************************
144 * WCMD_output_stderr - send output to current standard error device.
148 void WINAPIV WCMD_output_stderr (const WCHAR *format, ...) {
150 __ms_va_list ap;
151 WCHAR* string;
152 DWORD len;
154 __ms_va_start(ap,format);
155 string = NULL;
156 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
157 format, 0, 0, (LPWSTR)&string, 0, &ap);
158 __ms_va_end(ap);
159 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
160 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
161 else
163 WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
164 LocalFree(string);
168 /*******************************************************************
169 * WCMD_format_string - allocate a buffer and format a string
173 WCHAR* WINAPIV WCMD_format_string (const WCHAR *format, ...)
175 __ms_va_list ap;
176 WCHAR* string;
177 DWORD len;
179 __ms_va_start(ap,format);
180 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
181 format, 0, 0, (LPWSTR)&string, 0, &ap);
182 __ms_va_end(ap);
183 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
184 WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
185 string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
186 *string = 0;
188 return string;
191 void WCMD_enter_paged_mode(const WCHAR *msg)
193 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
195 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
196 max_height = consoleInfo.dwSize.Y;
197 max_width = consoleInfo.dwSize.X;
198 } else {
199 max_height = 25;
200 max_width = 80;
202 paged_mode = TRUE;
203 line_count = 0;
204 numChars = 0;
205 pagedMessage = (msg==NULL)? anykey : msg;
208 void WCMD_leave_paged_mode(void)
210 paged_mode = FALSE;
211 pagedMessage = NULL;
214 /***************************************************************************
215 * WCMD_Readfile
217 * Read characters in from a console/file, returning result in Unicode
219 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
221 DWORD numRead;
222 char *buffer;
224 /* Try to read from console as Unicode */
225 if (ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL)) return TRUE;
227 /* We assume it's a file handle and read then convert from assumed OEM codepage */
228 if (!(buffer = get_file_buffer()))
229 return FALSE;
231 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
232 return FALSE;
234 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
236 return TRUE;
239 /*******************************************************************
240 * WCMD_output_asis_handle
242 * Send output to specified handle without formatting e.g. when message contains '%'
244 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
245 DWORD count;
246 const WCHAR* ptr;
247 WCHAR string[1024];
248 HANDLE handle = GetStdHandle(std_handle);
250 if (paged_mode) {
251 do {
252 ptr = message;
253 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
254 numChars++;
255 ptr++;
257 if (*ptr == '\n') ptr++;
258 WCMD_output_asis_len(message, ptr - message, handle);
259 numChars = 0;
260 if (++line_count >= max_height - 1) {
261 line_count = 0;
262 WCMD_output_asis_len(pagedMessage, lstrlenW(pagedMessage), handle);
263 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
265 } while (((message = ptr) != NULL) && (*ptr));
266 } else {
267 WCMD_output_asis_len(message, lstrlenW(message), handle);
271 /*******************************************************************
272 * WCMD_output_asis
274 * Send output to current standard output device, without formatting
275 * e.g. when message contains '%'
277 void WCMD_output_asis (const WCHAR *message) {
278 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
281 /*******************************************************************
282 * WCMD_output_asis_stderr
284 * Send output to current standard error device, without formatting
285 * e.g. when message contains '%'
287 void WCMD_output_asis_stderr (const WCHAR *message) {
288 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
291 /****************************************************************************
292 * WCMD_print_error
294 * Print the message for GetLastError
297 void WCMD_print_error (void) {
298 LPVOID lpMsgBuf;
299 DWORD error_code;
300 int status;
302 error_code = GetLastError ();
303 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
304 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
305 if (!status) {
306 WINE_FIXME ("Cannot display message for error %d, status %d\n",
307 error_code, GetLastError());
308 return;
311 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
312 GetStdHandle(STD_ERROR_HANDLE));
313 LocalFree (lpMsgBuf);
314 WCMD_output_asis_len(L"\r\n", lstrlenW(L"\r\n"), GetStdHandle(STD_ERROR_HANDLE));
315 return;
318 /******************************************************************************
319 * WCMD_show_prompt
321 * Display the prompt on STDout
325 static void WCMD_show_prompt (BOOL newLine) {
327 int status;
328 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
329 WCHAR *p, *q;
330 DWORD len;
332 len = GetEnvironmentVariableW(L"PROMPT", prompt_string, ARRAY_SIZE(prompt_string));
333 if ((len == 0) || (len >= ARRAY_SIZE(prompt_string))) {
334 lstrcpyW(prompt_string, L"$P$G");
336 p = prompt_string;
337 q = out_string;
338 if (newLine) {
339 *q++ = '\r';
340 *q++ = '\n';
342 *q = '\0';
343 while (*p != '\0') {
344 if (*p != '$') {
345 *q++ = *p++;
346 *q = '\0';
348 else {
349 p++;
350 switch (toupper(*p)) {
351 case '$':
352 *q++ = '$';
353 break;
354 case 'A':
355 *q++ = '&';
356 break;
357 case 'B':
358 *q++ = '|';
359 break;
360 case 'C':
361 *q++ = '(';
362 break;
363 case 'D':
364 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH - (q - out_string));
365 while (*q) q++;
366 break;
367 case 'E':
368 *q++ = '\x1b';
369 break;
370 case 'F':
371 *q++ = ')';
372 break;
373 case 'G':
374 *q++ = '>';
375 break;
376 case 'H':
377 *q++ = '\b';
378 break;
379 case 'L':
380 *q++ = '<';
381 break;
382 case 'N':
383 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
384 if (status) {
385 *q++ = curdir[0];
387 break;
388 case 'P':
389 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
390 if (status) {
391 lstrcatW (q, curdir);
392 while (*q) q++;
394 break;
395 case 'Q':
396 *q++ = '=';
397 break;
398 case 'S':
399 *q++ = ' ';
400 break;
401 case 'T':
402 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
403 while (*q) q++;
404 break;
405 case 'V':
406 lstrcatW (q, version_string);
407 while (*q) q++;
408 break;
409 case '_':
410 *q++ = '\n';
411 break;
412 case '+':
413 if (pushd_directories) {
414 memset(q, '+', pushd_directories->u.stackdepth);
415 q = q + pushd_directories->u.stackdepth;
417 break;
419 p++;
420 *q = '\0';
423 WCMD_output_asis (out_string);
426 void *heap_xalloc(size_t size)
428 void *ret;
430 ret = heap_alloc(size);
431 if(!ret) {
432 ERR("Out of memory\n");
433 ExitProcess(1);
436 return ret;
439 /*************************************************************************
440 * WCMD_strsubstW
441 * Replaces a portion of a Unicode string with the specified string.
442 * It's up to the caller to ensure there is enough space in the
443 * destination buffer.
445 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
447 if (len < 0)
448 len=insert ? lstrlenW(insert) : 0;
449 if (start+len != next)
450 memmove(start+len, next, (lstrlenW(next) + 1) * sizeof(*next));
451 if (insert)
452 memcpy(start, insert, len * sizeof(*insert));
455 /***************************************************************************
456 * WCMD_skip_leading_spaces
458 * Return a pointer to the first non-whitespace character of string.
459 * Does not modify the input string.
461 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
463 WCHAR *ptr;
465 ptr = string;
466 while (*ptr == ' ' || *ptr == '\t') ptr++;
467 return ptr;
470 /***************************************************************************
471 * WCMD_keyword_ws_found
473 * Checks if the string located at ptr matches a keyword (of length len)
474 * followed by a whitespace character (space or tab)
476 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
477 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
478 ptr, len, keyword, len) == CSTR_EQUAL)
479 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
482 /*************************************************************************
483 * WCMD_strip_quotes
485 * Remove first and last quote WCHARacters, preserving all other text
486 * Returns the location of the final quote
488 WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
489 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
490 while((*dest=*src) != '\0') {
491 if (*src=='\"')
492 lastq=dest;
493 dest++; src++;
495 lastquote = lastq;
496 if (lastq) {
497 dest=lastq++;
498 while ((*dest++=*lastq++) != 0)
501 return lastquote;
505 /*************************************************************************
506 * WCMD_is_magic_envvar
507 * Return TRUE if s is '%'magicvar'%'
508 * and is not masked by a real environment variable.
511 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
513 int len;
515 if (s[0] != '%')
516 return FALSE; /* Didn't begin with % */
517 len = lstrlenW(s);
518 if (len < 2 || s[len-1] != '%')
519 return FALSE; /* Didn't end with another % */
521 if (CompareStringW(LOCALE_USER_DEFAULT,
522 NORM_IGNORECASE | SORT_STRINGSORT,
523 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
524 /* Name doesn't match. */
525 return FALSE;
528 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
529 /* Masked by real environment variable. */
530 return FALSE;
533 return TRUE;
536 /*************************************************************************
537 * WCMD_expand_envvar
539 * Expands environment variables, allowing for WCHARacter substitution
541 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
543 WCHAR *endOfVar = NULL, *s;
544 WCHAR *colonpos = NULL;
545 WCHAR thisVar[MAXSTRING];
546 WCHAR thisVarContents[MAXSTRING];
547 WCHAR savedchar = 0x00;
548 int len;
549 WCHAR Delims[] = L"%:"; /* First char gets replaced appropriately */
551 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
553 /* Find the end of the environment variable, and extract name */
554 Delims[0] = startchar;
555 endOfVar = wcspbrk(start+1, Delims);
557 if (endOfVar == NULL || *endOfVar==' ') {
559 /* In batch program, missing terminator for % and no following
560 ':' just removes the '%' */
561 if (context) {
562 WCMD_strsubstW(start, start + 1, NULL, 0);
563 return start;
564 } else {
566 /* In command processing, just ignore it - allows command line
567 syntax like: for %i in (a.a) do echo %i */
568 return start+1;
572 /* If ':' found, process remaining up until '%' (or stop at ':' if
573 a missing '%' */
574 if (*endOfVar==':') {
575 WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar);
576 if (endOfVar2 != NULL) endOfVar = endOfVar2;
579 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
580 thisVar[(endOfVar - start)+1] = 0x00;
581 colonpos = wcschr(thisVar+1, ':');
583 /* If there's complex substitution, just need %var% for now
584 to get the expanded data to play with */
585 if (colonpos) {
586 *colonpos = startchar;
587 savedchar = *(colonpos+1);
588 *(colonpos+1) = 0x00;
591 /* By now, we know the variable we want to expand but it may be
592 surrounded by '!' if we are in delayed expansion - if so convert
593 to % signs. */
594 if (startchar=='!') {
595 thisVar[0] = '%';
596 thisVar[(endOfVar - start)] = '%';
598 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
600 /* Expand to contents, if unchanged, return */
601 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
602 /* override if existing env var called that name */
603 if (WCMD_is_magic_envvar(thisVar, L"ERRORLEVEL")) {
604 wsprintfW(thisVarContents, L"%d", errorlevel);
605 len = lstrlenW(thisVarContents);
606 } else if (WCMD_is_magic_envvar(thisVar, L"DATE")) {
607 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
608 NULL, thisVarContents, MAXSTRING);
609 len = lstrlenW(thisVarContents);
610 } else if (WCMD_is_magic_envvar(thisVar, L"TIME")) {
611 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
612 NULL, thisVarContents, MAXSTRING);
613 len = lstrlenW(thisVarContents);
614 } else if (WCMD_is_magic_envvar(thisVar, L"CD")) {
615 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
616 len = lstrlenW(thisVarContents);
617 } else if (WCMD_is_magic_envvar(thisVar, L"RANDOM")) {
618 wsprintfW(thisVarContents, L"%d", rand() % 32768);
619 len = lstrlenW(thisVarContents);
620 } else {
622 len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents));
625 if (len == 0)
626 return endOfVar+1;
628 /* In a batch program, unknown env vars are replaced with nothing,
629 note syntax %garbage:1,3% results in anything after the ':'
630 except the %
631 From the command line, you just get back what you entered */
632 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
634 /* Restore the complex part after the compare */
635 if (colonpos) {
636 *colonpos = ':';
637 *(colonpos+1) = savedchar;
640 /* Command line - just ignore this */
641 if (context == NULL) return endOfVar+1;
644 /* Batch - replace unknown env var with nothing */
645 if (colonpos == NULL) {
646 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
647 } else {
648 len = lstrlenW(thisVar);
649 thisVar[len-1] = 0x00;
650 /* If %:...% supplied, : is retained */
651 if (colonpos == thisVar+1) {
652 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
653 } else {
654 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
657 return start;
661 /* See if we need to do complex substitution (any ':'s), if not
662 then our work here is done */
663 if (colonpos == NULL) {
664 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
665 return start;
668 /* Restore complex bit */
669 *colonpos = ':';
670 *(colonpos+1) = savedchar;
673 Handle complex substitutions:
674 xxx=yyy (replace xxx with yyy)
675 *xxx=yyy (replace up to and including xxx with yyy)
676 ~x (from x WCHARs in)
677 ~-x (from x WCHARs from the end)
678 ~x,y (from x WCHARs in for y WCHARacters)
679 ~x,-y (from x WCHARs in until y WCHARacters from the end)
682 /* ~ is substring manipulation */
683 if (savedchar == '~') {
685 int substrposition, substrlength = 0;
686 WCHAR *commapos = wcschr(colonpos+2, ',');
687 WCHAR *startCopy;
689 substrposition = wcstol(colonpos+2, NULL, 10);
690 if (commapos) substrlength = wcstol(commapos+1, NULL, 10);
692 /* Check bounds */
693 if (substrposition >= 0) {
694 startCopy = &thisVarContents[min(substrposition, len)];
695 } else {
696 startCopy = &thisVarContents[max(0, len+substrposition-1)];
699 if (commapos == NULL) {
700 /* Copy the lot */
701 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
702 } else if (substrlength < 0) {
704 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
705 if (copybytes > len) copybytes = len;
706 else if (copybytes < 0) copybytes = 0;
707 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
708 } else {
709 substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
710 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
713 /* search and replace manipulation */
714 } else {
715 WCHAR *equalspos = wcsstr(colonpos, L"=");
716 WCHAR *replacewith = equalspos+1;
717 WCHAR *found = NULL;
718 WCHAR *searchIn;
719 WCHAR *searchFor;
721 if (equalspos == NULL) return start+1;
722 s = heap_strdupW(endOfVar + 1);
724 /* Null terminate both strings */
725 thisVar[lstrlenW(thisVar)-1] = 0x00;
726 *equalspos = 0x00;
728 /* Since we need to be case insensitive, copy the 2 buffers */
729 searchIn = heap_strdupW(thisVarContents);
730 CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
731 searchFor = heap_strdupW(colonpos+1);
732 CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
734 /* Handle wildcard case */
735 if (*(colonpos+1) == '*') {
736 /* Search for string to replace */
737 found = wcsstr(searchIn, searchFor+1);
739 if (found) {
740 /* Do replacement */
741 lstrcpyW(start, replacewith);
742 lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
743 lstrcatW(start, s);
744 } else {
745 /* Copy as is */
746 lstrcpyW(start, thisVarContents);
747 lstrcatW(start, s);
750 } else {
751 /* Loop replacing all instances */
752 WCHAR *lastFound = searchIn;
753 WCHAR *outputposn = start;
755 *start = 0x00;
756 while ((found = wcsstr(lastFound, searchFor))) {
757 lstrcpynW(outputposn,
758 thisVarContents + (lastFound-searchIn),
759 (found - lastFound)+1);
760 outputposn = outputposn + (found - lastFound);
761 lstrcatW(outputposn, replacewith);
762 outputposn = outputposn + lstrlenW(replacewith);
763 lastFound = found + lstrlenW(searchFor);
765 lstrcatW(outputposn,
766 thisVarContents + (lastFound-searchIn));
767 lstrcatW(outputposn, s);
769 heap_free(s);
770 heap_free(searchIn);
771 heap_free(searchFor);
773 return start;
776 /*****************************************************************************
777 * Expand the command. Native expands lines from batch programs as they are
778 * read in and not again, except for 'for' variable substitution.
779 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
780 * atExecute is TRUE when the expansion is occurring as the command is executed
781 * rather than at parse time, i.e. delayed expansion and for loops need to be
782 * processed
784 static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
786 /* For commands in a context (batch program): */
787 /* Expand environment variables in a batch file %{0-9} first */
788 /* including support for any ~ modifiers */
789 /* Additionally: */
790 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
791 /* names allowing environment variable overrides */
792 /* NOTE: To support the %PATH:xxx% syntax, also perform */
793 /* manual expansion of environment variables here */
795 WCHAR *p = cmd;
796 WCHAR *t;
797 int i;
798 WCHAR *delayedp = NULL;
799 WCHAR startchar = '%';
800 WCHAR *normalp;
802 /* Display the FOR variables in effect */
803 for (i=0;i<52;i++) {
804 if (forloopcontext.variable[i]) {
805 WINE_TRACE("FOR variable context: %c = '%s'\n",
806 i<26?i+'a':(i-26)+'A',
807 wine_dbgstr_w(forloopcontext.variable[i]));
811 /* Find the next environment variable delimiter */
812 normalp = wcschr(p, '%');
813 if (delayed) delayedp = wcschr(p, '!');
814 if (!normalp) p = delayedp;
815 else if (!delayedp) p = normalp;
816 else p = min(p,delayedp);
817 if (p) startchar = *p;
819 while (p) {
821 WINE_TRACE("Translate command:%s %d (at: %s)\n",
822 wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
823 i = *(p+1) - '0';
825 /* Don't touch %% unless it's in Batch */
826 if (!atExecute && *(p+1) == startchar) {
827 if (context) {
828 WCMD_strsubstW(p, p+1, NULL, 0);
830 p+=1;
832 /* Replace %~ modifications if in batch program */
833 } else if (*(p+1) == '~') {
834 WCMD_HandleTildeModifiers(&p, atExecute);
835 p++;
837 /* Replace use of %0...%9 if in batch program*/
838 } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
839 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
840 NULL, TRUE, TRUE);
841 WCMD_strsubstW(p, p+2, t, -1);
843 /* Replace use of %* if in batch program*/
844 } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
845 WCHAR *startOfParms = NULL;
846 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
847 if (startOfParms != NULL) {
848 startOfParms += lstrlenW(thisParm);
849 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
850 WCMD_strsubstW(p, p+2, startOfParms, -1);
851 } else
852 WCMD_strsubstW(p, p+2, NULL, 0);
854 } else {
855 int forvaridx = FOR_VAR_IDX(*(p+1));
856 if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
857 /* Replace the 2 characters, % and for variable character */
858 WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
859 } else if (!atExecute || startchar == '!') {
860 p = WCMD_expand_envvar(p, startchar);
862 /* In a FOR loop, see if this is the variable to replace */
863 } else { /* Ignore %'s on second pass of batch program */
864 p++;
868 /* Find the next environment variable delimiter */
869 normalp = wcschr(p, '%');
870 if (delayed) delayedp = wcschr(p, '!');
871 if (!normalp) p = delayedp;
872 else if (!delayedp) p = normalp;
873 else p = min(p,delayedp);
874 if (p) startchar = *p;
877 return;
881 /*******************************************************************
882 * WCMD_parse - parse a command into parameters and qualifiers.
884 * On exit, all qualifiers are concatenated into q, the first string
885 * not beginning with "/" is in p1 and the
886 * second in p2. Any subsequent non-qualifier strings are lost.
887 * Parameters in quotes are handled.
889 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
891 int p = 0;
893 *q = *p1 = *p2 = '\0';
894 while (TRUE) {
895 switch (*s) {
896 case '/':
897 *q++ = *s++;
898 while ((*s != '\0') && (*s != ' ') && *s != '/') {
899 *q++ = towupper (*s++);
901 *q = '\0';
902 break;
903 case ' ':
904 case '\t':
905 s++;
906 break;
907 case '"':
908 s++;
909 while ((*s != '\0') && (*s != '"')) {
910 if (p == 0) *p1++ = *s++;
911 else if (p == 1) *p2++ = *s++;
912 else s++;
914 if (p == 0) *p1 = '\0';
915 if (p == 1) *p2 = '\0';
916 p++;
917 if (*s == '"') s++;
918 break;
919 case '\0':
920 return;
921 default:
922 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
923 && (*s != '=') && (*s != ',') ) {
924 if (p == 0) *p1++ = *s++;
925 else if (p == 1) *p2++ = *s++;
926 else s++;
928 /* Skip concurrent parms */
929 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
931 if (p == 0) *p1 = '\0';
932 if (p == 1) *p2 = '\0';
933 p++;
938 static void init_msvcrt_io_block(STARTUPINFOW* st)
940 STARTUPINFOW st_p;
941 /* fetch the parent MSVCRT info block if any, so that the child can use the
942 * same handles as its grand-father
944 st_p.cb = sizeof(STARTUPINFOW);
945 GetStartupInfoW(&st_p);
946 st->cbReserved2 = st_p.cbReserved2;
947 st->lpReserved2 = st_p.lpReserved2;
948 if (st_p.cbReserved2 && st_p.lpReserved2)
950 unsigned num = *(unsigned*)st_p.lpReserved2;
951 char* flags;
952 HANDLE* handles;
953 BYTE *ptr;
954 size_t sz;
956 /* Override the entries for fd 0,1,2 if we happened
957 * to change those std handles (this depends on the way cmd sets
958 * its new input & output handles)
960 sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
961 ptr = heap_xalloc(sz);
962 flags = (char*)(ptr + sizeof(unsigned));
963 handles = (HANDLE*)(flags + num * sizeof(char));
965 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
966 st->cbReserved2 = sz;
967 st->lpReserved2 = ptr;
969 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
970 if (num <= 0 || (flags[0] & WX_OPEN))
972 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
973 flags[0] |= WX_OPEN;
975 if (num <= 1 || (flags[1] & WX_OPEN))
977 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
978 flags[1] |= WX_OPEN;
980 if (num <= 2 || (flags[2] & WX_OPEN))
982 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
983 flags[2] |= WX_OPEN;
985 #undef WX_OPEN
989 /******************************************************************************
990 * WCMD_run_program
992 * Execute a command line as an external program. Must allow recursion.
994 * Precedence:
995 * Manual testing under windows shows PATHEXT plays a key part in this,
996 * and the search algorithm and precedence appears to be as follows.
998 * Search locations:
999 * If directory supplied on command, just use that directory
1000 * If extension supplied on command, look for that explicit name first
1001 * Otherwise, search in each directory on the path
1002 * Precedence:
1003 * If extension supplied on command, look for that explicit name first
1004 * Then look for supplied name .* (even if extension supplied, so
1005 * 'garbage.exe' will match 'garbage.exe.cmd')
1006 * If any found, cycle through PATHEXT looking for name.exe one by one
1007 * Launching
1008 * Once a match has been found, it is launched - Code currently uses
1009 * findexecutable to achieve this which is left untouched.
1010 * If an executable has not been found, and we were launched through
1011 * a call, we need to check if the command is an internal command,
1012 * so go back through wcmd_execute.
1015 void WCMD_run_program (WCHAR *command, BOOL called)
1017 WCHAR temp[MAX_PATH];
1018 WCHAR pathtosearch[MAXSTRING];
1019 WCHAR *pathposn;
1020 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1021 MAX_PATH, including null character */
1022 WCHAR *lastSlash;
1023 WCHAR pathext[MAXSTRING];
1024 WCHAR *firstParam;
1025 BOOL extensionsupplied = FALSE;
1026 BOOL explicit_path = FALSE;
1027 BOOL status;
1028 DWORD len;
1030 /* Quick way to get the filename is to extract the first argument. */
1031 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1032 firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1033 if (!firstParam) return;
1035 if (!firstParam[0]) {
1036 errorlevel = 0;
1037 return;
1040 /* Calculate the search path and stem to search for */
1041 if (wcspbrk(firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
1042 lstrcpyW(pathtosearch, L".;");
1043 len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
1044 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
1045 lstrcpyW(pathtosearch, L".");
1047 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
1048 if (lstrlenW(firstParam) >= MAX_PATH)
1050 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1051 return;
1054 lstrcpyW(stemofsearch, firstParam);
1056 } else {
1058 /* Convert eg. ..\fred to include a directory by removing file part */
1059 GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
1060 lastSlash = wcsrchr(pathtosearch, '\\');
1061 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1062 lstrcpyW(stemofsearch, lastSlash+1);
1064 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1065 c:\windows\a.bat syntax */
1066 if (lastSlash) *(lastSlash + 1) = 0x00;
1067 explicit_path = TRUE;
1070 /* Now extract PATHEXT */
1071 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
1072 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
1073 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
1076 /* Loop through the search path, dir by dir */
1077 pathposn = pathtosearch;
1078 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1079 wine_dbgstr_w(stemofsearch));
1080 while (pathposn) {
1081 WCHAR thisDir[MAX_PATH] = {'\0'};
1082 int length = 0;
1083 WCHAR *pos = NULL;
1084 BOOL found = FALSE;
1085 BOOL inside_quotes = FALSE;
1087 if (explicit_path)
1089 lstrcpyW(thisDir, pathposn);
1090 pathposn = NULL;
1092 else
1094 /* Work on the next directory on the search path */
1095 pos = pathposn;
1096 while ((inside_quotes || *pos != ';') && *pos != 0)
1098 if (*pos == '"')
1099 inside_quotes = !inside_quotes;
1100 pos++;
1103 if (*pos) /* Reached semicolon */
1105 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1106 thisDir[(pos-pathposn)] = 0x00;
1107 pathposn = pos+1;
1109 else /* Reached string end */
1111 lstrcpyW(thisDir, pathposn);
1112 pathposn = NULL;
1115 /* Remove quotes */
1116 length = lstrlenW(thisDir);
1117 if (thisDir[length - 1] == '"')
1118 thisDir[length - 1] = 0;
1120 if (*thisDir != '"')
1121 lstrcpyW(temp, thisDir);
1122 else
1123 lstrcpyW(temp, thisDir + 1);
1125 /* Since you can have eg. ..\.. on the path, need to expand
1126 to full information */
1127 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1130 /* 1. If extension supplied, see if that file exists */
1131 lstrcatW(thisDir, L"\\");
1132 lstrcatW(thisDir, stemofsearch);
1133 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
1135 /* 1. If extension supplied, see if that file exists */
1136 if (extensionsupplied) {
1137 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1138 found = TRUE;
1142 /* 2. Any .* matches? */
1143 if (!found) {
1144 HANDLE h;
1145 WIN32_FIND_DATAW finddata;
1147 lstrcatW(thisDir, L".*");
1148 h = FindFirstFileW(thisDir, &finddata);
1149 FindClose(h);
1150 if (h != INVALID_HANDLE_VALUE) {
1152 WCHAR *thisExt = pathext;
1154 /* 3. Yes - Try each path ext */
1155 while (thisExt) {
1156 WCHAR *nextExt = wcschr(thisExt, ';');
1158 if (nextExt) {
1159 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1160 pos[(nextExt-thisExt)] = 0x00;
1161 thisExt = nextExt+1;
1162 } else {
1163 lstrcpyW(pos, thisExt);
1164 thisExt = NULL;
1167 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1168 found = TRUE;
1169 thisExt = NULL;
1175 /* Once found, launch it */
1176 if (found) {
1177 STARTUPINFOW st;
1178 PROCESS_INFORMATION pe;
1179 SHFILEINFOW psfi;
1180 DWORD console;
1181 HINSTANCE hinst;
1182 WCHAR *ext = wcsrchr( thisDir, '.' );
1184 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1186 /* Special case BAT and CMD */
1187 if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) {
1188 BOOL oldinteractive = interactive;
1189 interactive = FALSE;
1190 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1191 interactive = oldinteractive;
1192 return;
1193 } else {
1195 /* thisDir contains the file to be launched, but with what?
1196 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1197 hinst = FindExecutableW (thisDir, NULL, temp);
1198 if ((INT_PTR)hinst < 32)
1199 console = 0;
1200 else
1201 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1203 ZeroMemory (&st, sizeof(STARTUPINFOW));
1204 st.cb = sizeof(STARTUPINFOW);
1205 init_msvcrt_io_block(&st);
1207 /* Launch the process and if a CUI wait on it to complete
1208 Note: Launching internal wine processes cannot specify a full path to exe */
1209 status = CreateProcessW(thisDir,
1210 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1211 heap_free(st.lpReserved2);
1212 if ((opt_c || opt_k) && !opt_s && !status
1213 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1214 /* strip first and last quote WCHARacters and try again */
1215 WCMD_strip_quotes(command);
1216 opt_s = TRUE;
1217 WCMD_run_program(command, called);
1218 return;
1221 if (!status)
1222 break;
1224 /* Always wait when non-interactive (cmd /c or in batch program),
1225 or for console applications */
1226 if (!interactive || (console && !HIWORD(console)))
1227 WaitForSingleObject (pe.hProcess, INFINITE);
1228 GetExitCodeProcess (pe.hProcess, &errorlevel);
1229 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1231 CloseHandle(pe.hProcess);
1232 CloseHandle(pe.hThread);
1233 return;
1238 /* Not found anywhere - were we called? */
1239 if (called) {
1240 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1242 /* Parse the command string, without reading any more input */
1243 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1244 WCMD_process_commands(toExecute, FALSE, called);
1245 WCMD_free_commands(toExecute);
1246 toExecute = NULL;
1247 return;
1250 /* Not found anywhere - give up */
1251 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1253 /* If a command fails to launch, it sets errorlevel 9009 - which
1254 does not seem to have any associated constant definition */
1255 errorlevel = 9009;
1256 return;
1260 /*****************************************************************************
1261 * Process one command. If the command is EXIT this routine does not return.
1262 * We will recurse through here executing batch files.
1263 * Note: If call is used to a non-existing program, we reparse the line and
1264 * try to run it as an internal command. 'retrycall' represents whether
1265 * we are attempting this retry.
1267 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1268 CMD_LIST **cmdList, BOOL retrycall)
1270 WCHAR *cmd, *parms_start, *redir;
1271 WCHAR *pos;
1272 int status, i, cmd_index;
1273 DWORD count, creationDisposition;
1274 HANDLE h;
1275 WCHAR *whichcmd;
1276 SECURITY_ATTRIBUTES sa;
1277 WCHAR *new_cmd = NULL;
1278 WCHAR *new_redir = NULL;
1279 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1280 GetStdHandle (STD_OUTPUT_HANDLE),
1281 GetStdHandle (STD_ERROR_HANDLE)};
1282 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1283 STD_OUTPUT_HANDLE,
1284 STD_ERROR_HANDLE};
1285 BOOL prev_echo_mode, piped = FALSE;
1287 WINE_TRACE("command on entry:%s (%p)\n",
1288 wine_dbgstr_w(command), cmdList);
1290 /* Move copy of the command onto the heap so it can be expanded */
1291 new_cmd = heap_xalloc(MAXSTRING * sizeof(WCHAR));
1292 lstrcpyW(new_cmd, command);
1293 cmd = new_cmd;
1295 /* Move copy of the redirects onto the heap so it can be expanded */
1296 new_redir = heap_xalloc(MAXSTRING * sizeof(WCHAR));
1297 redir = new_redir;
1299 /* Strip leading whitespaces, and a '@' if supplied */
1300 whichcmd = WCMD_skip_leading_spaces(cmd);
1301 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1302 if (whichcmd[0] == '@') whichcmd++;
1304 /* Check if the command entered is internal, and identify which one */
1305 count = 0;
1306 while (IsCharAlphaNumericW(whichcmd[count])) {
1307 count++;
1309 for (i=0; i<=WCMD_EXIT; i++) {
1310 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1311 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1313 cmd_index = i;
1314 parms_start = WCMD_skip_leading_spaces (&whichcmd[count]);
1316 /* If the next command is a pipe then we implement pipes by redirecting
1317 the output from this command to a temp file and input into the
1318 next command from that temp file.
1319 Note: Do not do this for a for or if statement as the pipe is for
1320 the individual statements, not the for or if itself.
1321 FIXME: Use of named pipes would make more sense here as currently this
1322 process has to finish before the next one can start but this requires
1323 a change to not wait for the first app to finish but rather the pipe */
1324 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) &&
1325 cmdList && (*cmdList)->nextcommand &&
1326 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1328 WCHAR temp_path[MAX_PATH];
1330 /* Remember piping is in action */
1331 WINE_TRACE("Output needs to be piped\n");
1332 piped = TRUE;
1334 /* Generate a unique temporary filename */
1335 GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
1336 GetTempFileNameW(temp_path, L"CMD", 0, (*cmdList)->nextcommand->pipeFile);
1337 WINE_TRACE("Using temporary file of %s\n",
1338 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1341 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1342 if (piped) {
1343 wsprintfW (new_redir, L"%s > %s", redirects, (*cmdList)->nextcommand->pipeFile);
1344 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1345 } else {
1346 lstrcpyW(new_redir, redirects);
1349 /* Expand variables in command line mode only (batch mode will
1350 be expanded as the line is read in, except for 'for' loops) */
1351 handleExpansion(new_cmd, (context != NULL), delayedsubst);
1352 handleExpansion(new_redir, (context != NULL), delayedsubst);
1355 * Changing default drive has to be handled as a special case, anything
1356 * else if it exists after whitespace is ignored
1359 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) &&
1360 (!cmd[2] || cmd[2] == ' ' || cmd[2] == '\t')) {
1361 WCHAR envvar[5];
1362 WCHAR dir[MAX_PATH];
1364 /* Ignore potential garbage on the same line */
1365 cmd[2]=0x00;
1367 /* According to MSDN CreateProcess docs, special env vars record
1368 the current directory on each drive, in the form =C:
1369 so see if one specified, and if so go back to it */
1370 lstrcpyW(envvar, L"=");
1371 lstrcatW(envvar, cmd);
1372 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1373 wsprintfW(cmd, L"%s\\", cmd);
1374 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1376 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1377 status = SetCurrentDirectoryW(cmd);
1378 if (!status) WCMD_print_error ();
1379 heap_free(cmd );
1380 heap_free(new_redir);
1381 return;
1384 sa.nLength = sizeof(sa);
1385 sa.lpSecurityDescriptor = NULL;
1386 sa.bInheritHandle = TRUE;
1389 * Redirect stdin, stdout and/or stderr if required.
1390 * Note: Do not do this for a for or if statement as the pipe is for
1391 * the individual statements, not the for or if itself.
1393 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) {
1394 /* STDIN could come from a preceding pipe, so delete on close if it does */
1395 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1396 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1397 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1398 FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
1399 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1400 if (h == INVALID_HANDLE_VALUE) {
1401 WCMD_print_error ();
1402 heap_free(cmd);
1403 heap_free(new_redir);
1404 return;
1406 SetStdHandle (STD_INPUT_HANDLE, h);
1408 /* No need to remember the temporary name any longer once opened */
1409 (*cmdList)->pipeFile[0] = 0x00;
1411 /* Otherwise STDIN could come from a '<' redirect */
1412 } else if ((pos = wcschr(new_redir,'<')) != NULL) {
1413 h = CreateFileW(WCMD_parameter(++pos, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1414 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1415 if (h == INVALID_HANDLE_VALUE) {
1416 WCMD_print_error ();
1417 heap_free(cmd);
1418 heap_free(new_redir);
1419 return;
1421 SetStdHandle (STD_INPUT_HANDLE, h);
1424 /* Scan the whole command looking for > and 2> */
1425 while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) {
1426 int handle = 0;
1428 if (pos > redir && (*(pos-1)=='2'))
1429 handle = 2;
1430 else
1431 handle = 1;
1433 pos++;
1434 if ('>' == *pos) {
1435 creationDisposition = OPEN_ALWAYS;
1436 pos++;
1438 else {
1439 creationDisposition = CREATE_ALWAYS;
1442 /* Add support for 2>&1 */
1443 redir = pos;
1444 if (*pos == '&') {
1445 int idx = *(pos+1) - '0';
1447 if (DuplicateHandle(GetCurrentProcess(),
1448 GetStdHandle(idx_stdhandles[idx]),
1449 GetCurrentProcess(),
1451 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1452 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1454 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1456 } else {
1457 WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE);
1458 h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
1459 &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1460 if (h == INVALID_HANDLE_VALUE) {
1461 WCMD_print_error ();
1462 heap_free(cmd);
1463 heap_free(new_redir);
1464 return;
1466 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1467 INVALID_SET_FILE_POINTER) {
1468 WCMD_print_error ();
1470 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1473 SetStdHandle (idx_stdhandles[handle], h);
1475 } else {
1476 WINE_TRACE("Not touching redirects for a FOR or IF command\n");
1478 WCMD_parse (parms_start, quals, param1, param2);
1479 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1481 if (i <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) {
1482 /* this is a help request for a builtin program */
1483 i = WCMD_HELP;
1484 memcpy(parms_start, whichcmd, count * sizeof(WCHAR));
1485 parms_start[count] = '\0';
1489 switch (i) {
1491 case WCMD_CALL:
1492 WCMD_call (parms_start);
1493 break;
1494 case WCMD_CD:
1495 case WCMD_CHDIR:
1496 WCMD_setshow_default (parms_start);
1497 break;
1498 case WCMD_CLS:
1499 WCMD_clear_screen ();
1500 break;
1501 case WCMD_COPY:
1502 WCMD_copy (parms_start);
1503 break;
1504 case WCMD_CTTY:
1505 WCMD_change_tty ();
1506 break;
1507 case WCMD_DATE:
1508 WCMD_setshow_date ();
1509 break;
1510 case WCMD_DEL:
1511 case WCMD_ERASE:
1512 WCMD_delete (parms_start);
1513 break;
1514 case WCMD_DIR:
1515 WCMD_directory (parms_start);
1516 break;
1517 case WCMD_ECHO:
1518 WCMD_echo(&whichcmd[count]);
1519 break;
1520 case WCMD_GOTO:
1521 WCMD_goto (cmdList);
1522 break;
1523 case WCMD_HELP:
1524 WCMD_give_help (parms_start);
1525 break;
1526 case WCMD_LABEL:
1527 WCMD_volume (TRUE, parms_start);
1528 break;
1529 case WCMD_MD:
1530 case WCMD_MKDIR:
1531 WCMD_create_dir (parms_start);
1532 break;
1533 case WCMD_MOVE:
1534 WCMD_move ();
1535 break;
1536 case WCMD_PATH:
1537 WCMD_setshow_path (parms_start);
1538 break;
1539 case WCMD_PAUSE:
1540 WCMD_pause ();
1541 break;
1542 case WCMD_PROMPT:
1543 WCMD_setshow_prompt ();
1544 break;
1545 case WCMD_REM:
1546 break;
1547 case WCMD_REN:
1548 case WCMD_RENAME:
1549 WCMD_rename ();
1550 break;
1551 case WCMD_RD:
1552 case WCMD_RMDIR:
1553 WCMD_remove_dir (parms_start);
1554 break;
1555 case WCMD_SETLOCAL:
1556 WCMD_setlocal(parms_start);
1557 break;
1558 case WCMD_ENDLOCAL:
1559 WCMD_endlocal();
1560 break;
1561 case WCMD_SET:
1562 WCMD_setshow_env (parms_start);
1563 break;
1564 case WCMD_SHIFT:
1565 WCMD_shift (parms_start);
1566 break;
1567 case WCMD_START:
1568 WCMD_start (parms_start);
1569 break;
1570 case WCMD_TIME:
1571 WCMD_setshow_time ();
1572 break;
1573 case WCMD_TITLE:
1574 if (lstrlenW(&whichcmd[count]) > 0)
1575 WCMD_title(&whichcmd[count+1]);
1576 break;
1577 case WCMD_TYPE:
1578 WCMD_type (parms_start);
1579 break;
1580 case WCMD_VER:
1581 WCMD_output_asis(L"\r\n");
1582 WCMD_version ();
1583 break;
1584 case WCMD_VERIFY:
1585 WCMD_verify (parms_start);
1586 break;
1587 case WCMD_VOL:
1588 WCMD_volume (FALSE, parms_start);
1589 break;
1590 case WCMD_PUSHD:
1591 WCMD_pushd(parms_start);
1592 break;
1593 case WCMD_POPD:
1594 WCMD_popd();
1595 break;
1596 case WCMD_ASSOC:
1597 WCMD_assoc(parms_start, TRUE);
1598 break;
1599 case WCMD_COLOR:
1600 WCMD_color();
1601 break;
1602 case WCMD_FTYPE:
1603 WCMD_assoc(parms_start, FALSE);
1604 break;
1605 case WCMD_MORE:
1606 WCMD_more(parms_start);
1607 break;
1608 case WCMD_CHOICE:
1609 WCMD_choice(parms_start);
1610 break;
1611 case WCMD_MKLINK:
1612 WCMD_mklink(parms_start);
1613 break;
1614 case WCMD_EXIT:
1615 WCMD_exit (cmdList);
1616 break;
1617 case WCMD_FOR:
1618 case WCMD_IF:
1619 /* Very oddly, probably because of all the special parsing required for
1620 these two commands, neither 'for' nor 'if' is supported when called,
1621 i.e. 'call if 1==1...' will fail. */
1622 if (!retrycall) {
1623 if (i==WCMD_FOR) WCMD_for (parms_start, cmdList);
1624 else if (i==WCMD_IF) WCMD_if (parms_start, cmdList);
1625 break;
1627 /* else: drop through */
1628 default:
1629 prev_echo_mode = echo_mode;
1630 WCMD_run_program (whichcmd, FALSE);
1631 echo_mode = prev_echo_mode;
1633 heap_free(cmd);
1634 heap_free(new_redir);
1636 /* Restore old handles */
1637 for (i=0; i<3; i++) {
1638 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1639 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1640 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1645 /*************************************************************************
1646 * WCMD_LoadMessage
1647 * Load a string from the resource file, handling any error
1648 * Returns string retrieved from resource file
1650 WCHAR *WCMD_LoadMessage(UINT id) {
1651 static WCHAR msg[2048];
1653 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
1654 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1655 lstrcpyW(msg, L"Failed!");
1657 return msg;
1660 /***************************************************************************
1661 * WCMD_DumpCommands
1663 * Dumps out the parsed command line to ensure syntax is correct
1665 static void WCMD_DumpCommands(CMD_LIST *commands) {
1666 CMD_LIST *thisCmd = commands;
1668 WINE_TRACE("Parsed line:\n");
1669 while (thisCmd != NULL) {
1670 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1671 thisCmd,
1672 thisCmd->prevDelim,
1673 thisCmd->bracketDepth,
1674 thisCmd->nextcommand,
1675 wine_dbgstr_w(thisCmd->command),
1676 wine_dbgstr_w(thisCmd->redirects));
1677 thisCmd = thisCmd->nextcommand;
1681 /***************************************************************************
1682 * WCMD_addCommand
1684 * Adds a command to the current command list
1686 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1687 WCHAR *redirs, int *redirLen,
1688 WCHAR **copyTo, int **copyToLen,
1689 CMD_DELIMITERS prevDelim, int curDepth,
1690 CMD_LIST **lastEntry, CMD_LIST **output) {
1692 CMD_LIST *thisEntry = NULL;
1694 /* Allocate storage for command */
1695 thisEntry = heap_xalloc(sizeof(CMD_LIST));
1697 /* Copy in the command */
1698 if (command) {
1699 thisEntry->command = heap_xalloc((*commandLen+1) * sizeof(WCHAR));
1700 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1701 thisEntry->command[*commandLen] = 0x00;
1703 /* Copy in the redirects */
1704 thisEntry->redirects = heap_xalloc((*redirLen+1) * sizeof(WCHAR));
1705 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1706 thisEntry->redirects[*redirLen] = 0x00;
1707 thisEntry->pipeFile[0] = 0x00;
1709 /* Reset the lengths */
1710 *commandLen = 0;
1711 *redirLen = 0;
1712 *copyToLen = commandLen;
1713 *copyTo = command;
1715 } else {
1716 thisEntry->command = NULL;
1717 thisEntry->redirects = NULL;
1718 thisEntry->pipeFile[0] = 0x00;
1721 /* Fill in other fields */
1722 thisEntry->nextcommand = NULL;
1723 thisEntry->prevDelim = prevDelim;
1724 thisEntry->bracketDepth = curDepth;
1725 if (*lastEntry) {
1726 (*lastEntry)->nextcommand = thisEntry;
1727 } else {
1728 *output = thisEntry;
1730 *lastEntry = thisEntry;
1734 /***************************************************************************
1735 * WCMD_IsEndQuote
1737 * Checks if the quote pointed to is the end-quote.
1739 * Quotes end if:
1741 * 1) The current parameter ends at EOL or at the beginning
1742 * of a redirection or pipe and not in a quote section.
1744 * 2) If the next character is a space and not in a quote section.
1746 * Returns TRUE if this is an end quote, and FALSE if it is not.
1749 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1751 int quoteCount = quoteIndex;
1752 int i;
1754 /* If we are not in a quoted section, then we are not an end-quote */
1755 if(quoteIndex == 0)
1757 return FALSE;
1760 /* Check how many quotes are left for this parameter */
1761 for(i=0;quote[i];i++)
1763 if(quote[i] == '"')
1765 quoteCount++;
1768 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1769 else if(((quoteCount % 2) == 0)
1770 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ') ||
1771 (quote[i] == '&')))
1773 break;
1777 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1778 be an end-quote */
1779 if(quoteIndex >= (quoteCount / 2))
1781 return TRUE;
1784 /* No cigar */
1785 return FALSE;
1788 /***************************************************************************
1789 * WCMD_ReadAndParseLine
1791 * Either uses supplied input or
1792 * Reads a file from the handle, and then...
1793 * Parse the text buffer, splitting into separate commands
1794 * - unquoted && strings split 2 commands but the 2nd is flagged as
1795 * following an &&
1796 * - ( as the first character just ups the bracket depth
1797 * - unquoted ) when bracket depth > 0 terminates a bracket and
1798 * adds a CMD_LIST structure with null command
1799 * - Anything else gets put into the command string (including
1800 * redirects)
1802 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1804 WCHAR *curPos;
1805 int inQuotes = 0;
1806 WCHAR curString[MAXSTRING];
1807 int curStringLen = 0;
1808 WCHAR curRedirs[MAXSTRING];
1809 int curRedirsLen = 0;
1810 WCHAR *curCopyTo;
1811 int *curLen;
1812 int curDepth = 0;
1813 CMD_LIST *lastEntry = NULL;
1814 CMD_DELIMITERS prevDelim = CMD_NONE;
1815 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1816 static const WCHAR remCmd[] = {'r','e','m'};
1817 static const WCHAR forCmd[] = {'f','o','r'};
1818 static const WCHAR ifCmd[] = {'i','f'};
1819 static const WCHAR ifElse[] = {'e','l','s','e'};
1820 BOOL inOneLine = FALSE;
1821 BOOL inFor = FALSE;
1822 BOOL inIn = FALSE;
1823 BOOL inIf = FALSE;
1824 BOOL inElse= FALSE;
1825 BOOL onlyWhiteSpace = FALSE;
1826 BOOL lastWasWhiteSpace = FALSE;
1827 BOOL lastWasDo = FALSE;
1828 BOOL lastWasIn = FALSE;
1829 BOOL lastWasElse = FALSE;
1830 BOOL lastWasRedirect = TRUE;
1831 BOOL lastWasCaret = FALSE;
1832 int lineCurDepth; /* Bracket depth when line was read in */
1833 BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
1835 /* Allocate working space for a command read from keyboard, file etc */
1836 if (!extraSpace)
1837 extraSpace = heap_xalloc((MAXSTRING+1) * sizeof(WCHAR));
1838 if (!extraSpace)
1840 WINE_ERR("Could not allocate memory for extraSpace\n");
1841 return NULL;
1844 /* If initial command read in, use that, otherwise get input from handle */
1845 if (optionalcmd != NULL) {
1846 lstrcpyW(extraSpace, optionalcmd);
1847 } else if (readFrom == INVALID_HANDLE_VALUE) {
1848 WINE_FIXME("No command nor handle supplied\n");
1849 } else {
1850 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1851 return NULL;
1853 curPos = extraSpace;
1855 /* Handle truncated input - issue warning */
1856 if (lstrlenW(extraSpace) == MAXSTRING -1) {
1857 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1858 WCMD_output_asis_stderr(extraSpace);
1859 WCMD_output_asis_stderr(L"\r\n");
1862 /* Replace env vars if in a batch context */
1863 if (context) handleExpansion(extraSpace, FALSE, FALSE);
1865 /* Skip preceding whitespace */
1866 while (*curPos == ' ' || *curPos == '\t') curPos++;
1868 /* Show prompt before batch line IF echo is on and in batch program */
1869 if (context && echo_mode && *curPos && (*curPos != '@')) {
1870 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1871 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1872 static const WCHAR echoSlash[] = {'e','c','h','o','/'};
1873 const DWORD len = ARRAY_SIZE(echoDot);
1874 DWORD curr_size = lstrlenW(curPos);
1875 DWORD min_len = (curr_size < len ? curr_size : len);
1876 WCMD_show_prompt(TRUE);
1877 WCMD_output_asis(curPos);
1878 /* I don't know why Windows puts a space here but it does */
1879 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1880 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1881 curPos, min_len, echoDot, len) != CSTR_EQUAL
1882 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1883 curPos, min_len, echoCol, len) != CSTR_EQUAL
1884 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1885 curPos, min_len, echoSlash, len) != CSTR_EQUAL)
1887 WCMD_output_asis(L" ");
1889 WCMD_output_asis(L"\r\n");
1892 /* Skip repeated 'no echo' characters */
1893 while (*curPos == '@') curPos++;
1895 /* Start with an empty string, copying to the command string */
1896 curStringLen = 0;
1897 curRedirsLen = 0;
1898 curCopyTo = curString;
1899 curLen = &curStringLen;
1900 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1901 lineCurDepth = curDepth; /* What was the curdepth at the beginning of the line */
1903 /* Parse every character on the line being processed */
1904 while (*curPos != 0x00) {
1906 WCHAR thisChar;
1908 /* Debugging AID:
1909 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1910 lastWasWhiteSpace, onlyWhiteSpace);
1913 /* Prevent overflow caused by the caret escape char */
1914 if (*curLen >= MAXSTRING) {
1915 WINE_ERR("Overflow detected in command\n");
1916 return NULL;
1919 /* Certain commands need special handling */
1920 if (curStringLen == 0 && curCopyTo == curString) {
1921 static const WCHAR forDO[] = {'d','o'};
1923 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
1924 if (WCMD_keyword_ws_found(remCmd, ARRAY_SIZE(remCmd), curPos) || *curPos == ':') {
1925 inOneLine = TRUE;
1927 } else if (WCMD_keyword_ws_found(forCmd, ARRAY_SIZE(forCmd), curPos)) {
1928 inFor = TRUE;
1930 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1931 is only true in the command portion of the IF statement, but this
1932 should suffice for now.
1933 To be able to handle ('s in the condition part take as much as evaluate_if_condition
1934 would take and skip parsing it here. */
1935 } else if (WCMD_keyword_ws_found(ifCmd, ARRAY_SIZE(ifCmd), curPos)) {
1936 int negate; /* Negate condition */
1937 int test; /* Condition evaluation result */
1938 WCHAR *p, *command;
1940 inIf = TRUE;
1942 p = curPos+(ARRAY_SIZE(ifCmd));
1943 while (*p == ' ' || *p == '\t')
1944 p++;
1945 WCMD_parse (p, quals, param1, param2);
1947 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
1948 set in a call to WCMD_parse before */
1949 if (evaluate_if_condition(p, &command, &test, &negate) != -1)
1951 int if_condition_len = command - curPos;
1952 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
1953 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
1954 wine_dbgstr_w(param2), wine_dbgstr_w(command), if_condition_len);
1955 memcpy(&curCopyTo[*curLen], curPos, if_condition_len*sizeof(WCHAR));
1956 (*curLen)+=if_condition_len;
1957 curPos+=if_condition_len;
1960 } else if (WCMD_keyword_ws_found(ifElse, ARRAY_SIZE(ifElse), curPos)) {
1961 const int keyw_len = ARRAY_SIZE(ifElse) + 1;
1962 inElse = TRUE;
1963 lastWasElse = TRUE;
1964 onlyWhiteSpace = TRUE;
1965 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1966 (*curLen)+=keyw_len;
1967 curPos+=keyw_len;
1969 /* If we had a single line if XXX which reaches an else (needs odd
1970 syntax like if 1=1 command && (command) else command we pretended
1971 to add brackets for the if, so they are now over */
1972 if (resetAtEndOfLine) {
1973 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
1974 resetAtEndOfLine = FALSE;
1975 curDepth = lineCurDepth;
1977 continue;
1979 /* In a for loop, the DO command will follow a close bracket followed by
1980 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1981 is then 0, and all whitespace is skipped */
1982 } else if (inFor && WCMD_keyword_ws_found(forDO, ARRAY_SIZE(forDO), curPos)) {
1983 const int keyw_len = ARRAY_SIZE(forDO) + 1;
1984 WINE_TRACE("Found 'DO '\n");
1985 lastWasDo = TRUE;
1986 onlyWhiteSpace = TRUE;
1987 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1988 (*curLen)+=keyw_len;
1989 curPos+=keyw_len;
1990 continue;
1992 } else if (curCopyTo == curString) {
1994 /* Special handling for the 'FOR' command */
1995 if (inFor && lastWasWhiteSpace) {
1996 static const WCHAR forIN[] = {'i','n'};
1998 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2000 if (WCMD_keyword_ws_found(forIN, ARRAY_SIZE(forIN), curPos)) {
2001 const int keyw_len = ARRAY_SIZE(forIN) + 1;
2002 WINE_TRACE("Found 'IN '\n");
2003 lastWasIn = TRUE;
2004 onlyWhiteSpace = TRUE;
2005 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
2006 (*curLen)+=keyw_len;
2007 curPos+=keyw_len;
2008 continue;
2013 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2014 the &&, quotes and redirection etc are ineffective, so just force
2015 the use of the default processing by skipping character specific
2016 matching below) */
2017 if (!inOneLine) thisChar = *curPos;
2018 else thisChar = 'X'; /* Character with no special processing */
2020 lastWasWhiteSpace = FALSE; /* Will be reset below */
2021 lastWasCaret = FALSE;
2023 switch (thisChar) {
2025 case '=': /* drop through - ignore token delimiters at the start of a command */
2026 case ',': /* drop through - ignore token delimiters at the start of a command */
2027 case '\t':/* drop through - ignore token delimiters at the start of a command */
2028 case ' ':
2029 /* If a redirect in place, it ends here */
2030 if (!inQuotes && !lastWasRedirect) {
2032 /* If finishing off a redirect, add a whitespace delimiter */
2033 if (curCopyTo == curRedirs) {
2034 curCopyTo[(*curLen)++] = ' ';
2036 curCopyTo = curString;
2037 curLen = &curStringLen;
2039 if (*curLen > 0) {
2040 curCopyTo[(*curLen)++] = *curPos;
2043 /* Remember just processed whitespace */
2044 lastWasWhiteSpace = TRUE;
2046 break;
2048 case '>': /* drop through - handle redirect chars the same */
2049 case '<':
2050 /* Make a redirect start here */
2051 if (!inQuotes) {
2052 curCopyTo = curRedirs;
2053 curLen = &curRedirsLen;
2054 lastWasRedirect = TRUE;
2057 /* See if 1>, 2> etc, in which case we have some patching up
2058 to do (provided there's a preceding whitespace, and enough
2059 chars read so far) */
2060 if (curStringLen > 2
2061 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
2062 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
2063 curStringLen--;
2064 curString[curStringLen] = 0x00;
2065 curCopyTo[(*curLen)++] = *(curPos-1);
2068 curCopyTo[(*curLen)++] = *curPos;
2070 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2071 do not process that ampersand as an AND operator */
2072 if (thisChar == '>' && *(curPos+1) == '&') {
2073 curCopyTo[(*curLen)++] = *(curPos+1);
2074 curPos++;
2076 break;
2078 case '|': /* Pipe character only if not || */
2079 if (!inQuotes) {
2080 lastWasRedirect = FALSE;
2082 /* Add an entry to the command list */
2083 if (curStringLen > 0) {
2085 /* Add the current command */
2086 WCMD_addCommand(curString, &curStringLen,
2087 curRedirs, &curRedirsLen,
2088 &curCopyTo, &curLen,
2089 prevDelim, curDepth,
2090 &lastEntry, output);
2094 if (*(curPos+1) == '|') {
2095 curPos++; /* Skip other | */
2096 prevDelim = CMD_ONFAILURE;
2097 } else {
2098 prevDelim = CMD_PIPE;
2101 /* If in an IF or ELSE statement, put subsequent chained
2102 commands at a higher depth as if brackets were supplied
2103 but remember to reset to the original depth at EOL */
2104 if ((inIf || inElse) && curDepth == lineCurDepth) {
2105 curDepth++;
2106 resetAtEndOfLine = TRUE;
2108 } else {
2109 curCopyTo[(*curLen)++] = *curPos;
2111 break;
2113 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2114 inQuotes--;
2115 } else {
2116 inQuotes++; /* Quotes within quotes are fun! */
2118 curCopyTo[(*curLen)++] = *curPos;
2119 lastWasRedirect = FALSE;
2120 break;
2122 case '(': /* If a '(' is the first non whitespace in a command portion
2123 ie start of line or just after &&, then we read until an
2124 unquoted ) is found */
2125 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2126 ", for(%d, In:%d, Do:%d)"
2127 ", if(%d, else:%d, lwe:%d)\n",
2128 *curLen, inQuotes,
2129 onlyWhiteSpace,
2130 inFor, lastWasIn, lastWasDo,
2131 inIf, inElse, lastWasElse);
2132 lastWasRedirect = FALSE;
2134 /* Ignore open brackets inside the for set */
2135 if (*curLen == 0 && !inIn) {
2136 curDepth++;
2138 /* If in quotes, ignore brackets */
2139 } else if (inQuotes) {
2140 curCopyTo[(*curLen)++] = *curPos;
2142 /* In a FOR loop, an unquoted '(' may occur straight after
2143 IN or DO
2144 In an IF statement just handle it regardless as we don't
2145 parse the operands
2146 In an ELSE statement, only allow it straight away after
2147 the ELSE and whitespace
2149 } else if (inIf ||
2150 (inElse && lastWasElse && onlyWhiteSpace) ||
2151 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2153 /* If entering into an 'IN', set inIn */
2154 if (inFor && lastWasIn && onlyWhiteSpace) {
2155 WINE_TRACE("Inside an IN\n");
2156 inIn = TRUE;
2159 /* Add the current command */
2160 WCMD_addCommand(curString, &curStringLen,
2161 curRedirs, &curRedirsLen,
2162 &curCopyTo, &curLen,
2163 prevDelim, curDepth,
2164 &lastEntry, output);
2166 curDepth++;
2167 } else {
2168 curCopyTo[(*curLen)++] = *curPos;
2170 break;
2172 case '^': if (!inQuotes) {
2173 /* If we reach the end of the input, we need to wait for more */
2174 if (*(curPos+1) == 0x00) {
2175 lastWasCaret = TRUE;
2176 WINE_TRACE("Caret found at end of line\n");
2177 break;
2179 curPos++;
2181 curCopyTo[(*curLen)++] = *curPos;
2182 break;
2184 case '&': if (!inQuotes) {
2185 lastWasRedirect = FALSE;
2187 /* Add an entry to the command list */
2188 if (curStringLen > 0) {
2190 /* Add the current command */
2191 WCMD_addCommand(curString, &curStringLen,
2192 curRedirs, &curRedirsLen,
2193 &curCopyTo, &curLen,
2194 prevDelim, curDepth,
2195 &lastEntry, output);
2199 if (*(curPos+1) == '&') {
2200 curPos++; /* Skip other & */
2201 prevDelim = CMD_ONSUCCESS;
2202 } else {
2203 prevDelim = CMD_NONE;
2205 /* If in an IF or ELSE statement, put subsequent chained
2206 commands at a higher depth as if brackets were supplied
2207 but remember to reset to the original depth at EOL */
2208 if ((inIf || inElse) && curDepth == lineCurDepth) {
2209 curDepth++;
2210 resetAtEndOfLine = TRUE;
2212 } else {
2213 curCopyTo[(*curLen)++] = *curPos;
2215 break;
2217 case ')': if (!inQuotes && curDepth > 0) {
2218 lastWasRedirect = FALSE;
2220 /* Add the current command if there is one */
2221 if (curStringLen) {
2223 /* Add the current command */
2224 WCMD_addCommand(curString, &curStringLen,
2225 curRedirs, &curRedirsLen,
2226 &curCopyTo, &curLen,
2227 prevDelim, curDepth,
2228 &lastEntry, output);
2231 /* Add an empty entry to the command list */
2232 prevDelim = CMD_NONE;
2233 WCMD_addCommand(NULL, &curStringLen,
2234 curRedirs, &curRedirsLen,
2235 &curCopyTo, &curLen,
2236 prevDelim, curDepth,
2237 &lastEntry, output);
2238 curDepth--;
2240 /* Leave inIn if necessary */
2241 if (inIn) inIn = FALSE;
2242 } else {
2243 curCopyTo[(*curLen)++] = *curPos;
2245 break;
2246 default:
2247 lastWasRedirect = FALSE;
2248 curCopyTo[(*curLen)++] = *curPos;
2251 curPos++;
2253 /* At various times we need to know if we have only skipped whitespace,
2254 so reset this variable and then it will remain true until a non
2255 whitespace is found */
2256 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2257 onlyWhiteSpace = FALSE;
2259 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2260 if (!lastWasWhiteSpace) {
2261 lastWasIn = lastWasDo = FALSE;
2264 /* If we have reached the end, add this command into the list
2265 Do not add command to list if escape char ^ was last */
2266 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2268 /* Add an entry to the command list */
2269 WCMD_addCommand(curString, &curStringLen,
2270 curRedirs, &curRedirsLen,
2271 &curCopyTo, &curLen,
2272 prevDelim, curDepth,
2273 &lastEntry, output);
2275 /* If we had a single line if or else, and we pretended to add
2276 brackets, end them now */
2277 if (resetAtEndOfLine) {
2278 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
2279 resetAtEndOfLine = FALSE;
2280 curDepth = lineCurDepth;
2284 /* If we have reached the end of the string, see if bracketing or
2285 final caret is outstanding */
2286 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2287 readFrom != INVALID_HANDLE_VALUE) {
2288 WCHAR *extraData;
2290 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2291 inOneLine = FALSE;
2292 prevDelim = CMD_NONE;
2293 inQuotes = 0;
2294 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2295 extraData = extraSpace;
2297 /* Read more, skipping any blank lines */
2298 do {
2299 WINE_TRACE("Read more input\n");
2300 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2301 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2302 break;
2304 /* Edge case for carets - a completely blank line (i.e. was just
2305 CRLF) is oddly added as an LF but then more data is received (but
2306 only once more!) */
2307 if (lastWasCaret) {
2308 if (*extraSpace == 0x00) {
2309 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2310 *extraData++ = '\r';
2311 *extraData = 0x00;
2312 } else break;
2315 } while (*extraData == 0x00);
2316 curPos = extraSpace;
2318 /* Skip preceding whitespace */
2319 while (*curPos == ' ' || *curPos == '\t') curPos++;
2321 /* Replace env vars if in a batch context */
2322 if (context) handleExpansion(curPos, FALSE, FALSE);
2324 /* Continue to echo commands IF echo is on and in batch program */
2325 if (context && echo_mode && *curPos && *curPos != '@') {
2326 WCMD_output_asis(extraSpace);
2327 WCMD_output_asis(L"\r\n");
2330 /* Skip repeated 'no echo' characters and whitespace */
2331 while (*curPos == '@' || *curPos == ' ' || *curPos == '\t') curPos++;
2335 /* Dump out the parsed output */
2336 WCMD_DumpCommands(*output);
2338 return extraSpace;
2341 /***************************************************************************
2342 * WCMD_process_commands
2344 * Process all the commands read in so far
2346 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2347 BOOL retrycall) {
2349 int bdepth = -1;
2351 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2353 /* Loop through the commands, processing them one by one */
2354 while (thisCmd) {
2356 CMD_LIST *origCmd = thisCmd;
2358 /* If processing one bracket only, and we find the end bracket
2359 entry (or less), return */
2360 if (oneBracket && !thisCmd->command &&
2361 bdepth <= thisCmd->bracketDepth) {
2362 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2363 thisCmd, thisCmd->nextcommand);
2364 return thisCmd->nextcommand;
2367 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2368 about them and it will be handled in there)
2369 Also, skip over any batch labels (eg. :fred) */
2370 if (thisCmd->command && thisCmd->command[0] != ':') {
2371 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2372 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2375 /* Step on unless the command itself already stepped on */
2376 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2378 return NULL;
2381 /***************************************************************************
2382 * WCMD_free_commands
2384 * Frees the storage held for a parsed command line
2385 * - This is not done in the process_commands, as eventually the current
2386 * pointer will be modified within the commands, and hence a single free
2387 * routine is simpler
2389 void WCMD_free_commands(CMD_LIST *cmds) {
2391 /* Loop through the commands, freeing them one by one */
2392 while (cmds) {
2393 CMD_LIST *thisCmd = cmds;
2394 cmds = cmds->nextcommand;
2395 heap_free(thisCmd->command);
2396 heap_free(thisCmd->redirects);
2397 heap_free(thisCmd);
2402 /*****************************************************************************
2403 * Main entry point. This is a console application so we have a main() not a
2404 * winmain().
2407 int __cdecl wmain (int argc, WCHAR *argvW[])
2409 WCHAR *cmdLine = NULL;
2410 WCHAR *cmd = NULL;
2411 WCHAR string[1024];
2412 WCHAR envvar[4];
2413 BOOL promptNewLine = TRUE;
2414 BOOL opt_q;
2415 int opt_t = 0;
2416 WCHAR comspec[MAX_PATH];
2417 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2418 RTL_OSVERSIONINFOEXW osv;
2419 char osver[50];
2420 STARTUPINFOW startupInfo;
2421 const WCHAR *arg;
2423 if (!GetEnvironmentVariableW(L"COMSPEC", comspec, ARRAY_SIZE(comspec)))
2425 GetSystemDirectoryW(comspec, ARRAY_SIZE(comspec) - ARRAY_SIZE(L"\\cmd.exe"));
2426 lstrcatW(comspec, L"\\cmd.exe");
2427 SetEnvironmentVariableW(L"COMSPEC", comspec);
2430 srand(time(NULL));
2432 /* Get the windows version being emulated */
2433 osv.dwOSVersionInfoSize = sizeof(osv);
2434 RtlGetVersion(&osv);
2436 /* Pre initialize some messages */
2437 lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2438 sprintf(osver, "%d.%d.%d", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
2439 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2440 lstrcpyW(version_string, cmd);
2441 LocalFree(cmd);
2442 cmd = NULL;
2444 /* Can't use argc/argv as it will have stripped quotes from parameters
2445 * meaning cmd.exe /C echo "quoted string" is impossible
2447 cmdLine = GetCommandLineW();
2448 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2450 while (*cmdLine && *cmdLine != '/') ++cmdLine;
2452 opt_c = opt_k = opt_q = opt_s = FALSE;
2454 for (arg = cmdLine; *arg; ++arg)
2456 if (arg[0] != '/')
2457 continue;
2459 switch (towlower(arg[1]))
2461 case 'a':
2462 unicodeOutput = FALSE;
2463 break;
2464 case 'c':
2465 opt_c = TRUE;
2466 break;
2467 case 'k':
2468 opt_k = TRUE;
2469 break;
2470 case 'q':
2471 opt_q = TRUE;
2472 break;
2473 case 's':
2474 opt_s = TRUE;
2475 break;
2476 case 't':
2477 if (arg[2] == ':')
2478 opt_t = wcstoul(&arg[3], NULL, 16);
2479 break;
2480 case 'u':
2481 unicodeOutput = TRUE;
2482 break;
2483 case 'v':
2484 if (arg[2] == ':')
2485 delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
2486 break;
2489 if (opt_c || opt_k)
2491 arg += 2;
2492 break;
2496 while (*arg && wcschr(L" \t,=;", *arg)) arg++;
2498 if (opt_q) {
2499 WCMD_echo(L"OFF");
2502 /* Until we start to read from the keyboard, stay as non-interactive */
2503 interactive = FALSE;
2505 SetEnvironmentVariableW(L"PROMPT", L"$P$G");
2507 if (opt_c || opt_k) {
2508 int len;
2509 WCHAR *q1 = NULL,*q2 = NULL,*p;
2511 /* Take a copy */
2512 cmd = heap_strdupW(arg);
2514 /* opt_s left unflagged if the command starts with and contains exactly
2515 * one quoted string (exactly two quote characters). The quoted string
2516 * must be an executable name that has whitespace and must not have the
2517 * following characters: &<>()@^| */
2519 if (!opt_s) {
2520 /* 1. Confirm there is at least one quote */
2521 q1 = wcschr(arg, '"');
2522 if (!q1) opt_s=1;
2525 if (!opt_s) {
2526 /* 2. Confirm there is a second quote */
2527 q2 = wcschr(q1+1, '"');
2528 if (!q2) opt_s=1;
2531 if (!opt_s) {
2532 /* 3. Ensure there are no more quotes */
2533 if (wcschr(q2+1, '"')) opt_s=1;
2536 /* check first parameter for a space and invalid characters. There must not be any
2537 * invalid characters, but there must be one or more whitespace */
2538 if (!opt_s) {
2539 opt_s = TRUE;
2540 p=q1;
2541 while (p!=q2) {
2542 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2543 || *p=='@' || *p=='^' || *p=='|') {
2544 opt_s = TRUE;
2545 break;
2547 if (*p==' ' || *p=='\t')
2548 opt_s = FALSE;
2549 p++;
2553 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2555 /* Finally, we only stay in new mode IF the first parameter is quoted and
2556 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2557 if (!opt_s) {
2558 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2559 WCHAR pathext[MAXSTRING];
2560 BOOL found = FALSE;
2562 /* Now extract PATHEXT */
2563 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
2564 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
2565 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
2568 /* If the supplied parameter has any directory information, look there */
2569 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2570 if (wcschr(thisArg, '\\') != NULL) {
2572 GetFullPathNameW(thisArg, ARRAY_SIZE(string), string, NULL);
2573 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2574 p = string + lstrlenW(string);
2576 /* Does file exist with this name? */
2577 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2578 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2579 found = TRUE;
2580 } else {
2581 WCHAR *thisExt = pathext;
2583 /* No - try with each of the PATHEXT extensions */
2584 while (!found && thisExt) {
2585 WCHAR *nextExt = wcschr(thisExt, ';');
2587 if (nextExt) {
2588 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2589 p[(nextExt-thisExt)] = 0x00;
2590 thisExt = nextExt+1;
2591 } else {
2592 lstrcpyW(p, thisExt);
2593 thisExt = NULL;
2596 /* Does file exist with this extension appended? */
2597 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2598 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2599 found = TRUE;
2604 /* Otherwise we now need to look in the path to see if we can find it */
2605 } else {
2606 /* Does file exist with this name? */
2607 if (SearchPathW(NULL, thisArg, NULL, ARRAY_SIZE(string), string, NULL) != 0) {
2608 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2609 found = TRUE;
2610 } else {
2611 WCHAR *thisExt = pathext;
2613 /* No - try with each of the PATHEXT extensions */
2614 while (!found && thisExt) {
2615 WCHAR *nextExt = wcschr(thisExt, ';');
2617 if (nextExt) {
2618 *nextExt = 0;
2619 nextExt = nextExt+1;
2620 } else {
2621 nextExt = NULL;
2624 /* Does file exist with this extension? */
2625 if (SearchPathW(NULL, thisArg, thisExt, ARRAY_SIZE(string), string, NULL) != 0) {
2626 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2627 wine_dbgstr_w(thisExt));
2628 found = TRUE;
2630 thisExt = nextExt;
2635 /* If not found, drop back to old behaviour */
2636 if (!found) {
2637 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2638 opt_s = TRUE;
2643 /* strip first and last quote characters if opt_s; check for invalid
2644 * executable is done later */
2645 if (opt_s && *cmd=='\"')
2646 WCMD_strip_quotes(cmd);
2649 /* Save cwd into appropriate env var (Must be before the /c processing */
2650 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
2651 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2652 wsprintfW(envvar, L"=%c:", string[0]);
2653 SetEnvironmentVariableW(envvar, string);
2654 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2657 if (opt_c) {
2658 /* If we do a "cmd /c command", we don't want to allocate a new
2659 * console since the command returns immediately. Rather, we use
2660 * the currently allocated input and output handles. This allows
2661 * us to pipe to and read from the command interpreter.
2664 /* Parse the command string, without reading any more input */
2665 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2666 WCMD_process_commands(toExecute, FALSE, FALSE);
2667 WCMD_free_commands(toExecute);
2668 toExecute = NULL;
2670 heap_free(cmd);
2671 return errorlevel;
2674 GetStartupInfoW(&startupInfo);
2675 if (startupInfo.lpTitle != NULL)
2676 SetConsoleTitleW(startupInfo.lpTitle);
2677 else
2678 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2680 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2681 if (opt_t) {
2682 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2683 defaultColor = opt_t & 0xFF;
2684 param1[0] = 0x00;
2685 WCMD_color();
2687 } else {
2688 /* Check HKCU\Software\Microsoft\Command Processor
2689 Then HKLM\Software\Microsoft\Command Processor
2690 for defaultcolour value
2691 Note Can be supplied as DWORD or REG_SZ
2692 Note2 When supplied as REG_SZ it's in decimal!!! */
2693 HKEY key;
2694 DWORD type;
2695 DWORD value=0, size=4;
2696 static const WCHAR regKeyW[] = L"Software\\Microsoft\\Command Processor";
2698 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2699 0, KEY_READ, &key) == ERROR_SUCCESS) {
2700 WCHAR strvalue[4];
2702 /* See if DWORD or REG_SZ */
2703 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
2704 if (type == REG_DWORD) {
2705 size = sizeof(DWORD);
2706 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
2707 } else if (type == REG_SZ) {
2708 size = ARRAY_SIZE(strvalue);
2709 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
2710 value = wcstoul(strvalue, NULL, 10);
2713 RegCloseKey(key);
2716 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2717 0, KEY_READ, &key) == ERROR_SUCCESS) {
2718 WCHAR strvalue[4];
2720 /* See if DWORD or REG_SZ */
2721 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type,
2722 NULL, NULL) == ERROR_SUCCESS) {
2723 if (type == REG_DWORD) {
2724 size = sizeof(DWORD);
2725 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
2726 } else if (type == REG_SZ) {
2727 size = ARRAY_SIZE(strvalue);
2728 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
2729 value = wcstoul(strvalue, NULL, 10);
2732 RegCloseKey(key);
2735 /* If one found, set the screen to that colour */
2736 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2737 defaultColor = value & 0xFF;
2738 param1[0] = 0x00;
2739 WCMD_color();
2744 if (opt_k) {
2745 /* Parse the command string, without reading any more input */
2746 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2747 WCMD_process_commands(toExecute, FALSE, FALSE);
2748 WCMD_free_commands(toExecute);
2749 toExecute = NULL;
2750 heap_free(cmd);
2754 * Loop forever getting commands and executing them.
2757 interactive = TRUE;
2758 if (!opt_k) WCMD_version ();
2759 while (TRUE) {
2761 /* Read until EOF (which for std input is never, but if redirect
2762 in place, may occur */
2763 if (echo_mode) WCMD_show_prompt(promptNewLine);
2764 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2765 break;
2766 WCMD_process_commands(toExecute, FALSE, FALSE);
2767 WCMD_free_commands(toExecute);
2768 promptNewLine = !!toExecute;
2769 toExecute = NULL;
2771 return 0;