include/mscvpdb.h: Use flexible array members for the rest of structures.
[wine.git] / programs / cmd / wcmdmain.c
blobd3ad93837d40f16fecca06ec077fee7c33f3bc05
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 int 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(void)
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 *q = '\0';
339 while (*p != '\0') {
340 if (*p != '$') {
341 *q++ = *p++;
342 *q = '\0';
344 else {
345 p++;
346 switch (towupper(*p)) {
347 case '$':
348 *q++ = '$';
349 break;
350 case 'A':
351 *q++ = '&';
352 break;
353 case 'B':
354 *q++ = '|';
355 break;
356 case 'C':
357 *q++ = '(';
358 break;
359 case 'D':
360 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH - (q - out_string));
361 while (*q) q++;
362 break;
363 case 'E':
364 *q++ = '\x1b';
365 break;
366 case 'F':
367 *q++ = ')';
368 break;
369 case 'G':
370 *q++ = '>';
371 break;
372 case 'H':
373 *q++ = '\b';
374 break;
375 case 'L':
376 *q++ = '<';
377 break;
378 case 'N':
379 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
380 if (status) {
381 *q++ = curdir[0];
383 break;
384 case 'P':
385 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
386 if (status) {
387 lstrcatW (q, curdir);
388 while (*q) q++;
390 break;
391 case 'Q':
392 *q++ = '=';
393 break;
394 case 'S':
395 *q++ = ' ';
396 break;
397 case 'T':
398 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
399 while (*q) q++;
400 break;
401 case 'V':
402 lstrcatW (q, version_string);
403 while (*q) q++;
404 break;
405 case '_':
406 *q++ = '\n';
407 break;
408 case '+':
409 if (pushd_directories) {
410 memset(q, '+', pushd_directories->u.stackdepth);
411 q = q + pushd_directories->u.stackdepth;
413 break;
415 p++;
416 *q = '\0';
419 WCMD_output_asis (out_string);
422 void *xrealloc(void *ptr, size_t size)
424 void *ret;
426 if (!(ret = realloc(ptr, size)))
428 ERR("Out of memory\n");
429 ExitProcess(1);
432 return ret;
435 /*************************************************************************
436 * WCMD_strsubstW
437 * Replaces a portion of a Unicode string with the specified string.
438 * It's up to the caller to ensure there is enough space in the
439 * destination buffer.
441 WCHAR *WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
443 if (len < 0)
444 len=insert ? lstrlenW(insert) : 0;
445 if (start+len != next)
446 memmove(start+len, next, (lstrlenW(next) + 1) * sizeof(*next));
447 if (insert)
448 memcpy(start, insert, len * sizeof(*insert));
449 return start + len;
452 BOOL WCMD_get_fullpath(const WCHAR* in, SIZE_T outsize, WCHAR* out, WCHAR** start)
454 DWORD ret = GetFullPathNameW(in, outsize, out, start);
455 if (!ret) return FALSE;
456 if (ret > outsize)
458 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_FILENAMETOOLONG));
459 return FALSE;
461 return TRUE;
464 /***************************************************************************
465 * WCMD_skip_leading_spaces
467 * Return a pointer to the first non-whitespace character of string.
468 * Does not modify the input string.
470 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
472 WCHAR *ptr;
474 ptr = string;
475 while (*ptr == ' ' || *ptr == '\t') ptr++;
476 return ptr;
479 /***************************************************************************
480 * WCMD_keyword_ws_found
482 * Checks if the string located at ptr matches a keyword (of length len)
483 * followed by a whitespace character (space or tab)
485 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, const WCHAR *ptr) {
486 const int len = lstrlenW(keyword);
487 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
488 ptr, len, keyword, len) == CSTR_EQUAL)
489 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
492 /*************************************************************************
493 * WCMD_strip_quotes
495 * Remove first and last quote WCHARacters, preserving all other text
496 * Returns the location of the final quote
498 WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
499 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
500 while((*dest=*src) != '\0') {
501 if (*src=='\"')
502 lastq=dest;
503 dest++; src++;
505 lastquote = lastq;
506 if (lastq) {
507 dest=lastq++;
508 while ((*dest++=*lastq++) != 0)
511 return lastquote;
515 /*************************************************************************
516 * WCMD_is_magic_envvar
517 * Return TRUE if s is '%'magicvar'%'
518 * and is not masked by a real environment variable.
521 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
523 int len;
525 if (s[0] != '%')
526 return FALSE; /* Didn't begin with % */
527 len = lstrlenW(s);
528 if (len < 2 || s[len-1] != '%')
529 return FALSE; /* Didn't end with another % */
531 if (CompareStringW(LOCALE_USER_DEFAULT,
532 NORM_IGNORECASE | SORT_STRINGSORT,
533 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
534 /* Name doesn't match. */
535 return FALSE;
538 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
539 /* Masked by real environment variable. */
540 return FALSE;
543 return TRUE;
546 /*************************************************************************
547 * WCMD_expand_envvar
549 * Expands environment variables, allowing for WCHARacter substitution
551 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
553 WCHAR *endOfVar = NULL, *s;
554 WCHAR *colonpos = NULL;
555 WCHAR thisVar[MAXSTRING];
556 WCHAR thisVarContents[MAXSTRING];
557 WCHAR savedchar = 0x00;
558 int len;
559 WCHAR Delims[] = L"%:"; /* First char gets replaced appropriately */
561 WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
563 /* Find the end of the environment variable, and extract name */
564 Delims[0] = startchar;
565 endOfVar = wcspbrk(start+1, Delims);
567 if (endOfVar == NULL || *endOfVar==' ') {
569 /* In batch program, missing terminator for % and no following
570 ':' just removes the '%' */
571 if (context) {
572 return WCMD_strsubstW(start, start + 1, NULL, 0);
573 } else {
575 /* In command processing, just ignore it - allows command line
576 syntax like: for %i in (a.a) do echo %i */
577 return start+1;
581 /* If ':' found, process remaining up until '%' (or stop at ':' if
582 a missing '%' */
583 if (*endOfVar==':') {
584 WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar);
585 if (endOfVar2 != NULL) endOfVar = endOfVar2;
588 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
589 thisVar[(endOfVar - start)+1] = 0x00;
590 colonpos = wcschr(thisVar+1, ':');
592 /* If there's complex substitution, just need %var% for now
593 to get the expanded data to play with */
594 if (colonpos) {
595 *colonpos = '%';
596 savedchar = *(colonpos+1);
597 *(colonpos+1) = 0x00;
600 /* By now, we know the variable we want to expand but it may be
601 surrounded by '!' if we are in delayed expansion - if so convert
602 to % signs. */
603 if (startchar=='!') {
604 thisVar[0] = '%';
605 thisVar[(endOfVar - start)] = '%';
607 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
609 /* Expand to contents, if unchanged, return */
610 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
611 /* override if existing env var called that name */
612 if (WCMD_is_magic_envvar(thisVar, L"ERRORLEVEL")) {
613 len = wsprintfW(thisVarContents, L"%d", errorlevel);
614 } else if (WCMD_is_magic_envvar(thisVar, L"DATE")) {
615 len = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
616 NULL, thisVarContents, ARRAY_SIZE(thisVarContents));
617 } else if (WCMD_is_magic_envvar(thisVar, L"TIME")) {
618 len = GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
619 NULL, thisVarContents, ARRAY_SIZE(thisVarContents));
620 } else if (WCMD_is_magic_envvar(thisVar, L"CD")) {
621 len = GetCurrentDirectoryW(ARRAY_SIZE(thisVarContents), thisVarContents);
622 } else if (WCMD_is_magic_envvar(thisVar, L"RANDOM")) {
623 len = wsprintfW(thisVarContents, L"%d", rand() % 32768);
624 } else {
625 if ((len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents)))) len--;
628 if (len == 0)
629 return endOfVar+1;
631 /* In a batch program, unknown env vars are replaced with nothing,
632 note syntax %garbage:1,3% results in anything after the ':'
633 except the %
634 From the command line, you just get back what you entered */
635 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
637 /* Restore the complex part after the compare */
638 if (colonpos) {
639 *colonpos = ':';
640 *(colonpos+1) = savedchar;
643 /* Command line - just ignore this */
644 if (context == NULL) return endOfVar+1;
646 /* Batch - replace unknown env var with nothing */
647 if (colonpos == NULL)
648 return WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
649 len = lstrlenW(thisVar);
650 thisVar[len-1] = 0x00;
651 /* If %:...% supplied, : is retained */
652 if (colonpos == thisVar+1)
653 return WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
654 return WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
657 /* See if we need to do complex substitution (any ':'s), if not
658 then our work here is done */
659 if (colonpos == NULL)
660 return WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
662 /* Restore complex bit */
663 *colonpos = ':';
664 *(colonpos+1) = savedchar;
667 Handle complex substitutions:
668 xxx=yyy (replace xxx with yyy)
669 *xxx=yyy (replace up to and including xxx with yyy)
670 ~x (from x WCHARs in)
671 ~-x (from x WCHARs from the end)
672 ~x,y (from x WCHARs in for y WCHARacters)
673 ~x,-y (from x WCHARs in until y WCHARacters from the end)
676 /* ~ is substring manipulation */
677 if (savedchar == '~') {
679 int substrposition, substrlength = 0;
680 WCHAR *commapos = wcschr(colonpos+2, ',');
681 WCHAR *startCopy;
683 substrposition = wcstol(colonpos+2, NULL, 10);
684 if (commapos) substrlength = wcstol(commapos+1, NULL, 10);
686 /* Check bounds */
687 if (substrposition >= 0) {
688 startCopy = &thisVarContents[min(substrposition, len - 1)];
689 } else {
690 startCopy = &thisVarContents[max(0, len + substrposition)];
693 if (commapos == NULL)
694 /* Copy the lot */
695 return WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
696 if (substrlength < 0) {
698 int copybytes = len + substrlength - (startCopy - thisVarContents);
699 if (copybytes >= len) copybytes = len - 1;
700 else if (copybytes < 0) copybytes = 0;
701 return WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
703 substrlength = min(substrlength, len - (startCopy - thisVarContents));
704 return WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
705 /* search and replace manipulation */
706 } else {
707 WCHAR *equalspos = wcsstr(colonpos, L"=");
708 WCHAR *replacewith = equalspos+1;
709 WCHAR *found = NULL;
710 WCHAR *searchIn;
711 WCHAR *searchFor;
712 WCHAR *ret;
714 if (equalspos == NULL) return start+1;
715 s = xstrdupW(endOfVar + 1);
717 /* Null terminate both strings */
718 thisVar[lstrlenW(thisVar)-1] = 0x00;
719 *equalspos = 0x00;
721 /* Since we need to be case insensitive, copy the 2 buffers */
722 searchIn = xstrdupW(thisVarContents);
723 CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
724 searchFor = xstrdupW(colonpos + 1);
725 CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
727 /* Handle wildcard case */
728 if (*(colonpos+1) == '*') {
729 /* Search for string to replace */
730 found = wcsstr(searchIn, searchFor+1);
732 if (found) {
733 /* Do replacement */
734 lstrcpyW(start, replacewith);
735 lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
736 ret = start + wcslen(start);
737 lstrcatW(start, s);
738 } else {
739 /* Copy as is */
740 lstrcpyW(start, thisVarContents);
741 ret = start + wcslen(start);
742 lstrcatW(start, s);
744 } else {
745 /* Loop replacing all instances */
746 WCHAR *lastFound = searchIn;
747 WCHAR *outputposn = start;
749 *start = 0x00;
750 while ((found = wcsstr(lastFound, searchFor))) {
751 lstrcpynW(outputposn,
752 thisVarContents + (lastFound-searchIn),
753 (found - lastFound)+1);
754 outputposn = outputposn + (found - lastFound);
755 lstrcatW(outputposn, replacewith);
756 outputposn = outputposn + lstrlenW(replacewith);
757 lastFound = found + lstrlenW(searchFor);
759 lstrcatW(outputposn,
760 thisVarContents + (lastFound-searchIn));
761 ret = outputposn + wcslen(outputposn);
762 lstrcatW(outputposn, s);
764 free(s);
765 free(searchIn);
766 free(searchFor);
767 return ret;
771 /*****************************************************************************
772 * Expand the command. Native expands lines from batch programs as they are
773 * read in and not again, except for 'for' variable substitution.
774 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
775 * atExecute is TRUE when the expansion is occurring as the command is executed
776 * rather than at parse time, i.e. delayed expansion and for loops need to be
777 * processed
779 static void handleExpansion(WCHAR *cmd, BOOL atExecute) {
781 /* For commands in a context (batch program): */
782 /* Expand environment variables in a batch file %{0-9} first */
783 /* including support for any ~ modifiers */
784 /* Additionally: */
785 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
786 /* names allowing environment variable overrides */
787 /* NOTE: To support the %PATH:xxx% syntax, also perform */
788 /* manual expansion of environment variables here */
790 WCHAR *p = cmd;
791 WCHAR *t;
792 int i;
793 BOOL delayed = atExecute ? delayedsubst : FALSE;
794 WCHAR *delayedp = NULL;
795 WCHAR startchar = '%';
796 WCHAR *normalp;
798 /* Display the FOR variables in effect */
799 for (i=0;i<MAX_FOR_VARIABLES;i++) {
800 if (forloopcontext->variable[i]) {
801 WINE_TRACE("FOR variable context: %c = '%s'\n",
802 for_var_index_to_char(i),
803 wine_dbgstr_w(forloopcontext->variable[i]));
807 for (;;)
809 /* Find the next environment variable delimiter */
810 normalp = wcschr(p, '%');
811 if (delayed) delayedp = wcschr(p, '!');
812 if (!normalp) p = delayedp;
813 else if (!delayedp) p = normalp;
814 else p = min(p,delayedp);
815 if (!p) break;
816 startchar = *p;
818 WINE_TRACE("Translate command:%s %d (at: %s)\n",
819 wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
820 i = *(p+1) - '0';
822 /* Don't touch %% unless it's in Batch */
823 if (!atExecute && *(p+1) == startchar) {
824 if (context) {
825 WCMD_strsubstW(p, p+1, NULL, 0);
826 p++;
828 else p+=2;
830 /* Replace %~ modifications if in batch program */
831 } else if (*(p+1) == '~') {
832 WCMD_HandleTildeModifiers(&p, atExecute);
833 p++;
835 /* Replace use of %0...%9 if in batch program*/
836 } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
837 t = WCMD_parameter(context -> command, i + context -> shift_count[i],
838 NULL, TRUE, TRUE);
839 p = WCMD_strsubstW(p, p+2, t, -1);
841 /* Replace use of %* if in batch program*/
842 } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
843 WCHAR *startOfParms = NULL;
844 WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
845 if (startOfParms != NULL) {
846 startOfParms += lstrlenW(thisParm);
847 while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
848 p = WCMD_strsubstW(p, p+2, startOfParms, -1);
849 } else
850 p = WCMD_strsubstW(p, p+2, NULL, 0);
852 } else {
853 int forvaridx = for_var_char_to_index(*(p+1));
854 if (startchar == '%' && forvaridx != -1 && forloopcontext->variable[forvaridx]) {
855 /* Replace the 2 characters, % and for variable character */
856 p = WCMD_strsubstW(p, p + 2, forloopcontext->variable[forvaridx], -1);
857 } else if (!atExecute || startchar == '!') {
858 p = WCMD_expand_envvar(p, startchar);
860 /* In a FOR loop, see if this is the variable to replace */
861 } else { /* Ignore %'s on second pass of batch program */
862 p++;
869 /*******************************************************************
870 * WCMD_parse - parse a command into parameters and qualifiers.
872 * On exit, all qualifiers are concatenated into q, the first string
873 * not beginning with "/" is in p1 and the
874 * second in p2. Any subsequent non-qualifier strings are lost.
875 * Parameters in quotes are handled.
877 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
879 int p = 0;
881 *q = *p1 = *p2 = '\0';
882 while (TRUE) {
883 switch (*s) {
884 case '/':
885 *q++ = *s++;
886 while ((*s != '\0') && (*s != ' ') && *s != '/') {
887 *q++ = towupper (*s++);
889 *q = '\0';
890 break;
891 case ' ':
892 case '\t':
893 s++;
894 break;
895 case '"':
896 s++;
897 while ((*s != '\0') && (*s != '"')) {
898 if (p == 0) *p1++ = *s++;
899 else if (p == 1) *p2++ = *s++;
900 else s++;
902 if (p == 0) *p1 = '\0';
903 if (p == 1) *p2 = '\0';
904 p++;
905 if (*s == '"') s++;
906 break;
907 case '\0':
908 return;
909 default:
910 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
911 && (*s != '=') && (*s != ',') ) {
912 if (p == 0) *p1++ = *s++;
913 else if (p == 1) *p2++ = *s++;
914 else s++;
916 /* Skip concurrent parms */
917 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
919 if (p == 0) *p1 = '\0';
920 if (p == 1) *p2 = '\0';
921 p++;
926 void WCMD_expand(const WCHAR *src, WCHAR *dst)
928 wcscpy(dst, src);
929 handleExpansion(dst, FALSE);
930 WCMD_parse(dst, quals, param1, param2);
933 /* ============================== */
934 /* Data structures for commands */
935 /* ============================== */
937 static void redirection_dispose_list(CMD_REDIRECTION *redir)
939 while (redir)
941 CMD_REDIRECTION *next = redir->next;
942 free(redir);
943 redir = next;
947 static CMD_REDIRECTION *redirection_create_file(enum CMD_REDIRECTION_KIND kind, unsigned fd, const WCHAR *file)
949 size_t len = wcslen(file) + 1;
950 CMD_REDIRECTION *redir = xalloc(offsetof(CMD_REDIRECTION, file[len]));
952 redir->kind = kind;
953 redir->fd = fd;
954 memcpy(redir->file, file, len * sizeof(WCHAR));
955 redir->next = NULL;
957 return redir;
960 static CMD_REDIRECTION *redirection_create_clone(unsigned fd, unsigned fd_clone)
962 CMD_REDIRECTION *redir = xalloc(sizeof(*redir));
964 redir->kind = REDIR_WRITE_CLONE;
965 redir->fd = fd;
966 redir->clone = fd_clone;
967 redir->next = NULL;
969 return redir;
972 static const char *debugstr_redirection(const CMD_REDIRECTION *redir)
974 switch (redir->kind)
976 case REDIR_READ_FROM:
977 return wine_dbg_sprintf("%u< (%ls)", redir->fd, redir->file);
978 case REDIR_WRITE_TO:
979 return wine_dbg_sprintf("%u> (%ls)", redir->fd, redir->file);
980 case REDIR_WRITE_APPEND:
981 return wine_dbg_sprintf("%u>> (%ls)", redir->fd, redir->file);
982 case REDIR_WRITE_CLONE:
983 return wine_dbg_sprintf("%u>&%u", redir->fd, redir->clone);
984 default:
985 return "-^-";
989 static WCHAR *command_create(const WCHAR *ptr, size_t len)
991 WCHAR *command = xalloc((len + 1) * sizeof(WCHAR));
992 memcpy(command, ptr, len * sizeof(WCHAR));
993 command[len] = L'\0';
994 return command;
997 static void for_control_dispose(CMD_FOR_CONTROL *for_ctrl)
999 free((void*)for_ctrl->set);
1000 switch (for_ctrl->operator)
1002 case CMD_FOR_FILE_SET:
1003 free((void*)for_ctrl->delims);
1004 free((void*)for_ctrl->tokens);
1005 break;
1006 case CMD_FOR_FILETREE:
1007 free((void*)for_ctrl->root_dir);
1008 break;
1009 default:
1010 break;
1014 const char *debugstr_for_control(const CMD_FOR_CONTROL *for_ctrl)
1016 static const char* for_ctrl_strings[] = {"tree", "file", "numbers"};
1017 const char *flags, *options;
1019 if (for_ctrl->operator >= ARRAY_SIZE(for_ctrl_strings))
1021 FIXME("Unexpected operator\n");
1022 return wine_dbg_sprintf("<<%u>>", for_ctrl->operator);
1025 if (for_ctrl->flags)
1026 flags = wine_dbg_sprintf("flags=%s%s%s ",
1027 (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE) ? "~recurse" : "",
1028 (for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) ? "~+files" : "",
1029 (for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) ? "~+dirs" : "");
1030 else
1031 flags = "";
1032 switch (for_ctrl->operator)
1034 case CMD_FOR_FILETREE:
1035 options = wine_dbg_sprintf("root=(%ls) ", for_ctrl->root_dir);
1036 break;
1037 case CMD_FOR_FILE_SET:
1039 WCHAR eol_buf[4] = {L'\'', for_ctrl->eol, L'\'', L'\0'};
1040 const WCHAR *eol = for_ctrl->eol ? eol_buf : L"<nul>";
1041 options = wine_dbg_sprintf("eol=%ls skip=%d use_backq=%c delims=%s tokens=%s ",
1042 eol, for_ctrl->num_lines_to_skip, for_ctrl->use_backq ? 'Y' : 'N',
1043 wine_dbgstr_w(for_ctrl->delims), wine_dbgstr_w(for_ctrl->tokens));
1045 break;
1046 default:
1047 options = "";
1048 break;
1050 return wine_dbg_sprintf("[FOR] %s %s%s%%%c (%ls)",
1051 for_ctrl_strings[for_ctrl->operator], flags, options,
1052 for_var_index_to_char(for_ctrl->variable_index), for_ctrl->set);
1055 static void for_control_create(enum for_control_operator for_op, unsigned flags, const WCHAR *options, int var_idx, CMD_FOR_CONTROL *for_ctrl)
1057 for_ctrl->operator = for_op;
1058 for_ctrl->flags = flags;
1059 for_ctrl->variable_index = var_idx;
1060 for_ctrl->set = NULL;
1061 switch (for_ctrl->operator)
1063 case CMD_FOR_FILETREE:
1064 for_ctrl->root_dir = options && *options ? xstrdupW(options) : NULL;
1065 break;
1066 default:
1067 break;
1071 static void for_control_create_fileset(unsigned flags, int var_idx, WCHAR eol, int num_lines_to_skip, BOOL use_backq,
1072 const WCHAR *delims, const WCHAR *tokens,
1073 CMD_FOR_CONTROL *for_ctrl)
1075 for_ctrl->operator = CMD_FOR_FILE_SET;
1076 for_ctrl->flags = flags;
1077 for_ctrl->variable_index = var_idx;
1078 for_ctrl->set = NULL;
1080 for_ctrl->eol = eol;
1081 for_ctrl->use_backq = use_backq;
1082 for_ctrl->num_lines_to_skip = num_lines_to_skip;
1083 for_ctrl->delims = delims;
1084 for_ctrl->tokens = tokens;
1087 static void for_control_append_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *set)
1089 if (for_ctrl->set)
1091 for_ctrl->set = xrealloc((void*)for_ctrl->set,
1092 (wcslen(for_ctrl->set) + 1 + wcslen(set) + 1) * sizeof(WCHAR));
1093 wcscat((WCHAR*)for_ctrl->set, L" ");
1094 wcscat((WCHAR*)for_ctrl->set, set);
1096 else
1097 for_ctrl->set = xstrdupW(set);
1100 void if_condition_dispose(CMD_IF_CONDITION *cond)
1102 switch (cond->op)
1104 case CMD_IF_ERRORLEVEL:
1105 case CMD_IF_EXIST:
1106 case CMD_IF_DEFINED:
1107 free((void*)cond->operand);
1108 break;
1109 case CMD_IF_BINOP_EQUAL:
1110 case CMD_IF_BINOP_LSS:
1111 case CMD_IF_BINOP_LEQ:
1112 case CMD_IF_BINOP_EQU:
1113 case CMD_IF_BINOP_NEQ:
1114 case CMD_IF_BINOP_GEQ:
1115 case CMD_IF_BINOP_GTR:
1116 free((void*)cond->left);
1117 free((void*)cond->right);
1118 break;
1122 static BOOL if_condition_parse(WCHAR *start, WCHAR **end, CMD_IF_CONDITION *cond)
1124 WCHAR *param_start;
1125 const WCHAR *param_copy;
1126 int narg = 0;
1128 if (cond) memset(cond, 0, sizeof(*cond));
1129 param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1130 /* /I is the only option supported */
1131 if (!wcsicmp(param_copy, L"/I"))
1133 param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1134 if (cond) cond->case_insensitive = 1;
1136 if (!wcsicmp(param_copy, L"NOT"))
1138 param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1139 if (cond) cond->negated = 1;
1141 if (!wcsicmp(param_copy, L"errorlevel"))
1143 param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1144 if (cond) cond->op = CMD_IF_ERRORLEVEL;
1145 if (cond) cond->operand = wcsdup(param_copy);
1147 else if (!wcsicmp(param_copy, L"exist"))
1149 param_copy = WCMD_parameter(start, narg++, &param_start, FALSE, FALSE);
1150 if (cond) cond->op = CMD_IF_EXIST;
1151 if (cond) cond->operand = wcsdup(param_copy);
1153 else if (!wcsicmp(param_copy, L"defined"))
1155 param_copy = WCMD_parameter(start, narg++, &param_start, TRUE, FALSE);
1156 if (cond) cond->op = CMD_IF_DEFINED;
1157 if (cond) cond->operand = wcsdup(param_copy);
1159 else /* comparison operation */
1161 if (*param_copy == L'\0') return FALSE;
1162 param_copy = WCMD_parameter(start, narg - 1, &param_start, TRUE, FALSE);
1163 if (cond) cond->left = wcsdup(param_copy);
1165 start = WCMD_skip_leading_spaces(param_start + wcslen(param_copy));
1167 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
1168 if (start[0] == L'=' && start[1] == L'=')
1170 start += 2; /* == */
1171 if (cond) cond->op = CMD_IF_BINOP_EQUAL;
1173 else
1175 static struct
1177 const WCHAR *name;
1178 enum cond_operator binop;
1180 allowed_operators[] = {{L"lss", CMD_IF_BINOP_LSS},
1181 {L"leq", CMD_IF_BINOP_LEQ},
1182 {L"equ", CMD_IF_BINOP_EQU},
1183 {L"neq", CMD_IF_BINOP_NEQ},
1184 {L"geq", CMD_IF_BINOP_GEQ},
1185 {L"gtr", CMD_IF_BINOP_GTR},
1187 int i;
1189 param_copy = WCMD_parameter(start, 0, &param_start, FALSE, FALSE);
1190 for (i = 0; i < ARRAY_SIZE(allowed_operators); i++)
1191 if (!wcsicmp(param_copy, allowed_operators[i].name)) break;
1192 if (i == ARRAY_SIZE(allowed_operators))
1194 if (cond) free((void*)cond->left);
1195 return FALSE;
1197 if (cond) cond->op = allowed_operators[i].binop;
1198 start += wcslen(param_copy);
1201 param_copy = WCMD_parameter(start, 0, &param_start, TRUE, FALSE);
1202 if (*param_copy == L'\0')
1204 if (cond) free((void*)cond->left);
1205 return FALSE;
1207 if (cond) cond->right = wcsdup(param_copy);
1209 start = param_start + wcslen(param_copy);
1210 narg = 0;
1212 /* check all remaning args are present, and compute pointer to end of condition */
1213 param_copy = WCMD_parameter(start, narg, end, TRUE, FALSE);
1214 return cond || *param_copy != L'\0';
1217 static const char *debugstr_if_condition(const CMD_IF_CONDITION *cond)
1219 const char *header = wine_dbg_sprintf("{{%s%s", cond->negated ? "not " : "", cond->case_insensitive ? "nocase " : "");
1221 switch (cond->op)
1223 case CMD_IF_ERRORLEVEL: return wine_dbg_sprintf("%serrorlevel %ls}}", header, cond->operand);
1224 case CMD_IF_EXIST: return wine_dbg_sprintf("%sexist %ls}}", header, cond->operand);
1225 case CMD_IF_DEFINED: return wine_dbg_sprintf("%sdefined %ls}}", header, cond->operand);
1226 case CMD_IF_BINOP_EQUAL: return wine_dbg_sprintf("%s%ls == %ls}}", header, cond->left, cond->right);
1228 case CMD_IF_BINOP_LSS: return wine_dbg_sprintf("%s%ls LSS %ls}}", header, cond->left, cond->right);
1229 case CMD_IF_BINOP_LEQ: return wine_dbg_sprintf("%s%ls LEQ %ls}}", header, cond->left, cond->right);
1230 case CMD_IF_BINOP_EQU: return wine_dbg_sprintf("%s%ls EQU %ls}}", header, cond->left, cond->right);
1231 case CMD_IF_BINOP_NEQ: return wine_dbg_sprintf("%s%ls NEQ %ls}}", header, cond->left, cond->right);
1232 case CMD_IF_BINOP_GEQ: return wine_dbg_sprintf("%s%ls GEQ %ls}}", header, cond->left, cond->right);
1233 case CMD_IF_BINOP_GTR: return wine_dbg_sprintf("%s%ls GTR %ls}}", header, cond->left, cond->right);
1234 default:
1235 FIXME("Unexpected condition operator %u\n", cond->op);
1236 return "{{}}";
1240 /***************************************************************************
1241 * node_dispose_tree
1243 * Frees the storage held for a parsed command line
1244 * - This is not done in the process_commands, as eventually the current
1245 * pointer will be modified within the commands, and hence a single free
1246 * routine is simpler
1248 void node_dispose_tree(CMD_NODE *node)
1250 /* Loop through the commands, freeing them one by one */
1251 if (!node) return;
1252 switch (node->op)
1254 case CMD_SINGLE:
1255 free(node->command);
1256 break;
1257 case CMD_CONCAT:
1258 case CMD_PIPE:
1259 case CMD_ONFAILURE:
1260 case CMD_ONSUCCESS:
1261 node_dispose_tree(node->left);
1262 node_dispose_tree(node->right);
1263 break;
1264 case CMD_IF:
1265 if_condition_dispose(&node->condition);
1266 node_dispose_tree(node->then_block);
1267 node_dispose_tree(node->else_block);
1268 break;
1269 case CMD_FOR:
1270 for_control_dispose(&node->for_ctrl);
1271 node_dispose_tree(node->do_block);
1272 break;
1274 redirection_dispose_list(node->redirects);
1275 free(node);
1278 static CMD_NODE *node_create_single(WCHAR *c)
1280 CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1282 new->op = CMD_SINGLE;
1283 new->command = c;
1284 new->redirects = NULL;
1286 return new;
1289 static CMD_NODE *node_create_binary(CMD_OPERATOR op, CMD_NODE *l, CMD_NODE *r)
1291 CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1293 new->op = op;
1294 new->left = l;
1295 new->right = r;
1296 new->redirects = NULL;
1298 return new;
1301 static CMD_NODE *node_create_if(CMD_IF_CONDITION *cond, CMD_NODE *then_block, CMD_NODE *else_block)
1303 CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1305 new->op = CMD_IF;
1306 new->condition = *cond;
1307 new->then_block = then_block;
1308 new->else_block = else_block;
1309 new->redirects = NULL;
1311 return new;
1314 static CMD_NODE *node_create_for(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *do_block)
1316 CMD_NODE *new = xalloc(sizeof(CMD_NODE));
1318 new->op = CMD_FOR;
1319 new->for_ctrl = *for_ctrl;
1320 new->do_block = do_block;
1321 new->redirects = NULL;
1323 return new;
1326 static void init_msvcrt_io_block(STARTUPINFOW* st)
1328 STARTUPINFOW st_p;
1329 /* fetch the parent MSVCRT info block if any, so that the child can use the
1330 * same handles as its grand-father
1332 st_p.cb = sizeof(STARTUPINFOW);
1333 GetStartupInfoW(&st_p);
1334 st->cbReserved2 = st_p.cbReserved2;
1335 st->lpReserved2 = st_p.lpReserved2;
1336 if (st_p.cbReserved2 && st_p.lpReserved2)
1338 unsigned num = *(unsigned*)st_p.lpReserved2;
1339 char* flags;
1340 HANDLE* handles;
1341 BYTE *ptr;
1342 size_t sz;
1344 /* Override the entries for fd 0,1,2 if we happened
1345 * to change those std handles (this depends on the way cmd sets
1346 * its new input & output handles)
1348 sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
1349 ptr = xalloc(sz);
1350 flags = (char*)(ptr + sizeof(unsigned));
1351 handles = (HANDLE*)(flags + num * sizeof(char));
1353 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
1354 st->cbReserved2 = sz;
1355 st->lpReserved2 = ptr;
1357 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
1358 if (num <= 0 || (flags[0] & WX_OPEN))
1360 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
1361 flags[0] |= WX_OPEN;
1363 if (num <= 1 || (flags[1] & WX_OPEN))
1365 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
1366 flags[1] |= WX_OPEN;
1368 if (num <= 2 || (flags[2] & WX_OPEN))
1370 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
1371 flags[2] |= WX_OPEN;
1373 #undef WX_OPEN
1377 static RETURN_CODE execute_single_command(const WCHAR *command);
1379 /******************************************************************************
1380 * WCMD_run_program
1382 * Execute a command line as an external program. Must allow recursion.
1384 * Precedence:
1385 * Manual testing under windows shows PATHEXT plays a key part in this,
1386 * and the search algorithm and precedence appears to be as follows.
1388 * Search locations:
1389 * If directory supplied on command, just use that directory
1390 * If extension supplied on command, look for that explicit name first
1391 * Otherwise, search in each directory on the path
1392 * Precedence:
1393 * If extension supplied on command, look for that explicit name first
1394 * Then look for supplied name .* (even if extension supplied, so
1395 * 'garbage.exe' will match 'garbage.exe.cmd')
1396 * If any found, cycle through PATHEXT looking for name.exe one by one
1397 * Launching
1398 * Once a match has been found, it is launched - Code currently uses
1399 * findexecutable to achieve this which is left untouched.
1400 * If an executable has not been found, and we were launched through
1401 * a call, we need to check if the command is an internal command,
1402 * so go back through wcmd_execute.
1405 RETURN_CODE WCMD_run_program(WCHAR *command, BOOL called)
1407 WCHAR temp[MAX_PATH];
1408 WCHAR pathtosearch[MAXSTRING];
1409 WCHAR *pathposn;
1410 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1411 MAX_PATH, including null character */
1412 WCHAR *lastSlash;
1413 WCHAR pathext[MAXSTRING];
1414 WCHAR *firstParam;
1415 BOOL extensionsupplied = FALSE;
1416 BOOL explicit_path = FALSE;
1417 BOOL status;
1418 DWORD len;
1420 /* Quick way to get the filename is to extract the first argument. */
1421 WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
1422 firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
1424 if (!firstParam[0]) return NO_ERROR;
1426 /* Calculate the search path and stem to search for */
1427 if (wcspbrk(firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
1428 lstrcpyW(pathtosearch, L".;");
1429 len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
1430 if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
1431 lstrcpyW(pathtosearch, L".");
1433 if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
1434 if (lstrlenW(firstParam) >= MAX_PATH)
1436 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1437 return ERROR_INVALID_FUNCTION;
1440 lstrcpyW(stemofsearch, firstParam);
1442 } else {
1444 /* Convert eg. ..\fred to include a directory by removing file part */
1445 if (!WCMD_get_fullpath(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL))
1446 return ERROR_INVALID_FUNCTION;
1447 lastSlash = wcsrchr(pathtosearch, '\\');
1448 if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1449 lstrcpyW(stemofsearch, lastSlash+1);
1451 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1452 c:\windows\a.bat syntax */
1453 if (lastSlash) *(lastSlash + 1) = 0x00;
1454 explicit_path = TRUE;
1457 /* Now extract PATHEXT */
1458 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
1459 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
1460 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
1463 /* Loop through the search path, dir by dir */
1464 pathposn = pathtosearch;
1465 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1466 wine_dbgstr_w(stemofsearch));
1467 while (pathposn) {
1468 WCHAR thisDir[MAX_PATH] = {'\0'};
1469 int length = 0;
1470 WCHAR *pos = NULL;
1471 BOOL found = FALSE;
1472 BOOL inside_quotes = FALSE;
1474 if (explicit_path)
1476 lstrcpyW(thisDir, pathposn);
1477 pathposn = NULL;
1479 else
1481 /* Work on the next directory on the search path */
1482 pos = pathposn;
1483 while ((inside_quotes || *pos != ';') && *pos != 0)
1485 if (*pos == '"')
1486 inside_quotes = !inside_quotes;
1487 pos++;
1490 if (*pos) /* Reached semicolon */
1492 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1493 thisDir[(pos-pathposn)] = 0x00;
1494 pathposn = pos+1;
1496 else /* Reached string end */
1498 lstrcpyW(thisDir, pathposn);
1499 pathposn = NULL;
1502 /* Remove quotes */
1503 length = lstrlenW(thisDir);
1504 if (thisDir[length - 1] == '"')
1505 thisDir[length - 1] = 0;
1507 if (*thisDir != '"')
1508 lstrcpyW(temp, thisDir);
1509 else
1510 lstrcpyW(temp, thisDir + 1);
1512 /* When temp is an empty string, skip over it. This needs
1513 to be done before the expansion, because WCMD_get_fullpath
1514 fails when given an empty string */
1515 if (*temp == '\0')
1516 continue;
1518 /* Since you can have eg. ..\.. on the path, need to expand
1519 to full information */
1520 if (!WCMD_get_fullpath(temp, ARRAY_SIZE(thisDir), thisDir, NULL))
1521 return ERROR_INVALID_FUNCTION;
1524 /* 1. If extension supplied, see if that file exists */
1525 lstrcatW(thisDir, L"\\");
1526 lstrcatW(thisDir, stemofsearch);
1527 pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
1529 /* 1. If extension supplied, see if that file exists */
1530 if (extensionsupplied) {
1531 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1532 found = TRUE;
1536 /* 2. Any .* matches? */
1537 if (!found) {
1538 HANDLE h;
1539 WIN32_FIND_DATAW finddata;
1541 lstrcatW(thisDir, L".*");
1542 h = FindFirstFileW(thisDir, &finddata);
1543 FindClose(h);
1544 if (h != INVALID_HANDLE_VALUE) {
1546 WCHAR *thisExt = pathext;
1548 /* 3. Yes - Try each path ext */
1549 while (thisExt) {
1550 WCHAR *nextExt = wcschr(thisExt, ';');
1552 if (nextExt) {
1553 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1554 pos[(nextExt-thisExt)] = 0x00;
1555 thisExt = nextExt+1;
1556 } else {
1557 lstrcpyW(pos, thisExt);
1558 thisExt = NULL;
1561 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1562 found = TRUE;
1563 thisExt = NULL;
1569 /* Once found, launch it */
1570 if (found) {
1571 STARTUPINFOW st;
1572 PROCESS_INFORMATION pe;
1573 SHFILEINFOW psfi;
1574 DWORD console;
1575 HINSTANCE hinst;
1576 WCHAR *ext = wcsrchr( thisDir, '.' );
1578 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1580 /* Special case BAT and CMD */
1581 if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) {
1582 RETURN_CODE return_code;
1583 BOOL oldinteractive = interactive;
1585 interactive = FALSE;
1586 return_code = WCMD_batch(thisDir, command, NULL, INVALID_HANDLE_VALUE);
1587 interactive = oldinteractive;
1588 if (context && !called) {
1589 TRACE("Batch completed, but was not 'called' so skipping outer batch too\n");
1590 context->skip_rest = TRUE;
1592 if (return_code != RETURN_CODE_ABORTED)
1593 errorlevel = return_code;
1594 return return_code;
1595 } else {
1596 DWORD exit_code;
1597 /* thisDir contains the file to be launched, but with what?
1598 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1599 hinst = FindExecutableW (thisDir, NULL, temp);
1600 if ((INT_PTR)hinst < 32)
1601 console = 0;
1602 else
1603 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1605 ZeroMemory (&st, sizeof(STARTUPINFOW));
1606 st.cb = sizeof(STARTUPINFOW);
1607 init_msvcrt_io_block(&st);
1609 /* Launch the process and if a CUI wait on it to complete
1610 Note: Launching internal wine processes cannot specify a full path to exe */
1611 status = CreateProcessW(thisDir,
1612 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1613 free(st.lpReserved2);
1614 if ((opt_c || opt_k) && !opt_s && !status
1615 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1616 /* strip first and last quote WCHARacters and try again */
1617 WCMD_strip_quotes(command);
1618 opt_s = TRUE;
1619 return WCMD_run_program(command, called);
1622 if (!status)
1623 break;
1625 /* Always wait when non-interactive (cmd /c or in batch program),
1626 or for console applications */
1627 if (!interactive || (console && !HIWORD(console)))
1628 WaitForSingleObject (pe.hProcess, INFINITE);
1629 GetExitCodeProcess (pe.hProcess, &exit_code);
1630 errorlevel = (exit_code == STILL_ACTIVE) ? NO_ERROR : exit_code;
1632 CloseHandle(pe.hProcess);
1633 CloseHandle(pe.hThread);
1634 return errorlevel;
1639 /* Not found anywhere - were we called? */
1640 if (called)
1641 return errorlevel = execute_single_command(command);
1643 /* Not found anywhere - give up */
1644 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
1646 /* If a command fails to launch, it sets errorlevel 9009 - which
1647 does not seem to have any associated constant definition */
1648 errorlevel = RETURN_CODE_CANT_LAUNCH;
1649 return ERROR_INVALID_FUNCTION;
1652 static BOOL set_std_redirections(CMD_REDIRECTION *redir)
1654 static DWORD std_index[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE};
1655 static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE};
1656 WCHAR expanded_filename[MAXSTRING];
1657 HANDLE h;
1659 for (; redir; redir = redir->next)
1661 CMD_REDIRECTION *next;
1663 /* if we have several elements changing same std stream, only use last one */
1664 for (next = redir->next; next; next = next->next)
1665 if (redir->fd == next->fd) break;
1666 if (next) continue;
1667 switch (redir->kind)
1669 case REDIR_READ_FROM:
1670 wcscpy(expanded_filename, redir->file);
1671 handleExpansion(expanded_filename, TRUE);
1672 h = CreateFileW(expanded_filename, GENERIC_READ, FILE_SHARE_READ,
1673 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1674 if (h == INVALID_HANDLE_VALUE)
1676 WARN("Failed to open (%ls)\n", expanded_filename);
1677 return FALSE;
1679 TRACE("Open (%ls) => %p\n", expanded_filename, h);
1680 break;
1681 case REDIR_WRITE_TO:
1682 case REDIR_WRITE_APPEND:
1684 DWORD disposition = redir->kind == REDIR_WRITE_TO ? CREATE_ALWAYS : OPEN_ALWAYS;
1685 wcscpy(expanded_filename, redir->file);
1686 handleExpansion(expanded_filename, TRUE);
1687 h = CreateFileW(expanded_filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
1688 &sa, disposition, FILE_ATTRIBUTE_NORMAL, NULL);
1689 if (h == INVALID_HANDLE_VALUE)
1691 WARN("Failed to open (%ls)\n", expanded_filename);
1692 return FALSE;
1694 TRACE("Open %u (%ls) => %p\n", redir->fd, expanded_filename, h);
1695 if (SetFilePointer(h, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
1696 WCMD_print_error();
1698 break;
1699 case REDIR_WRITE_CLONE:
1700 if (redir->clone > 2 || redir->clone == redir->fd)
1702 WARN("Can't duplicate %d from %d\n", redir->fd, redir->clone);
1703 return FALSE;
1705 if (!DuplicateHandle(GetCurrentProcess(),
1706 GetStdHandle(std_index[redir->clone]),
1707 GetCurrentProcess(),
1709 0, TRUE, DUPLICATE_SAME_ACCESS))
1711 WARN("Duplicating handle failed with gle %ld\n", GetLastError());
1712 return FALSE;
1714 break;
1716 if (redir->fd > 2)
1717 CloseHandle(h);
1718 else
1719 SetStdHandle(std_index[redir->fd], h);
1721 return TRUE;
1724 /*****************************************************************************
1725 * Process one command. If the command is EXIT this routine does not return.
1726 * We will recurse through here executing batch files.
1727 * Note: If call is used to a non-existing program, we reparse the line and
1728 * try to run it as an internal command. 'retrycall' represents whether
1729 * we are attempting this retry.
1731 static RETURN_CODE execute_single_command(const WCHAR *command)
1733 RETURN_CODE return_code;
1734 WCHAR *cmd, *parms_start;
1735 int cmd_index, count;
1736 WCHAR *whichcmd;
1737 WCHAR *new_cmd = NULL;
1738 BOOL prev_echo_mode;
1740 TRACE("command on entry:%s\n", wine_dbgstr_w(command));
1742 /* Move copy of the command onto the heap so it can be expanded */
1743 new_cmd = xalloc(MAXSTRING * sizeof(WCHAR));
1744 lstrcpyW(new_cmd, command);
1745 cmd = new_cmd;
1747 /* Strip leading whitespaces, and a '@' if supplied */
1748 whichcmd = WCMD_skip_leading_spaces(cmd);
1749 TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1750 if (whichcmd[0] == '@') whichcmd++;
1752 /* Check if the command entered is internal, and identify which one */
1753 count = 0;
1754 while (IsCharAlphaNumericW(whichcmd[count])) {
1755 count++;
1757 for (cmd_index=0; cmd_index<=WCMD_EXIT; cmd_index++) {
1758 if (count && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1759 whichcmd, count, inbuilt[cmd_index], -1) == CSTR_EQUAL) break;
1761 parms_start = WCMD_skip_leading_spaces (&whichcmd[count]);
1763 handleExpansion(new_cmd, TRUE);
1766 * Changing default drive has to be handled as a special case, anything
1767 * else if it exists after whitespace is ignored
1770 if (cmd[1] == L':' && (!cmd[2] || iswspace(cmd[2]))) {
1771 WCHAR envvar[5];
1772 WCHAR dir[MAX_PATH];
1774 /* Ignore potential garbage on the same line */
1775 cmd[2] = L'\0';
1777 /* According to MSDN CreateProcess docs, special env vars record
1778 the current directory on each drive, in the form =C:
1779 so see if one specified, and if so go back to it */
1780 lstrcpyW(envvar, L"=");
1781 lstrcatW(envvar, cmd);
1782 if (GetEnvironmentVariableW(envvar, dir, ARRAY_SIZE(dir)) == 0) {
1783 wsprintfW(cmd, L"%s\\", cmd);
1784 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1786 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1787 if (!SetCurrentDirectoryW(cmd))
1789 WCMD_print_error();
1790 return_code = errorlevel = ERROR_INVALID_FUNCTION;
1792 else return_code = NO_ERROR;
1793 goto cleanup;
1796 WCMD_parse (parms_start, quals, param1, param2);
1797 TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1799 if (cmd_index <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) {
1800 /* this is a help request for a builtin program */
1801 cmd_index = WCMD_HELP;
1802 memcpy(parms_start, whichcmd, count * sizeof(WCHAR));
1803 parms_start[count] = '\0';
1806 switch (cmd_index) {
1808 case WCMD_CALL:
1809 return_code = WCMD_call(parms_start);
1810 break;
1811 case WCMD_CD:
1812 case WCMD_CHDIR:
1813 return_code = WCMD_setshow_default(parms_start);
1814 break;
1815 case WCMD_CLS:
1816 return_code = WCMD_clear_screen();
1817 break;
1818 case WCMD_COPY:
1819 return_code = WCMD_copy(parms_start);
1820 break;
1821 case WCMD_DATE:
1822 return_code = WCMD_setshow_date();
1823 break;
1824 case WCMD_DEL:
1825 case WCMD_ERASE:
1826 return_code = WCMD_delete(parms_start);
1827 break;
1828 case WCMD_DIR:
1829 return_code = WCMD_directory(parms_start);
1830 break;
1831 case WCMD_ECHO:
1832 return_code = WCMD_echo(&whichcmd[count]);
1833 break;
1834 case WCMD_GOTO:
1835 return_code = WCMD_goto();
1836 break;
1837 case WCMD_HELP:
1838 return_code = WCMD_give_help(parms_start);
1839 break;
1840 case WCMD_LABEL:
1841 return_code = WCMD_label();
1842 break;
1843 case WCMD_MD:
1844 case WCMD_MKDIR:
1845 return_code = WCMD_create_dir(parms_start);
1846 break;
1847 case WCMD_MOVE:
1848 return_code = WCMD_move();
1849 break;
1850 case WCMD_PATH:
1851 return_code = WCMD_setshow_path(parms_start);
1852 break;
1853 case WCMD_PAUSE:
1854 return_code = WCMD_pause();
1855 break;
1856 case WCMD_PROMPT:
1857 return_code = WCMD_setshow_prompt();
1858 break;
1859 case WCMD_REM:
1860 return_code = NO_ERROR;
1861 break;
1862 case WCMD_REN:
1863 case WCMD_RENAME:
1864 return_code = WCMD_rename();
1865 break;
1866 case WCMD_RD:
1867 case WCMD_RMDIR:
1868 return_code = WCMD_remove_dir(parms_start);
1869 break;
1870 case WCMD_SETLOCAL:
1871 return_code = WCMD_setlocal(parms_start);
1872 break;
1873 case WCMD_ENDLOCAL:
1874 return_code = WCMD_endlocal();
1875 break;
1876 case WCMD_SET:
1877 return_code = WCMD_setshow_env(parms_start);
1878 break;
1879 case WCMD_SHIFT:
1880 return_code = WCMD_shift(parms_start);
1881 break;
1882 case WCMD_START:
1883 return_code = WCMD_start(parms_start);
1884 break;
1885 case WCMD_TIME:
1886 return_code = WCMD_setshow_time();
1887 break;
1888 case WCMD_TITLE:
1889 return_code = WCMD_title(parms_start);
1890 break;
1891 case WCMD_TYPE:
1892 return_code = WCMD_type(parms_start);
1893 break;
1894 case WCMD_VER:
1895 return_code = WCMD_version();
1896 break;
1897 case WCMD_VERIFY:
1898 return_code = WCMD_verify();
1899 break;
1900 case WCMD_VOL:
1901 return_code = WCMD_volume();
1902 break;
1903 case WCMD_PUSHD:
1904 return_code = WCMD_pushd(parms_start);
1905 break;
1906 case WCMD_POPD:
1907 return_code = WCMD_popd();
1908 break;
1909 case WCMD_ASSOC:
1910 return_code = WCMD_assoc(parms_start, TRUE);
1911 break;
1912 case WCMD_COLOR:
1913 return_code = WCMD_color();
1914 break;
1915 case WCMD_FTYPE:
1916 return_code = WCMD_assoc(parms_start, FALSE);
1917 break;
1918 case WCMD_MORE:
1919 return_code = WCMD_more(parms_start);
1920 break;
1921 case WCMD_CHOICE:
1922 return_code = WCMD_choice(parms_start);
1923 break;
1924 case WCMD_MKLINK:
1925 return_code = WCMD_mklink(parms_start);
1926 break;
1927 case WCMD_EXIT:
1928 return_code = WCMD_exit();
1929 break;
1930 default:
1931 prev_echo_mode = echo_mode;
1932 return_code = WCMD_run_program(whichcmd, FALSE);
1933 echo_mode = prev_echo_mode;
1936 cleanup:
1937 free(cmd);
1938 return return_code;
1941 /*************************************************************************
1942 * WCMD_LoadMessage
1943 * Load a string from the resource file, handling any error
1944 * Returns string retrieved from resource file
1946 WCHAR *WCMD_LoadMessage(UINT id) {
1947 static WCHAR msg[2048];
1949 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
1950 WINE_FIXME("LoadString failed with %ld\n", GetLastError());
1951 lstrcpyW(msg, L"Failed!");
1953 return msg;
1956 static WCHAR *find_chr(WCHAR *in, WCHAR *last, const WCHAR *delims)
1958 for (; in < last; in++)
1959 if (wcschr(delims, *in)) return in;
1960 return NULL;
1963 /***************************************************************************
1964 * WCMD_IsEndQuote
1966 * Checks if the quote pointed to is the end-quote.
1968 * Quotes end if:
1970 * 1) The current parameter ends at EOL or at the beginning
1971 * of a redirection or pipe and not in a quote section.
1973 * 2) If the next character is a space and not in a quote section.
1975 * Returns TRUE if this is an end quote, and FALSE if it is not.
1978 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1980 int quoteCount = quoteIndex;
1981 int i;
1983 /* If we are not in a quoted section, then we are not an end-quote */
1984 if(quoteIndex == 0)
1986 return FALSE;
1989 /* Check how many quotes are left for this parameter */
1990 for(i=0;quote[i];i++)
1992 if(quote[i] == '"')
1994 quoteCount++;
1997 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1998 else if(((quoteCount % 2) == 0)
1999 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ') ||
2000 (quote[i] == '&')))
2002 break;
2006 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
2007 be an end-quote */
2008 if(quoteIndex >= (quoteCount / 2))
2010 return TRUE;
2013 /* No cigar */
2014 return FALSE;
2017 static WCHAR *for_fileset_option_split(WCHAR *from, const WCHAR* key)
2019 size_t len = wcslen(key);
2021 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2022 from, len, key, len) != CSTR_EQUAL)
2023 return NULL;
2024 from += len;
2025 if (len && key[len - 1] == L'=')
2026 while (*from && *from != L' ' && *from != L'\t') from++;
2027 return from;
2030 static CMD_FOR_CONTROL *for_control_parse(WCHAR *opts_var)
2032 CMD_FOR_CONTROL *for_ctrl;
2033 enum for_control_operator for_op;
2034 WCHAR mode = L' ', option;
2035 WCHAR options[MAXSTRING];
2036 WCHAR *arg;
2037 unsigned flags = 0;
2038 int arg_index;
2039 int var_idx;
2041 options[0] = L'\0';
2042 /* native allows two options only in the /D /R case, a repetition of the option
2043 * and prints an error otherwise
2045 for (arg_index = 0; ; arg_index++)
2047 arg = WCMD_parameter(opts_var, arg_index, NULL, FALSE, FALSE);
2049 if (!arg || *arg != L'/') break;
2050 option = towupper(arg[1]);
2051 if (mode != L' ' && (mode != L'D' || option != 'R') && mode != option)
2052 break;
2053 switch (option)
2055 case L'R':
2056 if (mode == L'D')
2058 mode = L'X';
2059 break;
2061 /* fall thru */
2062 case L'D':
2063 case L'L':
2064 case L'F':
2065 mode = option;
2066 break;
2067 default:
2068 /* error unexpected 'arg' at this time */
2069 WARN("for qualifier '%c' unhandled\n", *arg);
2070 goto syntax_error;
2073 switch (mode)
2075 case L' ':
2076 for_op = CMD_FOR_FILETREE;
2077 flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES;
2078 break;
2079 case L'D':
2080 for_op = CMD_FOR_FILETREE;
2081 flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES;
2082 break;
2083 case L'X':
2084 for_op = CMD_FOR_FILETREE;
2085 flags = CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | CMD_FOR_FLAG_TREE_RECURSE;
2086 break;
2087 case L'R':
2088 for_op = CMD_FOR_FILETREE;
2089 flags = CMD_FOR_FLAG_TREE_INCLUDE_FILES | /*CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES | */CMD_FOR_FLAG_TREE_RECURSE;
2090 break;
2091 case L'L':
2092 for_op = CMD_FOR_NUMBERS;
2093 break;
2094 case L'F':
2095 for_op = CMD_FOR_FILE_SET;
2096 break;
2097 default:
2098 FIXME("Unexpected situation\n");
2099 return NULL;
2102 if (mode == L'F' || mode == L'R')
2104 /* Retrieve next parameter to see if is root/options (raw form required
2105 * with for /f, or unquoted in for /r)
2107 arg = WCMD_parameter(opts_var, arg_index, NULL, for_op == CMD_FOR_FILE_SET, FALSE);
2109 /* Next parm is either qualifier, path/options or variable -
2110 * only care about it if it is the path/options
2112 if (arg && *arg != L'/' && *arg != L'%')
2114 arg_index++;
2115 wcscpy(options, arg);
2119 /* Ensure line continues with variable */
2120 arg = WCMD_parameter(opts_var, arg_index++, NULL, FALSE, FALSE);
2121 if (!arg || *arg != L'%' || (var_idx = for_var_char_to_index(arg[1])) == -1)
2122 goto syntax_error; /* FIXME native prints the offending token "%<whatever>" was unexpected at this time */
2123 for_ctrl = xalloc(sizeof(*for_ctrl));
2124 if (for_op == CMD_FOR_FILE_SET)
2126 size_t len = wcslen(options);
2127 WCHAR *p = options, *end;
2128 WCHAR eol = L'\0';
2129 int num_lines_to_skip = 0;
2130 BOOL use_backq = FALSE;
2131 WCHAR *delims = NULL, *tokens = NULL;
2132 /* strip enclosing double-quotes when present */
2133 if (len >= 2 && p[0] == L'"' && p[len - 1] == L'"')
2135 p[len - 1] = L'\0';
2136 p++;
2138 for ( ; *p; p = end)
2140 p = WCMD_skip_leading_spaces(p);
2141 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
2142 if ((end = for_fileset_option_split(p, L"eol=")))
2144 /* assuming one char for eol marker */
2145 if (end != p + 5) goto syntax_error;
2146 eol = p[4];
2148 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
2149 else if ((end = for_fileset_option_split(p, L"skip=")))
2151 WCHAR *nextchar;
2152 num_lines_to_skip = wcstoul(p + 5, &nextchar, 0);
2153 if (end != nextchar) goto syntax_error;
2155 /* Save if usebackq semantics are in effect */
2156 else if ((end = for_fileset_option_split(p, L"usebackq")))
2157 use_backq = TRUE;
2158 /* Save the supplied delims */
2159 else if ((end = for_fileset_option_split(p, L"delims=")))
2161 size_t copy_len;
2163 /* interpret space when last character of whole options string as part of delims= */
2164 if (end[0] && !end[1]) end++;
2165 copy_len = end - (p + 7) /* delims= */;
2166 delims = xalloc((copy_len + 1) * sizeof(WCHAR));
2167 memcpy(delims, p + 7, copy_len * sizeof(WCHAR));
2168 delims[copy_len] = L'\0';
2170 /* Save the tokens being requested */
2171 else if ((end = for_fileset_option_split(p, L"tokens=")))
2173 size_t copy_len;
2175 copy_len = end - (p + 7) /* tokens= */;
2176 tokens = xalloc((copy_len + 1) * sizeof(WCHAR));
2177 memcpy(tokens, p + 7, copy_len * sizeof(WCHAR));
2178 tokens[copy_len] = L'\0';
2180 else
2182 WARN("FOR option not found %ls\n", p);
2183 goto syntax_error;
2186 for_control_create_fileset(flags, var_idx, eol, num_lines_to_skip, use_backq,
2187 delims ? delims : xstrdupW(L" \t"),
2188 tokens ? tokens : xstrdupW(L"1"), for_ctrl);
2190 else
2191 for_control_create(for_op, flags, options, var_idx, for_ctrl);
2192 return for_ctrl;
2193 syntax_error:
2194 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2195 return NULL;
2198 /* used to store additional information dedicated a given token */
2199 union token_parameter
2201 WCHAR *command;
2202 CMD_REDIRECTION *redirection;
2203 void *none;
2206 struct node_builder
2208 unsigned num;
2209 unsigned allocated;
2210 struct token
2212 enum builder_token
2214 TKN_EOF, TKN_EOL, TKN_REDIRECTION, TKN_FOR, TKN_IN, TKN_DO, TKN_IF, TKN_ELSE,
2215 TKN_OPENPAR, TKN_CLOSEPAR, TKN_AMP, TKN_BARBAR, TKN_AMPAMP, TKN_BAR, TKN_COMMAND,
2216 } token;
2217 union token_parameter parameter;
2218 } *stack;
2219 unsigned pos;
2220 unsigned opened_parenthesis;
2223 static const char* debugstr_token(enum builder_token tkn, union token_parameter tkn_pmt)
2225 static const char *tokens[] = {"EOF", "EOL", "REDIR", "FOR", "IN", "DO", "IF", "ELSE",
2226 "(", ")", "&", "||", "&&", "|", "CMD"};
2228 if (tkn >= ARRAY_SIZE(tokens)) return "<<<>>>";
2229 switch (tkn)
2231 case TKN_COMMAND: return wine_dbg_sprintf("%s {{%s}}", tokens[tkn], debugstr_w(tkn_pmt.command));
2232 case TKN_REDIRECTION: return wine_dbg_sprintf("%s {{%s}}", tokens[tkn], debugstr_redirection(tkn_pmt.redirection));
2233 default: return wine_dbg_sprintf("%s", tokens[tkn]);
2237 static unsigned token_get_precedence(enum builder_token tkn)
2239 switch (tkn)
2241 case TKN_EOL: return 5;
2242 case TKN_BAR: return 4;
2243 case TKN_AMPAMP: return 3;
2244 case TKN_BARBAR: return 2;
2245 case TKN_AMP: return 1;
2246 default: return 0;
2250 static void node_builder_init(struct node_builder *builder)
2252 memset(builder, 0, sizeof(*builder));
2255 static void node_builder_dispose(struct node_builder *builder)
2257 free(builder->stack);
2260 static void node_builder_push_token_parameter(struct node_builder *builder, enum builder_token tkn, union token_parameter pmt)
2262 if (builder->allocated <= builder->num)
2264 unsigned sz = builder->allocated ? 2 * builder->allocated : 64;
2265 builder->stack = xrealloc(builder->stack, sz * sizeof(builder->stack[0]));
2266 builder->allocated = sz;
2268 builder->stack[builder->num].token = tkn;
2269 builder->stack[builder->num].parameter = pmt;
2271 if (tkn == TKN_OPENPAR)
2272 builder->opened_parenthesis++;
2273 if (tkn == TKN_CLOSEPAR)
2274 builder->opened_parenthesis--;
2275 builder->num++;
2278 static void node_builder_push_token(struct node_builder *builder, enum builder_token tkn)
2280 union token_parameter pmt = {.none = NULL};
2281 node_builder_push_token_parameter(builder, tkn, pmt);
2284 static enum builder_token node_builder_peek_next_token(struct node_builder *builder, union token_parameter *pmt)
2286 enum builder_token tkn;
2288 if (builder->pos >= builder->num)
2290 tkn = TKN_EOF;
2291 if (pmt) pmt->none = NULL;
2293 else
2295 tkn = builder->stack[builder->pos].token;
2296 if (pmt)
2297 *pmt = builder->stack[builder->pos].parameter;
2299 return tkn;
2302 static void node_builder_consume(struct node_builder *builder)
2304 builder->stack[builder->pos].parameter.none = NULL;
2305 builder->pos++;
2308 static BOOL node_builder_expect_token(struct node_builder *builder, enum builder_token tkn)
2310 if (builder->pos >= builder->num || builder->stack[builder->pos].token != tkn)
2311 return FALSE;
2312 node_builder_consume(builder);
2313 return TRUE;
2316 static void redirection_list_append(CMD_REDIRECTION **redir, CMD_REDIRECTION *last)
2318 if (last)
2320 for ( ; *redir; redir = &(*redir)->next) {}
2321 *redir = last;
2325 static BOOL node_builder_parse(struct node_builder *builder, unsigned precedence, CMD_NODE **result)
2327 CMD_REDIRECTION *redir = NULL;
2328 unsigned bogus_line;
2329 CMD_NODE *left = NULL, *right;
2330 CMD_FOR_CONTROL *for_ctrl = NULL;
2331 union token_parameter pmt;
2332 enum builder_token tkn;
2333 BOOL done;
2335 #define ERROR_IF(x) if (x) {bogus_line = __LINE__; goto error_handling;}
2338 tkn = node_builder_peek_next_token(builder, &pmt);
2339 done = FALSE;
2341 TRACE("\t%u/%u) %s\n", builder->pos, builder->num, debugstr_token(tkn, pmt));
2342 switch (tkn)
2344 case TKN_EOF:
2345 /* always an error to read past end of tokens */
2346 ERROR_IF(TRUE);
2347 break;
2348 case TKN_EOL:
2349 done = TRUE;
2350 break;
2351 case TKN_OPENPAR:
2352 ERROR_IF(left);
2353 node_builder_consume(builder);
2354 /* empty lines are allowed here */
2355 while ((tkn = node_builder_peek_next_token(builder, &pmt)) == TKN_EOL)
2356 node_builder_consume(builder);
2357 ERROR_IF(!node_builder_parse(builder, 0, &left));
2358 /* temp before using precedence in chaining */
2359 while ((tkn = node_builder_peek_next_token(builder, &pmt)) != TKN_CLOSEPAR)
2361 ERROR_IF(tkn != TKN_EOL);
2362 node_builder_consume(builder);
2363 /* FIXME potential empty here?? */
2364 ERROR_IF(!node_builder_parse(builder, 0, &right));
2365 left = node_create_binary(CMD_CONCAT, left, right);
2367 node_builder_consume(builder);
2368 /* if we had redirection before '(', add them up front */
2369 if (redir)
2371 redirection_list_append(&redir, left->redirects);
2372 left->redirects = redir;
2373 redir = NULL;
2375 /* just in case we're handling: "(if ...) > a"... to not trigger errors in TKN_REDIRECTION */
2376 while (node_builder_peek_next_token(builder, &pmt) == TKN_REDIRECTION)
2378 redirection_list_append(&left->redirects, pmt.redirection);
2379 node_builder_consume(builder);
2381 break;
2382 /* shouldn't appear here... error handling ? */
2383 case TKN_IN:
2384 /* following tokens act as a delimiter for inner context; return to upper */
2385 case TKN_CLOSEPAR:
2386 case TKN_ELSE:
2387 case TKN_DO:
2388 done = TRUE;
2389 break;
2390 case TKN_AMP:
2391 ERROR_IF(!left);
2392 if (!(done = token_get_precedence(tkn) <= precedence))
2394 node_builder_consume(builder);
2395 if (node_builder_peek_next_token(builder, &pmt) == TKN_CLOSEPAR)
2397 done = TRUE;
2398 break;
2400 ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2401 left = node_create_binary(CMD_CONCAT, left, right);
2403 break;
2404 case TKN_AMPAMP:
2405 ERROR_IF(!left);
2406 if (!(done = token_get_precedence(tkn) <= precedence))
2408 node_builder_consume(builder);
2409 ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2410 left = node_create_binary(CMD_ONSUCCESS, left, right);
2412 break;
2413 case TKN_BAR:
2414 ERROR_IF(!left);
2415 if (!(done = token_get_precedence(tkn) <= precedence))
2417 node_builder_consume(builder);
2418 ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2419 left = node_create_binary(CMD_PIPE, left, right);
2421 break;
2422 case TKN_BARBAR:
2423 ERROR_IF(!left);
2424 if (!(done = token_get_precedence(tkn) <= precedence))
2426 node_builder_consume(builder);
2427 ERROR_IF(!node_builder_parse(builder, token_get_precedence(tkn), &right));
2428 left = node_create_binary(CMD_ONFAILURE, left, right);
2430 break;
2431 case TKN_COMMAND:
2432 ERROR_IF(left);
2433 left = node_create_single(pmt.command);
2434 node_builder_consume(builder);
2435 left->redirects = redir;
2436 redir = NULL;
2437 break;
2438 case TKN_IF:
2439 ERROR_IF(left);
2440 ERROR_IF(redir);
2442 WCHAR *end;
2443 CMD_IF_CONDITION cond;
2444 CMD_NODE *then_block;
2445 CMD_NODE *else_block;
2447 node_builder_consume(builder);
2448 tkn = node_builder_peek_next_token(builder, &pmt);
2449 ERROR_IF(tkn != TKN_COMMAND);
2450 if (!wcscmp(pmt.command, L"/?"))
2452 node_builder_consume(builder);
2453 free(pmt.command);
2454 left = node_create_single(command_create(L"help if", 7));
2455 break;
2457 ERROR_IF(!if_condition_parse(pmt.command, &end, &cond));
2458 free(pmt.command);
2459 node_builder_consume(builder);
2460 if (!node_builder_parse(builder, 0, &then_block))
2462 if_condition_dispose(&cond);
2463 ERROR_IF(TRUE);
2465 tkn = node_builder_peek_next_token(builder, NULL);
2466 if (tkn == TKN_ELSE)
2468 node_builder_consume(builder);
2469 if (!node_builder_parse(builder, 0, &else_block))
2471 if_condition_dispose(&cond);
2472 node_dispose_tree(then_block);
2473 ERROR_IF(TRUE);
2476 else
2477 else_block = NULL;
2478 left = node_create_if(&cond, then_block, else_block);
2480 break;
2481 case TKN_FOR:
2482 ERROR_IF(left);
2483 ERROR_IF(redir);
2485 CMD_NODE *do_block;
2487 node_builder_consume(builder);
2488 tkn = node_builder_peek_next_token(builder, &pmt);
2489 ERROR_IF(tkn != TKN_COMMAND);
2490 if (!wcscmp(pmt.command, L"/?"))
2492 node_builder_consume(builder);
2493 free(pmt.command);
2494 left = node_create_single(command_create(L"help for", 8));
2495 break;
2497 node_builder_consume(builder);
2498 for_ctrl = for_control_parse(pmt.command);
2499 free(pmt.command);
2500 ERROR_IF(for_ctrl == NULL);
2501 ERROR_IF(!node_builder_expect_token(builder, TKN_IN));
2502 ERROR_IF(!node_builder_expect_token(builder, TKN_OPENPAR));
2505 tkn = node_builder_peek_next_token(builder, &pmt);
2506 switch (tkn)
2508 case TKN_COMMAND:
2509 for_control_append_set(for_ctrl, pmt.command);
2510 free(pmt.command);
2511 break;
2512 case TKN_EOL:
2513 case TKN_CLOSEPAR:
2514 break;
2515 default:
2516 ERROR_IF(TRUE);
2518 node_builder_consume(builder);
2519 } while (tkn != TKN_CLOSEPAR);
2520 ERROR_IF(!node_builder_expect_token(builder, TKN_DO));
2521 ERROR_IF(!node_builder_parse(builder, 0, &do_block));
2522 left = node_create_for(for_ctrl, do_block);
2523 for_ctrl = NULL;
2525 break;
2526 case TKN_REDIRECTION:
2527 ERROR_IF(left && (left->op == CMD_IF || left->op == CMD_FOR));
2528 redirection_list_append(left ? &left->redirects : &redir, pmt.redirection);
2529 node_builder_consume(builder);
2530 break;
2532 } while (!done);
2533 #undef ERROR_IF
2534 *result = left;
2535 return TRUE;
2536 error_handling:
2537 TRACE("Parser failed at line %u:token %s\n", bogus_line, debugstr_token(tkn, pmt));
2538 node_dispose_tree(left);
2539 redirection_dispose_list(redir);
2540 if (for_ctrl) for_control_dispose(for_ctrl);
2542 return FALSE;
2545 static BOOL node_builder_generate(struct node_builder *builder, CMD_NODE **node)
2547 union token_parameter tkn_pmt;
2548 enum builder_token tkn;
2550 if (builder->opened_parenthesis)
2552 TRACE("Brackets do not match, error out without executing.\n");
2553 WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADPAREN));
2555 else
2557 if (node_builder_parse(builder, 0, node) &&
2558 builder->pos + 1 >= builder->num) /* consumed all tokens? */
2559 return TRUE;
2560 /* print error on first unused token */
2561 if (builder->pos < builder->num)
2563 WCHAR buffer[MAXSTRING];
2564 const WCHAR *tknstr;
2566 tkn = node_builder_peek_next_token(builder, &tkn_pmt);
2567 switch (tkn)
2569 case TKN_COMMAND:
2570 tknstr = tkn_pmt.command;
2571 break;
2572 case TKN_EOF:
2573 tknstr = WCMD_LoadMessage(WCMD_ENDOFFILE);
2574 break;
2575 case TKN_EOL:
2576 tknstr = WCMD_LoadMessage(WCMD_ENDOFLINE);
2577 break;
2578 case TKN_REDIRECTION:
2579 MultiByteToWideChar(CP_ACP, 0, debugstr_redirection(tkn_pmt.redirection), -1, buffer, ARRAY_SIZE(buffer));
2580 tknstr = buffer;
2581 break;
2582 case TKN_AMP:
2583 case TKN_AMPAMP:
2584 case TKN_BAR:
2585 case TKN_BARBAR:
2586 case TKN_FOR:
2587 case TKN_IN:
2588 case TKN_DO:
2589 case TKN_IF:
2590 case TKN_ELSE:
2591 case TKN_OPENPAR:
2592 case TKN_CLOSEPAR:
2593 MultiByteToWideChar(CP_ACP, 0, debugstr_token(tkn, tkn_pmt), -1, buffer, ARRAY_SIZE(buffer));
2594 tknstr = buffer;
2595 break;
2596 default:
2597 FIXME("Unexpected situation\n");
2598 tknstr = L"";
2599 break;
2601 WCMD_output_stderr(WCMD_LoadMessage(WCMD_BADTOKEN), tknstr);
2604 /* free remaining tokens */
2605 for (;;)
2607 tkn = node_builder_peek_next_token(builder, &tkn_pmt);
2608 if (tkn == TKN_EOF) break;
2609 if (tkn == TKN_COMMAND) free(tkn_pmt.command);
2610 if (tkn == TKN_REDIRECTION) redirection_dispose_list(tkn_pmt.redirection);
2611 node_builder_consume(builder);
2614 *node = NULL;
2615 return FALSE;
2618 static void lexer_push_command(struct node_builder *builder,
2619 WCHAR *command, int *commandLen,
2620 WCHAR *redirs, int *redirLen,
2621 WCHAR **copyTo, int **copyToLen)
2623 union token_parameter tkn_pmt;
2625 /* push first all redirections */
2626 if (*redirLen)
2628 WCHAR *pos;
2629 WCHAR *last = redirs + *redirLen;
2631 redirs[*redirLen] = 0;
2633 /* Create redirects, keeping order (eg "2>foo 1>&2") */
2634 for (pos = redirs; pos; )
2636 WCHAR *p = find_chr(pos, last, L"<>");
2637 WCHAR *filename;
2639 if (!p) break;
2641 if (*p == L'<')
2643 filename = WCMD_parameter(p + 1, 0, NULL, FALSE, FALSE);
2644 tkn_pmt.redirection = redirection_create_file(REDIR_READ_FROM, 0, filename);
2646 else
2648 unsigned fd = 1;
2649 unsigned op = REDIR_WRITE_TO;
2651 if (p > redirs && p[-1] >= L'2' && p[-1] <= L'9') fd = p[-1] - L'0';
2652 if (*++p == L'>') {p++; op = REDIR_WRITE_APPEND;}
2653 if (*p == L'&' && (p[1] >= L'0' && p[1] <= L'9'))
2655 tkn_pmt.redirection = redirection_create_clone(fd, p[1] - '0');
2656 p++;
2658 else
2660 filename = WCMD_parameter(p, 0, NULL, FALSE, FALSE);
2661 tkn_pmt.redirection = redirection_create_file(op, fd, filename);
2664 pos = p + 1;
2665 node_builder_push_token_parameter(builder, TKN_REDIRECTION, tkn_pmt);
2668 if (*commandLen)
2670 tkn_pmt.command = command_create(command, *commandLen);
2671 node_builder_push_token_parameter(builder, TKN_COMMAND, tkn_pmt);
2673 /* Reset the lengths */
2674 *commandLen = 0;
2675 *redirLen = 0;
2676 *copyToLen = commandLen;
2677 *copyTo = command;
2680 static WCHAR *fetch_next_line(BOOL feed, BOOL first_line, HANDLE from, WCHAR* buffer)
2682 /* display prompt */
2683 if (interactive && !context)
2685 /* native does is this way... not symmetrical wrt. echo_mode */
2686 if (!first_line)
2687 WCMD_output_asis(WCMD_LoadMessage(WCMD_MOREPROMPT));
2688 else if (echo_mode)
2689 WCMD_show_prompt();
2692 if (feed && !WCMD_fgets(buffer, MAXSTRING, from))
2694 buffer[0] = L'\0';
2695 return NULL;
2697 /* Handle truncated input - issue warning */
2698 if (wcslen(buffer) == MAXSTRING - 1)
2700 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
2701 WCMD_output_asis_stderr(buffer);
2702 WCMD_output_asis_stderr(L"\r\n");
2704 /* Replace env vars if in a batch context */
2705 handleExpansion(buffer, FALSE);
2707 buffer = WCMD_skip_leading_spaces(buffer);
2708 /* Show prompt before batch line IF echo is on and in batch program */
2709 if (context && echo_mode && *buffer && *buffer != '@')
2711 if (first_line)
2713 const size_t len = wcslen(L"echo.");
2714 size_t curr_size = wcslen(buffer);
2715 size_t min_len = curr_size < len ? curr_size : len;
2716 WCMD_output_asis(L"\r\n");
2717 WCMD_show_prompt();
2718 WCMD_output_asis(buffer);
2719 /* I don't know why Windows puts a space here but it does */
2720 /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
2721 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2722 buffer, min_len, L"echo.", len) != CSTR_EQUAL
2723 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2724 buffer, min_len, L"echo:", len) != CSTR_EQUAL
2725 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2726 buffer, min_len, L"echo/", len) != CSTR_EQUAL)
2728 WCMD_output_asis(L" ");
2731 else
2732 WCMD_output_asis(buffer);
2734 WCMD_output_asis(L"\r\n");
2737 /* Skip repeated 'no echo' characters and whitespace */
2738 while (*buffer == '@' || *buffer == L' ' || *buffer == L'\t') buffer++;
2739 return buffer;
2742 /***************************************************************************
2743 * WCMD_ReadAndParseLine
2745 * Either uses supplied input or
2746 * Reads a file from the handle, and then...
2747 * Parse the text buffer, splitting into separate commands
2748 * - unquoted && strings split 2 commands but the 2nd is flagged as
2749 * following an &&
2750 * - ( as the first character just ups the bracket depth
2751 * - unquoted ) when bracket depth > 0 terminates a bracket and
2752 * adds a CMD_LIST structure with null command
2753 * - Anything else gets put into the command string (including
2754 * redirects)
2756 enum read_parse_line WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_NODE **output, HANDLE readFrom)
2758 WCHAR *curPos;
2759 int inQuotes = 0;
2760 WCHAR curString[MAXSTRING];
2761 int curStringLen = 0;
2762 WCHAR curRedirs[MAXSTRING];
2763 int curRedirsLen = 0;
2764 WCHAR *curCopyTo;
2765 int *curLen;
2766 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
2767 BOOL inOneLine = FALSE;
2768 BOOL inFor = FALSE;
2769 BOOL inIf = FALSE;
2770 BOOL inElse= FALSE;
2771 BOOL onlyWhiteSpace = FALSE;
2772 BOOL lastWasWhiteSpace = FALSE;
2773 BOOL lastWasDo = FALSE;
2774 BOOL lastWasIn = FALSE;
2775 BOOL lastWasElse = FALSE;
2776 BOOL lastWasRedirect = TRUE;
2777 BOOL ignoreBracket = FALSE; /* Some expressions after if (set) require */
2778 /* handling brackets as a normal character */
2779 BOOL acceptCommand = TRUE;
2780 struct node_builder builder;
2781 BOOL ret;
2783 *output = NULL;
2784 /* Allocate working space for a command read from keyboard, file etc */
2785 if (!extraSpace)
2786 extraSpace = xalloc((MAXSTRING + 1) * sizeof(WCHAR));
2788 /* If initial command read in, use that, otherwise get input from handle */
2789 if (optionalcmd)
2790 wcscpy(extraSpace, optionalcmd);
2791 if (!(curPos = fetch_next_line(optionalcmd == NULL, TRUE, readFrom, extraSpace)))
2792 return RPL_EOF;
2794 TRACE("About to parse line (%ls)\n", extraSpace);
2796 node_builder_init(&builder);
2798 /* Start with an empty string, copying to the command string */
2799 curStringLen = 0;
2800 curRedirsLen = 0;
2801 curCopyTo = curString;
2802 curLen = &curStringLen;
2803 lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
2804 onlyWhiteSpace = TRUE;
2806 /* Parse every character on the line being processed */
2807 while (*curPos != 0x00) {
2809 WCHAR thisChar;
2811 /* Debugging AID:
2812 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2813 lastWasWhiteSpace, onlyWhiteSpace);
2816 /* Prevent overflow caused by the caret escape char */
2817 if (*curLen >= MAXSTRING) {
2818 WINE_ERR("Overflow detected in command\n");
2819 return RPL_SYNTAXERROR;
2822 /* Certain commands need special handling */
2823 if (curStringLen == 0 && curCopyTo == curString) {
2824 /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
2825 if (WCMD_keyword_ws_found(L"rem", curPos) || *curPos == ':') {
2826 inOneLine = TRUE;
2828 } else if (WCMD_keyword_ws_found(L"for", curPos)) {
2829 inFor = TRUE;
2830 node_builder_push_token(&builder, TKN_FOR);
2832 curPos = WCMD_skip_leading_spaces(curPos + 3); /* "for */
2833 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
2834 is only true in the command portion of the IF statement, but this
2835 should suffice for now.
2836 To be able to handle ('s in the condition part take as much as evaluate_if_condition
2837 would take and skip parsing it here. */
2838 acceptCommand = FALSE;
2839 } else if (acceptCommand && WCMD_keyword_ws_found(L"if", curPos)) {
2840 WCHAR *command;
2842 node_builder_push_token(&builder, TKN_IF);
2844 inIf = TRUE;
2846 curPos = WCMD_skip_leading_spaces(curPos + 2); /* "if" */
2847 if (if_condition_parse(curPos, &command, NULL))
2849 int if_condition_len = command - curPos;
2850 TRACE("p: %s, command: %s, if_condition_len: %d\n",
2851 wine_dbgstr_w(curPos), wine_dbgstr_w(command), if_condition_len);
2852 memcpy(&curCopyTo[*curLen], curPos, if_condition_len * sizeof(WCHAR));
2853 (*curLen) += if_condition_len;
2854 curPos += if_condition_len;
2856 /* FIXME we do parsing twice of condition (once here, second time in node_builder_parse) */
2857 lexer_push_command(&builder, curString, &curStringLen,
2858 curRedirs, &curRedirsLen,
2859 &curCopyTo, &curLen);
2862 if (WCMD_keyword_ws_found(L"set", curPos))
2863 ignoreBracket = TRUE;
2864 acceptCommand = TRUE;
2865 onlyWhiteSpace = TRUE;
2866 continue;
2867 } else if (WCMD_keyword_ws_found(L"else", curPos)) {
2868 inElse = TRUE;
2869 lastWasElse = TRUE;
2870 acceptCommand = TRUE;
2871 onlyWhiteSpace = TRUE;
2872 node_builder_push_token(&builder, TKN_ELSE);
2874 curPos = WCMD_skip_leading_spaces(curPos + 4 /* else */);
2875 continue;
2877 /* In a for loop, the DO command will follow a close bracket followed by
2878 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2879 is then 0, and all whitespace is skipped */
2880 } else if (inFor && WCMD_keyword_ws_found(L"do", curPos)) {
2882 WINE_TRACE("Found 'DO '\n");
2883 lastWasDo = TRUE;
2884 acceptCommand = TRUE;
2885 onlyWhiteSpace = TRUE;
2887 node_builder_push_token(&builder, TKN_DO);
2888 curPos = WCMD_skip_leading_spaces(curPos + 2 /* do */);
2889 continue;
2891 } else if (curCopyTo == curString) {
2893 /* Special handling for the 'FOR' command */
2894 if (inFor && lastWasWhiteSpace) {
2895 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2897 if (WCMD_keyword_ws_found(L"in", curPos)) {
2898 WINE_TRACE("Found 'IN '\n");
2900 lexer_push_command(&builder, curString, &curStringLen,
2901 curRedirs, &curRedirsLen,
2902 &curCopyTo, &curLen);
2903 node_builder_push_token(&builder, TKN_IN);
2904 lastWasIn = TRUE;
2905 onlyWhiteSpace = TRUE;
2906 curPos = WCMD_skip_leading_spaces(curPos + 2 /* in */);
2907 continue;
2912 /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
2913 the &&, quotes and redirection etc are ineffective, so just force
2914 the use of the default processing by skipping character specific
2915 matching below) */
2916 if (!inOneLine) thisChar = *curPos;
2917 else thisChar = 'X'; /* Character with no special processing */
2919 lastWasWhiteSpace = FALSE; /* Will be reset below */
2921 switch (thisChar) {
2923 case '=': /* drop through - ignore token delimiters at the start of a command */
2924 case ',': /* drop through - ignore token delimiters at the start of a command */
2925 case '\t':/* drop through - ignore token delimiters at the start of a command */
2926 case ' ':
2927 /* If a redirect in place, it ends here */
2928 if (!inQuotes && !lastWasRedirect) {
2930 /* If finishing off a redirect, add a whitespace delimiter */
2931 if (curCopyTo == curRedirs) {
2932 curCopyTo[(*curLen)++] = ' ';
2933 if (curStringLen == 0)
2934 onlyWhiteSpace = TRUE;
2936 curCopyTo = curString;
2937 curLen = &curStringLen;
2939 if (*curLen > 0) {
2940 curCopyTo[(*curLen)++] = *curPos;
2943 /* Remember just processed whitespace */
2944 lastWasWhiteSpace = TRUE;
2946 break;
2948 case '>': /* drop through - handle redirect chars the same */
2949 case '<':
2950 /* Make a redirect start here */
2951 if (!inQuotes) {
2952 curCopyTo = curRedirs;
2953 curLen = &curRedirsLen;
2954 lastWasRedirect = TRUE;
2957 /* See if 1>, 2> etc, in which case we have some patching up
2958 to do (provided there's a preceding whitespace, and enough
2959 chars read so far) */
2960 if (curPos[-1] >= '1' && curPos[-1] <= '9'
2961 && (curStringLen == 1 ||
2962 curPos[-2] == ' ' || curPos[-2] == '\t')) {
2963 curStringLen--;
2964 curString[curStringLen] = 0x00;
2965 curCopyTo[(*curLen)++] = *(curPos-1);
2968 curCopyTo[(*curLen)++] = *curPos;
2970 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2971 do not process that ampersand as an AND operator */
2972 if (thisChar == '>' && *(curPos+1) == '&') {
2973 curCopyTo[(*curLen)++] = *(curPos+1);
2974 curPos++;
2976 break;
2978 case '|': /* Pipe character only if not || */
2979 if (!inQuotes) {
2980 lastWasRedirect = FALSE;
2982 lexer_push_command(&builder, curString, &curStringLen,
2983 curRedirs, &curRedirsLen,
2984 &curCopyTo, &curLen);
2986 if (*(curPos+1) == '|') {
2987 curPos++; /* Skip other | */
2988 node_builder_push_token(&builder, TKN_BARBAR);
2989 } else {
2990 node_builder_push_token(&builder, TKN_BAR);
2992 acceptCommand = TRUE;
2993 onlyWhiteSpace = TRUE;
2994 thisChar = L' ';
2995 } else {
2996 curCopyTo[(*curLen)++] = *curPos;
2998 break;
3000 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
3001 inQuotes--;
3002 } else {
3003 inQuotes++; /* Quotes within quotes are fun! */
3005 curCopyTo[(*curLen)++] = *curPos;
3006 lastWasRedirect = FALSE;
3007 break;
3009 case '(': /* If a '(' is the first non whitespace in a command portion
3010 ie start of line or just after &&, then we read until an
3011 unquoted ) is found */
3012 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
3013 ", for(%d, In:%d, Do:%d)"
3014 ", if(%d, else:%d, lwe:%d)\n",
3015 *curLen, inQuotes,
3016 onlyWhiteSpace,
3017 inFor, lastWasIn, lastWasDo,
3018 inIf, inElse, lastWasElse);
3019 lastWasRedirect = FALSE;
3021 if (inQuotes) {
3022 curCopyTo[(*curLen)++] = *curPos;
3024 /* In a FOR loop, an unquoted '(' may occur straight after
3025 IN or DO
3026 In an IF statement just handle it regardless as we don't
3027 parse the operands
3028 In an ELSE statement, only allow it straight away after
3029 the ELSE and whitespace
3031 } else if ((acceptCommand && onlyWhiteSpace) ||
3032 (inIf && !ignoreBracket) ||
3033 (inElse && lastWasElse && onlyWhiteSpace) ||
3034 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
3036 /* Add the current command */
3037 lexer_push_command(&builder, curString, &curStringLen,
3038 curRedirs, &curRedirsLen,
3039 &curCopyTo, &curLen);
3040 node_builder_push_token(&builder, TKN_OPENPAR);
3041 acceptCommand = TRUE;
3042 onlyWhiteSpace = TRUE;
3043 thisChar = ' ';
3044 } else {
3045 curCopyTo[(*curLen)++] = *curPos;
3047 break;
3049 case '^': if (!inQuotes) {
3050 /* If we reach the end of the input, we need to wait for more */
3051 if (curPos[1] == L'\0') {
3052 TRACE("Caret found at end of line\n");
3053 extraSpace[0] = L'^';
3054 if (!fetch_next_line(TRUE, FALSE, readFrom, extraSpace + 1))
3055 break;
3056 if (!extraSpace[1]) /* empty line */
3058 extraSpace[1] = L'\r';
3059 if (!fetch_next_line(TRUE, FALSE, readFrom, extraSpace + 2))
3060 break;
3062 curPos = extraSpace;
3063 break;
3065 curPos++;
3067 curCopyTo[(*curLen)++] = *curPos;
3068 break;
3070 case '&': if (!inQuotes) {
3071 lastWasRedirect = FALSE;
3073 /* Add an entry to the command list */
3074 lexer_push_command(&builder, curString, &curStringLen,
3075 curRedirs, &curRedirsLen,
3076 &curCopyTo, &curLen);
3078 if (*(curPos+1) == '&') {
3079 curPos++; /* Skip other & */
3080 node_builder_push_token(&builder, TKN_AMPAMP);
3081 } else {
3082 node_builder_push_token(&builder, TKN_AMP);
3084 acceptCommand = TRUE;
3085 onlyWhiteSpace = TRUE;
3086 thisChar = ' ';
3087 } else {
3088 curCopyTo[(*curLen)++] = *curPos;
3090 break;
3092 case ')': if (!inQuotes && builder.opened_parenthesis > 0) {
3093 lastWasRedirect = FALSE;
3095 /* Add the current command if there is one */
3096 lexer_push_command(&builder, curString, &curStringLen,
3097 curRedirs, &curRedirsLen,
3098 &curCopyTo, &curLen);
3099 node_builder_push_token(&builder, TKN_CLOSEPAR);
3100 acceptCommand = FALSE;
3101 onlyWhiteSpace = TRUE;
3102 thisChar = ' ';
3103 } else {
3104 curCopyTo[(*curLen)++] = *curPos;
3106 break;
3107 default:
3108 lastWasRedirect = FALSE;
3109 curCopyTo[(*curLen)++] = *curPos;
3112 curPos++;
3114 /* At various times we need to know if we have only skipped whitespace,
3115 so reset this variable and then it will remain true until a non
3116 whitespace is found */
3117 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
3118 onlyWhiteSpace = FALSE;
3120 /* If we have reached the end, add this command into the list
3121 Do not add command to list if escape char ^ was last */
3122 if (*curPos == L'\0') {
3123 /* Add an entry to the command list */
3124 lexer_push_command(&builder, curString, &curStringLen,
3125 curRedirs, &curRedirsLen,
3126 &curCopyTo, &curLen);
3127 node_builder_push_token(&builder, TKN_EOL);
3129 /* If we have reached the end of the string, see if bracketing is outstanding */
3130 if (builder.opened_parenthesis > 0 && readFrom != INVALID_HANDLE_VALUE) {
3131 TRACE("Need to read more data as outstanding brackets or carets\n");
3132 inOneLine = FALSE;
3133 ignoreBracket = FALSE;
3134 inQuotes = 0;
3135 acceptCommand = TRUE;
3136 onlyWhiteSpace = TRUE;
3138 /* fetch next non empty line */
3139 do {
3140 curPos = fetch_next_line(TRUE, FALSE, readFrom, extraSpace);
3141 } while (curPos && *curPos == L'\0');
3142 if (!curPos)
3143 curPos = extraSpace;
3148 ret = node_builder_generate(&builder, output);
3149 node_builder_dispose(&builder);
3151 return ret ? RPL_SUCCESS : RPL_SYNTAXERROR;
3154 static BOOL if_condition_evaluate(CMD_IF_CONDITION *cond, int *test)
3156 WCHAR expanded_left[MAXSTRING];
3157 WCHAR expanded_right[MAXSTRING];
3158 int (WINAPI *cmp)(const WCHAR*, const WCHAR*) = cond->case_insensitive ? lstrcmpiW : lstrcmpW;
3160 TRACE("About to evaluate condition %s\n", debugstr_if_condition(cond));
3161 *test = 0;
3162 switch (cond->op)
3164 case CMD_IF_ERRORLEVEL:
3166 WCHAR *endptr;
3167 int level;
3169 wcscpy(expanded_left, cond->operand);
3170 handleExpansion(expanded_left, TRUE);
3171 level = wcstol(expanded_left, &endptr, 10);
3172 if (*endptr) return FALSE;
3173 *test = errorlevel >= level;
3175 break;
3176 case CMD_IF_EXIST:
3178 size_t len;
3179 WIN32_FIND_DATAW fd;
3180 HANDLE hff;
3182 wcscpy(expanded_left, cond->operand);
3183 handleExpansion(expanded_left, TRUE);
3184 if ((len = wcslen(expanded_left)))
3186 /* FindFirstFile does not like a directory path ending in '\' or '/', so append a '.' */
3187 if ((expanded_left[len - 1] == '\\' || expanded_left[len - 1] == '/') && len < MAXSTRING - 1)
3189 wcscat(expanded_left, L".");
3191 hff = FindFirstFileW(expanded_left, &fd);
3192 *test = (hff != INVALID_HANDLE_VALUE);
3193 if (*test) FindClose(hff);
3196 break;
3197 case CMD_IF_DEFINED:
3198 wcscpy(expanded_left, cond->operand);
3199 handleExpansion(expanded_left, TRUE);
3200 *test = GetEnvironmentVariableW(expanded_left, NULL, 0) > 0;
3201 break;
3202 case CMD_IF_BINOP_EQUAL:
3203 wcscpy(expanded_left, cond->left);
3204 handleExpansion(expanded_left, TRUE);
3205 wcscpy(expanded_right, cond->right);
3206 handleExpansion(expanded_right, TRUE);
3208 /* == is a special case, as it always compares strings */
3209 *test = (*cmp)(expanded_left, expanded_right) == 0;
3210 break;
3211 default:
3213 int left_int, right_int;
3214 WCHAR *end_left, *end_right;
3215 int cmp_val;
3217 wcscpy(expanded_left, cond->left);
3218 handleExpansion(expanded_left, TRUE);
3219 wcscpy(expanded_right, cond->right);
3220 handleExpansion(expanded_right, TRUE);
3222 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
3223 left_int = wcstol(expanded_left, &end_left, 0);
3224 right_int = wcstol(expanded_right, &end_right, 0);
3225 if (end_left > expanded_left && !*end_left && end_right > expanded_right && !*end_right)
3226 cmp_val = left_int - right_int;
3227 else
3228 cmp_val = (*cmp)(expanded_left, expanded_right);
3229 switch (cond->op)
3231 case CMD_IF_BINOP_LSS: *test = cmp_val < 0; break;
3232 case CMD_IF_BINOP_LEQ: *test = cmp_val <= 0; break;
3233 case CMD_IF_BINOP_EQU: *test = cmp_val == 0; break;
3234 case CMD_IF_BINOP_NEQ: *test = cmp_val != 0; break;
3235 case CMD_IF_BINOP_GEQ: *test = cmp_val >= 0; break;
3236 case CMD_IF_BINOP_GTR: *test = cmp_val > 0; break;
3237 default:
3238 FIXME("Unexpected comparison operator %u\n", cond->op);
3239 return FALSE;
3242 break;
3244 if (cond->negated) *test ^= 1;
3245 return TRUE;
3248 static RETURN_CODE for_loop_fileset_parse_line(CMD_NODE *node, int varidx, WCHAR *buffer,
3249 WCHAR forf_eol, const WCHAR *forf_delims, const WCHAR *forf_tokens)
3251 RETURN_CODE return_code = NO_ERROR;
3252 WCHAR *parm;
3253 int varoffset;
3254 int nexttoken, lasttoken = -1;
3255 BOOL starfound = FALSE;
3256 BOOL thisduplicate = FALSE;
3257 BOOL anyduplicates = FALSE;
3258 int totalfound;
3259 static WCHAR emptyW[] = L"";
3261 /* Extract the parameters based on the tokens= value (There will always
3262 be some value, as if it is not supplied, it defaults to tokens=1).
3263 Rough logic:
3264 Count how many tokens are named in the line, identify the lowest
3265 Empty (set to null terminated string) that number of named variables
3266 While lasttoken != nextlowest
3267 %letter = parameter number 'nextlowest'
3268 letter++ (if >26 or >52 abort)
3269 Go through token= string finding next lowest number
3270 If token ends in * set %letter = raw position of token(nextnumber+1)
3272 lasttoken = -1;
3273 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
3274 &starfound, &thisduplicate);
3276 TRACE("Using var=%lc on %d max\n", for_var_index_to_char(varidx), totalfound);
3277 /* Empty out variables */
3278 for (varoffset = 0;
3279 varoffset < totalfound && for_var_index_in_range(varidx, varoffset);
3280 varoffset++)
3281 WCMD_set_for_loop_variable(varidx + varoffset, emptyW);
3283 /* Loop extracting the tokens
3284 * Note: nexttoken of 0 means there were no tokens requested, to handle
3285 * the special case of tokens=*
3287 varoffset = 0;
3288 TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
3289 while (nexttoken > 0 && (nexttoken > lasttoken))
3291 anyduplicates |= thisduplicate;
3293 if (!for_var_index_in_range(varidx, varoffset))
3295 WARN("Out of range offset\n");
3296 break;
3298 /* Extract the token number requested and set into the next variable context */
3299 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
3300 TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
3301 varidx + varoffset, wine_dbgstr_w(parm));
3302 if (parm)
3304 WCMD_set_for_loop_variable(varidx + varoffset, parm);
3305 varoffset++;
3308 /* Find the next token */
3309 lasttoken = nexttoken;
3310 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
3311 &starfound, &thisduplicate);
3313 /* If all the rest of the tokens were requested, and there is still space in
3314 * the variable range, write them now
3316 if (!anyduplicates && starfound && for_var_index_in_range(varidx, varoffset))
3318 nexttoken++;
3319 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
3320 TRACE("Parsed all remaining tokens (%d) as parameter %s\n",
3321 varidx + varoffset, wine_dbgstr_w(parm));
3322 if (parm)
3323 WCMD_set_for_loop_variable(varidx + varoffset, parm);
3326 /* Execute the body of the for loop with these values */
3327 if (forloopcontext->variable[varidx] && forloopcontext->variable[varidx][0] != forf_eol)
3329 return_code = node_execute(node);
3331 else
3333 TRACE("Skipping line because of eol\n");
3335 return return_code;
3338 void WCMD_save_for_loop_context(BOOL reset)
3340 FOR_CONTEXT *new = xalloc(sizeof(*new));
3341 if (reset)
3342 memset(new, 0, sizeof(*new));
3343 else /* clone existing */
3344 *new = *forloopcontext;
3345 new->previous = forloopcontext;
3346 forloopcontext = new;
3349 void WCMD_restore_for_loop_context(void)
3351 FOR_CONTEXT *old = forloopcontext->previous;
3352 int varidx;
3353 if (!old)
3355 FIXME("Unexpected situation\n");
3356 return;
3358 for (varidx = 0; varidx < MAX_FOR_VARIABLES; varidx++)
3360 if (forloopcontext->variable[varidx] != old->variable[varidx])
3361 free(forloopcontext->variable[varidx]);
3363 free(forloopcontext);
3364 forloopcontext = old;
3367 void WCMD_set_for_loop_variable(int var_idx, const WCHAR *value)
3369 if (var_idx < 0 || var_idx >= MAX_FOR_VARIABLES) return;
3370 if (forloopcontext->previous &&
3371 forloopcontext->previous->variable[var_idx] != forloopcontext->variable[var_idx])
3372 free(forloopcontext->variable[var_idx]);
3373 forloopcontext->variable[var_idx] = xstrdupW(value);
3376 static BOOL match_ending_delim(WCHAR *string)
3378 WCHAR *to = string + wcslen(string);
3380 /* strip trailing delim */
3381 if (to > string) to--;
3382 if (to > string && *to == string[0])
3384 *to = L'\0';
3385 return TRUE;
3387 WARN("Can't find ending delimiter (%ls)\n", string);
3388 return FALSE;
3391 static RETURN_CODE for_control_execute_from_FILE(CMD_FOR_CONTROL *for_ctrl, FILE *input, CMD_NODE *node)
3393 WCHAR buffer[MAXSTRING];
3394 int skip_count = for_ctrl->num_lines_to_skip;
3395 RETURN_CODE return_code = NO_ERROR;
3397 /* Read line by line until end of file */
3398 while (fgetws(buffer, ARRAY_SIZE(buffer), input))
3400 size_t len;
3402 if (skip_count)
3404 TRACE("skipping %d\n", skip_count);
3405 skip_count--;
3406 continue;
3408 len = wcslen(buffer);
3409 /* Either our buffer isn't large enough to fit a full line, or there's a stray
3410 * '\0' in the buffer.
3412 if (!feof(input) && (len == 0 || (buffer[len - 1] != '\n' && buffer[len - 1] != '\r')))
3413 break;
3414 while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r'))
3415 buffer[--len] = L'\0';
3416 return_code = for_loop_fileset_parse_line(node, for_ctrl->variable_index, buffer,
3417 for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens);
3418 buffer[0] = 0;
3420 return return_code;
3423 static RETURN_CODE for_control_execute_fileset(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
3425 RETURN_CODE return_code = NO_ERROR;
3426 WCHAR set[MAXSTRING];
3427 WCHAR *args;
3428 size_t len;
3429 FILE *input;
3430 int i;
3432 wcscpy(set, for_ctrl->set);
3433 handleExpansion(set, TRUE);
3435 args = WCMD_skip_leading_spaces(set);
3436 for (len = wcslen(args); len && (args[len - 1] == L' ' || args[len - 1] == L'\t'); len--)
3437 args[len - 1] = L'\0';
3438 if (args[0] == (for_ctrl->use_backq ? L'\'' : L'"') && match_ending_delim(args))
3440 args++;
3441 if (!for_ctrl->num_lines_to_skip)
3443 return_code = for_loop_fileset_parse_line(node, for_ctrl->variable_index, args,
3444 for_ctrl->eol, for_ctrl->delims, for_ctrl->tokens);
3447 else if (args[0] == (for_ctrl->use_backq ? L'`' : L'\'') && match_ending_delim(args))
3449 WCHAR temp_cmd[MAX_PATH];
3451 args++;
3452 wsprintfW(temp_cmd, L"CMD.EXE /C %s", args);
3453 TRACE("Reading output of '%s'\n", wine_dbgstr_w(temp_cmd));
3454 input = _wpopen(temp_cmd, L"rt,ccs=unicode");
3455 if (!input)
3457 WCMD_print_error();
3458 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), args);
3459 return errorlevel = ERROR_INVALID_FUNCTION; /* FOR loop aborts at first failure here */
3461 return_code = for_control_execute_from_FILE(for_ctrl, input, node);
3462 pclose(input);
3464 else
3466 for (i = 0; ; i++)
3468 WCHAR *element = WCMD_parameter(args, i, NULL, TRUE, FALSE);
3469 if (!element || !*element) break;
3470 if (element[0] == L'"' && match_ending_delim(element)) element++;
3471 /* Open the file, read line by line and process */
3472 TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(element));
3473 input = _wfopen(element, L"rt,ccs=unicode");
3474 if (!input)
3476 WCMD_print_error();
3477 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), element);
3478 return errorlevel = ERROR_INVALID_FUNCTION; /* FOR loop aborts at first failure here */
3480 return_code = for_control_execute_from_FILE(for_ctrl, input, node);
3481 fclose(input);
3485 return return_code;
3488 static RETURN_CODE for_control_execute_set(CMD_FOR_CONTROL *for_ctrl, const WCHAR *from_dir, size_t ref_len, CMD_NODE *node)
3490 RETURN_CODE return_code = NO_ERROR;
3491 size_t len;
3492 WCHAR set[MAXSTRING];
3493 WCHAR buffer[MAX_PATH];
3494 int i;
3496 if (from_dir)
3498 len = wcslen(from_dir) + 1;
3499 wcscpy(buffer, from_dir);
3500 wcscat(buffer, L"\\");
3502 else
3503 len = 0;
3505 wcscpy(set, for_ctrl->set);
3506 handleExpansion(set, TRUE);
3507 for (i = 0; ; i++)
3509 WCHAR *element = WCMD_parameter(set, i, NULL, TRUE, FALSE);
3510 if (!element || !*element) break;
3511 if (len + wcslen(element) + 1 >= ARRAY_SIZE(buffer)) continue;
3513 wcscpy(&buffer[len], element);
3515 TRACE("Doing set element %ls\n", buffer);
3517 if (wcspbrk(element, L"?*"))
3519 WIN32_FIND_DATAW fd;
3520 HANDLE hff = FindFirstFileW(buffer, &fd);
3521 size_t insert_pos = (wcsrchr(buffer, L'\\') ? wcsrchr(buffer, L'\\') + 1 - buffer : 0);
3523 if (hff == INVALID_HANDLE_VALUE)
3525 TRACE("Couldn't FindFirstFile on %ls\n", buffer);
3526 continue;
3530 TRACE("Considering %ls\n", fd.cFileName);
3531 if (!lstrcmpW(fd.cFileName, L"..") || !lstrcmpW(fd.cFileName, L".")) continue;
3532 if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_FILES) &&
3533 !(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
3534 continue;
3535 if (!(for_ctrl->flags & CMD_FOR_FLAG_TREE_INCLUDE_DIRECTORIES) &&
3536 (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
3537 continue;
3539 if (insert_pos + wcslen(fd.cFileName) + 1 >= ARRAY_SIZE(buffer)) continue;
3540 wcscpy(&buffer[insert_pos], fd.cFileName);
3541 WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer);
3542 return_code = node_execute(node);
3543 } while (FindNextFileW(hff, &fd) != 0);
3544 FindClose(hff);
3546 else
3548 WCMD_set_for_loop_variable(for_ctrl->variable_index, buffer);
3549 return_code = node_execute(node);
3552 return return_code;
3555 static RETURN_CODE for_control_execute_walk_files(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
3557 DIRECTORY_STACK *dirs_to_walk;
3558 size_t ref_len;
3559 RETURN_CODE return_code = NO_ERROR;
3561 if (for_ctrl->root_dir)
3563 WCHAR buffer[MAXSTRING];
3565 wcscpy(buffer, for_ctrl->root_dir);
3566 handleExpansion(buffer, TRUE);
3567 dirs_to_walk = WCMD_dir_stack_create(buffer, NULL);
3569 else dirs_to_walk = WCMD_dir_stack_create(NULL, NULL);
3570 ref_len = wcslen(dirs_to_walk->dirName);
3572 while (dirs_to_walk)
3574 TRACE("About to walk %p %ls for %s\n", dirs_to_walk, dirs_to_walk->dirName, debugstr_for_control(for_ctrl));
3575 if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE)
3576 WCMD_add_dirstowalk(dirs_to_walk);
3578 return_code = for_control_execute_set(for_ctrl, dirs_to_walk->dirName, ref_len, node);
3579 /* If we are walking directories, move on to any which remain */
3580 dirs_to_walk = WCMD_dir_stack_free(dirs_to_walk);
3582 TRACE("Finished all directories.\n");
3584 return return_code;
3587 static RETURN_CODE for_control_execute_numbers(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
3589 RETURN_CODE return_code = NO_ERROR;
3590 WCHAR set[MAXSTRING];
3591 int numbers[3] = {0, 0, 0}, var;
3592 int i;
3594 wcscpy(set, for_ctrl->set);
3595 handleExpansion(set, TRUE);
3597 /* Note: native doesn't check the actual number of parameters, and set
3598 * them by default to 0.
3599 * so (-10 1) is interpreted as (-10 1 0)
3600 * and (10) loops for ever !!!
3602 for (i = 0; i < ARRAY_SIZE(numbers); i++)
3604 WCHAR *element = WCMD_parameter(set, i, NULL, FALSE, FALSE);
3605 if (!element || !*element) break;
3606 /* native doesn't no error handling */
3607 numbers[i] = wcstol(element, NULL, 0);
3610 for (var = numbers[0];
3611 (numbers[1] < 0) ? var >= numbers[2] : var <= numbers[2];
3612 var += numbers[1])
3614 WCHAR tmp[32];
3616 swprintf(tmp, ARRAY_SIZE(tmp), L"%d", var);
3617 WCMD_set_for_loop_variable(for_ctrl->variable_index, tmp);
3618 TRACE("Processing FOR number %s\n", wine_dbgstr_w(tmp));
3619 return_code = node_execute(node);
3621 return return_code;
3624 static RETURN_CODE for_control_execute(CMD_FOR_CONTROL *for_ctrl, CMD_NODE *node)
3626 RETURN_CODE return_code;
3628 if (!for_ctrl->set) return NO_ERROR;
3630 WCMD_save_for_loop_context(FALSE);
3632 switch (for_ctrl->operator)
3634 case CMD_FOR_FILETREE:
3635 if (for_ctrl->flags & CMD_FOR_FLAG_TREE_RECURSE)
3636 return_code = for_control_execute_walk_files(for_ctrl, node);
3637 else
3638 return_code = for_control_execute_set(for_ctrl, NULL, 0, node);
3639 break;
3640 case CMD_FOR_FILE_SET:
3641 return_code = for_control_execute_fileset(for_ctrl, node);
3642 break;
3643 case CMD_FOR_NUMBERS:
3644 return_code = for_control_execute_numbers(for_ctrl, node);
3645 break;
3646 default:
3647 return_code = NO_ERROR;
3648 break;
3650 WCMD_restore_for_loop_context();
3651 return return_code;
3654 RETURN_CODE node_execute(CMD_NODE *node)
3656 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
3657 GetStdHandle (STD_OUTPUT_HANDLE),
3658 GetStdHandle (STD_ERROR_HANDLE)};
3659 static DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE};
3661 RETURN_CODE return_code;
3662 int i, test;
3664 if (!node) return NO_ERROR;
3665 if (!set_std_redirections(node->redirects))
3667 WCMD_print_error();
3668 return_code = ERROR_INVALID_FUNCTION;
3670 else switch (node->op)
3672 case CMD_SINGLE:
3673 if (node->command[0] != ':')
3674 return_code = execute_single_command(node->command);
3675 else return_code = NO_ERROR;
3676 break;
3677 case CMD_CONCAT:
3678 return_code = node_execute(node->left);
3679 if (return_code != RETURN_CODE_ABORTED)
3680 return_code = node_execute(node->right);
3681 break;
3682 case CMD_ONSUCCESS:
3683 return_code = node_execute(node->left);
3684 if (return_code == NO_ERROR)
3685 return_code = node_execute(node->right);
3686 break;
3687 case CMD_ONFAILURE:
3688 return_code = node_execute(node->left);
3689 if (return_code != NO_ERROR && return_code != RETURN_CODE_ABORTED)
3691 /* that's needed for commands (POPD, RMDIR) that don't set errorlevel in case of failure. */
3692 errorlevel = return_code;
3693 return_code = node_execute(node->right);
3695 break;
3696 case CMD_PIPE:
3698 static SECURITY_ATTRIBUTES sa = {.nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE};
3699 WCHAR temp_path[MAX_PATH];
3700 WCHAR filename[MAX_PATH];
3701 CMD_REDIRECTION *output;
3702 HANDLE saved_output;
3703 BATCH_CONTEXT *saved_context = context;
3705 /* pipe LHS & RHS are run outside of any batch context */
3706 context = NULL;
3707 /* FIXME: a real pipe instead of writing to an intermediate file would be
3708 * better.
3709 * But waiting for completion of commands will require more work.
3711 /* FIXME check precedence (eg foo > a | more)
3712 * with following code, | has higher precedence than > a
3713 * (which is likely wrong IIRC, and not what previous code was doing)
3715 /* Generate a unique temporary filename */
3716 GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
3717 GetTempFileNameW(temp_path, L"CMD", 0, filename);
3718 TRACE("Using temporary file of %ls\n", filename);
3720 saved_output = GetStdHandle(STD_OUTPUT_HANDLE);
3721 /* set output for left hand side command */
3722 output = redirection_create_file(REDIR_WRITE_TO, 1, filename);
3723 if (set_std_redirections(output))
3725 RETURN_CODE return_code_left = node_execute(node->left);
3726 CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
3727 SetStdHandle(STD_OUTPUT_HANDLE, saved_output);
3729 if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context)
3730 ExitProcess(255);
3731 return_code = ERROR_INVALID_FUNCTION;
3732 if (return_code_left != RETURN_CODE_ABORTED && errorlevel != RETURN_CODE_CANT_LAUNCH)
3734 HANDLE h = CreateFileW(filename, GENERIC_READ,
3735 FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
3736 FILE_ATTRIBUTE_NORMAL, NULL);
3737 if (h != INVALID_HANDLE_VALUE)
3739 SetStdHandle(STD_INPUT_HANDLE, h);
3740 return_code = node_execute(node->right);
3741 if (errorlevel == RETURN_CODE_CANT_LAUNCH && saved_context)
3742 ExitProcess(255);
3745 DeleteFileW(filename);
3746 errorlevel = return_code;
3748 else return_code = ERROR_INVALID_FUNCTION;
3749 redirection_dispose_list(output);
3750 context = saved_context;
3752 break;
3753 case CMD_IF:
3754 if (if_condition_evaluate(&node->condition, &test))
3755 return_code = node_execute(test ? node->then_block : node->else_block);
3756 else
3757 return_code = ERROR_INVALID_FUNCTION;
3758 break;
3759 case CMD_FOR:
3760 return_code = for_control_execute(&node->for_ctrl, node->do_block);
3761 break;
3762 default:
3763 FIXME("Unexpected operator %u\n", node->op);
3764 return_code = ERROR_INVALID_FUNCTION;
3766 /* Restore old handles */
3767 for (i = 0; i < 3; i++)
3769 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i]))
3771 CloseHandle(GetStdHandle(idx_stdhandles[i]));
3772 SetStdHandle(idx_stdhandles[i], old_stdhandles[i]);
3775 return return_code;
3778 static BOOL WINAPI my_event_handler(DWORD ctrl)
3780 WCMD_output(L"\n");
3781 return ctrl == CTRL_C_EVENT;
3785 /*****************************************************************************
3786 * Main entry point. This is a console application so we have a main() not a
3787 * winmain().
3790 int __cdecl wmain (int argc, WCHAR *argvW[])
3792 WCHAR *cmdLine = NULL;
3793 WCHAR *cmd = NULL;
3794 WCHAR string[1024];
3795 WCHAR envvar[4];
3796 BOOL opt_q;
3797 int opt_t = 0;
3798 WCHAR comspec[MAX_PATH];
3799 CMD_NODE *toExecute = NULL; /* Commands left to be executed */
3800 RTL_OSVERSIONINFOEXW osv;
3801 char osver[50];
3802 STARTUPINFOW startupInfo;
3803 const WCHAR *arg;
3804 enum read_parse_line rpl_status;
3806 if (!GetEnvironmentVariableW(L"COMSPEC", comspec, ARRAY_SIZE(comspec)))
3808 GetSystemDirectoryW(comspec, ARRAY_SIZE(comspec) - ARRAY_SIZE(L"\\cmd.exe"));
3809 lstrcatW(comspec, L"\\cmd.exe");
3810 SetEnvironmentVariableW(L"COMSPEC", comspec);
3813 srand(time(NULL));
3815 /* Get the windows version being emulated */
3816 osv.dwOSVersionInfoSize = sizeof(osv);
3817 RtlGetVersion(&osv);
3819 /* Pre initialize some messages */
3820 lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
3821 sprintf(osver, "%ld.%ld.%ld", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
3822 cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
3823 lstrcpyW(version_string, cmd);
3824 LocalFree(cmd);
3825 cmd = NULL;
3827 /* init for loop context */
3828 forloopcontext = NULL;
3829 WCMD_save_for_loop_context(TRUE);
3831 /* Can't use argc/argv as it will have stripped quotes from parameters
3832 * meaning cmd.exe /C echo "quoted string" is impossible
3834 cmdLine = GetCommandLineW();
3835 WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
3837 while (*cmdLine && *cmdLine != '/') ++cmdLine;
3839 opt_c = opt_k = opt_q = opt_s = FALSE;
3841 for (arg = cmdLine; *arg; ++arg)
3843 if (arg[0] != '/')
3844 continue;
3846 switch (towlower(arg[1]))
3848 case 'a':
3849 unicodeOutput = FALSE;
3850 break;
3851 case 'c':
3852 opt_c = TRUE;
3853 break;
3854 case 'k':
3855 opt_k = TRUE;
3856 break;
3857 case 'q':
3858 opt_q = TRUE;
3859 break;
3860 case 's':
3861 opt_s = TRUE;
3862 break;
3863 case 't':
3864 if (arg[2] == ':')
3865 opt_t = wcstoul(&arg[3], NULL, 16);
3866 break;
3867 case 'u':
3868 unicodeOutput = TRUE;
3869 break;
3870 case 'v':
3871 if (arg[2] == ':')
3872 delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
3873 break;
3876 if (opt_c || opt_k)
3878 arg += 2;
3879 break;
3883 while (*arg && wcschr(L" \t,=;", *arg)) arg++;
3885 if (opt_q) {
3886 WCMD_echo(L"OFF");
3889 /* Until we start to read from the keyboard, stay as non-interactive */
3890 interactive = FALSE;
3892 SetEnvironmentVariableW(L"PROMPT", L"$P$G");
3894 if (opt_c || opt_k) {
3895 int len;
3896 WCHAR *q1 = NULL,*q2 = NULL,*p;
3898 /* Take a copy */
3899 cmd = xstrdupW(arg);
3901 /* opt_s left unflagged if the command starts with and contains exactly
3902 * one quoted string (exactly two quote characters). The quoted string
3903 * must be an executable name that has whitespace and must not have the
3904 * following characters: &<>()@^| */
3906 if (!opt_s) {
3907 /* 1. Confirm there is at least one quote */
3908 q1 = wcschr(arg, '"');
3909 if (!q1) opt_s=1;
3912 if (!opt_s) {
3913 /* 2. Confirm there is a second quote */
3914 q2 = wcschr(q1+1, '"');
3915 if (!q2) opt_s=1;
3918 if (!opt_s) {
3919 /* 3. Ensure there are no more quotes */
3920 if (wcschr(q2+1, '"')) opt_s=1;
3923 /* check first parameter for a space and invalid characters. There must not be any
3924 * invalid characters, but there must be one or more whitespace */
3925 if (!opt_s) {
3926 opt_s = TRUE;
3927 p=q1;
3928 while (p!=q2) {
3929 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
3930 || *p=='@' || *p=='^' || *p=='|') {
3931 opt_s = TRUE;
3932 break;
3934 if (*p==' ' || *p=='\t')
3935 opt_s = FALSE;
3936 p++;
3940 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
3942 /* Finally, we only stay in new mode IF the first parameter is quoted and
3943 is a valid executable, i.e. must exist, otherwise drop back to old mode */
3944 if (!opt_s) {
3945 WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
3946 WCHAR pathext[MAXSTRING];
3947 BOOL found = FALSE;
3949 /* Now extract PATHEXT */
3950 len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
3951 if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
3952 lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
3955 /* If the supplied parameter has any directory information, look there */
3956 WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
3957 if (wcschr(thisArg, '\\') != NULL) {
3959 if (!WCMD_get_fullpath(thisArg, ARRAY_SIZE(string), string, NULL)) return FALSE;
3960 WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
3961 p = string + lstrlenW(string);
3963 /* Does file exist with this name? */
3964 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
3965 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
3966 found = TRUE;
3967 } else {
3968 WCHAR *thisExt = pathext;
3970 /* No - try with each of the PATHEXT extensions */
3971 while (!found && thisExt) {
3972 WCHAR *nextExt = wcschr(thisExt, ';');
3974 if (nextExt) {
3975 memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
3976 p[(nextExt-thisExt)] = 0x00;
3977 thisExt = nextExt+1;
3978 } else {
3979 lstrcpyW(p, thisExt);
3980 thisExt = NULL;
3983 /* Does file exist with this extension appended? */
3984 if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
3985 WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
3986 found = TRUE;
3991 /* Otherwise we now need to look in the path to see if we can find it */
3992 } else {
3993 /* Does file exist with this name? */
3994 if (SearchPathW(NULL, thisArg, NULL, ARRAY_SIZE(string), string, NULL) != 0) {
3995 WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
3996 found = TRUE;
3997 } else {
3998 WCHAR *thisExt = pathext;
4000 /* No - try with each of the PATHEXT extensions */
4001 while (!found && thisExt) {
4002 WCHAR *nextExt = wcschr(thisExt, ';');
4004 if (nextExt) {
4005 *nextExt = 0;
4006 nextExt = nextExt+1;
4007 } else {
4008 nextExt = NULL;
4011 /* Does file exist with this extension? */
4012 if (SearchPathW(NULL, thisArg, thisExt, ARRAY_SIZE(string), string, NULL) != 0) {
4013 WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
4014 wine_dbgstr_w(thisExt));
4015 found = TRUE;
4017 thisExt = nextExt;
4022 /* If not found, drop back to old behaviour */
4023 if (!found) {
4024 WINE_TRACE("Binary not found, dropping back to old behaviour\n");
4025 opt_s = TRUE;
4030 /* strip first and last quote characters if opt_s; check for invalid
4031 * executable is done later */
4032 if (opt_s && *cmd=='\"')
4033 WCMD_strip_quotes(cmd);
4035 else
4037 SetConsoleCtrlHandler(my_event_handler, TRUE);
4040 /* Save cwd into appropriate env var (Must be before the /c processing */
4041 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
4042 if (IsCharAlphaW(string[0]) && string[1] == ':') {
4043 wsprintfW(envvar, L"=%c:", string[0]);
4044 SetEnvironmentVariableW(envvar, string);
4045 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
4048 if (opt_c) {
4049 /* If we do a "cmd /c command", we don't want to allocate a new
4050 * console since the command returns immediately. Rather, we use
4051 * the currently allocated input and output handles. This allows
4052 * us to pipe to and read from the command interpreter.
4055 /* Parse the command string, without reading any more input */
4056 rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
4057 if (rpl_status == RPL_SUCCESS && toExecute)
4059 node_execute(toExecute);
4060 node_dispose_tree(toExecute);
4062 else if (rpl_status == RPL_SYNTAXERROR)
4063 errorlevel = RETURN_CODE_SYNTAX_ERROR;
4065 return errorlevel;
4068 GetStartupInfoW(&startupInfo);
4069 if (startupInfo.lpTitle != NULL)
4070 SetConsoleTitleW(startupInfo.lpTitle);
4071 else
4072 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
4074 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
4075 if (opt_t) {
4076 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
4077 defaultColor = opt_t & 0xFF;
4078 param1[0] = 0x00;
4079 WCMD_color();
4081 } else {
4082 /* Check HKCU\Software\Microsoft\Command Processor
4083 Then HKLM\Software\Microsoft\Command Processor
4084 for defaultcolour value
4085 Note Can be supplied as DWORD or REG_SZ
4086 Note2 When supplied as REG_SZ it's in decimal!!! */
4087 HKEY key;
4088 DWORD type;
4089 DWORD value=0, size=4;
4090 static const WCHAR regKeyW[] = L"Software\\Microsoft\\Command Processor";
4092 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
4093 0, KEY_READ, &key) == ERROR_SUCCESS) {
4094 WCHAR strvalue[4];
4096 /* See if DWORD or REG_SZ */
4097 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
4098 if (type == REG_DWORD) {
4099 size = sizeof(DWORD);
4100 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
4101 } else if (type == REG_SZ) {
4102 size = sizeof(strvalue);
4103 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
4104 value = wcstoul(strvalue, NULL, 10);
4107 RegCloseKey(key);
4110 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
4111 0, KEY_READ, &key) == ERROR_SUCCESS) {
4112 WCHAR strvalue[4];
4114 /* See if DWORD or REG_SZ */
4115 if (RegQueryValueExW(key, L"DefaultColor", NULL, &type,
4116 NULL, NULL) == ERROR_SUCCESS) {
4117 if (type == REG_DWORD) {
4118 size = sizeof(DWORD);
4119 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
4120 } else if (type == REG_SZ) {
4121 size = sizeof(strvalue);
4122 RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
4123 value = wcstoul(strvalue, NULL, 10);
4126 RegCloseKey(key);
4129 /* If one found, set the screen to that colour */
4130 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
4131 defaultColor = value & 0xFF;
4132 param1[0] = 0x00;
4133 WCMD_color();
4138 if (opt_k)
4140 rpl_status = WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
4141 /* Parse the command string, without reading any more input */
4142 if (rpl_status == RPL_SUCCESS && toExecute)
4144 node_execute(toExecute);
4145 node_dispose_tree(toExecute);
4147 else if (rpl_status == RPL_SYNTAXERROR)
4148 errorlevel = RETURN_CODE_SYNTAX_ERROR;
4149 free(cmd);
4153 * Loop forever getting commands and executing them.
4156 interactive = TRUE;
4157 if (!opt_k) WCMD_output_asis(version_string);
4158 if (echo_mode) WCMD_output_asis(L"\r\n");
4159 /* Read until EOF (which for std input is never, but if redirect in place, may occur */
4160 while ((rpl_status = WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE))) != RPL_EOF)
4162 if (rpl_status == RPL_SUCCESS && toExecute)
4164 node_execute(toExecute);
4165 node_dispose_tree(toExecute);
4166 if (echo_mode) WCMD_output_asis(L"\r\n");
4169 return 0;