cmd: Get rid of unused parameter in WCMD_ReadFile.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
blob3f2cfcfa1afa684aa6220c997d879730c94da019
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 {'C','A','L','L','\0'},
36 {'C','D','\0'},
37 {'C','H','D','I','R','\0'},
38 {'C','L','S','\0'},
39 {'C','O','P','Y','\0'},
40 {'C','T','T','Y','\0'},
41 {'D','A','T','E','\0'},
42 {'D','E','L','\0'},
43 {'D','I','R','\0'},
44 {'E','C','H','O','\0'},
45 {'E','R','A','S','E','\0'},
46 {'F','O','R','\0'},
47 {'G','O','T','O','\0'},
48 {'H','E','L','P','\0'},
49 {'I','F','\0'},
50 {'L','A','B','E','L','\0'},
51 {'M','D','\0'},
52 {'M','K','D','I','R','\0'},
53 {'M','O','V','E','\0'},
54 {'P','A','T','H','\0'},
55 {'P','A','U','S','E','\0'},
56 {'P','R','O','M','P','T','\0'},
57 {'R','E','M','\0'},
58 {'R','E','N','\0'},
59 {'R','E','N','A','M','E','\0'},
60 {'R','D','\0'},
61 {'R','M','D','I','R','\0'},
62 {'S','E','T','\0'},
63 {'S','H','I','F','T','\0'},
64 {'T','I','M','E','\0'},
65 {'T','I','T','L','E','\0'},
66 {'T','Y','P','E','\0'},
67 {'V','E','R','I','F','Y','\0'},
68 {'V','E','R','\0'},
69 {'V','O','L','\0'},
70 {'E','N','D','L','O','C','A','L','\0'},
71 {'S','E','T','L','O','C','A','L','\0'},
72 {'P','U','S','H','D','\0'},
73 {'P','O','P','D','\0'},
74 {'A','S','S','O','C','\0'},
75 {'C','O','L','O','R','\0'},
76 {'F','T','Y','P','E','\0'},
77 {'M','O','R','E','\0'},
78 {'C','H','O','I','C','E','\0'},
79 {'E','X','I','T','\0'}
82 const WCHAR externals[NUM_EXTERNALS][10] = {
83 {'A','T','T','R','I','B','\0'},
84 {'X','C','O','P','Y','\0'}
87 HINSTANCE hinst;
88 DWORD errorlevel;
89 int defaultColor = 7;
90 BOOL echo_mode = TRUE;
91 static int opt_c, opt_k, opt_s;
92 const WCHAR newline[] = {'\r','\n','\0'};
93 static const WCHAR equalsW[] = {'=','\0'};
94 static const WCHAR closeBW[] = {')','\0'};
95 WCHAR anykey[100];
96 WCHAR version_string[100];
97 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
98 BATCH_CONTEXT *context = NULL;
99 extern struct env_stack *pushd_directories;
100 static const WCHAR *pagedMessage = NULL;
101 static char *output_bufA = NULL;
102 #define MAX_WRITECONSOLE_SIZE 65535
103 static BOOL unicodePipes = FALSE;
106 * Returns a buffer for reading from/writing to file
107 * Never freed
109 static char *get_file_buffer(void)
111 if (!output_bufA) {
112 output_bufA = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
113 if (!output_bufA)
114 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
116 return output_bufA;
119 /*******************************************************************
120 * WCMD_output_asis_len - send output to current standard output
122 * Output a formatted unicode string. Ideally this will go to the console
123 * and hence required WriteConsoleW to output it, however if file i/o is
124 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
126 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
128 DWORD nOut= 0;
129 DWORD res = 0;
131 /* If nothing to write, return (MORE does this sometimes) */
132 if (!len) return;
134 /* Try to write as unicode assuming it is to a console */
135 res = WriteConsoleW(device, message, len, &nOut, NULL);
137 /* If writing to console fails, assume its file
138 i/o so convert to OEM codepage and output */
139 if (!res) {
140 BOOL usedDefaultChar = FALSE;
141 DWORD convertedChars;
142 char *buffer;
144 if (!unicodePipes) {
146 if (!(buffer = get_file_buffer()))
147 return;
149 /* Convert to OEM, then output */
150 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
151 len, buffer, MAX_WRITECONSOLE_SIZE,
152 "?", &usedDefaultChar);
153 WriteFile(device, buffer, convertedChars,
154 &nOut, FALSE);
155 } else {
156 WriteFile(device, message, len*sizeof(WCHAR),
157 &nOut, FALSE);
160 return;
163 /*******************************************************************
164 * WCMD_output - send output to current standard output device.
168 void WCMD_output (const WCHAR *format, ...) {
170 va_list ap;
171 WCHAR string[1024];
172 int ret;
174 va_start(ap,format);
175 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
176 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
177 WINE_ERR("Output truncated\n" );
178 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
179 string[ret] = '\0';
181 va_end(ap);
182 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
185 /*******************************************************************
186 * WCMD_output_stderr - send output to current standard error device.
190 void WCMD_output_stderr (const WCHAR *format, ...) {
192 va_list ap;
193 WCHAR string[1024];
194 int ret;
196 va_start(ap,format);
197 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
198 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
199 WINE_ERR("Output truncated\n" );
200 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
201 string[ret] = '\0';
203 va_end(ap);
204 WCMD_output_asis_len(string, ret, GetStdHandle(STD_ERROR_HANDLE));
207 static int line_count;
208 static int max_height;
209 static int max_width;
210 static BOOL paged_mode;
211 static int numChars;
213 void WCMD_enter_paged_mode(const WCHAR *msg)
215 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
217 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
218 max_height = consoleInfo.dwSize.Y;
219 max_width = consoleInfo.dwSize.X;
220 } else {
221 max_height = 25;
222 max_width = 80;
224 paged_mode = TRUE;
225 line_count = 0;
226 numChars = 0;
227 pagedMessage = (msg==NULL)? anykey : msg;
230 void WCMD_leave_paged_mode(void)
232 paged_mode = FALSE;
233 pagedMessage = NULL;
236 /***************************************************************************
237 * WCMD_Readfile
239 * Read characters in from a console/file, returning result in Unicode
241 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
243 BOOL res;
245 /* Try to read from console as Unicode */
246 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
248 /* If reading from console has failed we assume its file
249 i/o so read in and convert from OEM codepage */
250 if (!res) {
252 DWORD numRead;
253 char *buffer;
255 if (!(buffer = get_file_buffer()))
256 return FALSE;
258 /* Read from file (assume OEM codepage) */
259 res = ReadFile(hIn, buffer, maxChars, &numRead, NULL);
261 /* Convert from OEM */
262 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead,
263 intoBuf, maxChars);
266 return res;
269 /*******************************************************************
270 * WCMD_output_asis_handle
272 * Send output to specified handle without formatting e.g. when message contains '%'
274 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
275 DWORD count;
276 const WCHAR* ptr;
277 WCHAR string[1024];
278 HANDLE handle = GetStdHandle(std_handle);
280 if (paged_mode) {
281 do {
282 ptr = message;
283 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
284 numChars++;
285 ptr++;
287 if (*ptr == '\n') ptr++;
288 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message), handle);
289 if (ptr) {
290 numChars = 0;
291 if (++line_count >= max_height - 1) {
292 line_count = 0;
293 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
294 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
297 } while (((message = ptr) != NULL) && (*ptr));
298 } else {
299 WCMD_output_asis_len(message, lstrlenW(message), handle);
303 /*******************************************************************
304 * WCMD_output_asis
306 * Send output to current standard output device, without formatting
307 * e.g. when message contains '%'
309 void WCMD_output_asis (const WCHAR *message) {
310 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
313 /*******************************************************************
314 * WCMD_output_asis_stderr
316 * Send output to current standard error device, without formatting
317 * e.g. when message contains '%'
319 void WCMD_output_asis_stderr (const WCHAR *message) {
320 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
323 /****************************************************************************
324 * WCMD_print_error
326 * Print the message for GetLastError
329 void WCMD_print_error (void) {
330 LPVOID lpMsgBuf;
331 DWORD error_code;
332 int status;
334 error_code = GetLastError ();
335 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
336 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
337 if (!status) {
338 WINE_FIXME ("Cannot display message for error %d, status %d\n",
339 error_code, GetLastError());
340 return;
343 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
344 GetStdHandle(STD_ERROR_HANDLE));
345 LocalFree (lpMsgBuf);
346 WCMD_output_asis_len (newline, lstrlenW(newline),
347 GetStdHandle(STD_ERROR_HANDLE));
348 return;
351 /******************************************************************************
352 * WCMD_show_prompt
354 * Display the prompt on STDout
358 static void WCMD_show_prompt (void) {
360 int status;
361 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
362 WCHAR *p, *q;
363 DWORD len;
364 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
366 len = GetEnvironmentVariableW(envPrompt, prompt_string,
367 sizeof(prompt_string)/sizeof(WCHAR));
368 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
369 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
370 strcpyW (prompt_string, dfltPrompt);
372 p = prompt_string;
373 q = out_string;
374 *q++ = '\r';
375 *q++ = '\n';
376 *q = '\0';
377 while (*p != '\0') {
378 if (*p != '$') {
379 *q++ = *p++;
380 *q = '\0';
382 else {
383 p++;
384 switch (toupper(*p)) {
385 case '$':
386 *q++ = '$';
387 break;
388 case 'A':
389 *q++ = '&';
390 break;
391 case 'B':
392 *q++ = '|';
393 break;
394 case 'C':
395 *q++ = '(';
396 break;
397 case 'D':
398 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
399 while (*q) q++;
400 break;
401 case 'E':
402 *q++ = '\E';
403 break;
404 case 'F':
405 *q++ = ')';
406 break;
407 case 'G':
408 *q++ = '>';
409 break;
410 case 'H':
411 *q++ = '\b';
412 break;
413 case 'L':
414 *q++ = '<';
415 break;
416 case 'N':
417 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
418 if (status) {
419 *q++ = curdir[0];
421 break;
422 case 'P':
423 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
424 if (status) {
425 strcatW (q, curdir);
426 while (*q) q++;
428 break;
429 case 'Q':
430 *q++ = '=';
431 break;
432 case 'S':
433 *q++ = ' ';
434 break;
435 case 'T':
436 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
437 while (*q) q++;
438 break;
439 case 'V':
440 strcatW (q, version_string);
441 while (*q) q++;
442 break;
443 case '_':
444 *q++ = '\n';
445 break;
446 case '+':
447 if (pushd_directories) {
448 memset(q, '+', pushd_directories->u.stackdepth);
449 q = q + pushd_directories->u.stackdepth;
451 break;
453 p++;
454 *q = '\0';
457 WCMD_output_asis (out_string);
461 /*************************************************************************
462 * WCMD_strdupW
463 * A wide version of strdup as its missing from unicode.h
465 WCHAR *WCMD_strdupW(const WCHAR *input) {
466 int len=strlenW(input)+1;
467 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
468 memcpy(result, input, len * sizeof(WCHAR));
469 return result;
472 /*************************************************************************
473 * WCMD_strsubstW
474 * Replaces a portion of a Unicode string with the specified string.
475 * It's up to the caller to ensure there is enough space in the
476 * destination buffer.
478 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
480 if (len < 0)
481 len=insert ? lstrlenW(insert) : 0;
482 if (start+len != next)
483 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
484 if (insert)
485 memcpy(start, insert, len * sizeof(*insert));
488 /***************************************************************************
489 * WCMD_skip_leading_spaces
491 * Return a pointer to the first non-whitespace character of string.
492 * Does not modify the input string.
494 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
496 WCHAR *ptr;
498 ptr = string;
499 while (*ptr == ' ' || *ptr == '\t') ptr++;
500 return ptr;
503 /***************************************************************************
504 * WCMD_keyword_ws_found
506 * Checks if the string located at ptr matches a keyword (of length len)
507 * followed by a whitespace character (space or tab)
509 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
510 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
511 ptr, len, keyword, len) == CSTR_EQUAL)
512 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
515 /*************************************************************************
516 * WCMD_opt_s_strip_quotes
518 * Remove first and last quote WCHARacters, preserving all other text
520 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
521 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
522 while((*dest=*src) != '\0') {
523 if (*src=='\"')
524 lastq=dest;
525 dest++, src++;
527 if (lastq) {
528 dest=lastq++;
529 while ((*dest++=*lastq++) != 0)
535 /*************************************************************************
536 * WCMD_is_magic_envvar
537 * Return TRUE if s is '%'magicvar'%'
538 * and is not masked by a real environment variable.
541 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
543 int len;
545 if (s[0] != '%')
546 return FALSE; /* Didn't begin with % */
547 len = strlenW(s);
548 if (len < 2 || s[len-1] != '%')
549 return FALSE; /* Didn't end with another % */
551 if (CompareStringW(LOCALE_USER_DEFAULT,
552 NORM_IGNORECASE | SORT_STRINGSORT,
553 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
554 /* Name doesn't match. */
555 return FALSE;
558 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
559 /* Masked by real environment variable. */
560 return FALSE;
563 return TRUE;
566 /*************************************************************************
567 * WCMD_expand_envvar
569 * Expands environment variables, allowing for WCHARacter substitution
571 static WCHAR *WCMD_expand_envvar(WCHAR *start,
572 const WCHAR *forVar, const WCHAR *forVal) {
573 WCHAR *endOfVar = NULL, *s;
574 WCHAR *colonpos = NULL;
575 WCHAR thisVar[MAXSTRING];
576 WCHAR thisVarContents[MAXSTRING];
577 WCHAR savedchar = 0x00;
578 int len;
580 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
581 static const WCHAR Date[] = {'D','A','T','E','\0'};
582 static const WCHAR Time[] = {'T','I','M','E','\0'};
583 static const WCHAR Cd[] = {'C','D','\0'};
584 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
585 static const WCHAR Delims[] = {'%',' ',':','\0'};
587 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
588 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
590 /* Find the end of the environment variable, and extract name */
591 endOfVar = strpbrkW(start+1, Delims);
593 if (endOfVar == NULL || *endOfVar==' ') {
595 /* In batch program, missing terminator for % and no following
596 ':' just removes the '%' */
597 if (context) {
598 WCMD_strsubstW(start, start + 1, NULL, 0);
599 return start;
600 } else {
602 /* In command processing, just ignore it - allows command line
603 syntax like: for %i in (a.a) do echo %i */
604 return start+1;
608 /* If ':' found, process remaining up until '%' (or stop at ':' if
609 a missing '%' */
610 if (*endOfVar==':') {
611 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
612 if (endOfVar2 != NULL) endOfVar = endOfVar2;
615 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
616 thisVar[(endOfVar - start)+1] = 0x00;
617 colonpos = strchrW(thisVar+1, ':');
619 /* If there's complex substitution, just need %var% for now
620 to get the expanded data to play with */
621 if (colonpos) {
622 *colonpos = '%';
623 savedchar = *(colonpos+1);
624 *(colonpos+1) = 0x00;
627 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
629 /* Expand to contents, if unchanged, return */
630 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
631 /* override if existing env var called that name */
632 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
633 static const WCHAR fmt[] = {'%','d','\0'};
634 wsprintfW(thisVarContents, fmt, errorlevel);
635 len = strlenW(thisVarContents);
636 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
637 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
638 NULL, thisVarContents, MAXSTRING);
639 len = strlenW(thisVarContents);
640 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
641 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
642 NULL, thisVarContents, MAXSTRING);
643 len = strlenW(thisVarContents);
644 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
645 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
646 len = strlenW(thisVarContents);
647 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
648 static const WCHAR fmt[] = {'%','d','\0'};
649 wsprintfW(thisVarContents, fmt, rand() % 32768);
650 len = strlenW(thisVarContents);
652 /* Look for a matching 'for' variable */
653 } else if (forVar &&
654 (CompareStringW(LOCALE_USER_DEFAULT,
655 SORT_STRINGSORT,
656 thisVar,
657 (colonpos - thisVar) - 1,
658 forVar, -1) == CSTR_EQUAL)) {
659 strcpyW(thisVarContents, forVal);
660 len = strlenW(thisVarContents);
662 } else {
664 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
665 sizeof(thisVarContents)/sizeof(WCHAR));
668 if (len == 0)
669 return endOfVar+1;
671 /* In a batch program, unknown env vars are replaced with nothing,
672 note syntax %garbage:1,3% results in anything after the ':'
673 except the %
674 From the command line, you just get back what you entered */
675 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
677 /* Restore the complex part after the compare */
678 if (colonpos) {
679 *colonpos = ':';
680 *(colonpos+1) = savedchar;
683 /* Command line - just ignore this */
684 if (context == NULL) return endOfVar+1;
687 /* Batch - replace unknown env var with nothing */
688 if (colonpos == NULL) {
689 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
690 } else {
691 len = strlenW(thisVar);
692 thisVar[len-1] = 0x00;
693 /* If %:...% supplied, : is retained */
694 if (colonpos == thisVar+1) {
695 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
696 } else {
697 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
700 return start;
704 /* See if we need to do complex substitution (any ':'s), if not
705 then our work here is done */
706 if (colonpos == NULL) {
707 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
708 return start;
711 /* Restore complex bit */
712 *colonpos = ':';
713 *(colonpos+1) = savedchar;
716 Handle complex substitutions:
717 xxx=yyy (replace xxx with yyy)
718 *xxx=yyy (replace up to and including xxx with yyy)
719 ~x (from x WCHARs in)
720 ~-x (from x WCHARs from the end)
721 ~x,y (from x WCHARs in for y WCHARacters)
722 ~x,-y (from x WCHARs in until y WCHARacters from the end)
725 /* ~ is substring manipulation */
726 if (savedchar == '~') {
728 int substrposition, substrlength = 0;
729 WCHAR *commapos = strchrW(colonpos+2, ',');
730 WCHAR *startCopy;
732 substrposition = atolW(colonpos+2);
733 if (commapos) substrlength = atolW(commapos+1);
735 /* Check bounds */
736 if (substrposition >= 0) {
737 startCopy = &thisVarContents[min(substrposition, len)];
738 } else {
739 startCopy = &thisVarContents[max(0, len+substrposition-1)];
742 if (commapos == NULL) {
743 /* Copy the lot */
744 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
745 } else if (substrlength < 0) {
747 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
748 if (copybytes > len) copybytes = len;
749 else if (copybytes < 0) copybytes = 0;
750 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
751 } else {
752 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
755 return start;
757 /* search and replace manipulation */
758 } else {
759 WCHAR *equalspos = strstrW(colonpos, equalsW);
760 WCHAR *replacewith = equalspos+1;
761 WCHAR *found = NULL;
762 WCHAR *searchIn;
763 WCHAR *searchFor;
765 if (equalspos == NULL) return start+1;
766 s = WCMD_strdupW(endOfVar + 1);
768 /* Null terminate both strings */
769 thisVar[strlenW(thisVar)-1] = 0x00;
770 *equalspos = 0x00;
772 /* Since we need to be case insensitive, copy the 2 buffers */
773 searchIn = WCMD_strdupW(thisVarContents);
774 CharUpperBuffW(searchIn, strlenW(thisVarContents));
775 searchFor = WCMD_strdupW(colonpos+1);
776 CharUpperBuffW(searchFor, strlenW(colonpos+1));
778 /* Handle wildcard case */
779 if (*(colonpos+1) == '*') {
780 /* Search for string to replace */
781 found = strstrW(searchIn, searchFor+1);
783 if (found) {
784 /* Do replacement */
785 strcpyW(start, replacewith);
786 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
787 strcatW(start, s);
788 } else {
789 /* Copy as is */
790 strcpyW(start, thisVarContents);
791 strcatW(start, s);
794 } else {
795 /* Loop replacing all instances */
796 WCHAR *lastFound = searchIn;
797 WCHAR *outputposn = start;
799 *start = 0x00;
800 while ((found = strstrW(lastFound, searchFor))) {
801 lstrcpynW(outputposn,
802 thisVarContents + (lastFound-searchIn),
803 (found - lastFound)+1);
804 outputposn = outputposn + (found - lastFound);
805 strcatW(outputposn, replacewith);
806 outputposn = outputposn + strlenW(replacewith);
807 lastFound = found + strlenW(searchFor);
809 strcatW(outputposn,
810 thisVarContents + (lastFound-searchIn));
811 strcatW(outputposn, s);
813 HeapFree(GetProcessHeap(), 0, s);
814 HeapFree(GetProcessHeap(), 0, searchIn);
815 HeapFree(GetProcessHeap(), 0, searchFor);
816 return start;
818 return start+1;
821 /*****************************************************************************
822 * Expand the command. Native expands lines from batch programs as they are
823 * read in and not again, except for 'for' variable substitution.
824 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
826 static void handleExpansion(WCHAR *cmd, BOOL justFors,
827 const WCHAR *forVariable, const WCHAR *forValue) {
829 /* For commands in a context (batch program): */
830 /* Expand environment variables in a batch file %{0-9} first */
831 /* including support for any ~ modifiers */
832 /* Additionally: */
833 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
834 /* names allowing environment variable overrides */
835 /* NOTE: To support the %PATH:xxx% syntax, also perform */
836 /* manual expansion of environment variables here */
838 WCHAR *p = cmd;
839 WCHAR *t;
840 int i;
842 while ((p = strchrW(p, '%'))) {
844 WINE_TRACE("Translate command:%s %d (at: %s)\n",
845 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
846 i = *(p+1) - '0';
848 /* Don't touch %% unless its in Batch */
849 if (!justFors && *(p+1) == '%') {
850 if (context) {
851 WCMD_strsubstW(p, p+1, NULL, 0);
853 p+=1;
855 /* Replace %~ modifications if in batch program */
856 } else if (*(p+1) == '~') {
857 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
858 p++;
860 /* Replace use of %0...%9 if in batch program*/
861 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
862 t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
863 WCMD_strsubstW(p, p+2, t, -1);
865 /* Replace use of %* if in batch program*/
866 } else if (!justFors && context && *(p+1)=='*') {
867 WCHAR *startOfParms = NULL;
868 t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
869 if (startOfParms != NULL)
870 WCMD_strsubstW(p, p+2, startOfParms, -1);
871 else
872 WCMD_strsubstW(p, p+2, NULL, 0);
874 } else if (forVariable &&
875 (CompareStringW(LOCALE_USER_DEFAULT,
876 SORT_STRINGSORT,
878 strlenW(forVariable),
879 forVariable, -1) == CSTR_EQUAL)) {
880 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
882 } else if (!justFors) {
883 p = WCMD_expand_envvar(p, forVariable, forValue);
885 /* In a FOR loop, see if this is the variable to replace */
886 } else { /* Ignore %'s on second pass of batch program */
887 p++;
891 return;
895 /*******************************************************************
896 * WCMD_parse - parse a command into parameters and qualifiers.
898 * On exit, all qualifiers are concatenated into q, the first string
899 * not beginning with "/" is in p1 and the
900 * second in p2. Any subsequent non-qualifier strings are lost.
901 * Parameters in quotes are handled.
903 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
905 int p = 0;
907 *q = *p1 = *p2 = '\0';
908 while (TRUE) {
909 switch (*s) {
910 case '/':
911 *q++ = *s++;
912 while ((*s != '\0') && (*s != ' ') && *s != '/') {
913 *q++ = toupperW (*s++);
915 *q = '\0';
916 break;
917 case ' ':
918 case '\t':
919 s++;
920 break;
921 case '"':
922 s++;
923 while ((*s != '\0') && (*s != '"')) {
924 if (p == 0) *p1++ = *s++;
925 else if (p == 1) *p2++ = *s++;
926 else s++;
928 if (p == 0) *p1 = '\0';
929 if (p == 1) *p2 = '\0';
930 p++;
931 if (*s == '"') s++;
932 break;
933 case '\0':
934 return;
935 default:
936 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
937 && (*s != '=') && (*s != ',') ) {
938 if (p == 0) *p1++ = *s++;
939 else if (p == 1) *p2++ = *s++;
940 else s++;
942 /* Skip concurrent parms */
943 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
945 if (p == 0) *p1 = '\0';
946 if (p == 1) *p2 = '\0';
947 p++;
952 static void init_msvcrt_io_block(STARTUPINFOW* st)
954 STARTUPINFOW st_p;
955 /* fetch the parent MSVCRT info block if any, so that the child can use the
956 * same handles as its grand-father
958 st_p.cb = sizeof(STARTUPINFOW);
959 GetStartupInfoW(&st_p);
960 st->cbReserved2 = st_p.cbReserved2;
961 st->lpReserved2 = st_p.lpReserved2;
962 if (st_p.cbReserved2 && st_p.lpReserved2)
964 /* Override the entries for fd 0,1,2 if we happened
965 * to change those std handles (this depends on the way cmd sets
966 * its new input & output handles)
968 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
969 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
970 if (ptr)
972 unsigned num = *(unsigned*)st_p.lpReserved2;
973 char* flags = (char*)(ptr + sizeof(unsigned));
974 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
976 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
977 st->cbReserved2 = sz;
978 st->lpReserved2 = ptr;
980 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
981 if (num <= 0 || (flags[0] & WX_OPEN))
983 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
984 flags[0] |= WX_OPEN;
986 if (num <= 1 || (flags[1] & WX_OPEN))
988 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
989 flags[1] |= WX_OPEN;
991 if (num <= 2 || (flags[2] & WX_OPEN))
993 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
994 flags[2] |= WX_OPEN;
996 #undef WX_OPEN
1001 /******************************************************************************
1002 * WCMD_run_program
1004 * Execute a command line as an external program. Must allow recursion.
1006 * Precedence:
1007 * Manual testing under windows shows PATHEXT plays a key part in this,
1008 * and the search algorithm and precedence appears to be as follows.
1010 * Search locations:
1011 * If directory supplied on command, just use that directory
1012 * If extension supplied on command, look for that explicit name first
1013 * Otherwise, search in each directory on the path
1014 * Precedence:
1015 * If extension supplied on command, look for that explicit name first
1016 * Then look for supplied name .* (even if extension supplied, so
1017 * 'garbage.exe' will match 'garbage.exe.cmd')
1018 * If any found, cycle through PATHEXT looking for name.exe one by one
1019 * Launching
1020 * Once a match has been found, it is launched - Code currently uses
1021 * findexecutable to achieve this which is left untouched.
1024 void WCMD_run_program (WCHAR *command, int called) {
1026 WCHAR temp[MAX_PATH];
1027 WCHAR pathtosearch[MAXSTRING];
1028 WCHAR *pathposn;
1029 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1030 MAX_PATH, including null character */
1031 WCHAR *lastSlash;
1032 WCHAR pathext[MAXSTRING];
1033 BOOL extensionsupplied = FALSE;
1034 BOOL launched = FALSE;
1035 BOOL status;
1036 BOOL assumeInternal = FALSE;
1037 DWORD len;
1038 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1039 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1040 static const WCHAR delims[] = {'/','\\',':','\0'};
1042 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
1043 if (!(*param1) && !(*param2))
1044 return;
1046 /* Calculate the search path and stem to search for */
1047 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
1048 static const WCHAR curDir[] = {'.',';','\0'};
1049 strcpyW(pathtosearch, curDir);
1050 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1051 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1052 static const WCHAR curDir[] = {'.','\0'};
1053 strcpyW (pathtosearch, curDir);
1055 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1056 if (strlenW(param1) >= MAX_PATH)
1058 WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
1059 return;
1062 strcpyW(stemofsearch, param1);
1064 } else {
1066 /* Convert eg. ..\fred to include a directory by removing file part */
1067 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1068 lastSlash = strrchrW(pathtosearch, '\\');
1069 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1070 strcpyW(stemofsearch, lastSlash+1);
1072 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1073 c:\windows\a.bat syntax */
1074 if (lastSlash) *(lastSlash + 1) = 0x00;
1077 /* Now extract PATHEXT */
1078 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1079 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1080 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1081 '.','c','o','m',';',
1082 '.','c','m','d',';',
1083 '.','e','x','e','\0'};
1084 strcpyW (pathext, dfltPathExt);
1087 /* Loop through the search path, dir by dir */
1088 pathposn = pathtosearch;
1089 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1090 wine_dbgstr_w(stemofsearch));
1091 while (!launched && pathposn) {
1093 WCHAR thisDir[MAX_PATH] = {'\0'};
1094 WCHAR *pos = NULL;
1095 BOOL found = FALSE;
1096 static const WCHAR slashW[] = {'\\','\0'};
1098 /* Work on the first directory on the search path */
1099 pos = strchrW(pathposn, ';');
1100 if (pos) {
1101 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1102 thisDir[(pos-pathposn)] = 0x00;
1103 pathposn = pos+1;
1105 } else {
1106 strcpyW(thisDir, pathposn);
1107 pathposn = NULL;
1110 /* Since you can have eg. ..\.. on the path, need to expand
1111 to full information */
1112 strcpyW(temp, thisDir);
1113 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1115 /* 1. If extension supplied, see if that file exists */
1116 strcatW(thisDir, slashW);
1117 strcatW(thisDir, stemofsearch);
1118 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1120 /* 1. If extension supplied, see if that file exists */
1121 if (extensionsupplied) {
1122 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1123 found = TRUE;
1127 /* 2. Any .* matches? */
1128 if (!found) {
1129 HANDLE h;
1130 WIN32_FIND_DATAW finddata;
1131 static const WCHAR allFiles[] = {'.','*','\0'};
1133 strcatW(thisDir,allFiles);
1134 h = FindFirstFileW(thisDir, &finddata);
1135 FindClose(h);
1136 if (h != INVALID_HANDLE_VALUE) {
1138 WCHAR *thisExt = pathext;
1140 /* 3. Yes - Try each path ext */
1141 while (thisExt) {
1142 WCHAR *nextExt = strchrW(thisExt, ';');
1144 if (nextExt) {
1145 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1146 pos[(nextExt-thisExt)] = 0x00;
1147 thisExt = nextExt+1;
1148 } else {
1149 strcpyW(pos, thisExt);
1150 thisExt = NULL;
1153 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1154 found = TRUE;
1155 thisExt = NULL;
1161 /* Internal programs won't be picked up by this search, so even
1162 though not found, try one last createprocess and wait for it
1163 to complete.
1164 Note: Ideally we could tell between a console app (wait) and a
1165 windows app, but the API's for it fail in this case */
1166 if (!found && pathposn == NULL) {
1167 WINE_TRACE("ASSUMING INTERNAL\n");
1168 assumeInternal = TRUE;
1169 } else {
1170 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1173 /* Once found, launch it */
1174 if (found || assumeInternal) {
1175 STARTUPINFOW st;
1176 PROCESS_INFORMATION pe;
1177 SHFILEINFOW psfi;
1178 DWORD console;
1179 HINSTANCE hinst;
1180 WCHAR *ext = strrchrW( thisDir, '.' );
1181 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1182 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1184 launched = TRUE;
1186 /* Special case BAT and CMD */
1187 if (ext && !strcmpiW(ext, batExt)) {
1188 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1189 return;
1190 } else if (ext && !strcmpiW(ext, cmdExt)) {
1191 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1192 return;
1193 } else {
1195 /* thisDir contains the file to be launched, but with what?
1196 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1197 hinst = FindExecutableW (thisDir, NULL, temp);
1198 if ((INT_PTR)hinst < 32)
1199 console = 0;
1200 else
1201 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1203 ZeroMemory (&st, sizeof(STARTUPINFOW));
1204 st.cb = sizeof(STARTUPINFOW);
1205 init_msvcrt_io_block(&st);
1207 /* Launch the process and if a CUI wait on it to complete
1208 Note: Launching internal wine processes cannot specify a full path to exe */
1209 status = CreateProcessW(assumeInternal?NULL : thisDir,
1210 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1211 if ((opt_c || opt_k) && !opt_s && !status
1212 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1213 /* strip first and last quote WCHARacters and try again */
1214 WCMD_opt_s_strip_quotes(command);
1215 opt_s=1;
1216 WCMD_run_program(command, called);
1217 return;
1220 if (!status)
1221 break;
1223 if (!assumeInternal && !console) errorlevel = 0;
1224 else
1226 /* Always wait when called in a batch program context */
1227 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1228 GetExitCodeProcess (pe.hProcess, &errorlevel);
1229 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1231 CloseHandle(pe.hProcess);
1232 CloseHandle(pe.hThread);
1233 return;
1238 /* Not found anywhere - give up */
1239 SetLastError(ERROR_FILE_NOT_FOUND);
1240 WCMD_print_error ();
1242 /* If a command fails to launch, it sets errorlevel 9009 - which
1243 does not seem to have any associated constant definition */
1244 errorlevel = 9009;
1245 return;
1249 /*****************************************************************************
1250 * Process one command. If the command is EXIT this routine does not return.
1251 * We will recurse through here executing batch files.
1253 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1254 const WCHAR *forVariable, const WCHAR *forValue,
1255 CMD_LIST **cmdList)
1257 WCHAR *cmd, *p, *redir;
1258 int status, i;
1259 DWORD count, creationDisposition;
1260 HANDLE h;
1261 WCHAR *whichcmd;
1262 SECURITY_ATTRIBUTES sa;
1263 WCHAR *new_cmd = NULL;
1264 WCHAR *new_redir = NULL;
1265 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1266 GetStdHandle (STD_OUTPUT_HANDLE),
1267 GetStdHandle (STD_ERROR_HANDLE)};
1268 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1269 STD_OUTPUT_HANDLE,
1270 STD_ERROR_HANDLE};
1271 BOOL prev_echo_mode, piped = FALSE;
1273 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1274 wine_dbgstr_w(command), cmdList,
1275 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1277 /* If the next command is a pipe then we implement pipes by redirecting
1278 the output from this command to a temp file and input into the
1279 next command from that temp file.
1280 FIXME: Use of named pipes would make more sense here as currently this
1281 process has to finish before the next one can start but this requires
1282 a change to not wait for the first app to finish but rather the pipe */
1283 if (cmdList && (*cmdList)->nextcommand &&
1284 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1286 WCHAR temp_path[MAX_PATH];
1287 static const WCHAR cmdW[] = {'C','M','D','\0'};
1289 /* Remember piping is in action */
1290 WINE_TRACE("Output needs to be piped\n");
1291 piped = TRUE;
1293 /* Generate a unique temporary filename */
1294 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1295 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1296 WINE_TRACE("Using temporary file of %s\n",
1297 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1300 /* Move copy of the command onto the heap so it can be expanded */
1301 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1302 if (!new_cmd)
1304 WINE_ERR("Could not allocate memory for new_cmd\n");
1305 return;
1307 strcpyW(new_cmd, command);
1309 /* Move copy of the redirects onto the heap so it can be expanded */
1310 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1311 if (!new_redir)
1313 WINE_ERR("Could not allocate memory for new_redir\n");
1314 HeapFree( GetProcessHeap(), 0, new_cmd );
1315 return;
1318 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1319 if (piped) {
1320 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1321 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1322 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1323 } else {
1324 strcpyW(new_redir, redirects);
1327 /* Expand variables in command line mode only (batch mode will
1328 be expanded as the line is read in, except for 'for' loops) */
1329 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1330 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1331 cmd = new_cmd;
1334 * Changing default drive has to be handled as a special case.
1337 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1338 WCHAR envvar[5];
1339 WCHAR dir[MAX_PATH];
1341 /* According to MSDN CreateProcess docs, special env vars record
1342 the current directory on each drive, in the form =C:
1343 so see if one specified, and if so go back to it */
1344 strcpyW(envvar, equalsW);
1345 strcatW(envvar, cmd);
1346 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1347 static const WCHAR fmt[] = {'%','s','\\','\0'};
1348 wsprintfW(cmd, fmt, cmd);
1349 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1351 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1352 status = SetCurrentDirectoryW(cmd);
1353 if (!status) WCMD_print_error ();
1354 HeapFree( GetProcessHeap(), 0, cmd );
1355 HeapFree( GetProcessHeap(), 0, new_redir );
1356 return;
1359 sa.nLength = sizeof(sa);
1360 sa.lpSecurityDescriptor = NULL;
1361 sa.bInheritHandle = TRUE;
1364 * Redirect stdin, stdout and/or stderr if required.
1367 /* STDIN could come from a preceding pipe, so delete on close if it does */
1368 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1369 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1370 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1371 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1372 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1373 if (h == INVALID_HANDLE_VALUE) {
1374 WCMD_print_error ();
1375 HeapFree( GetProcessHeap(), 0, cmd );
1376 HeapFree( GetProcessHeap(), 0, new_redir );
1377 return;
1379 SetStdHandle (STD_INPUT_HANDLE, h);
1381 /* No need to remember the temporary name any longer once opened */
1382 (*cmdList)->pipeFile[0] = 0x00;
1384 /* Otherwise STDIN could come from a '<' redirect */
1385 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1386 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1387 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1388 if (h == INVALID_HANDLE_VALUE) {
1389 WCMD_print_error ();
1390 HeapFree( GetProcessHeap(), 0, cmd );
1391 HeapFree( GetProcessHeap(), 0, new_redir );
1392 return;
1394 SetStdHandle (STD_INPUT_HANDLE, h);
1397 /* Scan the whole command looking for > and 2> */
1398 redir = new_redir;
1399 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1400 int handle = 0;
1402 if (p > redir && (*(p-1)=='2'))
1403 handle = 2;
1404 else
1405 handle = 1;
1407 p++;
1408 if ('>' == *p) {
1409 creationDisposition = OPEN_ALWAYS;
1410 p++;
1412 else {
1413 creationDisposition = CREATE_ALWAYS;
1416 /* Add support for 2>&1 */
1417 redir = p;
1418 if (*p == '&') {
1419 int idx = *(p+1) - '0';
1421 if (DuplicateHandle(GetCurrentProcess(),
1422 GetStdHandle(idx_stdhandles[idx]),
1423 GetCurrentProcess(),
1425 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1426 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1428 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1430 } else {
1431 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1432 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1433 FILE_ATTRIBUTE_NORMAL, NULL);
1434 if (h == INVALID_HANDLE_VALUE) {
1435 WCMD_print_error ();
1436 HeapFree( GetProcessHeap(), 0, cmd );
1437 HeapFree( GetProcessHeap(), 0, new_redir );
1438 return;
1440 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1441 INVALID_SET_FILE_POINTER) {
1442 WCMD_print_error ();
1444 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1447 SetStdHandle (idx_stdhandles[handle], h);
1451 * Strip leading whitespaces, and a '@' if supplied
1453 whichcmd = WCMD_skip_leading_spaces(cmd);
1454 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1455 if (whichcmd[0] == '@') whichcmd++;
1458 * Check if the command entered is internal. If it is, pass the rest of the
1459 * line down to the command. If not try to run a program.
1462 count = 0;
1463 while (IsCharAlphaNumericW(whichcmd[count])) {
1464 count++;
1466 for (i=0; i<=WCMD_EXIT; i++) {
1467 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1468 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1470 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1471 WCMD_parse (p, quals, param1, param2);
1472 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1474 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1475 /* this is a help request for a builtin program */
1476 i = WCMD_HELP;
1477 memcpy(p, whichcmd, count * sizeof(WCHAR));
1478 p[count] = '\0';
1482 switch (i) {
1484 case WCMD_CALL:
1485 WCMD_call (p);
1486 break;
1487 case WCMD_CD:
1488 case WCMD_CHDIR:
1489 WCMD_setshow_default (p);
1490 break;
1491 case WCMD_CLS:
1492 WCMD_clear_screen ();
1493 break;
1494 case WCMD_COPY:
1495 WCMD_copy ();
1496 break;
1497 case WCMD_CTTY:
1498 WCMD_change_tty ();
1499 break;
1500 case WCMD_DATE:
1501 WCMD_setshow_date ();
1502 break;
1503 case WCMD_DEL:
1504 case WCMD_ERASE:
1505 WCMD_delete (p);
1506 break;
1507 case WCMD_DIR:
1508 WCMD_directory (p);
1509 break;
1510 case WCMD_ECHO:
1511 WCMD_echo(&whichcmd[count]);
1512 break;
1513 case WCMD_FOR:
1514 WCMD_for (p, cmdList);
1515 break;
1516 case WCMD_GOTO:
1517 WCMD_goto (cmdList);
1518 break;
1519 case WCMD_HELP:
1520 WCMD_give_help (p);
1521 break;
1522 case WCMD_IF:
1523 WCMD_if (p, cmdList);
1524 break;
1525 case WCMD_LABEL:
1526 WCMD_volume (TRUE, p);
1527 break;
1528 case WCMD_MD:
1529 case WCMD_MKDIR:
1530 WCMD_create_dir (p);
1531 break;
1532 case WCMD_MOVE:
1533 WCMD_move ();
1534 break;
1535 case WCMD_PATH:
1536 WCMD_setshow_path (p);
1537 break;
1538 case WCMD_PAUSE:
1539 WCMD_pause ();
1540 break;
1541 case WCMD_PROMPT:
1542 WCMD_setshow_prompt ();
1543 break;
1544 case WCMD_REM:
1545 break;
1546 case WCMD_REN:
1547 case WCMD_RENAME:
1548 WCMD_rename ();
1549 break;
1550 case WCMD_RD:
1551 case WCMD_RMDIR:
1552 WCMD_remove_dir (p);
1553 break;
1554 case WCMD_SETLOCAL:
1555 WCMD_setlocal(p);
1556 break;
1557 case WCMD_ENDLOCAL:
1558 WCMD_endlocal();
1559 break;
1560 case WCMD_SET:
1561 WCMD_setshow_env (p);
1562 break;
1563 case WCMD_SHIFT:
1564 WCMD_shift (p);
1565 break;
1566 case WCMD_TIME:
1567 WCMD_setshow_time ();
1568 break;
1569 case WCMD_TITLE:
1570 if (strlenW(&whichcmd[count]) > 0)
1571 WCMD_title(&whichcmd[count+1]);
1572 break;
1573 case WCMD_TYPE:
1574 WCMD_type (p);
1575 break;
1576 case WCMD_VER:
1577 WCMD_output(newline);
1578 WCMD_version ();
1579 break;
1580 case WCMD_VERIFY:
1581 WCMD_verify (p);
1582 break;
1583 case WCMD_VOL:
1584 WCMD_volume (FALSE, p);
1585 break;
1586 case WCMD_PUSHD:
1587 WCMD_pushd(p);
1588 break;
1589 case WCMD_POPD:
1590 WCMD_popd();
1591 break;
1592 case WCMD_ASSOC:
1593 WCMD_assoc(p, TRUE);
1594 break;
1595 case WCMD_COLOR:
1596 WCMD_color();
1597 break;
1598 case WCMD_FTYPE:
1599 WCMD_assoc(p, FALSE);
1600 break;
1601 case WCMD_MORE:
1602 WCMD_more(p);
1603 break;
1604 case WCMD_CHOICE:
1605 WCMD_choice(p);
1606 break;
1607 case WCMD_EXIT:
1608 WCMD_exit (cmdList);
1609 break;
1610 default:
1611 prev_echo_mode = echo_mode;
1612 WCMD_run_program (whichcmd, 0);
1613 echo_mode = prev_echo_mode;
1615 HeapFree( GetProcessHeap(), 0, cmd );
1616 HeapFree( GetProcessHeap(), 0, new_redir );
1618 /* Restore old handles */
1619 for (i=0; i<3; i++) {
1620 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1621 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1622 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1627 /*************************************************************************
1628 * WCMD_LoadMessage
1629 * Load a string from the resource file, handling any error
1630 * Returns string retrieved from resource file
1632 WCHAR *WCMD_LoadMessage(UINT id) {
1633 static WCHAR msg[2048];
1634 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1636 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1637 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1638 strcpyW(msg, failedMsg);
1640 return msg;
1643 /***************************************************************************
1644 * WCMD_DumpCommands
1646 * Dumps out the parsed command line to ensure syntax is correct
1648 static void WCMD_DumpCommands(CMD_LIST *commands) {
1649 CMD_LIST *thisCmd = commands;
1651 WINE_TRACE("Parsed line:\n");
1652 while (thisCmd != NULL) {
1653 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1654 thisCmd,
1655 thisCmd->prevDelim,
1656 thisCmd->bracketDepth,
1657 thisCmd->nextcommand,
1658 wine_dbgstr_w(thisCmd->command),
1659 wine_dbgstr_w(thisCmd->redirects));
1660 thisCmd = thisCmd->nextcommand;
1664 /***************************************************************************
1665 * WCMD_addCommand
1667 * Adds a command to the current command list
1669 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1670 WCHAR *redirs, int *redirLen,
1671 WCHAR **copyTo, int **copyToLen,
1672 CMD_DELIMITERS prevDelim, int curDepth,
1673 CMD_LIST **lastEntry, CMD_LIST **output) {
1675 CMD_LIST *thisEntry = NULL;
1677 /* Allocate storage for command */
1678 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1680 /* Copy in the command */
1681 if (command) {
1682 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1683 (*commandLen+1) * sizeof(WCHAR));
1684 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1685 thisEntry->command[*commandLen] = 0x00;
1687 /* Copy in the redirects */
1688 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1689 (*redirLen+1) * sizeof(WCHAR));
1690 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1691 thisEntry->redirects[*redirLen] = 0x00;
1692 thisEntry->pipeFile[0] = 0x00;
1694 /* Reset the lengths */
1695 *commandLen = 0;
1696 *redirLen = 0;
1697 *copyToLen = commandLen;
1698 *copyTo = command;
1700 } else {
1701 thisEntry->command = NULL;
1702 thisEntry->redirects = NULL;
1703 thisEntry->pipeFile[0] = 0x00;
1706 /* Fill in other fields */
1707 thisEntry->nextcommand = NULL;
1708 thisEntry->prevDelim = prevDelim;
1709 thisEntry->bracketDepth = curDepth;
1710 if (*lastEntry) {
1711 (*lastEntry)->nextcommand = thisEntry;
1712 } else {
1713 *output = thisEntry;
1715 *lastEntry = thisEntry;
1719 /***************************************************************************
1720 * WCMD_IsEndQuote
1722 * Checks if the quote pointed to is the end-quote.
1724 * Quotes end if:
1726 * 1) The current parameter ends at EOL or at the beginning
1727 * of a redirection or pipe and not in a quote section.
1729 * 2) If the next character is a space and not in a quote section.
1731 * Returns TRUE if this is an end quote, and FALSE if it is not.
1734 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1736 int quoteCount = quoteIndex;
1737 int i;
1739 /* If we are not in a quoted section, then we are not an end-quote */
1740 if(quoteIndex == 0)
1742 return FALSE;
1745 /* Check how many quotes are left for this parameter */
1746 for(i=0;quote[i];i++)
1748 if(quote[i] == '"')
1750 quoteCount++;
1753 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1754 else if(((quoteCount % 2) == 0)
1755 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1757 break;
1761 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1762 be an end-quote */
1763 if(quoteIndex >= (quoteCount / 2))
1765 return TRUE;
1768 /* No cigar */
1769 return FALSE;
1772 /***************************************************************************
1773 * WCMD_ReadAndParseLine
1775 * Either uses supplied input or
1776 * Reads a file from the handle, and then...
1777 * Parse the text buffer, splitting into separate commands
1778 * - unquoted && strings split 2 commands but the 2nd is flagged as
1779 * following an &&
1780 * - ( as the first character just ups the bracket depth
1781 * - unquoted ) when bracket depth > 0 terminates a bracket and
1782 * adds a CMD_LIST structure with null command
1783 * - Anything else gets put into the command string (including
1784 * redirects)
1786 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output,
1787 HANDLE readFrom, const BOOL is_console_handle)
1789 WCHAR *curPos;
1790 int inQuotes = 0;
1791 WCHAR curString[MAXSTRING];
1792 int curStringLen = 0;
1793 WCHAR curRedirs[MAXSTRING];
1794 int curRedirsLen = 0;
1795 WCHAR *curCopyTo;
1796 int *curLen;
1797 int curDepth = 0;
1798 CMD_LIST *lastEntry = NULL;
1799 CMD_DELIMITERS prevDelim = CMD_NONE;
1800 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1801 static const WCHAR remCmd[] = {'r','e','m'};
1802 static const WCHAR forCmd[] = {'f','o','r'};
1803 static const WCHAR ifCmd[] = {'i','f'};
1804 static const WCHAR ifElse[] = {'e','l','s','e'};
1805 BOOL inRem = FALSE;
1806 BOOL inFor = FALSE;
1807 BOOL inIn = FALSE;
1808 BOOL inIf = FALSE;
1809 BOOL inElse= FALSE;
1810 BOOL onlyWhiteSpace = FALSE;
1811 BOOL lastWasWhiteSpace = FALSE;
1812 BOOL lastWasDo = FALSE;
1813 BOOL lastWasIn = FALSE;
1814 BOOL lastWasElse = FALSE;
1815 BOOL lastWasRedirect = TRUE;
1817 /* Allocate working space for a command read from keyboard, file etc */
1818 if (!extraSpace)
1819 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1820 if (!extraSpace)
1822 WINE_ERR("Could not allocate memory for extraSpace\n");
1823 return NULL;
1826 /* If initial command read in, use that, otherwise get input from handle */
1827 if (optionalcmd != NULL) {
1828 strcpyW(extraSpace, optionalcmd);
1829 } else if (readFrom == INVALID_HANDLE_VALUE) {
1830 WINE_FIXME("No command nor handle supplied\n");
1831 } else {
1832 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom, is_console_handle))
1833 return NULL;
1835 curPos = extraSpace;
1837 /* Handle truncated input - issue warning */
1838 if (strlenW(extraSpace) == MAXSTRING -1) {
1839 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1840 WCMD_output_asis(extraSpace);
1841 WCMD_output_asis(newline);
1844 /* Replace env vars if in a batch context */
1845 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1846 /* Show prompt before batch line IF echo is on and in batch program */
1847 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1848 static const WCHAR spc[]={' ','\0'};
1849 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1850 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1851 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1852 DWORD curr_size = strlenW(extraSpace);
1853 DWORD min_len = (curr_size < len ? curr_size : len);
1854 WCMD_show_prompt();
1855 WCMD_output_asis(extraSpace);
1856 /* I don't know why Windows puts a space here but it does */
1857 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1858 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1859 extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1860 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1861 extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1863 WCMD_output_asis(spc);
1865 WCMD_output_asis(newline);
1868 /* Start with an empty string, copying to the command string */
1869 curStringLen = 0;
1870 curRedirsLen = 0;
1871 curCopyTo = curString;
1872 curLen = &curStringLen;
1873 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1875 /* Parse every character on the line being processed */
1876 while (*curPos != 0x00) {
1878 WCHAR thisChar;
1880 /* Debugging AID:
1881 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1882 lastWasWhiteSpace, onlyWhiteSpace);
1885 /* Certain commands need special handling */
1886 if (curStringLen == 0 && curCopyTo == curString) {
1887 static const WCHAR forDO[] = {'d','o'};
1889 /* If command starts with 'rem ', ignore any &&, ( etc. */
1890 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1891 inRem = TRUE;
1893 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1894 inFor = TRUE;
1896 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1897 is only true in the command portion of the IF statement, but this
1898 should suffice for now
1899 FIXME: Silly syntax like "if 1(==1( (
1900 echo they equal
1901 )" will be parsed wrong */
1902 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1903 inIf = TRUE;
1905 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1906 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1907 inElse = TRUE;
1908 lastWasElse = TRUE;
1909 onlyWhiteSpace = TRUE;
1910 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1911 (*curLen)+=keyw_len;
1912 curPos+=keyw_len;
1913 continue;
1915 /* In a for loop, the DO command will follow a close bracket followed by
1916 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1917 is then 0, and all whitespace is skipped */
1918 } else if (inFor &&
1919 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1920 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1921 WINE_TRACE("Found 'DO '\n");
1922 lastWasDo = TRUE;
1923 onlyWhiteSpace = TRUE;
1924 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1925 (*curLen)+=keyw_len;
1926 curPos+=keyw_len;
1927 continue;
1929 } else if (curCopyTo == curString) {
1931 /* Special handling for the 'FOR' command */
1932 if (inFor && lastWasWhiteSpace) {
1933 static const WCHAR forIN[] = {'i','n'};
1935 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1937 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1938 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1939 WINE_TRACE("Found 'IN '\n");
1940 lastWasIn = TRUE;
1941 onlyWhiteSpace = TRUE;
1942 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1943 (*curLen)+=keyw_len;
1944 curPos+=keyw_len;
1945 continue;
1950 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1951 so just use the default processing ie skip character specific
1952 matching below */
1953 if (!inRem) thisChar = *curPos;
1954 else thisChar = 'X'; /* Character with no special processing */
1956 lastWasWhiteSpace = FALSE; /* Will be reset below */
1958 switch (thisChar) {
1960 case '=': /* drop through - ignore token delimiters at the start of a command */
1961 case ',': /* drop through - ignore token delimiters at the start of a command */
1962 case '\t':/* drop through - ignore token delimiters at the start of a command */
1963 case ' ':
1964 /* If a redirect in place, it ends here */
1965 if (!inQuotes && !lastWasRedirect) {
1967 /* If finishing off a redirect, add a whitespace delimiter */
1968 if (curCopyTo == curRedirs) {
1969 curCopyTo[(*curLen)++] = ' ';
1971 curCopyTo = curString;
1972 curLen = &curStringLen;
1974 if (*curLen > 0) {
1975 curCopyTo[(*curLen)++] = *curPos;
1978 /* Remember just processed whitespace */
1979 lastWasWhiteSpace = TRUE;
1981 break;
1983 case '>': /* drop through - handle redirect chars the same */
1984 case '<':
1985 /* Make a redirect start here */
1986 if (!inQuotes) {
1987 curCopyTo = curRedirs;
1988 curLen = &curRedirsLen;
1989 lastWasRedirect = TRUE;
1992 /* See if 1>, 2> etc, in which case we have some patching up
1993 to do (provided there's a preceding whitespace, and enough
1994 chars read so far) */
1995 if (curStringLen > 2
1996 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1997 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1998 curStringLen--;
1999 curString[curStringLen] = 0x00;
2000 curCopyTo[(*curLen)++] = *(curPos-1);
2003 curCopyTo[(*curLen)++] = *curPos;
2005 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
2006 do not process that ampersand as an AND operator */
2007 if (thisChar == '>' && *(curPos+1) == '&') {
2008 curCopyTo[(*curLen)++] = *(curPos+1);
2009 curPos++;
2011 break;
2013 case '|': /* Pipe character only if not || */
2014 if (!inQuotes) {
2015 lastWasRedirect = FALSE;
2017 /* Add an entry to the command list */
2018 if (curStringLen > 0) {
2020 /* Add the current command */
2021 WCMD_addCommand(curString, &curStringLen,
2022 curRedirs, &curRedirsLen,
2023 &curCopyTo, &curLen,
2024 prevDelim, curDepth,
2025 &lastEntry, output);
2029 if (*(curPos+1) == '|') {
2030 curPos++; /* Skip other | */
2031 prevDelim = CMD_ONFAILURE;
2032 } else {
2033 prevDelim = CMD_PIPE;
2035 } else {
2036 curCopyTo[(*curLen)++] = *curPos;
2038 break;
2040 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2041 inQuotes--;
2042 } else {
2043 inQuotes++; /* Quotes within quotes are fun! */
2045 curCopyTo[(*curLen)++] = *curPos;
2046 lastWasRedirect = FALSE;
2047 break;
2049 case '(': /* If a '(' is the first non whitespace in a command portion
2050 ie start of line or just after &&, then we read until an
2051 unquoted ) is found */
2052 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2053 ", for(%d, In:%d, Do:%d)"
2054 ", if(%d, else:%d, lwe:%d)\n",
2055 *curLen, inQuotes,
2056 onlyWhiteSpace,
2057 inFor, lastWasIn, lastWasDo,
2058 inIf, inElse, lastWasElse);
2059 lastWasRedirect = FALSE;
2061 /* Ignore open brackets inside the for set */
2062 if (*curLen == 0 && !inIn) {
2063 curDepth++;
2065 /* If in quotes, ignore brackets */
2066 } else if (inQuotes) {
2067 curCopyTo[(*curLen)++] = *curPos;
2069 /* In a FOR loop, an unquoted '(' may occur straight after
2070 IN or DO
2071 In an IF statement just handle it regardless as we don't
2072 parse the operands
2073 In an ELSE statement, only allow it straight away after
2074 the ELSE and whitespace
2076 } else if (inIf ||
2077 (inElse && lastWasElse && onlyWhiteSpace) ||
2078 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2080 /* If entering into an 'IN', set inIn */
2081 if (inFor && lastWasIn && onlyWhiteSpace) {
2082 WINE_TRACE("Inside an IN\n");
2083 inIn = TRUE;
2086 /* Add the current command */
2087 WCMD_addCommand(curString, &curStringLen,
2088 curRedirs, &curRedirsLen,
2089 &curCopyTo, &curLen,
2090 prevDelim, curDepth,
2091 &lastEntry, output);
2093 curDepth++;
2094 } else {
2095 curCopyTo[(*curLen)++] = *curPos;
2097 break;
2099 case '&': if (!inQuotes) {
2100 lastWasRedirect = FALSE;
2102 /* Add an entry to the command list */
2103 if (curStringLen > 0) {
2105 /* Add the current command */
2106 WCMD_addCommand(curString, &curStringLen,
2107 curRedirs, &curRedirsLen,
2108 &curCopyTo, &curLen,
2109 prevDelim, curDepth,
2110 &lastEntry, output);
2114 if (*(curPos+1) == '&') {
2115 curPos++; /* Skip other & */
2116 prevDelim = CMD_ONSUCCESS;
2117 } else {
2118 prevDelim = CMD_NONE;
2120 } else {
2121 curCopyTo[(*curLen)++] = *curPos;
2123 break;
2125 case ')': if (!inQuotes && curDepth > 0) {
2126 lastWasRedirect = FALSE;
2128 /* Add the current command if there is one */
2129 if (curStringLen) {
2131 /* Add the current command */
2132 WCMD_addCommand(curString, &curStringLen,
2133 curRedirs, &curRedirsLen,
2134 &curCopyTo, &curLen,
2135 prevDelim, curDepth,
2136 &lastEntry, output);
2139 /* Add an empty entry to the command list */
2140 prevDelim = CMD_NONE;
2141 WCMD_addCommand(NULL, &curStringLen,
2142 curRedirs, &curRedirsLen,
2143 &curCopyTo, &curLen,
2144 prevDelim, curDepth,
2145 &lastEntry, output);
2146 curDepth--;
2148 /* Leave inIn if necessary */
2149 if (inIn) inIn = FALSE;
2150 } else {
2151 curCopyTo[(*curLen)++] = *curPos;
2153 break;
2154 default:
2155 lastWasRedirect = FALSE;
2156 curCopyTo[(*curLen)++] = *curPos;
2159 curPos++;
2161 /* At various times we need to know if we have only skipped whitespace,
2162 so reset this variable and then it will remain true until a non
2163 whitespace is found */
2164 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2165 onlyWhiteSpace = FALSE;
2167 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2168 if (!lastWasWhiteSpace) {
2169 lastWasIn = lastWasDo = FALSE;
2172 /* If we have reached the end, add this command into the list */
2173 if (*curPos == 0x00 && *curLen > 0) {
2175 /* Add an entry to the command list */
2176 WCMD_addCommand(curString, &curStringLen,
2177 curRedirs, &curRedirsLen,
2178 &curCopyTo, &curLen,
2179 prevDelim, curDepth,
2180 &lastEntry, output);
2183 /* If we have reached the end of the string, see if bracketing outstanding */
2184 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2185 inRem = FALSE;
2186 prevDelim = CMD_NONE;
2187 inQuotes = 0;
2188 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2190 /* Read more, skipping any blank lines */
2191 while (*extraSpace == 0x00) {
2192 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2193 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom, is_console_handle))
2194 break;
2196 curPos = extraSpace;
2197 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2198 /* Continue to echo commands IF echo is on and in batch program */
2199 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2200 WCMD_output_asis(extraSpace);
2201 WCMD_output_asis(newline);
2206 /* Dump out the parsed output */
2207 WCMD_DumpCommands(*output);
2209 return extraSpace;
2212 /***************************************************************************
2213 * WCMD_process_commands
2215 * Process all the commands read in so far
2217 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2218 const WCHAR *var, const WCHAR *val) {
2220 int bdepth = -1;
2222 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2224 /* Loop through the commands, processing them one by one */
2225 while (thisCmd) {
2227 CMD_LIST *origCmd = thisCmd;
2229 /* If processing one bracket only, and we find the end bracket
2230 entry (or less), return */
2231 if (oneBracket && !thisCmd->command &&
2232 bdepth <= thisCmd->bracketDepth) {
2233 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2234 thisCmd, thisCmd->nextcommand);
2235 return thisCmd->nextcommand;
2238 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2239 about them and it will be handled in there)
2240 Also, skip over any batch labels (eg. :fred) */
2241 if (thisCmd->command && thisCmd->command[0] != ':') {
2242 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2243 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2246 /* Step on unless the command itself already stepped on */
2247 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2249 return NULL;
2252 /***************************************************************************
2253 * WCMD_free_commands
2255 * Frees the storage held for a parsed command line
2256 * - This is not done in the process_commands, as eventually the current
2257 * pointer will be modified within the commands, and hence a single free
2258 * routine is simpler
2260 void WCMD_free_commands(CMD_LIST *cmds) {
2262 /* Loop through the commands, freeing them one by one */
2263 while (cmds) {
2264 CMD_LIST *thisCmd = cmds;
2265 cmds = cmds->nextcommand;
2266 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2267 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2268 HeapFree(GetProcessHeap(), 0, thisCmd);
2273 /*****************************************************************************
2274 * Main entry point. This is a console application so we have a main() not a
2275 * winmain().
2278 int wmain (int argc, WCHAR *argvW[])
2280 int args;
2281 WCHAR *cmd = NULL;
2282 WCHAR string[1024];
2283 WCHAR envvar[4];
2284 int opt_q;
2285 BOOL is_console;
2286 DWORD dummy;
2287 int opt_t = 0;
2288 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2289 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2290 char ansiVersion[100];
2291 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2293 srand(time(NULL));
2295 /* Pre initialize some messages */
2296 strcpy(ansiVersion, PACKAGE_VERSION);
2297 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2298 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2299 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2301 args = argc;
2302 opt_c=opt_k=opt_q=opt_s=0;
2303 while (args > 0)
2305 WCHAR c;
2306 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2307 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2308 argvW++;
2309 args--;
2310 continue;
2313 c=(*argvW)[1];
2314 if (tolowerW(c)=='c') {
2315 opt_c=1;
2316 } else if (tolowerW(c)=='q') {
2317 opt_q=1;
2318 } else if (tolowerW(c)=='k') {
2319 opt_k=1;
2320 } else if (tolowerW(c)=='s') {
2321 opt_s=1;
2322 } else if (tolowerW(c)=='a') {
2323 unicodePipes=FALSE;
2324 } else if (tolowerW(c)=='u') {
2325 unicodePipes=TRUE;
2326 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2327 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2328 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2329 /* Ignored for compatibility with Windows */
2332 if ((*argvW)[2]==0) {
2333 argvW++;
2334 args--;
2336 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2338 *argvW+=2;
2341 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2342 break;
2345 if (opt_q) {
2346 static const WCHAR eoff[] = {'O','F','F','\0'};
2347 WCMD_echo(eoff);
2350 if (opt_c || opt_k) {
2351 int len,qcount;
2352 WCHAR** arg;
2353 int argsLeft;
2354 WCHAR* p;
2356 /* opt_s left unflagged if the command starts with and contains exactly
2357 * one quoted string (exactly two quote characters). The quoted string
2358 * must be an executable name that has whitespace and must not have the
2359 * following characters: &<>()@^| */
2361 /* Build the command to execute */
2362 len = 0;
2363 qcount = 0;
2364 argsLeft = args;
2365 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2367 int has_space,bcount;
2368 WCHAR* a;
2370 has_space=0;
2371 bcount=0;
2372 a=*arg;
2373 if( !*a ) has_space=1;
2374 while (*a!='\0') {
2375 if (*a=='\\') {
2376 bcount++;
2377 } else {
2378 if (*a==' ' || *a=='\t') {
2379 has_space=1;
2380 } else if (*a=='"') {
2381 /* doubling of '\' preceding a '"',
2382 * plus escaping of said '"'
2384 len+=2*bcount+1;
2385 qcount++;
2387 bcount=0;
2389 a++;
2391 len+=(a-*arg) + 1; /* for the separating space */
2392 if (has_space)
2394 len+=2; /* for the quotes */
2395 qcount+=2;
2399 if (qcount!=2)
2400 opt_s=1;
2402 /* check argvW[0] for a space and invalid characters */
2403 if (!opt_s) {
2404 opt_s=1;
2405 p=*argvW;
2406 while (*p!='\0') {
2407 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2408 || *p=='@' || *p=='^' || *p=='|') {
2409 opt_s=1;
2410 break;
2412 if (*p==' ')
2413 opt_s=0;
2414 p++;
2418 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2419 if (!cmd)
2420 exit(1);
2422 p = cmd;
2423 argsLeft = args;
2424 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2426 int has_space,has_quote;
2427 WCHAR* a;
2429 /* Check for quotes and spaces in this argument */
2430 has_space=has_quote=0;
2431 a=*arg;
2432 if( !*a ) has_space=1;
2433 while (*a!='\0') {
2434 if (*a==' ' || *a=='\t') {
2435 has_space=1;
2436 if (has_quote)
2437 break;
2438 } else if (*a=='"') {
2439 has_quote=1;
2440 if (has_space)
2441 break;
2443 a++;
2446 /* Now transfer it to the command line */
2447 if (has_space)
2448 *p++='"';
2449 if (has_quote) {
2450 int bcount;
2451 WCHAR* a;
2453 bcount=0;
2454 a=*arg;
2455 while (*a!='\0') {
2456 if (*a=='\\') {
2457 *p++=*a;
2458 bcount++;
2459 } else {
2460 if (*a=='"') {
2461 int i;
2463 /* Double all the '\\' preceding this '"', plus one */
2464 for (i=0;i<=bcount;i++)
2465 *p++='\\';
2466 *p++='"';
2467 } else {
2468 *p++=*a;
2470 bcount=0;
2472 a++;
2474 } else {
2475 strcpyW(p,*arg);
2476 p+=strlenW(*arg);
2478 if (has_space)
2479 *p++='"';
2480 *p++=' ';
2482 if (p > cmd)
2483 p--; /* remove last space */
2484 *p = '\0';
2486 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2488 /* strip first and last quote characters if opt_s; check for invalid
2489 * executable is done later */
2490 if (opt_s && *cmd=='\"')
2491 WCMD_opt_s_strip_quotes(cmd);
2494 if (opt_c) {
2495 /* If we do a "cmd /c command", we don't want to allocate a new
2496 * console since the command returns immediately. Rather, we use
2497 * the currently allocated input and output handles. This allows
2498 * us to pipe to and read from the command interpreter.
2501 /* Parse the command string, without reading any more input */
2502 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE, FALSE);
2503 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2504 WCMD_free_commands(toExecute);
2505 toExecute = NULL;
2507 HeapFree(GetProcessHeap(), 0, cmd);
2508 return errorlevel;
2511 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2512 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2513 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2515 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2516 if (opt_t) {
2517 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2518 defaultColor = opt_t & 0xFF;
2519 param1[0] = 0x00;
2520 WCMD_color();
2522 } else {
2523 /* Check HKCU\Software\Microsoft\Command Processor
2524 Then HKLM\Software\Microsoft\Command Processor
2525 for defaultcolour value
2526 Note Can be supplied as DWORD or REG_SZ
2527 Note2 When supplied as REG_SZ it's in decimal!!! */
2528 HKEY key;
2529 DWORD type;
2530 DWORD value=0, size=4;
2531 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2532 'M','i','c','r','o','s','o','f','t','\\',
2533 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2534 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2536 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2537 0, KEY_READ, &key) == ERROR_SUCCESS) {
2538 WCHAR strvalue[4];
2540 /* See if DWORD or REG_SZ */
2541 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2542 NULL, NULL) == ERROR_SUCCESS) {
2543 if (type == REG_DWORD) {
2544 size = sizeof(DWORD);
2545 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2546 (LPBYTE)&value, &size);
2547 } else if (type == REG_SZ) {
2548 size = sizeof(strvalue)/sizeof(WCHAR);
2549 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2550 (LPBYTE)strvalue, &size);
2551 value = strtoulW(strvalue, NULL, 10);
2554 RegCloseKey(key);
2557 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2558 0, KEY_READ, &key) == ERROR_SUCCESS) {
2559 WCHAR strvalue[4];
2561 /* See if DWORD or REG_SZ */
2562 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2563 NULL, NULL) == ERROR_SUCCESS) {
2564 if (type == REG_DWORD) {
2565 size = sizeof(DWORD);
2566 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2567 (LPBYTE)&value, &size);
2568 } else if (type == REG_SZ) {
2569 size = sizeof(strvalue)/sizeof(WCHAR);
2570 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2571 (LPBYTE)strvalue, &size);
2572 value = strtoulW(strvalue, NULL, 10);
2575 RegCloseKey(key);
2578 /* If one found, set the screen to that colour */
2579 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2580 defaultColor = value & 0xFF;
2581 param1[0] = 0x00;
2582 WCMD_color();
2587 /* Save cwd into appropriate env var */
2588 GetCurrentDirectoryW(1024, string);
2589 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2590 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2591 wsprintfW(envvar, fmt, string[0]);
2592 SetEnvironmentVariableW(envvar, string);
2593 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2596 if (opt_k) {
2597 /* Parse the command string, without reading any more input */
2598 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE, FALSE);
2599 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2600 WCMD_free_commands(toExecute);
2601 toExecute = NULL;
2602 HeapFree(GetProcessHeap(), 0, cmd);
2606 * Loop forever getting commands and executing them.
2609 SetEnvironmentVariableW(promptW, defaultpromptW);
2610 WCMD_version ();
2611 is_console = !!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dummy);
2612 while (TRUE) {
2614 /* Read until EOF (which for std input is never, but if redirect
2615 in place, may occur */
2616 if (echo_mode) WCMD_show_prompt();
2617 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE), is_console))
2618 break;
2619 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2620 WCMD_free_commands(toExecute);
2621 toExecute = NULL;
2623 return 0;