dmime/tests: Avoid discarding old notifications in test.
[wine.git] / programs / cmd / wcmdmain.c
blob843fef8ea50d1d1eec4915b0142b00e6690ecb54
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 = 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 va_list ap;
126 WCHAR* string;
127 DWORD len;
129 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 va_end(ap);
134 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
135 WINE_FIXME("Could not format string: le=%lu, 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 va_list ap;
151 WCHAR* string;
152 DWORD len;
154 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 va_end(ap);
159 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
160 WINE_FIXME("Could not format string: le=%lu, 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 va_list ap;
176 WCHAR* string;
177 DWORD len;
179 va_start(ap,format);
180 len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
181 format, 0, 0, (LPWSTR)&string, 0, &ap);
182 va_end(ap);
183 if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
184 WINE_FIXME("Could not format string: le=%lu, 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 %ld, status %ld\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 (towupper(*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 *xalloc(size_t size)
428 void *ret;
430 ret = malloc(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 BOOL WCMD_get_fullpath(const WCHAR* in, SIZE_T outsize, WCHAR* out, WCHAR** start)
457 DWORD ret = GetFullPathNameW(in, outsize, out, start);
458 if (!ret) return FALSE;
459 if (ret > outsize)
461 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_FILENAMETOOLONG));
462 return FALSE;
464 return TRUE;
467 /***************************************************************************
468 * WCMD_skip_leading_spaces
470 * Return a pointer to the first non-whitespace character of string.
471 * Does not modify the input string.
473 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
475 WCHAR *ptr;
477 ptr = string;
478 while (*ptr == ' ' || *ptr == '\t') ptr++;
479 return ptr;
482 /***************************************************************************
483 * WCMD_keyword_ws_found
485 * Checks if the string located at ptr matches a keyword (of length len)
486 * followed by a whitespace character (space or tab)
488 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, const WCHAR *ptr) {
489 const int len = lstrlenW(keyword);
490 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
491 ptr, len, keyword, len) == CSTR_EQUAL)
492 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
495 /*************************************************************************
496 * WCMD_strip_quotes
498 * Remove first and last quote WCHARacters, preserving all other text
499 * Returns the location of the final quote
501 WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
502 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
503 while((*dest=*src) != '\0') {
504 if (*src=='\"')
505 lastq=dest;
506 dest++; src++;
508 lastquote = lastq;
509 if (lastq) {
510 dest=lastq++;
511 while ((*dest++=*lastq++) != 0)
514 return lastquote;
518 /*************************************************************************
519 * WCMD_is_magic_envvar
520 * Return TRUE if s is '%'magicvar'%'
521 * and is not masked by a real environment variable.
524 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
526 int len;
528 if (s[0] != '%')
529 return FALSE; /* Didn't begin with % */
530 len = lstrlenW(s);
531 if (len < 2 || s[len-1] != '%')
532 return FALSE; /* Didn't end with another % */
534 if (CompareStringW(LOCALE_USER_DEFAULT,
535 NORM_IGNORECASE | SORT_STRINGSORT,
536 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
537 /* Name doesn't match. */
538 return FALSE;
541 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
542 /* Masked by real environment variable. */
543 return FALSE;
546 return TRUE;
549 /*************************************************************************
550 * WCMD_expand_envvar
552 * Expands environment variables, allowing for WCHARacter substitution
554 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
556 WCHAR *endOfVar = NULL, *s;
557 WCHAR *colonpos = NULL;
558 WCHAR thisVar[MAXSTRING];
559 WCHAR thisVarContents[MAXSTRING];
560 WCHAR savedchar = 0x00;
561 int len;
562 WCHAR Delims[] = L"%:"; /* First char gets replaced appropriately */
564 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
566 /* Find the end of the environment variable, and extract name */
567 Delims[0] = startchar;
568 endOfVar = wcspbrk(start+1, Delims);
570 if (endOfVar == NULL || *endOfVar==' ') {
572 /* In batch program, missing terminator for % and no following
573 ':' just removes the '%' */
574 if (context) {
575 WCMD_strsubstW(start, start + 1, NULL, 0);
576 return start;
577 } else {
579 /* In command processing, just ignore it - allows command line
580 syntax like: for %i in (a.a) do echo %i */
581 return start+1;
585 /* If ':' found, process remaining up until '%' (or stop at ':' if
586 a missing '%' */
587 if (*endOfVar==':') {
588 WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar);
589 if (endOfVar2 != NULL) endOfVar = endOfVar2;
592 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
593 thisVar[(endOfVar - start)+1] = 0x00;
594 colonpos = wcschr(thisVar+1, ':');
596 /* If there's complex substitution, just need %var% for now
597 to get the expanded data to play with */
598 if (colonpos) {
599 *colonpos = '%';
600 savedchar = *(colonpos+1);
601 *(colonpos+1) = 0x00;
604 /* By now, we know the variable we want to expand but it may be
605 surrounded by '!' if we are in delayed expansion - if so convert
606 to % signs. */
607 if (startchar=='!') {
608 thisVar[0] = '%';
609 thisVar[(endOfVar - start)] = '%';
611 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
613 /* Expand to contents, if unchanged, return */
614 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
615 /* override if existing env var called that name */
616 if (WCMD_is_magic_envvar(thisVar, L"ERRORLEVEL")) {
617 wsprintfW(thisVarContents, L"%d", errorlevel);
618 len = lstrlenW(thisVarContents);
619 } else if (WCMD_is_magic_envvar(thisVar, L"DATE")) {
620 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
621 NULL, thisVarContents, MAXSTRING);
622 len = lstrlenW(thisVarContents);
623 } else if (WCMD_is_magic_envvar(thisVar, L"TIME")) {
624 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
625 NULL, thisVarContents, MAXSTRING);
626 len = lstrlenW(thisVarContents);
627 } else if (WCMD_is_magic_envvar(thisVar, L"CD")) {
628 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
629 len = lstrlenW(thisVarContents);
630 } else if (WCMD_is_magic_envvar(thisVar, L"RANDOM")) {
631 wsprintfW(thisVarContents, L"%d", rand() % 32768);
632 len = lstrlenW(thisVarContents);
633 } else {
635 len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents));
638 if (len == 0)
639 return endOfVar+1;
641 /* In a batch program, unknown env vars are replaced with nothing,
642 note syntax %garbage:1,3% results in anything after the ':'
643 except the %
644 From the command line, you just get back what you entered */
645 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
647 /* Restore the complex part after the compare */
648 if (colonpos) {
649 *colonpos = ':';
650 *(colonpos+1) = savedchar;
653 /* Command line - just ignore this */
654 if (context == NULL) return endOfVar+1;
657 /* Batch - replace unknown env var with nothing */
658 if (colonpos == NULL) {
659 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
660 } else {
661 len = lstrlenW(thisVar);
662 thisVar[len-1] = 0x00;
663 /* If %:...% supplied, : is retained */
664 if (colonpos == thisVar+1) {
665 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
666 } else {
667 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
670 return start;
674 /* See if we need to do complex substitution (any ':'s), if not
675 then our work here is done */
676 if (colonpos == NULL) {
677 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
678 return start;
681 /* Restore complex bit */
682 *colonpos = ':';
683 *(colonpos+1) = savedchar;
686 Handle complex substitutions:
687 xxx=yyy (replace xxx with yyy)
688 *xxx=yyy (replace up to and including xxx with yyy)
689 ~x (from x WCHARs in)
690 ~-x (from x WCHARs from the end)
691 ~x,y (from x WCHARs in for y WCHARacters)
692 ~x,-y (from x WCHARs in until y WCHARacters from the end)
695 /* ~ is substring manipulation */
696 if (savedchar == '~') {
698 int substrposition, substrlength = 0;
699 WCHAR *commapos = wcschr(colonpos+2, ',');
700 WCHAR *startCopy;
702 substrposition = wcstol(colonpos+2, NULL, 10);
703 if (commapos) substrlength = wcstol(commapos+1, NULL, 10);
705 /* Check bounds */
706 if (substrposition >= 0) {
707 startCopy = &thisVarContents[min(substrposition, len)];
708 } else {
709 startCopy = &thisVarContents[max(0, len+substrposition-1)];
712 if (commapos == NULL) {
713 /* Copy the lot */
714 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
715 } else if (substrlength < 0) {
717 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
718 if (copybytes > len) copybytes = len;
719 else if (copybytes < 0) copybytes = 0;
720 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
721 } else {
722 substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
723 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
726 /* search and replace manipulation */
727 } else {
728 WCHAR *equalspos = wcsstr(colonpos, L"=");
729 WCHAR *replacewith = equalspos+1;
730 WCHAR *found = NULL;
731 WCHAR *searchIn;
732 WCHAR *searchFor;
734 if (equalspos == NULL) return start+1;
735 s = xstrdupW(endOfVar + 1);
737 /* Null terminate both strings */
738 thisVar[lstrlenW(thisVar)-1] = 0x00;
739 *equalspos = 0x00;
741 /* Since we need to be case insensitive, copy the 2 buffers */
742 searchIn = xstrdupW(thisVarContents);
743 CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
744 searchFor = xstrdupW(colonpos + 1);
745 CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
747 /* Handle wildcard case */
748 if (*(colonpos+1) == '*') {
749 /* Search for string to replace */
750 found = wcsstr(searchIn, searchFor+1);
752 if (found) {
753 /* Do replacement */
754 lstrcpyW(start, replacewith);
755 lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
756 lstrcatW(start, s);
757 } else {
758 /* Copy as is */
759 lstrcpyW(start, thisVarContents);
760 lstrcatW(start, s);
763 } else {
764 /* Loop replacing all instances */
765 WCHAR *lastFound = searchIn;
766 WCHAR *outputposn = start;
768 *start = 0x00;
769 while ((found = wcsstr(lastFound, searchFor))) {
770 lstrcpynW(outputposn,
771 thisVarContents + (lastFound-searchIn),
772 (found - lastFound)+1);
773 outputposn = outputposn + (found - lastFound);
774 lstrcatW(outputposn, replacewith);
775 outputposn = outputposn + lstrlenW(replacewith);
776 lastFound = found + lstrlenW(searchFor);
778 lstrcatW(outputposn,
779 thisVarContents + (lastFound-searchIn));
780 lstrcatW(outputposn, s);
782 free(s);
783 free(searchIn);
784 free(searchFor);
786 return start;
789 /*****************************************************************************
790 * Expand the command. Native expands lines from batch programs as they are
791 * read in and not again, except for 'for' variable substitution.
792 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
793 * atExecute is TRUE when the expansion is occurring as the command is executed
794 * rather than at parse time, i.e. delayed expansion and for loops need to be
795 * processed
797 static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
799 /* For commands in a context (batch program): */
800 /* Expand environment variables in a batch file %{0-9} first */
801 /* including support for any ~ modifiers */
802 /* Additionally: */
803 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
804 /* names allowing environment variable overrides */
805 /* NOTE: To support the %PATH:xxx% syntax, also perform */
806 /* manual expansion of environment variables here */
808 WCHAR *p = cmd;
809 WCHAR *t;
810 int i;
811 WCHAR *delayedp = NULL;
812 WCHAR startchar = '%';
813 WCHAR *normalp;
815 /* Display the FOR variables in effect */
816 for (i=0;i<52;i++) {
817 if (forloopcontext.variable[i]) {
818 WINE_TRACE("FOR variable context: %c = '%s'\n",
819 i<26?i+'a':(i-26)+'A',
820 wine_dbgstr_w(forloopcontext.variable[i]));
824 /* Find the next environment variable delimiter */
825 normalp = wcschr(p, '%');
826 if (delayed) delayedp = wcschr(p, '!');
827 if (!normalp) p = delayedp;
828 else if (!delayedp) p = normalp;
829 else p = min(p,delayedp);
830 if (p) startchar = *p;
832 while (p) {
834 WINE_TRACE("Translate command:%s %d (at: %s)\n",
835 wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
836 i = *(p+1) - '0';
838 /* Don't touch %% unless it's in Batch */
839 if (!atExecute && *(p+1) == startchar) {
840 if (context) {
841 WCMD_strsubstW(p, p+1, NULL, 0);
843 p+=1;
845 /* Replace %~ modifications if in batch program */
846 } else if (*(p+1) == '~') {
847 WCMD_HandleTildeModifiers(&p, atExecute);
848 p++;
850 /* Replace use of %0...%9 if in batch program*/
851 } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
852 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
853 NULL, TRUE, TRUE);
854 WCMD_strsubstW(p, p+2, t, -1);
856 /* Replace use of %* if in batch program*/
857 } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
858 WCHAR *startOfParms = NULL;
859 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
860 if (startOfParms != NULL) {
861 startOfParms += lstrlenW(thisParm);
862 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
863 WCMD_strsubstW(p, p+2, startOfParms, -1);
864 } else
865 WCMD_strsubstW(p, p+2, NULL, 0);
867 } else {
868 int forvaridx = FOR_VAR_IDX(*(p+1));
869 if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
870 /* Replace the 2 characters, % and for variable character */
871 WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
872 } else if (!atExecute || startchar == '!') {
873 p = WCMD_expand_envvar(p, startchar);
875 /* In a FOR loop, see if this is the variable to replace */
876 } else { /* Ignore %'s on second pass of batch program */
877 p++;
881 /* Find the next environment variable delimiter */
882 normalp = wcschr(p, '%');
883 if (delayed) delayedp = wcschr(p, '!');
884 if (!normalp) p = delayedp;
885 else if (!delayedp) p = normalp;
886 else p = min(p,delayedp);
887 if (p) startchar = *p;
890 return;
894 /*******************************************************************
895 * WCMD_parse - parse a command into parameters and qualifiers.
897 * On exit, all qualifiers are concatenated into q, the first string
898 * not beginning with "/" is in p1 and the
899 * second in p2. Any subsequent non-qualifier strings are lost.
900 * Parameters in quotes are handled.
902 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
904 int p = 0;
906 *q = *p1 = *p2 = '\0';
907 while (TRUE) {
908 switch (*s) {
909 case '/':
910 *q++ = *s++;
911 while ((*s != '\0') && (*s != ' ') && *s != '/') {
912 *q++ = towupper (*s++);
914 *q = '\0';
915 break;
916 case ' ':
917 case '\t':
918 s++;
919 break;
920 case '"':
921 s++;
922 while ((*s != '\0') && (*s != '"')) {
923 if (p == 0) *p1++ = *s++;
924 else if (p == 1) *p2++ = *s++;
925 else s++;
927 if (p == 0) *p1 = '\0';
928 if (p == 1) *p2 = '\0';
929 p++;
930 if (*s == '"') s++;
931 break;
932 case '\0':
933 return;
934 default:
935 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
936 && (*s != '=') && (*s != ',') ) {
937 if (p == 0) *p1++ = *s++;
938 else if (p == 1) *p2++ = *s++;
939 else s++;
941 /* Skip concurrent parms */
942 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
944 if (p == 0) *p1 = '\0';
945 if (p == 1) *p2 = '\0';
946 p++;
951 static void init_msvcrt_io_block(STARTUPINFOW* st)
953 STARTUPINFOW st_p;
954 /* fetch the parent MSVCRT info block if any, so that the child can use the
955 * same handles as its grand-father
957 st_p.cb = sizeof(STARTUPINFOW);
958 GetStartupInfoW(&st_p);
959 st->cbReserved2 = st_p.cbReserved2;
960 st->lpReserved2 = st_p.lpReserved2;
961 if (st_p.cbReserved2 && st_p.lpReserved2)
963 unsigned num = *(unsigned*)st_p.lpReserved2;
964 char* flags;
965 HANDLE* handles;
966 BYTE *ptr;
967 size_t sz;
969 /* Override the entries for fd 0,1,2 if we happened
970 * to change those std handles (this depends on the way cmd sets
971 * its new input & output handles)
973 sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
974 ptr = xalloc(sz);
975 flags = (char*)(ptr + sizeof(unsigned));
976 handles = (HANDLE*)(flags + num * sizeof(char));
978 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
979 st->cbReserved2 = sz;
980 st->lpReserved2 = ptr;
982 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
983 if (num <= 0 || (flags[0] & WX_OPEN))
985 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
986 flags[0] |= WX_OPEN;
988 if (num <= 1 || (flags[1] & WX_OPEN))
990 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
991 flags[1] |= WX_OPEN;
993 if (num <= 2 || (flags[2] & WX_OPEN))
995 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
996 flags[2] |= WX_OPEN;
998 #undef WX_OPEN
1002 /******************************************************************************
1003 * WCMD_run_program
1005 * Execute a command line as an external program. Must allow recursion.
1007 * Precedence:
1008 * Manual testing under windows shows PATHEXT plays a key part in this,
1009 * and the search algorithm and precedence appears to be as follows.
1011 * Search locations:
1012 * If directory supplied on command, just use that directory
1013 * If extension supplied on command, look for that explicit name first
1014 * Otherwise, search in each directory on the path
1015 * Precedence:
1016 * If extension supplied on command, look for that explicit name first
1017 * Then look for supplied name .* (even if extension supplied, so
1018 * 'garbage.exe' will match 'garbage.exe.cmd')
1019 * If any found, cycle through PATHEXT looking for name.exe one by one
1020 * Launching
1021 * Once a match has been found, it is launched - Code currently uses
1022 * findexecutable to achieve this which is left untouched.
1023 * If an executable has not been found, and we were launched through
1024 * a call, we need to check if the command is an internal command,
1025 * so go back through wcmd_execute.
1028 void WCMD_run_program (WCHAR *command, BOOL called)
1030 WCHAR temp[MAX_PATH];
1031 WCHAR pathtosearch[MAXSTRING];
1032 WCHAR *pathposn;
1033 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1034 MAX_PATH, including null character */
1035 WCHAR *lastSlash;
1036 WCHAR pathext[MAXSTRING];
1037 WCHAR *firstParam;
1038 BOOL extensionsupplied = FALSE;
1039 BOOL explicit_path = FALSE;
1040 BOOL status;
1041 DWORD len;
1043 /* Quick way to get the filename is to extract the first argument. */
1044 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1045 firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1046 if (!firstParam) return;
1048 if (!firstParam[0]) {
1049 errorlevel = 0;
1050 return;
1053 /* Calculate the search path and stem to search for */
1054 if (wcspbrk(firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
1055 lstrcpyW(pathtosearch, L".;");
1056 len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
1057 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
1058 lstrcpyW(pathtosearch, L".");
1060 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
1061 if (lstrlenW(firstParam) >= MAX_PATH)
1063 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1064 return;
1067 lstrcpyW(stemofsearch, firstParam);
1069 } else {
1071 /* Convert eg. ..\fred to include a directory by removing file part */
1072 if (!WCMD_get_fullpath(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL)) return;
1073 lastSlash = wcsrchr(pathtosearch, '\\');
1074 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1075 lstrcpyW(stemofsearch, lastSlash+1);
1077 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1078 c:\windows\a.bat syntax */
1079 if (lastSlash) *(lastSlash + 1) = 0x00;
1080 explicit_path = TRUE;
1083 /* Now extract PATHEXT */
1084 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
1085 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
1086 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
1089 /* Loop through the search path, dir by dir */
1090 pathposn = pathtosearch;
1091 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1092 wine_dbgstr_w(stemofsearch));
1093 while (pathposn) {
1094 WCHAR thisDir[MAX_PATH] = {'\0'};
1095 int length = 0;
1096 WCHAR *pos = NULL;
1097 BOOL found = FALSE;
1098 BOOL inside_quotes = FALSE;
1100 if (explicit_path)
1102 lstrcpyW(thisDir, pathposn);
1103 pathposn = NULL;
1105 else
1107 /* Work on the next directory on the search path */
1108 pos = pathposn;
1109 while ((inside_quotes || *pos != ';') && *pos != 0)
1111 if (*pos == '"')
1112 inside_quotes = !inside_quotes;
1113 pos++;
1116 if (*pos) /* Reached semicolon */
1118 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1119 thisDir[(pos-pathposn)] = 0x00;
1120 pathposn = pos+1;
1122 else /* Reached string end */
1124 lstrcpyW(thisDir, pathposn);
1125 pathposn = NULL;
1128 /* Remove quotes */
1129 length = lstrlenW(thisDir);
1130 if (thisDir[length - 1] == '"')
1131 thisDir[length - 1] = 0;
1133 if (*thisDir != '"')
1134 lstrcpyW(temp, thisDir);
1135 else
1136 lstrcpyW(temp, thisDir + 1);
1138 /* When temp is an empty string, skip over it. This needs
1139 to be done before the expansion, because WCMD_get_fullpath
1140 fails when given an empty string */
1141 if (*temp == '\0')
1142 continue;
1144 /* Since you can have eg. ..\.. on the path, need to expand
1145 to full information */
1146 if (!WCMD_get_fullpath(temp, ARRAY_SIZE(thisDir), thisDir, NULL)) return;
1149 /* 1. If extension supplied, see if that file exists */
1150 lstrcatW(thisDir, L"\\");
1151 lstrcatW(thisDir, stemofsearch);
1152 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
1154 /* 1. If extension supplied, see if that file exists */
1155 if (extensionsupplied) {
1156 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1157 found = TRUE;
1161 /* 2. Any .* matches? */
1162 if (!found) {
1163 HANDLE h;
1164 WIN32_FIND_DATAW finddata;
1166 lstrcatW(thisDir, L".*");
1167 h = FindFirstFileW(thisDir, &finddata);
1168 FindClose(h);
1169 if (h != INVALID_HANDLE_VALUE) {
1171 WCHAR *thisExt = pathext;
1173 /* 3. Yes - Try each path ext */
1174 while (thisExt) {
1175 WCHAR *nextExt = wcschr(thisExt, ';');
1177 if (nextExt) {
1178 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1179 pos[(nextExt-thisExt)] = 0x00;
1180 thisExt = nextExt+1;
1181 } else {
1182 lstrcpyW(pos, thisExt);
1183 thisExt = NULL;
1186 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1187 found = TRUE;
1188 thisExt = NULL;
1194 /* Once found, launch it */
1195 if (found) {
1196 STARTUPINFOW st;
1197 PROCESS_INFORMATION pe;
1198 SHFILEINFOW psfi;
1199 DWORD console;
1200 HINSTANCE hinst;
1201 WCHAR *ext = wcsrchr( thisDir, '.' );
1203 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1205 /* Special case BAT and CMD */
1206 if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) {
1207 BOOL oldinteractive = interactive;
1208 interactive = FALSE;
1209 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1210 interactive = oldinteractive;
1211 return;
1212 } else {
1214 /* thisDir contains the file to be launched, but with what?
1215 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1216 hinst = FindExecutableW (thisDir, NULL, temp);
1217 if ((INT_PTR)hinst < 32)
1218 console = 0;
1219 else
1220 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1222 ZeroMemory (&st, sizeof(STARTUPINFOW));
1223 st.cb = sizeof(STARTUPINFOW);
1224 init_msvcrt_io_block(&st);
1226 /* Launch the process and if a CUI wait on it to complete
1227 Note: Launching internal wine processes cannot specify a full path to exe */
1228 status = CreateProcessW(thisDir,
1229 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1230 free(st.lpReserved2);
1231 if ((opt_c || opt_k) && !opt_s && !status
1232 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1233 /* strip first and last quote WCHARacters and try again */
1234 WCMD_strip_quotes(command);
1235 opt_s = TRUE;
1236 WCMD_run_program(command, called);
1237 return;
1240 if (!status)
1241 break;
1243 /* Always wait when non-interactive (cmd /c or in batch program),
1244 or for console applications */
1245 if (!interactive || (console && !HIWORD(console)))
1246 WaitForSingleObject (pe.hProcess, INFINITE);
1247 GetExitCodeProcess (pe.hProcess, &errorlevel);
1248 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1250 CloseHandle(pe.hProcess);
1251 CloseHandle(pe.hThread);
1252 return;
1257 /* Not found anywhere - were we called? */
1258 if (called) {
1259 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
1261 /* Parse the command string, without reading any more input */
1262 WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
1263 WCMD_process_commands(toExecute, FALSE, called);
1264 WCMD_free_commands(toExecute);
1265 toExecute = NULL;
1266 return;
1269 /* Not found anywhere - give up */
1270 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1272 /* If a command fails to launch, it sets errorlevel 9009 - which
1273 does not seem to have any associated constant definition */
1274 errorlevel = 9009;
1275 return;
1279 /*****************************************************************************
1280 * Process one command. If the command is EXIT this routine does not return.
1281 * We will recurse through here executing batch files.
1282 * Note: If call is used to a non-existing program, we reparse the line and
1283 * try to run it as an internal command. 'retrycall' represents whether
1284 * we are attempting this retry.
1286 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1287 CMD_LIST **cmdList, BOOL retrycall)
1289 WCHAR *cmd, *parms_start, *redir;
1290 WCHAR *pos;
1291 int status, i, cmd_index;
1292 DWORD count, creationDisposition;
1293 HANDLE h;
1294 WCHAR *whichcmd;
1295 SECURITY_ATTRIBUTES sa;
1296 WCHAR *new_cmd = NULL;
1297 WCHAR *new_redir = NULL;
1298 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1299 GetStdHandle (STD_OUTPUT_HANDLE),
1300 GetStdHandle (STD_ERROR_HANDLE)};
1301 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1302 STD_OUTPUT_HANDLE,
1303 STD_ERROR_HANDLE};
1304 BOOL prev_echo_mode, piped = FALSE;
1306 WINE_TRACE("command on entry:%s (%p)\n",
1307 wine_dbgstr_w(command), cmdList);
1309 /* Move copy of the command onto the heap so it can be expanded */
1310 new_cmd = xalloc(MAXSTRING * sizeof(WCHAR));
1311 lstrcpyW(new_cmd, command);
1312 cmd = new_cmd;
1314 /* Move copy of the redirects onto the heap so it can be expanded */
1315 new_redir = xalloc(MAXSTRING * sizeof(WCHAR));
1316 redir = new_redir;
1318 /* Strip leading whitespaces, and a '@' if supplied */
1319 whichcmd = WCMD_skip_leading_spaces(cmd);
1320 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1321 if (whichcmd[0] == '@') whichcmd++;
1323 /* Check if the command entered is internal, and identify which one */
1324 count = 0;
1325 while (IsCharAlphaNumericW(whichcmd[count])) {
1326 count++;
1328 for (i=0; i<=WCMD_EXIT; i++) {
1329 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1330 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1332 cmd_index = i;
1333 parms_start = WCMD_skip_leading_spaces (&whichcmd[count]);
1335 /* If the next command is a pipe then we implement pipes by redirecting
1336 the output from this command to a temp file and input into the
1337 next command from that temp file.
1338 Note: Do not do this for a for or if statement as the pipe is for
1339 the individual statements, not the for or if itself.
1340 FIXME: Use of named pipes would make more sense here as currently this
1341 process has to finish before the next one can start but this requires
1342 a change to not wait for the first app to finish but rather the pipe */
1343 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) &&
1344 cmdList && (*cmdList)->nextcommand &&
1345 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1347 WCHAR temp_path[MAX_PATH];
1349 /* Remember piping is in action */
1350 WINE_TRACE("Output needs to be piped\n");
1351 piped = TRUE;
1353 /* Generate a unique temporary filename */
1354 GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
1355 GetTempFileNameW(temp_path, L"CMD", 0, (*cmdList)->nextcommand->pipeFile);
1356 WINE_TRACE("Using temporary file of %s\n",
1357 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1360 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1361 if (piped) {
1362 wsprintfW (new_redir, L"%s > %s", redirects, (*cmdList)->nextcommand->pipeFile);
1363 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1364 } else {
1365 lstrcpyW(new_redir, redirects);
1368 /* Expand variables in command line mode only (batch mode will
1369 be expanded as the line is read in, except for 'for' loops) */
1370 handleExpansion(new_cmd, (context != NULL), delayedsubst);
1371 handleExpansion(new_redir, (context != NULL), delayedsubst);
1374 * Changing default drive has to be handled as a special case, anything
1375 * else if it exists after whitespace is ignored
1378 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) &&
1379 (!cmd[2] || cmd[2] == ' ' || cmd[2] == '\t')) {
1380 WCHAR envvar[5];
1381 WCHAR dir[MAX_PATH];
1383 /* Ignore potential garbage on the same line */
1384 cmd[2]=0x00;
1386 /* According to MSDN CreateProcess docs, special env vars record
1387 the current directory on each drive, in the form =C:
1388 so see if one specified, and if so go back to it */
1389 lstrcpyW(envvar, L"=");
1390 lstrcatW(envvar, cmd);
1391 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1392 wsprintfW(cmd, L"%s\\", cmd);
1393 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1395 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1396 status = SetCurrentDirectoryW(cmd);
1397 if (!status) WCMD_print_error ();
1398 free(cmd);
1399 free(new_redir);
1400 return;
1403 sa.nLength = sizeof(sa);
1404 sa.lpSecurityDescriptor = NULL;
1405 sa.bInheritHandle = TRUE;
1408 * Redirect stdin, stdout and/or stderr if required.
1409 * Note: Do not do this for a for or if statement as the pipe is for
1410 * the individual statements, not the for or if itself.
1412 if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) {
1413 /* STDIN could come from a preceding pipe, so delete on close if it does */
1414 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1415 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1416 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1417 FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
1418 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1419 if (h == INVALID_HANDLE_VALUE) {
1420 WCMD_print_error ();
1421 free(cmd);
1422 free(new_redir);
1423 return;
1425 SetStdHandle (STD_INPUT_HANDLE, h);
1427 /* No need to remember the temporary name any longer once opened */
1428 (*cmdList)->pipeFile[0] = 0x00;
1430 /* Otherwise STDIN could come from a '<' redirect */
1431 } else if ((pos = wcschr(new_redir,'<')) != NULL) {
1432 h = CreateFileW(WCMD_parameter(++pos, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
1433 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1434 if (h == INVALID_HANDLE_VALUE) {
1435 WCMD_print_error ();
1436 free(cmd);
1437 free(new_redir);
1438 return;
1440 SetStdHandle (STD_INPUT_HANDLE, h);
1443 /* Scan the whole command looking for > and 2> */
1444 while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) {
1445 int handle = 0;
1447 if (pos > redir && (*(pos-1)=='2'))
1448 handle = 2;
1449 else
1450 handle = 1;
1452 pos++;
1453 if ('>' == *pos) {
1454 creationDisposition = OPEN_ALWAYS;
1455 pos++;
1457 else {
1458 creationDisposition = CREATE_ALWAYS;
1461 /* Add support for 2>&1 */
1462 redir = pos;
1463 if (*pos == '&') {
1464 int idx = *(pos+1) - '0';
1466 if (DuplicateHandle(GetCurrentProcess(),
1467 GetStdHandle(idx_stdhandles[idx]),
1468 GetCurrentProcess(),
1470 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1471 WINE_FIXME("Duplicating handle failed with gle %ld\n", GetLastError());
1473 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1475 } else {
1476 WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE);
1477 h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
1478 &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
1479 if (h == INVALID_HANDLE_VALUE) {
1480 WCMD_print_error ();
1481 free(cmd);
1482 free(new_redir);
1483 return;
1485 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1486 INVALID_SET_FILE_POINTER) {
1487 WCMD_print_error ();
1489 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1492 SetStdHandle (idx_stdhandles[handle], h);
1494 } else {
1495 WINE_TRACE("Not touching redirects for a FOR or IF command\n");
1497 WCMD_parse (parms_start, quals, param1, param2);
1498 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1500 if (i <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) {
1501 /* this is a help request for a builtin program */
1502 i = WCMD_HELP;
1503 memcpy(parms_start, whichcmd, count * sizeof(WCHAR));
1504 parms_start[count] = '\0';
1508 switch (i) {
1510 case WCMD_CALL:
1511 WCMD_call (parms_start);
1512 break;
1513 case WCMD_CD:
1514 case WCMD_CHDIR:
1515 WCMD_setshow_default (parms_start);
1516 break;
1517 case WCMD_CLS:
1518 WCMD_clear_screen ();
1519 break;
1520 case WCMD_COPY:
1521 WCMD_copy (parms_start);
1522 break;
1523 case WCMD_CTTY:
1524 WCMD_change_tty ();
1525 break;
1526 case WCMD_DATE:
1527 WCMD_setshow_date ();
1528 break;
1529 case WCMD_DEL:
1530 case WCMD_ERASE:
1531 WCMD_delete (parms_start);
1532 break;
1533 case WCMD_DIR:
1534 WCMD_directory (parms_start);
1535 break;
1536 case WCMD_ECHO:
1537 WCMD_echo(&whichcmd[count]);
1538 break;
1539 case WCMD_GOTO:
1540 WCMD_goto (cmdList);
1541 break;
1542 case WCMD_HELP:
1543 WCMD_give_help (parms_start);
1544 break;
1545 case WCMD_LABEL:
1546 WCMD_volume (TRUE, parms_start);
1547 break;
1548 case WCMD_MD:
1549 case WCMD_MKDIR:
1550 WCMD_create_dir (parms_start);
1551 break;
1552 case WCMD_MOVE:
1553 WCMD_move ();
1554 break;
1555 case WCMD_PATH:
1556 WCMD_setshow_path (parms_start);
1557 break;
1558 case WCMD_PAUSE:
1559 WCMD_pause ();
1560 break;
1561 case WCMD_PROMPT:
1562 WCMD_setshow_prompt ();
1563 break;
1564 case WCMD_REM:
1565 break;
1566 case WCMD_REN:
1567 case WCMD_RENAME:
1568 WCMD_rename ();
1569 break;
1570 case WCMD_RD:
1571 case WCMD_RMDIR:
1572 WCMD_remove_dir (parms_start);
1573 break;
1574 case WCMD_SETLOCAL:
1575 WCMD_setlocal(parms_start);
1576 break;
1577 case WCMD_ENDLOCAL:
1578 WCMD_endlocal();
1579 break;
1580 case WCMD_SET:
1581 WCMD_setshow_env (parms_start);
1582 break;
1583 case WCMD_SHIFT:
1584 WCMD_shift (parms_start);
1585 break;
1586 case WCMD_START:
1587 WCMD_start (parms_start);
1588 break;
1589 case WCMD_TIME:
1590 WCMD_setshow_time ();
1591 break;
1592 case WCMD_TITLE:
1593 if (lstrlenW(&whichcmd[count]) > 0)
1594 WCMD_title(&whichcmd[count+1]);
1595 break;
1596 case WCMD_TYPE:
1597 WCMD_type (parms_start);
1598 break;
1599 case WCMD_VER:
1600 WCMD_output_asis(L"\r\n");
1601 WCMD_version ();
1602 break;
1603 case WCMD_VERIFY:
1604 WCMD_verify (parms_start);
1605 break;
1606 case WCMD_VOL:
1607 WCMD_volume (FALSE, parms_start);
1608 break;
1609 case WCMD_PUSHD:
1610 WCMD_pushd(parms_start);
1611 break;
1612 case WCMD_POPD:
1613 WCMD_popd();
1614 break;
1615 case WCMD_ASSOC:
1616 WCMD_assoc(parms_start, TRUE);
1617 break;
1618 case WCMD_COLOR:
1619 WCMD_color();
1620 break;
1621 case WCMD_FTYPE:
1622 WCMD_assoc(parms_start, FALSE);
1623 break;
1624 case WCMD_MORE:
1625 WCMD_more(parms_start);
1626 break;
1627 case WCMD_CHOICE:
1628 WCMD_choice(parms_start);
1629 break;
1630 case WCMD_MKLINK:
1631 WCMD_mklink(parms_start);
1632 break;
1633 case WCMD_EXIT:
1634 WCMD_exit (cmdList);
1635 break;
1636 case WCMD_FOR:
1637 case WCMD_IF:
1638 /* Very oddly, probably because of all the special parsing required for
1639 these two commands, neither 'for' nor 'if' is supported when called,
1640 i.e. 'call if 1==1...' will fail. */
1641 if (!retrycall) {
1642 if (i==WCMD_FOR) WCMD_for (parms_start, cmdList);
1643 else if (i==WCMD_IF) WCMD_if (parms_start, cmdList);
1644 break;
1646 /* else: drop through */
1647 default:
1648 prev_echo_mode = echo_mode;
1649 WCMD_run_program (whichcmd, FALSE);
1650 echo_mode = prev_echo_mode;
1652 free(cmd);
1653 free(new_redir);
1655 /* Restore old handles */
1656 for (i=0; i<3; i++) {
1657 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1658 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1659 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1664 /*************************************************************************
1665 * WCMD_LoadMessage
1666 * Load a string from the resource file, handling any error
1667 * Returns string retrieved from resource file
1669 WCHAR *WCMD_LoadMessage(UINT id) {
1670 static WCHAR msg[2048];
1672 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
1673 WINE_FIXME("LoadString failed with %ld\n", GetLastError());
1674 lstrcpyW(msg, L"Failed!");
1676 return msg;
1679 /***************************************************************************
1680 * WCMD_DumpCommands
1682 * Dumps out the parsed command line to ensure syntax is correct
1684 static void WCMD_DumpCommands(CMD_LIST *commands) {
1685 CMD_LIST *thisCmd = commands;
1687 WINE_TRACE("Parsed line:\n");
1688 while (thisCmd != NULL) {
1689 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1690 thisCmd,
1691 thisCmd->prevDelim,
1692 thisCmd->bracketDepth,
1693 thisCmd->nextcommand,
1694 wine_dbgstr_w(thisCmd->command),
1695 wine_dbgstr_w(thisCmd->redirects));
1696 thisCmd = thisCmd->nextcommand;
1700 /***************************************************************************
1701 * WCMD_addCommand
1703 * Adds a command to the current command list
1705 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1706 WCHAR *redirs, int *redirLen,
1707 WCHAR **copyTo, int **copyToLen,
1708 CMD_DELIMITERS prevDelim, int curDepth,
1709 CMD_LIST **lastEntry, CMD_LIST **output) {
1711 CMD_LIST *thisEntry = NULL;
1713 /* Allocate storage for command */
1714 thisEntry = xalloc(sizeof(CMD_LIST));
1716 /* Copy in the command */
1717 if (command) {
1718 thisEntry->command = xalloc((*commandLen + 1) * sizeof(WCHAR));
1719 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1720 thisEntry->command[*commandLen] = 0x00;
1722 /* Copy in the redirects */
1723 thisEntry->redirects = xalloc((*redirLen + 1) * sizeof(WCHAR));
1724 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1725 thisEntry->redirects[*redirLen] = 0x00;
1726 thisEntry->pipeFile[0] = 0x00;
1728 /* Reset the lengths */
1729 *commandLen = 0;
1730 *redirLen = 0;
1731 *copyToLen = commandLen;
1732 *copyTo = command;
1734 } else {
1735 thisEntry->command = NULL;
1736 thisEntry->redirects = NULL;
1737 thisEntry->pipeFile[0] = 0x00;
1740 /* Fill in other fields */
1741 thisEntry->nextcommand = NULL;
1742 thisEntry->prevDelim = prevDelim;
1743 thisEntry->bracketDepth = curDepth;
1744 if (*lastEntry) {
1745 (*lastEntry)->nextcommand = thisEntry;
1746 } else {
1747 *output = thisEntry;
1749 *lastEntry = thisEntry;
1753 /***************************************************************************
1754 * WCMD_IsEndQuote
1756 * Checks if the quote pointed to is the end-quote.
1758 * Quotes end if:
1760 * 1) The current parameter ends at EOL or at the beginning
1761 * of a redirection or pipe and not in a quote section.
1763 * 2) If the next character is a space and not in a quote section.
1765 * Returns TRUE if this is an end quote, and FALSE if it is not.
1768 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1770 int quoteCount = quoteIndex;
1771 int i;
1773 /* If we are not in a quoted section, then we are not an end-quote */
1774 if(quoteIndex == 0)
1776 return FALSE;
1779 /* Check how many quotes are left for this parameter */
1780 for(i=0;quote[i];i++)
1782 if(quote[i] == '"')
1784 quoteCount++;
1787 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1788 else if(((quoteCount % 2) == 0)
1789 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ') ||
1790 (quote[i] == '&')))
1792 break;
1796 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1797 be an end-quote */
1798 if(quoteIndex >= (quoteCount / 2))
1800 return TRUE;
1803 /* No cigar */
1804 return FALSE;
1807 /***************************************************************************
1808 * WCMD_ReadAndParseLine
1810 * Either uses supplied input or
1811 * Reads a file from the handle, and then...
1812 * Parse the text buffer, splitting into separate commands
1813 * - unquoted && strings split 2 commands but the 2nd is flagged as
1814 * following an &&
1815 * - ( as the first character just ups the bracket depth
1816 * - unquoted ) when bracket depth > 0 terminates a bracket and
1817 * adds a CMD_LIST structure with null command
1818 * - Anything else gets put into the command string (including
1819 * redirects)
1821 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1823 WCHAR *curPos;
1824 int inQuotes = 0;
1825 WCHAR curString[MAXSTRING];
1826 int curStringLen = 0;
1827 WCHAR curRedirs[MAXSTRING];
1828 int curRedirsLen = 0;
1829 WCHAR *curCopyTo;
1830 int *curLen;
1831 int curDepth = 0;
1832 CMD_LIST *lastEntry = NULL;
1833 CMD_DELIMITERS prevDelim = CMD_NONE;
1834 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1835 BOOL inOneLine = FALSE;
1836 BOOL inFor = FALSE;
1837 BOOL inIn = FALSE;
1838 BOOL inIf = FALSE;
1839 BOOL inElse= FALSE;
1840 BOOL onlyWhiteSpace = FALSE;
1841 BOOL lastWasWhiteSpace = FALSE;
1842 BOOL lastWasDo = FALSE;
1843 BOOL lastWasIn = FALSE;
1844 BOOL lastWasElse = FALSE;
1845 BOOL lastWasRedirect = TRUE;
1846 BOOL lastWasCaret = FALSE;
1847 BOOL ignoreBracket = FALSE; /* Some expressions after if (set) require */
1848 /* handling brackets as a normal character */
1849 int lineCurDepth; /* Bracket depth when line was read in */
1850 BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
1852 /* Allocate working space for a command read from keyboard, file etc */
1853 if (!extraSpace)
1854 extraSpace = xalloc((MAXSTRING + 1) * sizeof(WCHAR));
1855 if (!extraSpace)
1857 WINE_ERR("Could not allocate memory for extraSpace\n");
1858 return NULL;
1861 /* If initial command read in, use that, otherwise get input from handle */
1862 if (optionalcmd != NULL) {
1863 lstrcpyW(extraSpace, optionalcmd);
1864 } else if (readFrom == INVALID_HANDLE_VALUE) {
1865 WINE_FIXME("No command nor handle supplied\n");
1866 } else {
1867 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1868 return NULL;
1870 curPos = extraSpace;
1872 /* Handle truncated input - issue warning */
1873 if (lstrlenW(extraSpace) == MAXSTRING -1) {
1874 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1875 WCMD_output_asis_stderr(extraSpace);
1876 WCMD_output_asis_stderr(L"\r\n");
1879 /* Replace env vars if in a batch context */
1880 if (context) handleExpansion(extraSpace, FALSE, FALSE);
1882 /* Skip preceding whitespace */
1883 while (*curPos == ' ' || *curPos == '\t') curPos++;
1885 /* Show prompt before batch line IF echo is on and in batch program */
1886 if (context && echo_mode && *curPos && (*curPos != '@')) {
1887 const DWORD len = lstrlenW(L"echo.");
1888 DWORD curr_size = lstrlenW(curPos);
1889 DWORD min_len = (curr_size < len ? curr_size : len);
1890 WCMD_show_prompt(TRUE);
1891 WCMD_output_asis(curPos);
1892 /* I don't know why Windows puts a space here but it does */
1893 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
1894 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1895 curPos, min_len, L"echo.", len) != CSTR_EQUAL
1896 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1897 curPos, min_len, L"echo:", len) != CSTR_EQUAL
1898 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1899 curPos, min_len, L"echo/", len) != CSTR_EQUAL)
1901 WCMD_output_asis(L" ");
1903 WCMD_output_asis(L"\r\n");
1906 /* Skip repeated 'no echo' characters */
1907 while (*curPos == '@') curPos++;
1909 /* Start with an empty string, copying to the command string */
1910 curStringLen = 0;
1911 curRedirsLen = 0;
1912 curCopyTo = curString;
1913 curLen = &curStringLen;
1914 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
1915 lineCurDepth = curDepth; /* What was the curdepth at the beginning of the line */
1917 /* Parse every character on the line being processed */
1918 while (*curPos != 0x00) {
1920 WCHAR thisChar;
1922 /* Debugging AID:
1923 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1924 lastWasWhiteSpace, onlyWhiteSpace);
1927 /* Prevent overflow caused by the caret escape char */
1928 if (*curLen >= MAXSTRING) {
1929 WINE_ERR("Overflow detected in command\n");
1930 return NULL;
1933 /* Certain commands need special handling */
1934 if (curStringLen == 0 && curCopyTo == curString) {
1935 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
1936 if (WCMD_keyword_ws_found(L"rem", curPos) || *curPos == ':') {
1937 inOneLine = TRUE;
1939 } else if (WCMD_keyword_ws_found(L"for", curPos)) {
1940 inFor = TRUE;
1942 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1943 is only true in the command portion of the IF statement, but this
1944 should suffice for now.
1945 To be able to handle ('s in the condition part take as much as evaluate_if_condition
1946 would take and skip parsing it here. */
1947 } else if (WCMD_keyword_ws_found(L"if", curPos)) {
1948 int negate; /* Negate condition */
1949 int test; /* Condition evaluation result */
1950 WCHAR *p, *command;
1952 inIf = TRUE;
1954 p = curPos+(lstrlenW(L"if"));
1955 while (*p == ' ' || *p == '\t')
1956 p++;
1957 WCMD_parse (p, quals, param1, param2);
1959 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
1960 set in a call to WCMD_parse before */
1961 if (evaluate_if_condition(p, &command, &test, &negate) != -1)
1963 int if_condition_len = command - curPos;
1964 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
1965 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
1966 wine_dbgstr_w(param2), wine_dbgstr_w(command), if_condition_len);
1967 memcpy(&curCopyTo[*curLen], curPos, if_condition_len*sizeof(WCHAR));
1968 (*curLen)+=if_condition_len;
1969 curPos+=if_condition_len;
1972 if (WCMD_keyword_ws_found(L"set", curPos))
1973 ignoreBracket = TRUE;
1975 } else if (WCMD_keyword_ws_found(L"else", curPos)) {
1976 const int keyw_len = lstrlenW(L"else") + 1;
1977 inElse = TRUE;
1978 lastWasElse = TRUE;
1979 onlyWhiteSpace = TRUE;
1980 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1981 (*curLen)+=keyw_len;
1982 curPos+=keyw_len;
1984 /* If we had a single line if XXX which reaches an else (needs odd
1985 syntax like if 1=1 command && (command) else command we pretended
1986 to add brackets for the if, so they are now over */
1987 if (resetAtEndOfLine) {
1988 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
1989 resetAtEndOfLine = FALSE;
1990 curDepth = lineCurDepth;
1992 continue;
1994 /* In a for loop, the DO command will follow a close bracket followed by
1995 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1996 is then 0, and all whitespace is skipped */
1997 } else if (inFor && WCMD_keyword_ws_found(L"do", curPos)) {
1998 const int keyw_len = lstrlenW(L"do") + 1;
1999 WINE_TRACE("Found 'DO '\n");
2000 lastWasDo = TRUE;
2001 onlyWhiteSpace = TRUE;
2002 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
2003 (*curLen)+=keyw_len;
2004 curPos+=keyw_len;
2005 continue;
2007 } else if (curCopyTo == curString) {
2009 /* Special handling for the 'FOR' command */
2010 if (inFor && lastWasWhiteSpace) {
2011 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2013 if (WCMD_keyword_ws_found(L"in", curPos)) {
2014 const int keyw_len = lstrlenW(L"in") + 1;
2015 WINE_TRACE("Found 'IN '\n");
2016 lastWasIn = TRUE;
2017 onlyWhiteSpace = TRUE;
2018 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
2019 (*curLen)+=keyw_len;
2020 curPos+=keyw_len;
2021 continue;
2026 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2027 the &&, quotes and redirection etc are ineffective, so just force
2028 the use of the default processing by skipping character specific
2029 matching below) */
2030 if (!inOneLine) thisChar = *curPos;
2031 else thisChar = 'X'; /* Character with no special processing */
2033 lastWasWhiteSpace = FALSE; /* Will be reset below */
2034 lastWasCaret = FALSE;
2036 switch (thisChar) {
2038 case '=': /* drop through - ignore token delimiters at the start of a command */
2039 case ',': /* drop through - ignore token delimiters at the start of a command */
2040 case '\t':/* drop through - ignore token delimiters at the start of a command */
2041 case ' ':
2042 /* If a redirect in place, it ends here */
2043 if (!inQuotes && !lastWasRedirect) {
2045 /* If finishing off a redirect, add a whitespace delimiter */
2046 if (curCopyTo == curRedirs) {
2047 curCopyTo[(*curLen)++] = ' ';
2049 curCopyTo = curString;
2050 curLen = &curStringLen;
2052 if (*curLen > 0) {
2053 curCopyTo[(*curLen)++] = *curPos;
2056 /* Remember just processed whitespace */
2057 lastWasWhiteSpace = TRUE;
2059 break;
2061 case '>': /* drop through - handle redirect chars the same */
2062 case '<':
2063 /* Make a redirect start here */
2064 if (!inQuotes) {
2065 curCopyTo = curRedirs;
2066 curLen = &curRedirsLen;
2067 lastWasRedirect = TRUE;
2070 /* See if 1>, 2> etc, in which case we have some patching up
2071 to do (provided there's a preceding whitespace, and enough
2072 chars read so far) */
2073 if (curPos[-1] >= '1' && curPos[-1] <= '9'
2074 && (curStringLen == 1 ||
2075 curPos[-2] == ' ' || curPos[-2] == '\t')) {
2076 curStringLen--;
2077 curString[curStringLen] = 0x00;
2078 curCopyTo[(*curLen)++] = *(curPos-1);
2081 curCopyTo[(*curLen)++] = *curPos;
2083 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2084 do not process that ampersand as an AND operator */
2085 if (thisChar == '>' && *(curPos+1) == '&') {
2086 curCopyTo[(*curLen)++] = *(curPos+1);
2087 curPos++;
2089 break;
2091 case '|': /* Pipe character only if not || */
2092 if (!inQuotes) {
2093 lastWasRedirect = FALSE;
2095 /* Add an entry to the command list */
2096 if (curStringLen > 0) {
2098 /* Add the current command */
2099 WCMD_addCommand(curString, &curStringLen,
2100 curRedirs, &curRedirsLen,
2101 &curCopyTo, &curLen,
2102 prevDelim, curDepth,
2103 &lastEntry, output);
2107 if (*(curPos+1) == '|') {
2108 curPos++; /* Skip other | */
2109 prevDelim = CMD_ONFAILURE;
2110 } else {
2111 prevDelim = CMD_PIPE;
2114 /* If in an IF or ELSE statement, put subsequent chained
2115 commands at a higher depth as if brackets were supplied
2116 but remember to reset to the original depth at EOL */
2117 if ((inIf || inElse) && curDepth == lineCurDepth) {
2118 curDepth++;
2119 resetAtEndOfLine = TRUE;
2121 } else {
2122 curCopyTo[(*curLen)++] = *curPos;
2124 break;
2126 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2127 inQuotes--;
2128 } else {
2129 inQuotes++; /* Quotes within quotes are fun! */
2131 curCopyTo[(*curLen)++] = *curPos;
2132 lastWasRedirect = FALSE;
2133 break;
2135 case '(': /* If a '(' is the first non whitespace in a command portion
2136 ie start of line or just after &&, then we read until an
2137 unquoted ) is found */
2138 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2139 ", for(%d, In:%d, Do:%d)"
2140 ", if(%d, else:%d, lwe:%d)\n",
2141 *curLen, inQuotes,
2142 onlyWhiteSpace,
2143 inFor, lastWasIn, lastWasDo,
2144 inIf, inElse, lastWasElse);
2145 lastWasRedirect = FALSE;
2147 /* Ignore open brackets inside the for set */
2148 if (*curLen == 0 && !inIn) {
2149 curDepth++;
2151 /* If in quotes, ignore brackets */
2152 } else if (inQuotes) {
2153 curCopyTo[(*curLen)++] = *curPos;
2155 /* In a FOR loop, an unquoted '(' may occur straight after
2156 IN or DO
2157 In an IF statement just handle it regardless as we don't
2158 parse the operands
2159 In an ELSE statement, only allow it straight away after
2160 the ELSE and whitespace
2162 } else if ((inIf && !ignoreBracket) ||
2163 (inElse && lastWasElse && onlyWhiteSpace) ||
2164 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2166 /* If entering into an 'IN', set inIn */
2167 if (inFor && lastWasIn && onlyWhiteSpace) {
2168 WINE_TRACE("Inside an IN\n");
2169 inIn = TRUE;
2172 /* Add the current command */
2173 WCMD_addCommand(curString, &curStringLen,
2174 curRedirs, &curRedirsLen,
2175 &curCopyTo, &curLen,
2176 prevDelim, curDepth,
2177 &lastEntry, output);
2179 curDepth++;
2180 } else {
2181 curCopyTo[(*curLen)++] = *curPos;
2183 break;
2185 case '^': if (!inQuotes) {
2186 /* If we reach the end of the input, we need to wait for more */
2187 if (*(curPos+1) == 0x00) {
2188 lastWasCaret = TRUE;
2189 WINE_TRACE("Caret found at end of line\n");
2190 break;
2192 curPos++;
2194 curCopyTo[(*curLen)++] = *curPos;
2195 break;
2197 case '&': if (!inQuotes) {
2198 lastWasRedirect = FALSE;
2200 /* Add an entry to the command list */
2201 if (curStringLen > 0) {
2203 /* Add the current command */
2204 WCMD_addCommand(curString, &curStringLen,
2205 curRedirs, &curRedirsLen,
2206 &curCopyTo, &curLen,
2207 prevDelim, curDepth,
2208 &lastEntry, output);
2212 if (*(curPos+1) == '&') {
2213 curPos++; /* Skip other & */
2214 prevDelim = CMD_ONSUCCESS;
2215 } else {
2216 prevDelim = CMD_NONE;
2218 /* If in an IF or ELSE statement, put subsequent chained
2219 commands at a higher depth as if brackets were supplied
2220 but remember to reset to the original depth at EOL */
2221 if ((inIf || inElse) && curDepth == lineCurDepth) {
2222 curDepth++;
2223 resetAtEndOfLine = TRUE;
2225 } else {
2226 curCopyTo[(*curLen)++] = *curPos;
2228 break;
2230 case ')': if (!inQuotes && curDepth > 0) {
2231 lastWasRedirect = FALSE;
2233 /* Add the current command if there is one */
2234 if (curStringLen) {
2236 /* Add the current command */
2237 WCMD_addCommand(curString, &curStringLen,
2238 curRedirs, &curRedirsLen,
2239 &curCopyTo, &curLen,
2240 prevDelim, curDepth,
2241 &lastEntry, output);
2244 /* Add an empty entry to the command list */
2245 prevDelim = CMD_NONE;
2246 WCMD_addCommand(NULL, &curStringLen,
2247 curRedirs, &curRedirsLen,
2248 &curCopyTo, &curLen,
2249 prevDelim, curDepth,
2250 &lastEntry, output);
2251 curDepth--;
2253 /* Leave inIn if necessary */
2254 if (inIn) inIn = FALSE;
2255 } else {
2256 curCopyTo[(*curLen)++] = *curPos;
2258 break;
2259 default:
2260 lastWasRedirect = FALSE;
2261 curCopyTo[(*curLen)++] = *curPos;
2264 curPos++;
2266 /* At various times we need to know if we have only skipped whitespace,
2267 so reset this variable and then it will remain true until a non
2268 whitespace is found */
2269 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2270 onlyWhiteSpace = FALSE;
2272 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2273 if (!lastWasWhiteSpace) {
2274 lastWasIn = lastWasDo = FALSE;
2277 /* If we have reached the end, add this command into the list
2278 Do not add command to list if escape char ^ was last */
2279 if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
2281 /* Add an entry to the command list */
2282 WCMD_addCommand(curString, &curStringLen,
2283 curRedirs, &curRedirsLen,
2284 &curCopyTo, &curLen,
2285 prevDelim, curDepth,
2286 &lastEntry, output);
2288 /* If we had a single line if or else, and we pretended to add
2289 brackets, end them now */
2290 if (resetAtEndOfLine) {
2291 WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
2292 resetAtEndOfLine = FALSE;
2293 curDepth = lineCurDepth;
2297 /* If we have reached the end of the string, see if bracketing or
2298 final caret is outstanding */
2299 if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
2300 readFrom != INVALID_HANDLE_VALUE) {
2301 WCHAR *extraData;
2303 WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
2304 inOneLine = FALSE;
2305 ignoreBracket = FALSE;
2306 prevDelim = CMD_NONE;
2307 inQuotes = 0;
2308 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2309 extraData = extraSpace;
2311 /* Read more, skipping any blank lines */
2312 do {
2313 WINE_TRACE("Read more input\n");
2314 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2315 if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
2316 break;
2318 /* Edge case for carets - a completely blank line (i.e. was just
2319 CRLF) is oddly added as an LF but then more data is received (but
2320 only once more!) */
2321 if (lastWasCaret) {
2322 if (*extraSpace == 0x00) {
2323 WINE_TRACE("Read nothing, so appending LF char and will try again\n");
2324 *extraData++ = '\r';
2325 *extraData = 0x00;
2326 } else break;
2329 } while (*extraData == 0x00);
2330 curPos = extraSpace;
2332 /* Skip preceding whitespace */
2333 while (*curPos == ' ' || *curPos == '\t') curPos++;
2335 /* Replace env vars if in a batch context */
2336 if (context) handleExpansion(curPos, FALSE, FALSE);
2338 /* Continue to echo commands IF echo is on and in batch program */
2339 if (context && echo_mode && *curPos && *curPos != '@') {
2340 WCMD_output_asis(extraSpace);
2341 WCMD_output_asis(L"\r\n");
2344 /* Skip repeated 'no echo' characters and whitespace */
2345 while (*curPos == '@' || *curPos == ' ' || *curPos == '\t') curPos++;
2349 /* Dump out the parsed output */
2350 WCMD_DumpCommands(*output);
2352 return extraSpace;
2355 /***************************************************************************
2356 * WCMD_process_commands
2358 * Process all the commands read in so far
2360 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2361 BOOL retrycall) {
2363 int bdepth = -1;
2365 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2367 /* Loop through the commands, processing them one by one */
2368 while (thisCmd) {
2370 CMD_LIST *origCmd = thisCmd;
2372 /* If processing one bracket only, and we find the end bracket
2373 entry (or less), return */
2374 if (oneBracket && !thisCmd->command &&
2375 bdepth <= thisCmd->bracketDepth) {
2376 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2377 thisCmd, thisCmd->nextcommand);
2378 return thisCmd->nextcommand;
2381 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2382 about them and it will be handled in there)
2383 Also, skip over any batch labels (eg. :fred) */
2384 if (thisCmd->command && thisCmd->command[0] != ':') {
2385 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2386 WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
2389 /* Step on unless the command itself already stepped on */
2390 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2392 return NULL;
2395 /***************************************************************************
2396 * WCMD_free_commands
2398 * Frees the storage held for a parsed command line
2399 * - This is not done in the process_commands, as eventually the current
2400 * pointer will be modified within the commands, and hence a single free
2401 * routine is simpler
2403 void WCMD_free_commands(CMD_LIST *cmds) {
2405 /* Loop through the commands, freeing them one by one */
2406 while (cmds) {
2407 CMD_LIST *thisCmd = cmds;
2408 cmds = cmds->nextcommand;
2409 free(thisCmd->command);
2410 free(thisCmd->redirects);
2411 free(thisCmd);
2415 static BOOL WINAPI my_event_handler(DWORD ctrl)
2417 WCMD_output(L"\n");
2418 return ctrl == CTRL_C_EVENT;
2422 /*****************************************************************************
2423 * Main entry point. This is a console application so we have a main() not a
2424 * winmain().
2427 int __cdecl wmain (int argc, WCHAR *argvW[])
2429 WCHAR *cmdLine = NULL;
2430 WCHAR *cmd = NULL;
2431 WCHAR string[1024];
2432 WCHAR envvar[4];
2433 BOOL promptNewLine = TRUE;
2434 BOOL opt_q;
2435 int opt_t = 0;
2436 WCHAR comspec[MAX_PATH];
2437 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2438 RTL_OSVERSIONINFOEXW osv;
2439 char osver[50];
2440 STARTUPINFOW startupInfo;
2441 const WCHAR *arg;
2443 if (!GetEnvironmentVariableW(L"COMSPEC", comspec, ARRAY_SIZE(comspec)))
2445 GetSystemDirectoryW(comspec, ARRAY_SIZE(comspec) - ARRAY_SIZE(L"\\cmd.exe"));
2446 lstrcatW(comspec, L"\\cmd.exe");
2447 SetEnvironmentVariableW(L"COMSPEC", comspec);
2450 srand(time(NULL));
2452 /* Get the windows version being emulated */
2453 osv.dwOSVersionInfoSize = sizeof(osv);
2454 RtlGetVersion(&osv);
2456 /* Pre initialize some messages */
2457 lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2458 sprintf(osver, "%ld.%ld.%ld", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
2459 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
2460 lstrcpyW(version_string, cmd);
2461 LocalFree(cmd);
2462 cmd = NULL;
2464 /* Can't use argc/argv as it will have stripped quotes from parameters
2465 * meaning cmd.exe /C echo "quoted string" is impossible
2467 cmdLine = GetCommandLineW();
2468 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
2470 while (*cmdLine && *cmdLine != '/') ++cmdLine;
2472 opt_c = opt_k = opt_q = opt_s = FALSE;
2474 for (arg = cmdLine; *arg; ++arg)
2476 if (arg[0] != '/')
2477 continue;
2479 switch (towlower(arg[1]))
2481 case 'a':
2482 unicodeOutput = FALSE;
2483 break;
2484 case 'c':
2485 opt_c = TRUE;
2486 break;
2487 case 'k':
2488 opt_k = TRUE;
2489 break;
2490 case 'q':
2491 opt_q = TRUE;
2492 break;
2493 case 's':
2494 opt_s = TRUE;
2495 break;
2496 case 't':
2497 if (arg[2] == ':')
2498 opt_t = wcstoul(&arg[3], NULL, 16);
2499 break;
2500 case 'u':
2501 unicodeOutput = TRUE;
2502 break;
2503 case 'v':
2504 if (arg[2] == ':')
2505 delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
2506 break;
2509 if (opt_c || opt_k)
2511 arg += 2;
2512 break;
2516 while (*arg && wcschr(L" \t,=;", *arg)) arg++;
2518 if (opt_q) {
2519 WCMD_echo(L"OFF");
2522 /* Until we start to read from the keyboard, stay as non-interactive */
2523 interactive = FALSE;
2525 SetEnvironmentVariableW(L"PROMPT", L"$P$G");
2527 if (opt_c || opt_k) {
2528 int len;
2529 WCHAR *q1 = NULL,*q2 = NULL,*p;
2531 /* Take a copy */
2532 cmd = xstrdupW(arg);
2534 /* opt_s left unflagged if the command starts with and contains exactly
2535 * one quoted string (exactly two quote characters). The quoted string
2536 * must be an executable name that has whitespace and must not have the
2537 * following characters: &<>()@^| */
2539 if (!opt_s) {
2540 /* 1. Confirm there is at least one quote */
2541 q1 = wcschr(arg, '"');
2542 if (!q1) opt_s=1;
2545 if (!opt_s) {
2546 /* 2. Confirm there is a second quote */
2547 q2 = wcschr(q1+1, '"');
2548 if (!q2) opt_s=1;
2551 if (!opt_s) {
2552 /* 3. Ensure there are no more quotes */
2553 if (wcschr(q2+1, '"')) opt_s=1;
2556 /* check first parameter for a space and invalid characters. There must not be any
2557 * invalid characters, but there must be one or more whitespace */
2558 if (!opt_s) {
2559 opt_s = TRUE;
2560 p=q1;
2561 while (p!=q2) {
2562 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2563 || *p=='@' || *p=='^' || *p=='|') {
2564 opt_s = TRUE;
2565 break;
2567 if (*p==' ' || *p=='\t')
2568 opt_s = FALSE;
2569 p++;
2573 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2575 /* Finally, we only stay in new mode IF the first parameter is quoted and
2576 is a valid executable, i.e. must exist, otherwise drop back to old mode */
2577 if (!opt_s) {
2578 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
2579 WCHAR pathext[MAXSTRING];
2580 BOOL found = FALSE;
2582 /* Now extract PATHEXT */
2583 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
2584 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
2585 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
2588 /* If the supplied parameter has any directory information, look there */
2589 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
2590 if (wcschr(thisArg, '\\') != NULL) {
2592 if (!WCMD_get_fullpath(thisArg, ARRAY_SIZE(string), string, NULL)) return FALSE;
2593 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
2594 p = string + lstrlenW(string);
2596 /* Does file exist with this name? */
2597 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2598 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2599 found = TRUE;
2600 } else {
2601 WCHAR *thisExt = pathext;
2603 /* No - try with each of the PATHEXT extensions */
2604 while (!found && thisExt) {
2605 WCHAR *nextExt = wcschr(thisExt, ';');
2607 if (nextExt) {
2608 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
2609 p[(nextExt-thisExt)] = 0x00;
2610 thisExt = nextExt+1;
2611 } else {
2612 lstrcpyW(p, thisExt);
2613 thisExt = NULL;
2616 /* Does file exist with this extension appended? */
2617 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
2618 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
2619 found = TRUE;
2624 /* Otherwise we now need to look in the path to see if we can find it */
2625 } else {
2626 /* Does file exist with this name? */
2627 if (SearchPathW(NULL, thisArg, NULL, ARRAY_SIZE(string), string, NULL) != 0) {
2628 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
2629 found = TRUE;
2630 } else {
2631 WCHAR *thisExt = pathext;
2633 /* No - try with each of the PATHEXT extensions */
2634 while (!found && thisExt) {
2635 WCHAR *nextExt = wcschr(thisExt, ';');
2637 if (nextExt) {
2638 *nextExt = 0;
2639 nextExt = nextExt+1;
2640 } else {
2641 nextExt = NULL;
2644 /* Does file exist with this extension? */
2645 if (SearchPathW(NULL, thisArg, thisExt, ARRAY_SIZE(string), string, NULL) != 0) {
2646 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
2647 wine_dbgstr_w(thisExt));
2648 found = TRUE;
2650 thisExt = nextExt;
2655 /* If not found, drop back to old behaviour */
2656 if (!found) {
2657 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
2658 opt_s = TRUE;
2663 /* strip first and last quote characters if opt_s; check for invalid
2664 * executable is done later */
2665 if (opt_s && *cmd=='\"')
2666 WCMD_strip_quotes(cmd);
2668 else
2670 SetConsoleCtrlHandler(my_event_handler, TRUE);
2673 /* Save cwd into appropriate env var (Must be before the /c processing */
2674 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
2675 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2676 wsprintfW(envvar, L"=%c:", string[0]);
2677 SetEnvironmentVariableW(envvar, string);
2678 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2681 if (opt_c) {
2682 /* If we do a "cmd /c command", we don't want to allocate a new
2683 * console since the command returns immediately. Rather, we use
2684 * the currently allocated input and output handles. This allows
2685 * us to pipe to and read from the command interpreter.
2688 /* Parse the command string, without reading any more input */
2689 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2690 WCMD_process_commands(toExecute, FALSE, FALSE);
2691 WCMD_free_commands(toExecute);
2692 toExecute = NULL;
2694 free(cmd);
2695 return errorlevel;
2698 GetStartupInfoW(&startupInfo);
2699 if (startupInfo.lpTitle != NULL)
2700 SetConsoleTitleW(startupInfo.lpTitle);
2701 else
2702 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2704 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2705 if (opt_t) {
2706 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2707 defaultColor = opt_t & 0xFF;
2708 param1[0] = 0x00;
2709 WCMD_color();
2711 } else {
2712 /* Check HKCU\Software\Microsoft\Command Processor
2713 Then HKLM\Software\Microsoft\Command Processor
2714 for defaultcolour value
2715 Note Can be supplied as DWORD or REG_SZ
2716 Note2 When supplied as REG_SZ it's in decimal!!! */
2717 HKEY key;
2718 DWORD type;
2719 DWORD value=0, size=4;
2720 static const WCHAR regKeyW[] = L"Software\\Microsoft\\Command Processor";
2722 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2723 0, KEY_READ, &key) == ERROR_SUCCESS) {
2724 WCHAR strvalue[4];
2726 /* See if DWORD or REG_SZ */
2727 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
2728 if (type == REG_DWORD) {
2729 size = sizeof(DWORD);
2730 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
2731 } else if (type == REG_SZ) {
2732 size = sizeof(strvalue);
2733 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
2734 value = wcstoul(strvalue, NULL, 10);
2737 RegCloseKey(key);
2740 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2741 0, KEY_READ, &key) == ERROR_SUCCESS) {
2742 WCHAR strvalue[4];
2744 /* See if DWORD or REG_SZ */
2745 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type,
2746 NULL, NULL) == ERROR_SUCCESS) {
2747 if (type == REG_DWORD) {
2748 size = sizeof(DWORD);
2749 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
2750 } else if (type == REG_SZ) {
2751 size = sizeof(strvalue);
2752 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
2753 value = wcstoul(strvalue, NULL, 10);
2756 RegCloseKey(key);
2759 /* If one found, set the screen to that colour */
2760 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2761 defaultColor = value & 0xFF;
2762 param1[0] = 0x00;
2763 WCMD_color();
2768 if (opt_k) {
2769 /* Parse the command string, without reading any more input */
2770 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2771 WCMD_process_commands(toExecute, FALSE, FALSE);
2772 WCMD_free_commands(toExecute);
2773 toExecute = NULL;
2774 free(cmd);
2778 * Loop forever getting commands and executing them.
2781 interactive = TRUE;
2782 if (!opt_k) WCMD_version ();
2783 while (TRUE) {
2785 /* Read until EOF (which for std input is never, but if redirect
2786 in place, may occur */
2787 if (echo_mode) WCMD_show_prompt(promptNewLine);
2788 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2789 break;
2790 WCMD_process_commands(toExecute, FALSE, FALSE);
2791 WCMD_free_commands(toExecute);
2792 promptNewLine = !!toExecute;
2793 toExecute = NULL;
2795 return 0;