mscoree/tests: Mark a weird w2k3 result as broken.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
blob5280679b63d5bbdb1d24f939668766231233bf82
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
34 const WCHAR inbuilt[][10] = {
35 {'A','T','T','R','I','B','\0'},
36 {'C','A','L','L','\0'},
37 {'C','D','\0'},
38 {'C','H','D','I','R','\0'},
39 {'C','L','S','\0'},
40 {'C','O','P','Y','\0'},
41 {'C','T','T','Y','\0'},
42 {'D','A','T','E','\0'},
43 {'D','E','L','\0'},
44 {'D','I','R','\0'},
45 {'E','C','H','O','\0'},
46 {'E','R','A','S','E','\0'},
47 {'F','O','R','\0'},
48 {'G','O','T','O','\0'},
49 {'H','E','L','P','\0'},
50 {'I','F','\0'},
51 {'L','A','B','E','L','\0'},
52 {'M','D','\0'},
53 {'M','K','D','I','R','\0'},
54 {'M','O','V','E','\0'},
55 {'P','A','T','H','\0'},
56 {'P','A','U','S','E','\0'},
57 {'P','R','O','M','P','T','\0'},
58 {'R','E','M','\0'},
59 {'R','E','N','\0'},
60 {'R','E','N','A','M','E','\0'},
61 {'R','D','\0'},
62 {'R','M','D','I','R','\0'},
63 {'S','E','T','\0'},
64 {'S','H','I','F','T','\0'},
65 {'T','I','M','E','\0'},
66 {'T','I','T','L','E','\0'},
67 {'T','Y','P','E','\0'},
68 {'V','E','R','I','F','Y','\0'},
69 {'V','E','R','\0'},
70 {'V','O','L','\0'},
71 {'E','N','D','L','O','C','A','L','\0'},
72 {'S','E','T','L','O','C','A','L','\0'},
73 {'P','U','S','H','D','\0'},
74 {'P','O','P','D','\0'},
75 {'A','S','S','O','C','\0'},
76 {'C','O','L','O','R','\0'},
77 {'F','T','Y','P','E','\0'},
78 {'M','O','R','E','\0'},
79 {'C','H','O','I','C','E','\0'},
80 {'E','X','I','T','\0'}
83 HINSTANCE hinst;
84 DWORD errorlevel;
85 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
86 static int opt_c, opt_k, opt_s;
87 const WCHAR newline[] = {'\r','\n','\0'};
88 static const WCHAR equalsW[] = {'=','\0'};
89 static const WCHAR closeBW[] = {')','\0'};
90 WCHAR anykey[100];
91 WCHAR version_string[100];
92 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
93 BATCH_CONTEXT *context = NULL;
94 extern struct env_stack *pushd_directories;
95 static const WCHAR *pagedMessage = NULL;
96 static char *output_bufA = NULL;
97 #define MAX_WRITECONSOLE_SIZE 65535
98 BOOL unicodePipes = FALSE;
101 /*******************************************************************
102 * WCMD_output_asis_len - send output to current standard output
104 * Output a formatted unicode string. Ideally this will go to the console
105 * and hence required WriteConsoleW to output it, however if file i/o is
106 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
108 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
110 DWORD nOut= 0;
111 DWORD res = 0;
113 /* If nothing to write, return (MORE does this sometimes) */
114 if (!len) return;
116 /* Try to write as unicode assuming it is to a console */
117 res = WriteConsoleW(device, message, len, &nOut, NULL);
119 /* If writing to console fails, assume its file
120 i/o so convert to OEM codepage and output */
121 if (!res) {
122 BOOL usedDefaultChar = FALSE;
123 DWORD convertedChars;
125 if (!unicodePipes) {
127 * Allocate buffer to use when writing to file. (Not freed, as one off)
129 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
130 MAX_WRITECONSOLE_SIZE);
131 if (!output_bufA) {
132 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
133 return;
136 /* Convert to OEM, then output */
137 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
138 len, output_bufA, MAX_WRITECONSOLE_SIZE,
139 "?", &usedDefaultChar);
140 WriteFile(device, output_bufA, convertedChars,
141 &nOut, FALSE);
142 } else {
143 WriteFile(device, message, len*sizeof(WCHAR),
144 &nOut, FALSE);
147 return;
150 /*******************************************************************
151 * WCMD_output - send output to current standard output device.
155 void WCMD_output (const WCHAR *format, ...) {
157 va_list ap;
158 WCHAR string[1024];
159 int ret;
161 va_start(ap,format);
162 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
163 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
164 WINE_ERR("Output truncated in WCMD_output\n" );
165 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
166 string[ret] = '\0';
168 va_end(ap);
169 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
173 static int line_count;
174 static int max_height;
175 static int max_width;
176 static BOOL paged_mode;
177 static int numChars;
179 void WCMD_enter_paged_mode(const WCHAR *msg)
181 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
183 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
184 max_height = consoleInfo.dwSize.Y;
185 max_width = consoleInfo.dwSize.X;
186 } else {
187 max_height = 25;
188 max_width = 80;
190 paged_mode = TRUE;
191 line_count = 0;
192 numChars = 0;
193 pagedMessage = (msg==NULL)? anykey : msg;
196 void WCMD_leave_paged_mode(void)
198 paged_mode = FALSE;
199 pagedMessage = NULL;
202 /***************************************************************************
203 * WCMD_Readfile
205 * Read characters in from a console/file, returning result in Unicode
206 * with signature identical to ReadFile
208 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
209 LPDWORD charsRead, const LPOVERLAPPED unused) {
211 BOOL res;
213 /* Try to read from console as Unicode */
214 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
216 /* If reading from console has failed we assume its file
217 i/o so read in and convert from OEM codepage */
218 if (!res) {
220 DWORD numRead;
222 * Allocate buffer to use when reading from file. Not freed
224 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
225 MAX_WRITECONSOLE_SIZE);
226 if (!output_bufA) {
227 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
228 return 0;
231 /* Read from file (assume OEM codepage) */
232 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
234 /* Convert from OEM */
235 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
236 intoBuf, maxChars);
239 return res;
242 /*******************************************************************
243 * WCMD_output_asis - send output to current standard output device.
244 * without formatting eg. when message contains '%'
246 void WCMD_output_asis (const WCHAR *message) {
247 DWORD count;
248 const WCHAR* ptr;
249 WCHAR string[1024];
251 if (paged_mode) {
252 do {
253 ptr = message;
254 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
255 numChars++;
256 ptr++;
258 if (*ptr == '\n') ptr++;
259 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
260 GetStdHandle(STD_OUTPUT_HANDLE));
261 if (ptr) {
262 numChars = 0;
263 if (++line_count >= max_height - 1) {
264 line_count = 0;
265 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
266 GetStdHandle(STD_OUTPUT_HANDLE));
267 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
268 sizeof(string)/sizeof(WCHAR), &count, NULL);
271 } while (((message = ptr) != NULL) && (*ptr));
272 } else {
273 WCMD_output_asis_len(message, lstrlenW(message),
274 GetStdHandle(STD_OUTPUT_HANDLE));
278 /****************************************************************************
279 * WCMD_print_error
281 * Print the message for GetLastError
284 void WCMD_print_error (void) {
285 LPVOID lpMsgBuf;
286 DWORD error_code;
287 int status;
289 error_code = GetLastError ();
290 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
291 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
292 if (!status) {
293 WINE_FIXME ("Cannot display message for error %d, status %d\n",
294 error_code, GetLastError());
295 return;
298 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
299 GetStdHandle(STD_ERROR_HANDLE));
300 LocalFree (lpMsgBuf);
301 WCMD_output_asis_len (newline, lstrlenW(newline),
302 GetStdHandle(STD_ERROR_HANDLE));
303 return;
306 /******************************************************************************
307 * WCMD_show_prompt
309 * Display the prompt on STDout
313 static void WCMD_show_prompt (void) {
315 int status;
316 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
317 WCHAR *p, *q;
318 DWORD len;
319 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
321 len = GetEnvironmentVariableW(envPrompt, prompt_string,
322 sizeof(prompt_string)/sizeof(WCHAR));
323 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
324 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
325 strcpyW (prompt_string, dfltPrompt);
327 p = prompt_string;
328 q = out_string;
329 *q++ = '\r';
330 *q++ = '\n';
331 *q = '\0';
332 while (*p != '\0') {
333 if (*p != '$') {
334 *q++ = *p++;
335 *q = '\0';
337 else {
338 p++;
339 switch (toupper(*p)) {
340 case '$':
341 *q++ = '$';
342 break;
343 case 'A':
344 *q++ = '&';
345 break;
346 case 'B':
347 *q++ = '|';
348 break;
349 case 'C':
350 *q++ = '(';
351 break;
352 case 'D':
353 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
354 while (*q) q++;
355 break;
356 case 'E':
357 *q++ = '\E';
358 break;
359 case 'F':
360 *q++ = ')';
361 break;
362 case 'G':
363 *q++ = '>';
364 break;
365 case 'H':
366 *q++ = '\b';
367 break;
368 case 'L':
369 *q++ = '<';
370 break;
371 case 'N':
372 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
373 if (status) {
374 *q++ = curdir[0];
376 break;
377 case 'P':
378 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
379 if (status) {
380 strcatW (q, curdir);
381 while (*q) q++;
383 break;
384 case 'Q':
385 *q++ = '=';
386 break;
387 case 'S':
388 *q++ = ' ';
389 break;
390 case 'T':
391 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
392 while (*q) q++;
393 break;
394 case 'V':
395 strcatW (q, version_string);
396 while (*q) q++;
397 break;
398 case '_':
399 *q++ = '\n';
400 break;
401 case '+':
402 if (pushd_directories) {
403 memset(q, '+', pushd_directories->u.stackdepth);
404 q = q + pushd_directories->u.stackdepth;
406 break;
408 p++;
409 *q = '\0';
412 WCMD_output_asis (out_string);
416 /*************************************************************************
417 * WCMD_strdupW
418 * A wide version of strdup as its missing from unicode.h
420 WCHAR *WCMD_strdupW(WCHAR *input) {
421 int len=strlenW(input)+1;
422 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
423 memcpy(result, input, len * sizeof(WCHAR));
424 return result;
427 /*************************************************************************
428 * WCMD_strsubstW
429 * Replaces a portion of a Unicode string with the specified string.
430 * It's up to the caller to ensure there is enough space in the
431 * destination buffer.
433 void WCMD_strsubstW(WCHAR* start, WCHAR* next, WCHAR* insert, int len) {
435 if (len < 0)
436 len=insert ? lstrlenW(insert) : 0;
437 if (start+len != next)
438 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
439 if (insert)
440 memcpy(start, insert, len * sizeof(*insert));
443 /***************************************************************************
444 * WCMD_strtrim_leading_spaces
446 * Remove leading spaces from a string. Return a pointer to the first
447 * non-space character. Does not modify the input string
449 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
451 WCHAR *ptr;
453 ptr = string;
454 while (*ptr == ' ') ptr++;
455 return ptr;
458 /*************************************************************************
459 * WCMD_opt_s_strip_quotes
461 * Remove first and last quote WCHARacters, preserving all other text
463 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
464 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
465 while((*dest=*src) != '\0') {
466 if (*src=='\"')
467 lastq=dest;
468 dest++, src++;
470 if (lastq) {
471 dest=lastq++;
472 while ((*dest++=*lastq++) != 0)
478 /*************************************************************************
479 * WCMD_is_magic_envvar
480 * Return TRUE if s is '%'magicvar'%'
481 * and is not masked by a real environment variable.
484 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
486 int len;
488 if (s[0] != '%')
489 return FALSE; /* Didn't begin with % */
490 len = strlenW(s);
491 if (len < 2 || s[len-1] != '%')
492 return FALSE; /* Didn't end with another % */
494 if (CompareStringW(LOCALE_USER_DEFAULT,
495 NORM_IGNORECASE | SORT_STRINGSORT,
496 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
497 /* Name doesn't match. */
498 return FALSE;
501 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
502 /* Masked by real environment variable. */
503 return FALSE;
506 return TRUE;
509 /*************************************************************************
510 * WCMD_expand_envvar
512 * Expands environment variables, allowing for WCHARacter substitution
514 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
515 WCHAR *endOfVar = NULL, *s;
516 WCHAR *colonpos = NULL;
517 WCHAR thisVar[MAXSTRING];
518 WCHAR thisVarContents[MAXSTRING];
519 WCHAR savedchar = 0x00;
520 int len;
522 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
523 static const WCHAR Date[] = {'D','A','T','E','\0'};
524 static const WCHAR Time[] = {'T','I','M','E','\0'};
525 static const WCHAR Cd[] = {'C','D','\0'};
526 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
527 static const WCHAR Delims[] = {'%',' ',':','\0'};
529 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
530 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
532 /* Find the end of the environment variable, and extract name */
533 endOfVar = strpbrkW(start+1, Delims);
535 if (endOfVar == NULL || *endOfVar==' ') {
537 /* In batch program, missing terminator for % and no following
538 ':' just removes the '%' */
539 if (context) {
540 WCMD_strsubstW(start, start + 1, NULL, 0);
541 return start;
542 } else {
544 /* In command processing, just ignore it - allows command line
545 syntax like: for %i in (a.a) do echo %i */
546 return start+1;
550 /* If ':' found, process remaining up until '%' (or stop at ':' if
551 a missing '%' */
552 if (*endOfVar==':') {
553 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
554 if (endOfVar2 != NULL) endOfVar = endOfVar2;
557 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
558 thisVar[(endOfVar - start)+1] = 0x00;
559 colonpos = strchrW(thisVar+1, ':');
561 /* If there's complex substitution, just need %var% for now
562 to get the expanded data to play with */
563 if (colonpos) {
564 *colonpos = '%';
565 savedchar = *(colonpos+1);
566 *(colonpos+1) = 0x00;
569 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
571 /* Expand to contents, if unchanged, return */
572 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
573 /* override if existing env var called that name */
574 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
575 static const WCHAR fmt[] = {'%','d','\0'};
576 wsprintfW(thisVarContents, fmt, errorlevel);
577 len = strlenW(thisVarContents);
578 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
579 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
580 NULL, thisVarContents, MAXSTRING);
581 len = strlenW(thisVarContents);
582 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
583 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
584 NULL, thisVarContents, MAXSTRING);
585 len = strlenW(thisVarContents);
586 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
587 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
588 len = strlenW(thisVarContents);
589 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
590 static const WCHAR fmt[] = {'%','d','\0'};
591 wsprintfW(thisVarContents, fmt, rand() % 32768);
592 len = strlenW(thisVarContents);
594 /* Look for a matching 'for' variable */
595 } else if (forVar &&
596 (CompareStringW(LOCALE_USER_DEFAULT,
597 SORT_STRINGSORT,
598 thisVar,
599 (colonpos - thisVar) - 1,
600 forVar, -1) == 2)) {
601 strcpyW(thisVarContents, forVal);
602 len = strlenW(thisVarContents);
604 } else {
606 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
607 sizeof(thisVarContents)/sizeof(WCHAR));
610 if (len == 0)
611 return endOfVar+1;
613 /* In a batch program, unknown env vars are replaced with nothing,
614 note syntax %garbage:1,3% results in anything after the ':'
615 except the %
616 From the command line, you just get back what you entered */
617 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
619 /* Restore the complex part after the compare */
620 if (colonpos) {
621 *colonpos = ':';
622 *(colonpos+1) = savedchar;
625 /* Command line - just ignore this */
626 if (context == NULL) return endOfVar+1;
629 /* Batch - replace unknown env var with nothing */
630 if (colonpos == NULL) {
631 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
632 } else {
633 len = strlenW(thisVar);
634 thisVar[len-1] = 0x00;
635 /* If %:...% supplied, : is retained */
636 if (colonpos == thisVar+1) {
637 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
638 } else {
639 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
642 return start;
646 /* See if we need to do complex substitution (any ':'s), if not
647 then our work here is done */
648 if (colonpos == NULL) {
649 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
650 return start;
653 /* Restore complex bit */
654 *colonpos = ':';
655 *(colonpos+1) = savedchar;
658 Handle complex substitutions:
659 xxx=yyy (replace xxx with yyy)
660 *xxx=yyy (replace up to and including xxx with yyy)
661 ~x (from x WCHARs in)
662 ~-x (from x WCHARs from the end)
663 ~x,y (from x WCHARs in for y WCHARacters)
664 ~x,-y (from x WCHARs in until y WCHARacters from the end)
667 /* ~ is substring manipulation */
668 if (savedchar == '~') {
670 int substrposition, substrlength = 0;
671 WCHAR *commapos = strchrW(colonpos+2, ',');
672 WCHAR *startCopy;
674 substrposition = atolW(colonpos+2);
675 if (commapos) substrlength = atolW(commapos+1);
677 /* Check bounds */
678 if (substrposition >= 0) {
679 startCopy = &thisVarContents[min(substrposition, len)];
680 } else {
681 startCopy = &thisVarContents[max(0, len+substrposition-1)];
684 if (commapos == NULL) {
685 /* Copy the lot */
686 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
687 } else if (substrlength < 0) {
689 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
690 if (copybytes > len) copybytes = len;
691 else if (copybytes < 0) copybytes = 0;
692 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
693 } else {
694 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
697 return start;
699 /* search and replace manipulation */
700 } else {
701 WCHAR *equalspos = strstrW(colonpos, equalsW);
702 WCHAR *replacewith = equalspos+1;
703 WCHAR *found = NULL;
704 WCHAR *searchIn;
705 WCHAR *searchFor;
707 if (equalspos == NULL) return start+1;
708 s = WCMD_strdupW(endOfVar + 1);
710 /* Null terminate both strings */
711 thisVar[strlenW(thisVar)-1] = 0x00;
712 *equalspos = 0x00;
714 /* Since we need to be case insensitive, copy the 2 buffers */
715 searchIn = WCMD_strdupW(thisVarContents);
716 CharUpperBuffW(searchIn, strlenW(thisVarContents));
717 searchFor = WCMD_strdupW(colonpos+1);
718 CharUpperBuffW(searchFor, strlenW(colonpos+1));
720 /* Handle wildcard case */
721 if (*(colonpos+1) == '*') {
722 /* Search for string to replace */
723 found = strstrW(searchIn, searchFor+1);
725 if (found) {
726 /* Do replacement */
727 strcpyW(start, replacewith);
728 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
729 strcatW(start, s);
730 } else {
731 /* Copy as is */
732 strcpyW(start, thisVarContents);
733 strcatW(start, s);
736 } else {
737 /* Loop replacing all instances */
738 WCHAR *lastFound = searchIn;
739 WCHAR *outputposn = start;
741 *start = 0x00;
742 while ((found = strstrW(lastFound, searchFor))) {
743 lstrcpynW(outputposn,
744 thisVarContents + (lastFound-searchIn),
745 (found - lastFound)+1);
746 outputposn = outputposn + (found - lastFound);
747 strcatW(outputposn, replacewith);
748 outputposn = outputposn + strlenW(replacewith);
749 lastFound = found + strlenW(searchFor);
751 strcatW(outputposn,
752 thisVarContents + (lastFound-searchIn));
753 strcatW(outputposn, s);
755 HeapFree(GetProcessHeap(), 0, s);
756 HeapFree(GetProcessHeap(), 0, searchIn);
757 HeapFree(GetProcessHeap(), 0, searchFor);
758 return start;
760 return start+1;
763 /*****************************************************************************
764 * Expand the command. Native expands lines from batch programs as they are
765 * read in and not again, except for 'for' variable substitution.
766 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
768 static void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
770 /* For commands in a context (batch program): */
771 /* Expand environment variables in a batch file %{0-9} first */
772 /* including support for any ~ modifiers */
773 /* Additionally: */
774 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
775 /* names allowing environment variable overrides */
776 /* NOTE: To support the %PATH:xxx% syntax, also perform */
777 /* manual expansion of environment variables here */
779 WCHAR *p = cmd;
780 WCHAR *t;
781 int i;
783 while ((p = strchrW(p, '%'))) {
785 WINE_TRACE("Translate command:%s %d (at: %s)\n",
786 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
787 i = *(p+1) - '0';
789 /* Don't touch %% unless its in Batch */
790 if (!justFors && *(p+1) == '%') {
791 if (context) {
792 WCMD_strsubstW(p, p+1, NULL, 0);
794 p+=1;
796 /* Replace %~ modifications if in batch program */
797 } else if (*(p+1) == '~') {
798 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
799 p++;
801 /* Replace use of %0...%9 if in batch program*/
802 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
803 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
804 WCMD_strsubstW(p, p+2, t, -1);
806 /* Replace use of %* if in batch program*/
807 } else if (!justFors && context && *(p+1)=='*') {
808 WCHAR *startOfParms = NULL;
809 t = WCMD_parameter (context -> command, 1, &startOfParms);
810 if (startOfParms != NULL)
811 WCMD_strsubstW(p, p+2, startOfParms, -1);
812 else
813 WCMD_strsubstW(p, p+2, NULL, 0);
815 } else if (forVariable &&
816 (CompareStringW(LOCALE_USER_DEFAULT,
817 SORT_STRINGSORT,
819 strlenW(forVariable),
820 forVariable, -1) == 2)) {
821 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
823 } else if (!justFors) {
824 p = WCMD_expand_envvar(p, forVariable, forValue);
826 /* In a FOR loop, see if this is the variable to replace */
827 } else { /* Ignore %'s on second pass of batch program */
828 p++;
832 return;
836 /*******************************************************************
837 * WCMD_parse - parse a command into parameters and qualifiers.
839 * On exit, all qualifiers are concatenated into q, the first string
840 * not beginning with "/" is in p1 and the
841 * second in p2. Any subsequent non-qualifier strings are lost.
842 * Parameters in quotes are handled.
844 static void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
846 int p = 0;
848 *q = *p1 = *p2 = '\0';
849 while (TRUE) {
850 switch (*s) {
851 case '/':
852 *q++ = *s++;
853 while ((*s != '\0') && (*s != ' ') && *s != '/') {
854 *q++ = toupperW (*s++);
856 *q = '\0';
857 break;
858 case ' ':
859 case '\t':
860 s++;
861 break;
862 case '"':
863 s++;
864 while ((*s != '\0') && (*s != '"')) {
865 if (p == 0) *p1++ = *s++;
866 else if (p == 1) *p2++ = *s++;
867 else s++;
869 if (p == 0) *p1 = '\0';
870 if (p == 1) *p2 = '\0';
871 p++;
872 if (*s == '"') s++;
873 break;
874 case '\0':
875 return;
876 default:
877 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
878 && (*s != '=') && (*s != ',') ) {
879 if (p == 0) *p1++ = *s++;
880 else if (p == 1) *p2++ = *s++;
881 else s++;
883 /* Skip concurrent parms */
884 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
886 if (p == 0) *p1 = '\0';
887 if (p == 1) *p2 = '\0';
888 p++;
893 static void init_msvcrt_io_block(STARTUPINFOW* st)
895 STARTUPINFOW st_p;
896 /* fetch the parent MSVCRT info block if any, so that the child can use the
897 * same handles as its grand-father
899 st_p.cb = sizeof(STARTUPINFOW);
900 GetStartupInfoW(&st_p);
901 st->cbReserved2 = st_p.cbReserved2;
902 st->lpReserved2 = st_p.lpReserved2;
903 if (st_p.cbReserved2 && st_p.lpReserved2)
905 /* Override the entries for fd 0,1,2 if we happened
906 * to change those std handles (this depends on the way wcmd sets
907 * it's new input & output handles)
909 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
910 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
911 if (ptr)
913 unsigned num = *(unsigned*)st_p.lpReserved2;
914 char* flags = (char*)(ptr + sizeof(unsigned));
915 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
917 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
918 st->cbReserved2 = sz;
919 st->lpReserved2 = ptr;
921 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
922 if (num <= 0 || (flags[0] & WX_OPEN))
924 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
925 flags[0] |= WX_OPEN;
927 if (num <= 1 || (flags[1] & WX_OPEN))
929 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
930 flags[1] |= WX_OPEN;
932 if (num <= 2 || (flags[2] & WX_OPEN))
934 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
935 flags[2] |= WX_OPEN;
937 #undef WX_OPEN
942 /******************************************************************************
943 * WCMD_run_program
945 * Execute a command line as an external program. Must allow recursion.
947 * Precedence:
948 * Manual testing under windows shows PATHEXT plays a key part in this,
949 * and the search algorithm and precedence appears to be as follows.
951 * Search locations:
952 * If directory supplied on command, just use that directory
953 * If extension supplied on command, look for that explicit name first
954 * Otherwise, search in each directory on the path
955 * Precedence:
956 * If extension supplied on command, look for that explicit name first
957 * Then look for supplied name .* (even if extension supplied, so
958 * 'garbage.exe' will match 'garbage.exe.cmd')
959 * If any found, cycle through PATHEXT looking for name.exe one by one
960 * Launching
961 * Once a match has been found, it is launched - Code currently uses
962 * findexecutable to achieve this which is left untouched.
965 void WCMD_run_program (WCHAR *command, int called) {
967 WCHAR temp[MAX_PATH];
968 WCHAR pathtosearch[MAXSTRING];
969 WCHAR *pathposn;
970 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
971 MAX_PATH, including null character */
972 WCHAR *lastSlash;
973 WCHAR pathext[MAXSTRING];
974 BOOL extensionsupplied = FALSE;
975 BOOL launched = FALSE;
976 BOOL status;
977 BOOL assumeInternal = FALSE;
978 DWORD len;
979 static const WCHAR envPath[] = {'P','A','T','H','\0'};
980 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
981 static const WCHAR delims[] = {'/','\\',':','\0'};
983 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
984 if (!(*param1) && !(*param2))
985 return;
987 /* Calculate the search path and stem to search for */
988 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
989 static const WCHAR curDir[] = {'.',';','\0'};
990 strcpyW(pathtosearch, curDir);
991 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
992 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
993 static const WCHAR curDir[] = {'.','\0'};
994 strcpyW (pathtosearch, curDir);
996 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
997 if (strlenW(param1) >= MAX_PATH)
999 WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
1000 return;
1003 strcpyW(stemofsearch, param1);
1005 } else {
1007 /* Convert eg. ..\fred to include a directory by removing file part */
1008 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1009 lastSlash = strrchrW(pathtosearch, '\\');
1010 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1011 strcpyW(stemofsearch, lastSlash+1);
1013 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1014 c:\windows\a.bat syntax */
1015 if (lastSlash) *(lastSlash + 1) = 0x00;
1018 /* Now extract PATHEXT */
1019 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1020 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1021 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1022 '.','c','o','m',';',
1023 '.','c','m','d',';',
1024 '.','e','x','e','\0'};
1025 strcpyW (pathext, dfltPathExt);
1028 /* Loop through the search path, dir by dir */
1029 pathposn = pathtosearch;
1030 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1031 wine_dbgstr_w(stemofsearch));
1032 while (!launched && pathposn) {
1034 WCHAR thisDir[MAX_PATH] = {'\0'};
1035 WCHAR *pos = NULL;
1036 BOOL found = FALSE;
1037 const WCHAR slashW[] = {'\\','\0'};
1039 /* Work on the first directory on the search path */
1040 pos = strchrW(pathposn, ';');
1041 if (pos) {
1042 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1043 thisDir[(pos-pathposn)] = 0x00;
1044 pathposn = pos+1;
1046 } else {
1047 strcpyW(thisDir, pathposn);
1048 pathposn = NULL;
1051 /* Since you can have eg. ..\.. on the path, need to expand
1052 to full information */
1053 strcpyW(temp, thisDir);
1054 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1056 /* 1. If extension supplied, see if that file exists */
1057 strcatW(thisDir, slashW);
1058 strcatW(thisDir, stemofsearch);
1059 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1061 /* 1. If extension supplied, see if that file exists */
1062 if (extensionsupplied) {
1063 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1064 found = TRUE;
1068 /* 2. Any .* matches? */
1069 if (!found) {
1070 HANDLE h;
1071 WIN32_FIND_DATAW finddata;
1072 static const WCHAR allFiles[] = {'.','*','\0'};
1074 strcatW(thisDir,allFiles);
1075 h = FindFirstFileW(thisDir, &finddata);
1076 FindClose(h);
1077 if (h != INVALID_HANDLE_VALUE) {
1079 WCHAR *thisExt = pathext;
1081 /* 3. Yes - Try each path ext */
1082 while (thisExt) {
1083 WCHAR *nextExt = strchrW(thisExt, ';');
1085 if (nextExt) {
1086 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1087 pos[(nextExt-thisExt)] = 0x00;
1088 thisExt = nextExt+1;
1089 } else {
1090 strcpyW(pos, thisExt);
1091 thisExt = NULL;
1094 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1095 found = TRUE;
1096 thisExt = NULL;
1102 /* Internal programs won't be picked up by this search, so even
1103 though not found, try one last createprocess and wait for it
1104 to complete.
1105 Note: Ideally we could tell between a console app (wait) and a
1106 windows app, but the API's for it fail in this case */
1107 if (!found && pathposn == NULL) {
1108 WINE_TRACE("ASSUMING INTERNAL\n");
1109 assumeInternal = TRUE;
1110 } else {
1111 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1114 /* Once found, launch it */
1115 if (found || assumeInternal) {
1116 STARTUPINFOW st;
1117 PROCESS_INFORMATION pe;
1118 SHFILEINFOW psfi;
1119 DWORD console;
1120 HINSTANCE hinst;
1121 WCHAR *ext = strrchrW( thisDir, '.' );
1122 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1123 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1125 launched = TRUE;
1127 /* Special case BAT and CMD */
1128 if (ext && !strcmpiW(ext, batExt)) {
1129 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1130 return;
1131 } else if (ext && !strcmpiW(ext, cmdExt)) {
1132 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1133 return;
1134 } else {
1136 /* thisDir contains the file to be launched, but with what?
1137 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1138 hinst = FindExecutableW (thisDir, NULL, temp);
1139 if ((INT_PTR)hinst < 32)
1140 console = 0;
1141 else
1142 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1144 ZeroMemory (&st, sizeof(STARTUPINFOW));
1145 st.cb = sizeof(STARTUPINFOW);
1146 init_msvcrt_io_block(&st);
1148 /* Launch the process and if a CUI wait on it to complete
1149 Note: Launching internal wine processes cannot specify a full path to exe */
1150 status = CreateProcessW(assumeInternal?NULL : thisDir,
1151 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1152 if ((opt_c || opt_k) && !opt_s && !status
1153 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1154 /* strip first and last quote WCHARacters and try again */
1155 WCMD_opt_s_strip_quotes(command);
1156 opt_s=1;
1157 WCMD_run_program(command, called);
1158 return;
1161 if (!status)
1162 break;
1164 if (!assumeInternal && !console) errorlevel = 0;
1165 else
1167 /* Always wait when called in a batch program context */
1168 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1169 GetExitCodeProcess (pe.hProcess, &errorlevel);
1170 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1172 CloseHandle(pe.hProcess);
1173 CloseHandle(pe.hThread);
1174 return;
1179 /* Not found anywhere - give up */
1180 SetLastError(ERROR_FILE_NOT_FOUND);
1181 WCMD_print_error ();
1183 /* If a command fails to launch, it sets errorlevel 9009 - which
1184 does not seem to have any associated constant definition */
1185 errorlevel = 9009;
1186 return;
1190 /*****************************************************************************
1191 * Process one command. If the command is EXIT this routine does not return.
1192 * We will recurse through here executing batch files.
1194 void WCMD_execute (WCHAR *command, WCHAR *redirects,
1195 WCHAR *forVariable, WCHAR *forValue,
1196 CMD_LIST **cmdList)
1198 WCHAR *cmd, *p, *redir;
1199 int status, i;
1200 DWORD count, creationDisposition;
1201 HANDLE h;
1202 WCHAR *whichcmd;
1203 SECURITY_ATTRIBUTES sa;
1204 WCHAR *new_cmd = NULL;
1205 WCHAR *new_redir = NULL;
1206 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1207 GetStdHandle (STD_OUTPUT_HANDLE),
1208 GetStdHandle (STD_ERROR_HANDLE)};
1209 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1210 STD_OUTPUT_HANDLE,
1211 STD_ERROR_HANDLE};
1212 BOOL piped = FALSE;
1214 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1215 wine_dbgstr_w(command), cmdList,
1216 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1218 /* If the next command is a pipe then we implement pipes by redirecting
1219 the output from this command to a temp file and input into the
1220 next command from that temp file.
1221 FIXME: Use of named pipes would make more sense here as currently this
1222 process has to finish before the next one can start but this requires
1223 a change to not wait for the first app to finish but rather the pipe */
1224 if (cmdList && (*cmdList)->nextcommand &&
1225 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1227 WCHAR temp_path[MAX_PATH];
1228 static const WCHAR cmdW[] = {'C','M','D','\0'};
1230 /* Remember piping is in action */
1231 WINE_TRACE("Output needs to be piped\n");
1232 piped = TRUE;
1234 /* Generate a unique temporary filename */
1235 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1236 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1237 WINE_TRACE("Using temporary file of %s\n",
1238 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1241 /* Move copy of the command onto the heap so it can be expanded */
1242 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1243 if (!new_cmd)
1245 WINE_ERR("Could not allocate memory for new_cmd\n");
1246 return;
1248 strcpyW(new_cmd, command);
1250 /* Move copy of the redirects onto the heap so it can be expanded */
1251 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1252 if (!new_redir)
1254 WINE_ERR("Could not allocate memory for new_redir\n");
1255 HeapFree( GetProcessHeap(), 0, new_cmd );
1256 return;
1259 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1260 if (piped) {
1261 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1262 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1263 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1264 } else {
1265 strcpyW(new_redir, redirects);
1268 /* Expand variables in command line mode only (batch mode will
1269 be expanded as the line is read in, except for 'for' loops) */
1270 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1271 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1272 cmd = new_cmd;
1275 * Changing default drive has to be handled as a special case.
1278 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1279 WCHAR envvar[5];
1280 WCHAR dir[MAX_PATH];
1282 /* According to MSDN CreateProcess docs, special env vars record
1283 the current directory on each drive, in the form =C:
1284 so see if one specified, and if so go back to it */
1285 strcpyW(envvar, equalsW);
1286 strcatW(envvar, cmd);
1287 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1288 static const WCHAR fmt[] = {'%','s','\\','\0'};
1289 wsprintfW(cmd, fmt, cmd);
1290 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1292 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1293 status = SetCurrentDirectoryW(cmd);
1294 if (!status) WCMD_print_error ();
1295 HeapFree( GetProcessHeap(), 0, cmd );
1296 HeapFree( GetProcessHeap(), 0, new_redir );
1297 return;
1300 sa.nLength = sizeof(sa);
1301 sa.lpSecurityDescriptor = NULL;
1302 sa.bInheritHandle = TRUE;
1305 * Redirect stdin, stdout and/or stderr if required.
1308 /* STDIN could come from a preceding pipe, so delete on close if it does */
1309 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1310 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1311 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1312 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1313 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1314 if (h == INVALID_HANDLE_VALUE) {
1315 WCMD_print_error ();
1316 HeapFree( GetProcessHeap(), 0, cmd );
1317 HeapFree( GetProcessHeap(), 0, new_redir );
1318 return;
1320 SetStdHandle (STD_INPUT_HANDLE, h);
1322 /* No need to remember the temporary name any longer once opened */
1323 (*cmdList)->pipeFile[0] = 0x00;
1325 /* Otherwise STDIN could come from a '<' redirect */
1326 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1327 h = CreateFileW(WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
1328 FILE_ATTRIBUTE_NORMAL, NULL);
1329 if (h == INVALID_HANDLE_VALUE) {
1330 WCMD_print_error ();
1331 HeapFree( GetProcessHeap(), 0, cmd );
1332 HeapFree( GetProcessHeap(), 0, new_redir );
1333 return;
1335 SetStdHandle (STD_INPUT_HANDLE, h);
1338 /* Scan the whole command looking for > and 2> */
1339 redir = new_redir;
1340 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1341 int handle = 0;
1343 if (*(p-1)!='2') {
1344 handle = 1;
1345 } else {
1346 handle = 2;
1349 p++;
1350 if ('>' == *p) {
1351 creationDisposition = OPEN_ALWAYS;
1352 p++;
1354 else {
1355 creationDisposition = CREATE_ALWAYS;
1358 /* Add support for 2>&1 */
1359 redir = p;
1360 if (*p == '&') {
1361 int idx = *(p+1) - '0';
1363 if (DuplicateHandle(GetCurrentProcess(),
1364 GetStdHandle(idx_stdhandles[idx]),
1365 GetCurrentProcess(),
1367 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1368 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1370 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1372 } else {
1373 WCHAR *param = WCMD_parameter (p, 0, NULL);
1374 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1375 FILE_ATTRIBUTE_NORMAL, NULL);
1376 if (h == INVALID_HANDLE_VALUE) {
1377 WCMD_print_error ();
1378 HeapFree( GetProcessHeap(), 0, cmd );
1379 HeapFree( GetProcessHeap(), 0, new_redir );
1380 return;
1382 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1383 INVALID_SET_FILE_POINTER) {
1384 WCMD_print_error ();
1386 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1389 SetStdHandle (idx_stdhandles[handle], h);
1393 * Strip leading whitespaces, and a '@' if supplied
1395 whichcmd = WCMD_strtrim_leading_spaces(cmd);
1396 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1397 if (whichcmd[0] == '@') whichcmd++;
1400 * Check if the command entered is internal. If it is, pass the rest of the
1401 * line down to the command. If not try to run a program.
1404 count = 0;
1405 while (IsCharAlphaNumericW(whichcmd[count])) {
1406 count++;
1408 for (i=0; i<=WCMD_EXIT; i++) {
1409 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1410 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1412 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
1413 WCMD_parse (p, quals, param1, param2);
1414 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1416 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1417 /* this is a help request for a builtin program */
1418 i = WCMD_HELP;
1419 memcpy(p, whichcmd, count * sizeof(WCHAR));
1420 p[count] = '\0';
1424 switch (i) {
1426 case WCMD_ATTRIB:
1427 WCMD_setshow_attrib ();
1428 break;
1429 case WCMD_CALL:
1430 WCMD_call (p);
1431 break;
1432 case WCMD_CD:
1433 case WCMD_CHDIR:
1434 WCMD_setshow_default (p);
1435 break;
1436 case WCMD_CLS:
1437 WCMD_clear_screen ();
1438 break;
1439 case WCMD_COPY:
1440 WCMD_copy ();
1441 break;
1442 case WCMD_CTTY:
1443 WCMD_change_tty ();
1444 break;
1445 case WCMD_DATE:
1446 WCMD_setshow_date ();
1447 break;
1448 case WCMD_DEL:
1449 case WCMD_ERASE:
1450 WCMD_delete (p);
1451 break;
1452 case WCMD_DIR:
1453 WCMD_directory (p);
1454 break;
1455 case WCMD_ECHO:
1456 WCMD_echo(&whichcmd[count]);
1457 break;
1458 case WCMD_FOR:
1459 WCMD_for (p, cmdList);
1460 break;
1461 case WCMD_GOTO:
1462 WCMD_goto (cmdList);
1463 break;
1464 case WCMD_HELP:
1465 WCMD_give_help (p);
1466 break;
1467 case WCMD_IF:
1468 WCMD_if (p, cmdList);
1469 break;
1470 case WCMD_LABEL:
1471 WCMD_volume (1, p);
1472 break;
1473 case WCMD_MD:
1474 case WCMD_MKDIR:
1475 WCMD_create_dir ();
1476 break;
1477 case WCMD_MOVE:
1478 WCMD_move ();
1479 break;
1480 case WCMD_PATH:
1481 WCMD_setshow_path (p);
1482 break;
1483 case WCMD_PAUSE:
1484 WCMD_pause ();
1485 break;
1486 case WCMD_PROMPT:
1487 WCMD_setshow_prompt ();
1488 break;
1489 case WCMD_REM:
1490 break;
1491 case WCMD_REN:
1492 case WCMD_RENAME:
1493 WCMD_rename ();
1494 break;
1495 case WCMD_RD:
1496 case WCMD_RMDIR:
1497 WCMD_remove_dir (p);
1498 break;
1499 case WCMD_SETLOCAL:
1500 WCMD_setlocal(p);
1501 break;
1502 case WCMD_ENDLOCAL:
1503 WCMD_endlocal();
1504 break;
1505 case WCMD_SET:
1506 WCMD_setshow_env (p);
1507 break;
1508 case WCMD_SHIFT:
1509 WCMD_shift (p);
1510 break;
1511 case WCMD_TIME:
1512 WCMD_setshow_time ();
1513 break;
1514 case WCMD_TITLE:
1515 if (strlenW(&whichcmd[count]) > 0)
1516 WCMD_title(&whichcmd[count+1]);
1517 break;
1518 case WCMD_TYPE:
1519 WCMD_type (p);
1520 break;
1521 case WCMD_VER:
1522 WCMD_version ();
1523 break;
1524 case WCMD_VERIFY:
1525 WCMD_verify (p);
1526 break;
1527 case WCMD_VOL:
1528 WCMD_volume (0, p);
1529 break;
1530 case WCMD_PUSHD:
1531 WCMD_pushd(p);
1532 break;
1533 case WCMD_POPD:
1534 WCMD_popd();
1535 break;
1536 case WCMD_ASSOC:
1537 WCMD_assoc(p, TRUE);
1538 break;
1539 case WCMD_COLOR:
1540 WCMD_color();
1541 break;
1542 case WCMD_FTYPE:
1543 WCMD_assoc(p, FALSE);
1544 break;
1545 case WCMD_MORE:
1546 WCMD_more(p);
1547 break;
1548 case WCMD_CHOICE:
1549 WCMD_choice(p);
1550 break;
1551 case WCMD_EXIT:
1552 WCMD_exit (cmdList);
1553 break;
1554 default:
1555 WCMD_run_program (whichcmd, 0);
1557 HeapFree( GetProcessHeap(), 0, cmd );
1558 HeapFree( GetProcessHeap(), 0, new_redir );
1560 /* Restore old handles */
1561 for (i=0; i<3; i++) {
1562 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1563 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1564 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1568 /*************************************************************************
1569 * WCMD_LoadMessage
1570 * Load a string from the resource file, handling any error
1571 * Returns string retrieved from resource file
1573 WCHAR *WCMD_LoadMessage(UINT id) {
1574 static WCHAR msg[2048];
1575 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1577 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1578 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1579 strcpyW(msg, failedMsg);
1581 return msg;
1584 /***************************************************************************
1585 * WCMD_DumpCommands
1587 * Dumps out the parsed command line to ensure syntax is correct
1589 static void WCMD_DumpCommands(CMD_LIST *commands) {
1590 CMD_LIST *thisCmd = commands;
1592 WINE_TRACE("Parsed line:\n");
1593 while (thisCmd != NULL) {
1594 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1595 thisCmd,
1596 thisCmd->prevDelim,
1597 thisCmd->bracketDepth,
1598 thisCmd->nextcommand,
1599 wine_dbgstr_w(thisCmd->command),
1600 wine_dbgstr_w(thisCmd->redirects));
1601 thisCmd = thisCmd->nextcommand;
1605 /***************************************************************************
1606 * WCMD_addCommand
1608 * Adds a command to the current command list
1610 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1611 WCHAR *redirs, int *redirLen,
1612 WCHAR **copyTo, int **copyToLen,
1613 CMD_DELIMITERS prevDelim, int curDepth,
1614 CMD_LIST **lastEntry, CMD_LIST **output) {
1616 CMD_LIST *thisEntry = NULL;
1618 /* Allocate storage for command */
1619 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1621 /* Copy in the command */
1622 if (command) {
1623 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1624 (*commandLen+1) * sizeof(WCHAR));
1625 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1626 thisEntry->command[*commandLen] = 0x00;
1628 /* Copy in the redirects */
1629 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1630 (*redirLen+1) * sizeof(WCHAR));
1631 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1632 thisEntry->redirects[*redirLen] = 0x00;
1633 thisEntry->pipeFile[0] = 0x00;
1635 /* Reset the lengths */
1636 *commandLen = 0;
1637 *redirLen = 0;
1638 *copyToLen = commandLen;
1639 *copyTo = command;
1641 } else {
1642 thisEntry->command = NULL;
1643 thisEntry->redirects = NULL;
1644 thisEntry->pipeFile[0] = 0x00;
1647 /* Fill in other fields */
1648 thisEntry->nextcommand = NULL;
1649 thisEntry->prevDelim = prevDelim;
1650 thisEntry->bracketDepth = curDepth;
1651 if (*lastEntry) {
1652 (*lastEntry)->nextcommand = thisEntry;
1653 } else {
1654 *output = thisEntry;
1656 *lastEntry = thisEntry;
1660 /***************************************************************************
1661 * WCMD_IsEndQuote
1663 * Checks if the quote pointet to is the end-quote.
1665 * Quotes end if:
1667 * 1) The current parameter ends at EOL or at the beginning
1668 * of a redirection or pipe and not in a quote section.
1670 * 2) If the next character is a space and not in a quote section.
1672 * Returns TRUE if this is an end quote, and FALSE if it is not.
1675 static BOOL WCMD_IsEndQuote(WCHAR *quote, int quoteIndex)
1677 int quoteCount = quoteIndex;
1678 int i;
1680 /* If we are not in a quoted section, then we are not an end-quote */
1681 if(quoteIndex == 0)
1683 return FALSE;
1686 /* Check how many quotes are left for this parameter */
1687 for(i=0;quote[i];i++)
1689 if(quote[i] == '"')
1691 quoteCount++;
1694 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1695 else if(((quoteCount % 2) == 0)
1696 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1698 break;
1702 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1703 be an end-quote */
1704 if(quoteIndex >= (quoteCount / 2))
1706 return TRUE;
1709 /* No cigar */
1710 return FALSE;
1713 /***************************************************************************
1714 * WCMD_ReadAndParseLine
1716 * Either uses supplied input or
1717 * Reads a file from the handle, and then...
1718 * Parse the text buffer, spliting into separate commands
1719 * - unquoted && strings split 2 commands but the 2nd is flagged as
1720 * following an &&
1721 * - ( as the first character just ups the bracket depth
1722 * - unquoted ) when bracket depth > 0 terminates a bracket and
1723 * adds a CMD_LIST structure with null command
1724 * - Anything else gets put into the command string (including
1725 * redirects)
1727 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1729 WCHAR *curPos;
1730 int inQuotes = 0;
1731 WCHAR curString[MAXSTRING];
1732 int curStringLen = 0;
1733 WCHAR curRedirs[MAXSTRING];
1734 int curRedirsLen = 0;
1735 WCHAR *curCopyTo;
1736 int *curLen;
1737 int curDepth = 0;
1738 CMD_LIST *lastEntry = NULL;
1739 CMD_DELIMITERS prevDelim = CMD_NONE;
1740 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1741 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1742 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1743 const WCHAR ifCmd[] = {'i','f',' ','\0'};
1744 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1745 BOOL inRem = FALSE;
1746 BOOL inFor = FALSE;
1747 BOOL inIn = FALSE;
1748 BOOL inIf = FALSE;
1749 BOOL inElse= FALSE;
1750 BOOL onlyWhiteSpace = FALSE;
1751 BOOL lastWasWhiteSpace = FALSE;
1752 BOOL lastWasDo = FALSE;
1753 BOOL lastWasIn = FALSE;
1754 BOOL lastWasElse = FALSE;
1755 BOOL lastWasRedirect = TRUE;
1757 /* Allocate working space for a command read from keyboard, file etc */
1758 if (!extraSpace)
1759 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1760 if (!extraSpace)
1762 WINE_ERR("Could not allocate memory for extraSpace\n");
1763 return NULL;
1766 /* If initial command read in, use that, otherwise get input from handle */
1767 if (optionalcmd != NULL) {
1768 strcpyW(extraSpace, optionalcmd);
1769 } else if (readFrom == INVALID_HANDLE_VALUE) {
1770 WINE_FIXME("No command nor handle supplied\n");
1771 } else {
1772 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1774 curPos = extraSpace;
1776 /* Handle truncated input - issue warning */
1777 if (strlenW(extraSpace) == MAXSTRING -1) {
1778 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1779 WCMD_output_asis(extraSpace);
1780 WCMD_output_asis(newline);
1783 /* Replace env vars if in a batch context */
1784 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1785 /* Show prompt before batch line IF echo is on and in batch program */
1786 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1787 const WCHAR spc[]={' ','\0'};
1788 WCMD_show_prompt();
1789 WCMD_output_asis(extraSpace);
1790 /* I don't know why Windows puts a space here but it does */
1791 WCMD_output_asis(spc);
1792 WCMD_output_asis(newline);
1795 /* Start with an empty string, copying to the command string */
1796 curStringLen = 0;
1797 curRedirsLen = 0;
1798 curCopyTo = curString;
1799 curLen = &curStringLen;
1800 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1802 /* Parse every character on the line being processed */
1803 while (*curPos != 0x00) {
1805 WCHAR thisChar;
1807 /* Debugging AID:
1808 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1809 lastWasWhiteSpace, onlyWhiteSpace);
1812 /* Certain commands need special handling */
1813 if (curStringLen == 0 && curCopyTo == curString) {
1814 const WCHAR forDO[] = {'d','o',' ','\0'};
1816 /* If command starts with 'rem', ignore any &&, ( etc */
1817 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1818 curPos, 4, remCmd, -1) == 2) {
1819 inRem = TRUE;
1821 /* If command starts with 'for', handle ('s mid line after IN or DO */
1822 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1823 curPos, 4, forCmd, -1) == 2) {
1824 inFor = TRUE;
1826 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1827 is only true in the command portion of the IF statement, but this
1828 should suffice for now
1829 FIXME: Silly syntax like "if 1(==1( (
1830 echo they equal
1831 )" will be parsed wrong */
1832 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1833 curPos, 3, ifCmd, -1) == 2) {
1834 inIf = TRUE;
1836 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1837 curPos, 5, ifElse, -1) == 2) {
1838 inElse = TRUE;
1839 lastWasElse = TRUE;
1840 onlyWhiteSpace = TRUE;
1841 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1842 (*curLen)+=5;
1843 curPos+=5;
1844 continue;
1846 /* In a for loop, the DO command will follow a close bracket followed by
1847 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1848 is then 0, and all whitespace is skipped */
1849 } else if (inFor &&
1850 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1851 curPos, 3, forDO, -1) == 2)) {
1852 WINE_TRACE("Found DO\n");
1853 lastWasDo = TRUE;
1854 onlyWhiteSpace = TRUE;
1855 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1856 (*curLen)+=3;
1857 curPos+=3;
1858 continue;
1860 } else if (curCopyTo == curString) {
1862 /* Special handling for the 'FOR' command */
1863 if (inFor && lastWasWhiteSpace) {
1864 const WCHAR forIN[] = {'i','n',' ','\0'};
1866 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1868 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1869 curPos, 3, forIN, -1) == 2) {
1870 WINE_TRACE("Found IN\n");
1871 lastWasIn = TRUE;
1872 onlyWhiteSpace = TRUE;
1873 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1874 (*curLen)+=3;
1875 curPos+=3;
1876 continue;
1881 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1882 so just use the default processing ie skip character specific
1883 matching below */
1884 if (!inRem) thisChar = *curPos;
1885 else thisChar = 'X'; /* Character with no special processing */
1887 lastWasWhiteSpace = FALSE; /* Will be reset below */
1889 switch (thisChar) {
1891 case '=': /* drop through - ignore token delimiters at the start of a command */
1892 case ',': /* drop through - ignore token delimiters at the start of a command */
1893 case '\t':/* drop through - ignore token delimiters at the start of a command */
1894 case ' ':
1895 /* If a redirect in place, it ends here */
1896 if (!inQuotes && !lastWasRedirect) {
1898 /* If finishing off a redirect, add a whitespace delimiter */
1899 if (curCopyTo == curRedirs) {
1900 curCopyTo[(*curLen)++] = ' ';
1902 curCopyTo = curString;
1903 curLen = &curStringLen;
1905 if (*curLen > 0) {
1906 curCopyTo[(*curLen)++] = *curPos;
1909 /* Remember just processed whitespace */
1910 lastWasWhiteSpace = TRUE;
1912 break;
1914 case '>': /* drop through - handle redirect chars the same */
1915 case '<':
1916 /* Make a redirect start here */
1917 if (!inQuotes) {
1918 curCopyTo = curRedirs;
1919 curLen = &curRedirsLen;
1920 lastWasRedirect = TRUE;
1923 /* See if 1>, 2> etc, in which case we have some patching up
1924 to do */
1925 if (curPos != extraSpace &&
1926 *(curPos-1)>='1' && *(curPos-1)<='9') {
1928 curStringLen--;
1929 curString[curStringLen] = 0x00;
1930 curCopyTo[(*curLen)++] = *(curPos-1);
1933 curCopyTo[(*curLen)++] = *curPos;
1935 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1936 do not process that ampersand as an AND operator */
1937 if (thisChar == '>' && *(curPos+1) == '&') {
1938 curCopyTo[(*curLen)++] = *(curPos+1);
1939 curPos++;
1941 break;
1943 case '|': /* Pipe character only if not || */
1944 if (!inQuotes) {
1945 lastWasRedirect = FALSE;
1947 /* Add an entry to the command list */
1948 if (curStringLen > 0) {
1950 /* Add the current command */
1951 WCMD_addCommand(curString, &curStringLen,
1952 curRedirs, &curRedirsLen,
1953 &curCopyTo, &curLen,
1954 prevDelim, curDepth,
1955 &lastEntry, output);
1959 if (*(curPos+1) == '|') {
1960 curPos++; /* Skip other | */
1961 prevDelim = CMD_ONFAILURE;
1962 } else {
1963 prevDelim = CMD_PIPE;
1965 } else {
1966 curCopyTo[(*curLen)++] = *curPos;
1968 break;
1970 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1971 inQuotes--;
1972 } else {
1973 inQuotes++; /* Quotes within quotes are fun! */
1975 curCopyTo[(*curLen)++] = *curPos;
1976 lastWasRedirect = FALSE;
1977 break;
1979 case '(': /* If a '(' is the first non whitespace in a command portion
1980 ie start of line or just after &&, then we read until an
1981 unquoted ) is found */
1982 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1983 ", for(%d, In:%d, Do:%d)"
1984 ", if(%d, else:%d, lwe:%d)\n",
1985 *curLen, inQuotes,
1986 onlyWhiteSpace,
1987 inFor, lastWasIn, lastWasDo,
1988 inIf, inElse, lastWasElse);
1989 lastWasRedirect = FALSE;
1991 /* Ignore open brackets inside the for set */
1992 if (*curLen == 0 && !inIn) {
1993 curDepth++;
1995 /* If in quotes, ignore brackets */
1996 } else if (inQuotes) {
1997 curCopyTo[(*curLen)++] = *curPos;
1999 /* In a FOR loop, an unquoted '(' may occur straight after
2000 IN or DO
2001 In an IF statement just handle it regardless as we don't
2002 parse the operands
2003 In an ELSE statement, only allow it straight away after
2004 the ELSE and whitespace
2006 } else if (inIf ||
2007 (inElse && lastWasElse && onlyWhiteSpace) ||
2008 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2010 /* If entering into an 'IN', set inIn */
2011 if (inFor && lastWasIn && onlyWhiteSpace) {
2012 WINE_TRACE("Inside an IN\n");
2013 inIn = TRUE;
2016 /* Add the current command */
2017 WCMD_addCommand(curString, &curStringLen,
2018 curRedirs, &curRedirsLen,
2019 &curCopyTo, &curLen,
2020 prevDelim, curDepth,
2021 &lastEntry, output);
2023 curDepth++;
2024 } else {
2025 curCopyTo[(*curLen)++] = *curPos;
2027 break;
2029 case '&': if (!inQuotes) {
2030 lastWasRedirect = FALSE;
2032 /* Add an entry to the command list */
2033 if (curStringLen > 0) {
2035 /* Add the current command */
2036 WCMD_addCommand(curString, &curStringLen,
2037 curRedirs, &curRedirsLen,
2038 &curCopyTo, &curLen,
2039 prevDelim, curDepth,
2040 &lastEntry, output);
2044 if (*(curPos+1) == '&') {
2045 curPos++; /* Skip other & */
2046 prevDelim = CMD_ONSUCCESS;
2047 } else {
2048 prevDelim = CMD_NONE;
2050 } else {
2051 curCopyTo[(*curLen)++] = *curPos;
2053 break;
2055 case ')': if (!inQuotes && curDepth > 0) {
2056 lastWasRedirect = FALSE;
2058 /* Add the current command if there is one */
2059 if (curStringLen) {
2061 /* Add the current command */
2062 WCMD_addCommand(curString, &curStringLen,
2063 curRedirs, &curRedirsLen,
2064 &curCopyTo, &curLen,
2065 prevDelim, curDepth,
2066 &lastEntry, output);
2069 /* Add an empty entry to the command list */
2070 prevDelim = CMD_NONE;
2071 WCMD_addCommand(NULL, &curStringLen,
2072 curRedirs, &curRedirsLen,
2073 &curCopyTo, &curLen,
2074 prevDelim, curDepth,
2075 &lastEntry, output);
2076 curDepth--;
2078 /* Leave inIn if necessary */
2079 if (inIn) inIn = FALSE;
2080 } else {
2081 curCopyTo[(*curLen)++] = *curPos;
2083 break;
2084 default:
2085 lastWasRedirect = FALSE;
2086 curCopyTo[(*curLen)++] = *curPos;
2089 curPos++;
2091 /* At various times we need to know if we have only skipped whitespace,
2092 so reset this variable and then it will remain true until a non
2093 whitespace is found */
2094 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2096 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2097 if (!lastWasWhiteSpace) {
2098 lastWasIn = lastWasDo = FALSE;
2101 /* If we have reached the end, add this command into the list */
2102 if (*curPos == 0x00 && *curLen > 0) {
2104 /* Add an entry to the command list */
2105 WCMD_addCommand(curString, &curStringLen,
2106 curRedirs, &curRedirsLen,
2107 &curCopyTo, &curLen,
2108 prevDelim, curDepth,
2109 &lastEntry, output);
2112 /* If we have reached the end of the string, see if bracketing outstanding */
2113 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2114 inRem = FALSE;
2115 prevDelim = CMD_NONE;
2116 inQuotes = 0;
2117 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2119 /* Read more, skipping any blank lines */
2120 while (*extraSpace == 0x00) {
2121 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2122 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2124 curPos = extraSpace;
2125 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2126 /* Continue to echo commands IF echo is on and in batch program */
2127 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2128 WCMD_output_asis(extraSpace);
2129 WCMD_output_asis(newline);
2134 /* Dump out the parsed output */
2135 WCMD_DumpCommands(*output);
2137 return extraSpace;
2140 /***************************************************************************
2141 * WCMD_process_commands
2143 * Process all the commands read in so far
2145 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2146 WCHAR *var, WCHAR *val) {
2148 int bdepth = -1;
2150 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2152 /* Loop through the commands, processing them one by one */
2153 while (thisCmd) {
2155 CMD_LIST *origCmd = thisCmd;
2157 /* If processing one bracket only, and we find the end bracket
2158 entry (or less), return */
2159 if (oneBracket && !thisCmd->command &&
2160 bdepth <= thisCmd->bracketDepth) {
2161 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2162 thisCmd, thisCmd->nextcommand);
2163 return thisCmd->nextcommand;
2166 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2167 about them and it will be handled in there)
2168 Also, skip over any batch labels (eg. :fred) */
2169 if (thisCmd->command && thisCmd->command[0] != ':') {
2170 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2171 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2174 /* Step on unless the command itself already stepped on */
2175 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2177 return NULL;
2180 /***************************************************************************
2181 * WCMD_free_commands
2183 * Frees the storage held for a parsed command line
2184 * - This is not done in the process_commands, as eventually the current
2185 * pointer will be modified within the commands, and hence a single free
2186 * routine is simpler
2188 void WCMD_free_commands(CMD_LIST *cmds) {
2190 /* Loop through the commands, freeing them one by one */
2191 while (cmds) {
2192 CMD_LIST *thisCmd = cmds;
2193 cmds = cmds->nextcommand;
2194 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2195 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2196 HeapFree(GetProcessHeap(), 0, thisCmd);
2201 /*****************************************************************************
2202 * Main entry point. This is a console application so we have a main() not a
2203 * winmain().
2206 int wmain (int argc, WCHAR *argvW[])
2208 int args;
2209 WCHAR *cmd = NULL;
2210 WCHAR string[1024];
2211 WCHAR envvar[4];
2212 HANDLE h;
2213 int opt_q;
2214 int opt_t = 0;
2215 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2216 'b','a','t','\0'};
2217 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2218 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2219 char ansiVersion[100];
2220 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2222 srand(time(NULL));
2224 /* Pre initialize some messages */
2225 strcpy(ansiVersion, PACKAGE_VERSION);
2226 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2227 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2228 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2230 args = argc;
2231 opt_c=opt_k=opt_q=opt_s=0;
2232 while (args > 0)
2234 WCHAR c;
2235 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2236 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2237 argvW++;
2238 args--;
2239 continue;
2242 c=(*argvW)[1];
2243 if (tolowerW(c)=='c') {
2244 opt_c=1;
2245 } else if (tolowerW(c)=='q') {
2246 opt_q=1;
2247 } else if (tolowerW(c)=='k') {
2248 opt_k=1;
2249 } else if (tolowerW(c)=='s') {
2250 opt_s=1;
2251 } else if (tolowerW(c)=='a') {
2252 unicodePipes=FALSE;
2253 } else if (tolowerW(c)=='u') {
2254 unicodePipes=TRUE;
2255 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2256 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2257 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2258 /* Ignored for compatibility with Windows */
2261 if ((*argvW)[2]==0) {
2262 argvW++;
2263 args--;
2265 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2267 *argvW+=2;
2270 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2271 break;
2274 if (opt_q) {
2275 const WCHAR eoff[] = {'O','F','F','\0'};
2276 WCMD_echo(eoff);
2279 if (opt_c || opt_k) {
2280 int len,qcount;
2281 WCHAR** arg;
2282 int argsLeft;
2283 WCHAR* p;
2285 /* opt_s left unflagged if the command starts with and contains exactly
2286 * one quoted string (exactly two quote characters). The quoted string
2287 * must be an executable name that has whitespace and must not have the
2288 * following characters: &<>()@^| */
2290 /* Build the command to execute */
2291 len = 0;
2292 qcount = 0;
2293 argsLeft = args;
2294 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2296 int has_space,bcount;
2297 WCHAR* a;
2299 has_space=0;
2300 bcount=0;
2301 a=*arg;
2302 if( !*a ) has_space=1;
2303 while (*a!='\0') {
2304 if (*a=='\\') {
2305 bcount++;
2306 } else {
2307 if (*a==' ' || *a=='\t') {
2308 has_space=1;
2309 } else if (*a=='"') {
2310 /* doubling of '\' preceding a '"',
2311 * plus escaping of said '"'
2313 len+=2*bcount+1;
2314 qcount++;
2316 bcount=0;
2318 a++;
2320 len+=(a-*arg) + 1; /* for the separating space */
2321 if (has_space)
2323 len+=2; /* for the quotes */
2324 qcount+=2;
2328 if (qcount!=2)
2329 opt_s=1;
2331 /* check argvW[0] for a space and invalid characters */
2332 if (!opt_s) {
2333 opt_s=1;
2334 p=*argvW;
2335 while (*p!='\0') {
2336 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2337 || *p=='@' || *p=='^' || *p=='|') {
2338 opt_s=1;
2339 break;
2341 if (*p==' ')
2342 opt_s=0;
2343 p++;
2347 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2348 if (!cmd)
2349 exit(1);
2351 p = cmd;
2352 argsLeft = args;
2353 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2355 int has_space,has_quote;
2356 WCHAR* a;
2358 /* Check for quotes and spaces in this argument */
2359 has_space=has_quote=0;
2360 a=*arg;
2361 if( !*a ) has_space=1;
2362 while (*a!='\0') {
2363 if (*a==' ' || *a=='\t') {
2364 has_space=1;
2365 if (has_quote)
2366 break;
2367 } else if (*a=='"') {
2368 has_quote=1;
2369 if (has_space)
2370 break;
2372 a++;
2375 /* Now transfer it to the command line */
2376 if (has_space)
2377 *p++='"';
2378 if (has_quote) {
2379 int bcount;
2380 WCHAR* a;
2382 bcount=0;
2383 a=*arg;
2384 while (*a!='\0') {
2385 if (*a=='\\') {
2386 *p++=*a;
2387 bcount++;
2388 } else {
2389 if (*a=='"') {
2390 int i;
2392 /* Double all the '\\' preceding this '"', plus one */
2393 for (i=0;i<=bcount;i++)
2394 *p++='\\';
2395 *p++='"';
2396 } else {
2397 *p++=*a;
2399 bcount=0;
2401 a++;
2403 } else {
2404 strcpyW(p,*arg);
2405 p+=strlenW(*arg);
2407 if (has_space)
2408 *p++='"';
2409 *p++=' ';
2411 if (p > cmd)
2412 p--; /* remove last space */
2413 *p = '\0';
2415 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2417 /* strip first and last quote characters if opt_s; check for invalid
2418 * executable is done later */
2419 if (opt_s && *cmd=='\"')
2420 WCMD_opt_s_strip_quotes(cmd);
2423 if (opt_c) {
2424 /* If we do a "wcmd /c command", we don't want to allocate a new
2425 * console since the command returns immediately. Rather, we use
2426 * the currently allocated input and output handles. This allows
2427 * us to pipe to and read from the command interpreter.
2430 /* Parse the command string, without reading any more input */
2431 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2432 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2433 WCMD_free_commands(toExecute);
2434 toExecute = NULL;
2436 HeapFree(GetProcessHeap(), 0, cmd);
2437 return errorlevel;
2440 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2441 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2442 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2444 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2445 if (opt_t) {
2446 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2447 defaultColor = opt_t & 0xFF;
2448 param1[0] = 0x00;
2449 WCMD_color();
2451 } else {
2452 /* Check HKCU\Software\Microsoft\Command Processor
2453 Then HKLM\Software\Microsoft\Command Processor
2454 for defaultcolour value
2455 Note Can be supplied as DWORD or REG_SZ
2456 Note2 When supplied as REG_SZ it's in decimal!!! */
2457 HKEY key;
2458 DWORD type;
2459 DWORD value=0, size=4;
2460 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2461 'M','i','c','r','o','s','o','f','t','\\',
2462 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2463 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2465 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2466 0, KEY_READ, &key) == ERROR_SUCCESS) {
2467 WCHAR strvalue[4];
2469 /* See if DWORD or REG_SZ */
2470 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2471 NULL, NULL) == ERROR_SUCCESS) {
2472 if (type == REG_DWORD) {
2473 size = sizeof(DWORD);
2474 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2475 (LPBYTE)&value, &size);
2476 } else if (type == REG_SZ) {
2477 size = sizeof(strvalue)/sizeof(WCHAR);
2478 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2479 (LPBYTE)strvalue, &size);
2480 value = strtoulW(strvalue, NULL, 10);
2483 RegCloseKey(key);
2486 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2487 0, KEY_READ, &key) == ERROR_SUCCESS) {
2488 WCHAR strvalue[4];
2490 /* See if DWORD or REG_SZ */
2491 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2492 NULL, NULL) == ERROR_SUCCESS) {
2493 if (type == REG_DWORD) {
2494 size = sizeof(DWORD);
2495 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2496 (LPBYTE)&value, &size);
2497 } else if (type == REG_SZ) {
2498 size = sizeof(strvalue)/sizeof(WCHAR);
2499 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2500 (LPBYTE)strvalue, &size);
2501 value = strtoulW(strvalue, NULL, 10);
2504 RegCloseKey(key);
2507 /* If one found, set the screen to that colour */
2508 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2509 defaultColor = value & 0xFF;
2510 param1[0] = 0x00;
2511 WCMD_color();
2516 /* Save cwd into appropriate env var */
2517 GetCurrentDirectoryW(1024, string);
2518 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2519 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2520 wsprintfW(envvar, fmt, string[0]);
2521 SetEnvironmentVariableW(envvar, string);
2522 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2525 if (opt_k) {
2526 /* Parse the command string, without reading any more input */
2527 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2528 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2529 WCMD_free_commands(toExecute);
2530 toExecute = NULL;
2531 HeapFree(GetProcessHeap(), 0, cmd);
2535 * If there is an AUTOEXEC.BAT file, try to execute it.
2538 GetFullPathNameW (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2539 h = CreateFileW(string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2540 if (h != INVALID_HANDLE_VALUE) {
2541 CloseHandle (h);
2542 #if 0
2543 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2544 #endif
2548 * Loop forever getting commands and executing them.
2551 SetEnvironmentVariableW(promptW, defaultpromptW);
2552 WCMD_version ();
2553 while (TRUE) {
2555 /* Read until EOF (which for std input is never, but if redirect
2556 in place, may occur */
2557 WCMD_show_prompt ();
2558 if (WCMD_ReadAndParseLine(NULL, &toExecute,
2559 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2560 break;
2561 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2562 WCMD_free_commands(toExecute);
2563 toExecute = NULL;
2565 return 0;