msvcp90: Added basic_string::reserve implementation.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
bloba4726e864c4d4dde1fb40eecd1370dd6639c8c40
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 DWORD numRead;
244 char *buffer;
246 if (WCMD_is_console_handle(hIn))
247 /* Try to read from console as Unicode */
248 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
250 /* We assume it's a file handle and read then convert from assumed OEM codepage */
251 if (!(buffer = get_file_buffer()))
252 return FALSE;
254 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
255 return FALSE;
257 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
259 return TRUE;
262 /*******************************************************************
263 * WCMD_output_asis_handle
265 * Send output to specified handle without formatting e.g. when message contains '%'
267 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
268 DWORD count;
269 const WCHAR* ptr;
270 WCHAR string[1024];
271 HANDLE handle = GetStdHandle(std_handle);
273 if (paged_mode) {
274 do {
275 ptr = message;
276 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
277 numChars++;
278 ptr++;
280 if (*ptr == '\n') ptr++;
281 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message), handle);
282 if (ptr) {
283 numChars = 0;
284 if (++line_count >= max_height - 1) {
285 line_count = 0;
286 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
287 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
290 } while (((message = ptr) != NULL) && (*ptr));
291 } else {
292 WCMD_output_asis_len(message, lstrlenW(message), handle);
296 /*******************************************************************
297 * WCMD_output_asis
299 * Send output to current standard output device, without formatting
300 * e.g. when message contains '%'
302 void WCMD_output_asis (const WCHAR *message) {
303 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
306 /*******************************************************************
307 * WCMD_output_asis_stderr
309 * Send output to current standard error device, without formatting
310 * e.g. when message contains '%'
312 void WCMD_output_asis_stderr (const WCHAR *message) {
313 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
316 /****************************************************************************
317 * WCMD_print_error
319 * Print the message for GetLastError
322 void WCMD_print_error (void) {
323 LPVOID lpMsgBuf;
324 DWORD error_code;
325 int status;
327 error_code = GetLastError ();
328 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
329 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
330 if (!status) {
331 WINE_FIXME ("Cannot display message for error %d, status %d\n",
332 error_code, GetLastError());
333 return;
336 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
337 GetStdHandle(STD_ERROR_HANDLE));
338 LocalFree (lpMsgBuf);
339 WCMD_output_asis_len (newline, lstrlenW(newline),
340 GetStdHandle(STD_ERROR_HANDLE));
341 return;
344 /******************************************************************************
345 * WCMD_show_prompt
347 * Display the prompt on STDout
351 static void WCMD_show_prompt (void) {
353 int status;
354 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
355 WCHAR *p, *q;
356 DWORD len;
357 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
359 len = GetEnvironmentVariableW(envPrompt, prompt_string,
360 sizeof(prompt_string)/sizeof(WCHAR));
361 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
362 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
363 strcpyW (prompt_string, dfltPrompt);
365 p = prompt_string;
366 q = out_string;
367 *q++ = '\r';
368 *q++ = '\n';
369 *q = '\0';
370 while (*p != '\0') {
371 if (*p != '$') {
372 *q++ = *p++;
373 *q = '\0';
375 else {
376 p++;
377 switch (toupper(*p)) {
378 case '$':
379 *q++ = '$';
380 break;
381 case 'A':
382 *q++ = '&';
383 break;
384 case 'B':
385 *q++ = '|';
386 break;
387 case 'C':
388 *q++ = '(';
389 break;
390 case 'D':
391 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
392 while (*q) q++;
393 break;
394 case 'E':
395 *q++ = '\E';
396 break;
397 case 'F':
398 *q++ = ')';
399 break;
400 case 'G':
401 *q++ = '>';
402 break;
403 case 'H':
404 *q++ = '\b';
405 break;
406 case 'L':
407 *q++ = '<';
408 break;
409 case 'N':
410 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
411 if (status) {
412 *q++ = curdir[0];
414 break;
415 case 'P':
416 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
417 if (status) {
418 strcatW (q, curdir);
419 while (*q) q++;
421 break;
422 case 'Q':
423 *q++ = '=';
424 break;
425 case 'S':
426 *q++ = ' ';
427 break;
428 case 'T':
429 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
430 while (*q) q++;
431 break;
432 case 'V':
433 strcatW (q, version_string);
434 while (*q) q++;
435 break;
436 case '_':
437 *q++ = '\n';
438 break;
439 case '+':
440 if (pushd_directories) {
441 memset(q, '+', pushd_directories->u.stackdepth);
442 q = q + pushd_directories->u.stackdepth;
444 break;
446 p++;
447 *q = '\0';
450 WCMD_output_asis (out_string);
454 /*************************************************************************
455 * WCMD_strdupW
456 * A wide version of strdup as its missing from unicode.h
458 WCHAR *WCMD_strdupW(const WCHAR *input) {
459 int len=strlenW(input)+1;
460 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
461 memcpy(result, input, len * sizeof(WCHAR));
462 return result;
465 /*************************************************************************
466 * WCMD_strsubstW
467 * Replaces a portion of a Unicode string with the specified string.
468 * It's up to the caller to ensure there is enough space in the
469 * destination buffer.
471 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
473 if (len < 0)
474 len=insert ? lstrlenW(insert) : 0;
475 if (start+len != next)
476 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
477 if (insert)
478 memcpy(start, insert, len * sizeof(*insert));
481 /***************************************************************************
482 * WCMD_skip_leading_spaces
484 * Return a pointer to the first non-whitespace character of string.
485 * Does not modify the input string.
487 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
489 WCHAR *ptr;
491 ptr = string;
492 while (*ptr == ' ' || *ptr == '\t') ptr++;
493 return ptr;
496 /***************************************************************************
497 * WCMD_keyword_ws_found
499 * Checks if the string located at ptr matches a keyword (of length len)
500 * followed by a whitespace character (space or tab)
502 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
503 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
504 ptr, len, keyword, len) == CSTR_EQUAL)
505 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
508 /*************************************************************************
509 * WCMD_opt_s_strip_quotes
511 * Remove first and last quote WCHARacters, preserving all other text
513 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
514 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
515 while((*dest=*src) != '\0') {
516 if (*src=='\"')
517 lastq=dest;
518 dest++, src++;
520 if (lastq) {
521 dest=lastq++;
522 while ((*dest++=*lastq++) != 0)
528 /*************************************************************************
529 * WCMD_is_magic_envvar
530 * Return TRUE if s is '%'magicvar'%'
531 * and is not masked by a real environment variable.
534 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
536 int len;
538 if (s[0] != '%')
539 return FALSE; /* Didn't begin with % */
540 len = strlenW(s);
541 if (len < 2 || s[len-1] != '%')
542 return FALSE; /* Didn't end with another % */
544 if (CompareStringW(LOCALE_USER_DEFAULT,
545 NORM_IGNORECASE | SORT_STRINGSORT,
546 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
547 /* Name doesn't match. */
548 return FALSE;
551 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
552 /* Masked by real environment variable. */
553 return FALSE;
556 return TRUE;
559 /*************************************************************************
560 * WCMD_expand_envvar
562 * Expands environment variables, allowing for WCHARacter substitution
564 static WCHAR *WCMD_expand_envvar(WCHAR *start,
565 const WCHAR *forVar, const WCHAR *forVal) {
566 WCHAR *endOfVar = NULL, *s;
567 WCHAR *colonpos = NULL;
568 WCHAR thisVar[MAXSTRING];
569 WCHAR thisVarContents[MAXSTRING];
570 WCHAR savedchar = 0x00;
571 int len;
573 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
574 static const WCHAR Date[] = {'D','A','T','E','\0'};
575 static const WCHAR Time[] = {'T','I','M','E','\0'};
576 static const WCHAR Cd[] = {'C','D','\0'};
577 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
578 static const WCHAR Delims[] = {'%',' ',':','\0'};
580 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
581 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
583 /* Find the end of the environment variable, and extract name */
584 endOfVar = strpbrkW(start+1, Delims);
586 if (endOfVar == NULL || *endOfVar==' ') {
588 /* In batch program, missing terminator for % and no following
589 ':' just removes the '%' */
590 if (context) {
591 WCMD_strsubstW(start, start + 1, NULL, 0);
592 return start;
593 } else {
595 /* In command processing, just ignore it - allows command line
596 syntax like: for %i in (a.a) do echo %i */
597 return start+1;
601 /* If ':' found, process remaining up until '%' (or stop at ':' if
602 a missing '%' */
603 if (*endOfVar==':') {
604 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
605 if (endOfVar2 != NULL) endOfVar = endOfVar2;
608 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
609 thisVar[(endOfVar - start)+1] = 0x00;
610 colonpos = strchrW(thisVar+1, ':');
612 /* If there's complex substitution, just need %var% for now
613 to get the expanded data to play with */
614 if (colonpos) {
615 *colonpos = '%';
616 savedchar = *(colonpos+1);
617 *(colonpos+1) = 0x00;
620 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
622 /* Expand to contents, if unchanged, return */
623 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
624 /* override if existing env var called that name */
625 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
626 static const WCHAR fmt[] = {'%','d','\0'};
627 wsprintfW(thisVarContents, fmt, errorlevel);
628 len = strlenW(thisVarContents);
629 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
630 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
631 NULL, thisVarContents, MAXSTRING);
632 len = strlenW(thisVarContents);
633 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
634 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
635 NULL, thisVarContents, MAXSTRING);
636 len = strlenW(thisVarContents);
637 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
638 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
639 len = strlenW(thisVarContents);
640 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
641 static const WCHAR fmt[] = {'%','d','\0'};
642 wsprintfW(thisVarContents, fmt, rand() % 32768);
643 len = strlenW(thisVarContents);
645 /* Look for a matching 'for' variable */
646 } else if (forVar &&
647 (CompareStringW(LOCALE_USER_DEFAULT,
648 SORT_STRINGSORT,
649 thisVar,
650 (colonpos - thisVar) - 1,
651 forVar, -1) == CSTR_EQUAL)) {
652 strcpyW(thisVarContents, forVal);
653 len = strlenW(thisVarContents);
655 } else {
657 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
658 sizeof(thisVarContents)/sizeof(WCHAR));
661 if (len == 0)
662 return endOfVar+1;
664 /* In a batch program, unknown env vars are replaced with nothing,
665 note syntax %garbage:1,3% results in anything after the ':'
666 except the %
667 From the command line, you just get back what you entered */
668 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
670 /* Restore the complex part after the compare */
671 if (colonpos) {
672 *colonpos = ':';
673 *(colonpos+1) = savedchar;
676 /* Command line - just ignore this */
677 if (context == NULL) return endOfVar+1;
680 /* Batch - replace unknown env var with nothing */
681 if (colonpos == NULL) {
682 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
683 } else {
684 len = strlenW(thisVar);
685 thisVar[len-1] = 0x00;
686 /* If %:...% supplied, : is retained */
687 if (colonpos == thisVar+1) {
688 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
689 } else {
690 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
693 return start;
697 /* See if we need to do complex substitution (any ':'s), if not
698 then our work here is done */
699 if (colonpos == NULL) {
700 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
701 return start;
704 /* Restore complex bit */
705 *colonpos = ':';
706 *(colonpos+1) = savedchar;
709 Handle complex substitutions:
710 xxx=yyy (replace xxx with yyy)
711 *xxx=yyy (replace up to and including xxx with yyy)
712 ~x (from x WCHARs in)
713 ~-x (from x WCHARs from the end)
714 ~x,y (from x WCHARs in for y WCHARacters)
715 ~x,-y (from x WCHARs in until y WCHARacters from the end)
718 /* ~ is substring manipulation */
719 if (savedchar == '~') {
721 int substrposition, substrlength = 0;
722 WCHAR *commapos = strchrW(colonpos+2, ',');
723 WCHAR *startCopy;
725 substrposition = atolW(colonpos+2);
726 if (commapos) substrlength = atolW(commapos+1);
728 /* Check bounds */
729 if (substrposition >= 0) {
730 startCopy = &thisVarContents[min(substrposition, len)];
731 } else {
732 startCopy = &thisVarContents[max(0, len+substrposition-1)];
735 if (commapos == NULL) {
736 /* Copy the lot */
737 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
738 } else if (substrlength < 0) {
740 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
741 if (copybytes > len) copybytes = len;
742 else if (copybytes < 0) copybytes = 0;
743 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
744 } else {
745 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
748 return start;
750 /* search and replace manipulation */
751 } else {
752 WCHAR *equalspos = strstrW(colonpos, equalsW);
753 WCHAR *replacewith = equalspos+1;
754 WCHAR *found = NULL;
755 WCHAR *searchIn;
756 WCHAR *searchFor;
758 if (equalspos == NULL) return start+1;
759 s = WCMD_strdupW(endOfVar + 1);
761 /* Null terminate both strings */
762 thisVar[strlenW(thisVar)-1] = 0x00;
763 *equalspos = 0x00;
765 /* Since we need to be case insensitive, copy the 2 buffers */
766 searchIn = WCMD_strdupW(thisVarContents);
767 CharUpperBuffW(searchIn, strlenW(thisVarContents));
768 searchFor = WCMD_strdupW(colonpos+1);
769 CharUpperBuffW(searchFor, strlenW(colonpos+1));
771 /* Handle wildcard case */
772 if (*(colonpos+1) == '*') {
773 /* Search for string to replace */
774 found = strstrW(searchIn, searchFor+1);
776 if (found) {
777 /* Do replacement */
778 strcpyW(start, replacewith);
779 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
780 strcatW(start, s);
781 } else {
782 /* Copy as is */
783 strcpyW(start, thisVarContents);
784 strcatW(start, s);
787 } else {
788 /* Loop replacing all instances */
789 WCHAR *lastFound = searchIn;
790 WCHAR *outputposn = start;
792 *start = 0x00;
793 while ((found = strstrW(lastFound, searchFor))) {
794 lstrcpynW(outputposn,
795 thisVarContents + (lastFound-searchIn),
796 (found - lastFound)+1);
797 outputposn = outputposn + (found - lastFound);
798 strcatW(outputposn, replacewith);
799 outputposn = outputposn + strlenW(replacewith);
800 lastFound = found + strlenW(searchFor);
802 strcatW(outputposn,
803 thisVarContents + (lastFound-searchIn));
804 strcatW(outputposn, s);
806 HeapFree(GetProcessHeap(), 0, s);
807 HeapFree(GetProcessHeap(), 0, searchIn);
808 HeapFree(GetProcessHeap(), 0, searchFor);
809 return start;
811 return start+1;
814 /*****************************************************************************
815 * Expand the command. Native expands lines from batch programs as they are
816 * read in and not again, except for 'for' variable substitution.
817 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
819 static void handleExpansion(WCHAR *cmd, BOOL justFors,
820 const WCHAR *forVariable, const WCHAR *forValue) {
822 /* For commands in a context (batch program): */
823 /* Expand environment variables in a batch file %{0-9} first */
824 /* including support for any ~ modifiers */
825 /* Additionally: */
826 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
827 /* names allowing environment variable overrides */
828 /* NOTE: To support the %PATH:xxx% syntax, also perform */
829 /* manual expansion of environment variables here */
831 WCHAR *p = cmd;
832 WCHAR *t;
833 int i;
835 while ((p = strchrW(p, '%'))) {
837 WINE_TRACE("Translate command:%s %d (at: %s)\n",
838 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
839 i = *(p+1) - '0';
841 /* Don't touch %% unless its in Batch */
842 if (!justFors && *(p+1) == '%') {
843 if (context) {
844 WCMD_strsubstW(p, p+1, NULL, 0);
846 p+=1;
848 /* Replace %~ modifications if in batch program */
849 } else if (*(p+1) == '~') {
850 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
851 p++;
853 /* Replace use of %0...%9 if in batch program*/
854 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
855 t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
856 WCMD_strsubstW(p, p+2, t, -1);
858 /* Replace use of %* if in batch program*/
859 } else if (!justFors && context && *(p+1)=='*') {
860 WCHAR *startOfParms = NULL;
861 t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
862 if (startOfParms != NULL)
863 WCMD_strsubstW(p, p+2, startOfParms, -1);
864 else
865 WCMD_strsubstW(p, p+2, NULL, 0);
867 } else if (forVariable &&
868 (CompareStringW(LOCALE_USER_DEFAULT,
869 SORT_STRINGSORT,
871 strlenW(forVariable),
872 forVariable, -1) == CSTR_EQUAL)) {
873 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
875 } else if (!justFors) {
876 p = WCMD_expand_envvar(p, forVariable, forValue);
878 /* In a FOR loop, see if this is the variable to replace */
879 } else { /* Ignore %'s on second pass of batch program */
880 p++;
884 return;
888 /*******************************************************************
889 * WCMD_parse - parse a command into parameters and qualifiers.
891 * On exit, all qualifiers are concatenated into q, the first string
892 * not beginning with "/" is in p1 and the
893 * second in p2. Any subsequent non-qualifier strings are lost.
894 * Parameters in quotes are handled.
896 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
898 int p = 0;
900 *q = *p1 = *p2 = '\0';
901 while (TRUE) {
902 switch (*s) {
903 case '/':
904 *q++ = *s++;
905 while ((*s != '\0') && (*s != ' ') && *s != '/') {
906 *q++ = toupperW (*s++);
908 *q = '\0';
909 break;
910 case ' ':
911 case '\t':
912 s++;
913 break;
914 case '"':
915 s++;
916 while ((*s != '\0') && (*s != '"')) {
917 if (p == 0) *p1++ = *s++;
918 else if (p == 1) *p2++ = *s++;
919 else s++;
921 if (p == 0) *p1 = '\0';
922 if (p == 1) *p2 = '\0';
923 p++;
924 if (*s == '"') s++;
925 break;
926 case '\0':
927 return;
928 default:
929 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
930 && (*s != '=') && (*s != ',') ) {
931 if (p == 0) *p1++ = *s++;
932 else if (p == 1) *p2++ = *s++;
933 else s++;
935 /* Skip concurrent parms */
936 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
938 if (p == 0) *p1 = '\0';
939 if (p == 1) *p2 = '\0';
940 p++;
945 static void init_msvcrt_io_block(STARTUPINFOW* st)
947 STARTUPINFOW st_p;
948 /* fetch the parent MSVCRT info block if any, so that the child can use the
949 * same handles as its grand-father
951 st_p.cb = sizeof(STARTUPINFOW);
952 GetStartupInfoW(&st_p);
953 st->cbReserved2 = st_p.cbReserved2;
954 st->lpReserved2 = st_p.lpReserved2;
955 if (st_p.cbReserved2 && st_p.lpReserved2)
957 /* Override the entries for fd 0,1,2 if we happened
958 * to change those std handles (this depends on the way cmd sets
959 * its new input & output handles)
961 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
962 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
963 if (ptr)
965 unsigned num = *(unsigned*)st_p.lpReserved2;
966 char* flags = (char*)(ptr + sizeof(unsigned));
967 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
969 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
970 st->cbReserved2 = sz;
971 st->lpReserved2 = ptr;
973 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
974 if (num <= 0 || (flags[0] & WX_OPEN))
976 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
977 flags[0] |= WX_OPEN;
979 if (num <= 1 || (flags[1] & WX_OPEN))
981 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
982 flags[1] |= WX_OPEN;
984 if (num <= 2 || (flags[2] & WX_OPEN))
986 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
987 flags[2] |= WX_OPEN;
989 #undef WX_OPEN
994 /******************************************************************************
995 * WCMD_run_program
997 * Execute a command line as an external program. Must allow recursion.
999 * Precedence:
1000 * Manual testing under windows shows PATHEXT plays a key part in this,
1001 * and the search algorithm and precedence appears to be as follows.
1003 * Search locations:
1004 * If directory supplied on command, just use that directory
1005 * If extension supplied on command, look for that explicit name first
1006 * Otherwise, search in each directory on the path
1007 * Precedence:
1008 * If extension supplied on command, look for that explicit name first
1009 * Then look for supplied name .* (even if extension supplied, so
1010 * 'garbage.exe' will match 'garbage.exe.cmd')
1011 * If any found, cycle through PATHEXT looking for name.exe one by one
1012 * Launching
1013 * Once a match has been found, it is launched - Code currently uses
1014 * findexecutable to achieve this which is left untouched.
1017 void WCMD_run_program (WCHAR *command, int called) {
1019 WCHAR temp[MAX_PATH];
1020 WCHAR pathtosearch[MAXSTRING];
1021 WCHAR *pathposn;
1022 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
1023 MAX_PATH, including null character */
1024 WCHAR *lastSlash;
1025 WCHAR pathext[MAXSTRING];
1026 BOOL extensionsupplied = FALSE;
1027 BOOL launched = FALSE;
1028 BOOL status;
1029 BOOL assumeInternal = FALSE;
1030 DWORD len;
1031 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1032 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1033 static const WCHAR delims[] = {'/','\\',':','\0'};
1035 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
1036 if (!(*param1) && !(*param2))
1037 return;
1039 /* Calculate the search path and stem to search for */
1040 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
1041 static const WCHAR curDir[] = {'.',';','\0'};
1042 strcpyW(pathtosearch, curDir);
1043 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1044 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1045 static const WCHAR curDir[] = {'.','\0'};
1046 strcpyW (pathtosearch, curDir);
1048 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1049 if (strlenW(param1) >= MAX_PATH)
1051 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1052 return;
1055 strcpyW(stemofsearch, param1);
1057 } else {
1059 /* Convert eg. ..\fred to include a directory by removing file part */
1060 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1061 lastSlash = strrchrW(pathtosearch, '\\');
1062 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1063 strcpyW(stemofsearch, lastSlash+1);
1065 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1066 c:\windows\a.bat syntax */
1067 if (lastSlash) *(lastSlash + 1) = 0x00;
1070 /* Now extract PATHEXT */
1071 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1072 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1073 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1074 '.','c','o','m',';',
1075 '.','c','m','d',';',
1076 '.','e','x','e','\0'};
1077 strcpyW (pathext, dfltPathExt);
1080 /* Loop through the search path, dir by dir */
1081 pathposn = pathtosearch;
1082 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1083 wine_dbgstr_w(stemofsearch));
1084 while (!launched && pathposn) {
1086 WCHAR thisDir[MAX_PATH] = {'\0'};
1087 WCHAR *pos = NULL;
1088 BOOL found = FALSE;
1089 static const WCHAR slashW[] = {'\\','\0'};
1091 /* Work on the first directory on the search path */
1092 pos = strchrW(pathposn, ';');
1093 if (pos) {
1094 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1095 thisDir[(pos-pathposn)] = 0x00;
1096 pathposn = pos+1;
1098 } else {
1099 strcpyW(thisDir, pathposn);
1100 pathposn = NULL;
1103 /* Since you can have eg. ..\.. on the path, need to expand
1104 to full information */
1105 strcpyW(temp, thisDir);
1106 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1108 /* 1. If extension supplied, see if that file exists */
1109 strcatW(thisDir, slashW);
1110 strcatW(thisDir, stemofsearch);
1111 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1113 /* 1. If extension supplied, see if that file exists */
1114 if (extensionsupplied) {
1115 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1116 found = TRUE;
1120 /* 2. Any .* matches? */
1121 if (!found) {
1122 HANDLE h;
1123 WIN32_FIND_DATAW finddata;
1124 static const WCHAR allFiles[] = {'.','*','\0'};
1126 strcatW(thisDir,allFiles);
1127 h = FindFirstFileW(thisDir, &finddata);
1128 FindClose(h);
1129 if (h != INVALID_HANDLE_VALUE) {
1131 WCHAR *thisExt = pathext;
1133 /* 3. Yes - Try each path ext */
1134 while (thisExt) {
1135 WCHAR *nextExt = strchrW(thisExt, ';');
1137 if (nextExt) {
1138 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1139 pos[(nextExt-thisExt)] = 0x00;
1140 thisExt = nextExt+1;
1141 } else {
1142 strcpyW(pos, thisExt);
1143 thisExt = NULL;
1146 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1147 found = TRUE;
1148 thisExt = NULL;
1154 /* Internal programs won't be picked up by this search, so even
1155 though not found, try one last createprocess and wait for it
1156 to complete.
1157 Note: Ideally we could tell between a console app (wait) and a
1158 windows app, but the API's for it fail in this case */
1159 if (!found && pathposn == NULL) {
1160 WINE_TRACE("ASSUMING INTERNAL\n");
1161 assumeInternal = TRUE;
1162 } else {
1163 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1166 /* Once found, launch it */
1167 if (found || assumeInternal) {
1168 STARTUPINFOW st;
1169 PROCESS_INFORMATION pe;
1170 SHFILEINFOW psfi;
1171 DWORD console;
1172 HINSTANCE hinst;
1173 WCHAR *ext = strrchrW( thisDir, '.' );
1174 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1175 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1177 launched = TRUE;
1179 /* Special case BAT and CMD */
1180 if (ext && !strcmpiW(ext, batExt)) {
1181 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1182 return;
1183 } else if (ext && !strcmpiW(ext, cmdExt)) {
1184 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1185 return;
1186 } else {
1188 /* thisDir contains the file to be launched, but with what?
1189 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1190 hinst = FindExecutableW (thisDir, NULL, temp);
1191 if ((INT_PTR)hinst < 32)
1192 console = 0;
1193 else
1194 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1196 ZeroMemory (&st, sizeof(STARTUPINFOW));
1197 st.cb = sizeof(STARTUPINFOW);
1198 init_msvcrt_io_block(&st);
1200 /* Launch the process and if a CUI wait on it to complete
1201 Note: Launching internal wine processes cannot specify a full path to exe */
1202 status = CreateProcessW(assumeInternal?NULL : thisDir,
1203 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1204 if ((opt_c || opt_k) && !opt_s && !status
1205 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1206 /* strip first and last quote WCHARacters and try again */
1207 WCMD_opt_s_strip_quotes(command);
1208 opt_s=1;
1209 WCMD_run_program(command, called);
1210 return;
1213 if (!status)
1214 break;
1216 if (!assumeInternal && !console) errorlevel = 0;
1217 else
1219 /* Always wait when called in a batch program context */
1220 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1221 GetExitCodeProcess (pe.hProcess, &errorlevel);
1222 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1224 CloseHandle(pe.hProcess);
1225 CloseHandle(pe.hThread);
1226 return;
1231 /* Not found anywhere - give up */
1232 SetLastError(ERROR_FILE_NOT_FOUND);
1233 WCMD_print_error ();
1235 /* If a command fails to launch, it sets errorlevel 9009 - which
1236 does not seem to have any associated constant definition */
1237 errorlevel = 9009;
1238 return;
1242 /*****************************************************************************
1243 * Process one command. If the command is EXIT this routine does not return.
1244 * We will recurse through here executing batch files.
1246 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1247 const WCHAR *forVariable, const WCHAR *forValue,
1248 CMD_LIST **cmdList)
1250 WCHAR *cmd, *p, *redir;
1251 int status, i;
1252 DWORD count, creationDisposition;
1253 HANDLE h;
1254 WCHAR *whichcmd;
1255 SECURITY_ATTRIBUTES sa;
1256 WCHAR *new_cmd = NULL;
1257 WCHAR *new_redir = NULL;
1258 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1259 GetStdHandle (STD_OUTPUT_HANDLE),
1260 GetStdHandle (STD_ERROR_HANDLE)};
1261 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1262 STD_OUTPUT_HANDLE,
1263 STD_ERROR_HANDLE};
1264 BOOL prev_echo_mode, piped = FALSE;
1266 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1267 wine_dbgstr_w(command), cmdList,
1268 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1270 /* If the next command is a pipe then we implement pipes by redirecting
1271 the output from this command to a temp file and input into the
1272 next command from that temp file.
1273 FIXME: Use of named pipes would make more sense here as currently this
1274 process has to finish before the next one can start but this requires
1275 a change to not wait for the first app to finish but rather the pipe */
1276 if (cmdList && (*cmdList)->nextcommand &&
1277 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1279 WCHAR temp_path[MAX_PATH];
1280 static const WCHAR cmdW[] = {'C','M','D','\0'};
1282 /* Remember piping is in action */
1283 WINE_TRACE("Output needs to be piped\n");
1284 piped = TRUE;
1286 /* Generate a unique temporary filename */
1287 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1288 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1289 WINE_TRACE("Using temporary file of %s\n",
1290 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1293 /* Move copy of the command onto the heap so it can be expanded */
1294 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1295 if (!new_cmd)
1297 WINE_ERR("Could not allocate memory for new_cmd\n");
1298 return;
1300 strcpyW(new_cmd, command);
1302 /* Move copy of the redirects onto the heap so it can be expanded */
1303 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1304 if (!new_redir)
1306 WINE_ERR("Could not allocate memory for new_redir\n");
1307 HeapFree( GetProcessHeap(), 0, new_cmd );
1308 return;
1311 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1312 if (piped) {
1313 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1314 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1315 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1316 } else {
1317 strcpyW(new_redir, redirects);
1320 /* Expand variables in command line mode only (batch mode will
1321 be expanded as the line is read in, except for 'for' loops) */
1322 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1323 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1324 cmd = new_cmd;
1327 * Changing default drive has to be handled as a special case.
1330 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1331 WCHAR envvar[5];
1332 WCHAR dir[MAX_PATH];
1334 /* According to MSDN CreateProcess docs, special env vars record
1335 the current directory on each drive, in the form =C:
1336 so see if one specified, and if so go back to it */
1337 strcpyW(envvar, equalsW);
1338 strcatW(envvar, cmd);
1339 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1340 static const WCHAR fmt[] = {'%','s','\\','\0'};
1341 wsprintfW(cmd, fmt, cmd);
1342 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1344 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1345 status = SetCurrentDirectoryW(cmd);
1346 if (!status) WCMD_print_error ();
1347 HeapFree( GetProcessHeap(), 0, cmd );
1348 HeapFree( GetProcessHeap(), 0, new_redir );
1349 return;
1352 sa.nLength = sizeof(sa);
1353 sa.lpSecurityDescriptor = NULL;
1354 sa.bInheritHandle = TRUE;
1357 * Redirect stdin, stdout and/or stderr if required.
1360 /* STDIN could come from a preceding pipe, so delete on close if it does */
1361 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1362 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1363 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1364 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1365 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1366 if (h == INVALID_HANDLE_VALUE) {
1367 WCMD_print_error ();
1368 HeapFree( GetProcessHeap(), 0, cmd );
1369 HeapFree( GetProcessHeap(), 0, new_redir );
1370 return;
1372 SetStdHandle (STD_INPUT_HANDLE, h);
1374 /* No need to remember the temporary name any longer once opened */
1375 (*cmdList)->pipeFile[0] = 0x00;
1377 /* Otherwise STDIN could come from a '<' redirect */
1378 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1379 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1380 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1381 if (h == INVALID_HANDLE_VALUE) {
1382 WCMD_print_error ();
1383 HeapFree( GetProcessHeap(), 0, cmd );
1384 HeapFree( GetProcessHeap(), 0, new_redir );
1385 return;
1387 SetStdHandle (STD_INPUT_HANDLE, h);
1390 /* Scan the whole command looking for > and 2> */
1391 redir = new_redir;
1392 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1393 int handle = 0;
1395 if (p > redir && (*(p-1)=='2'))
1396 handle = 2;
1397 else
1398 handle = 1;
1400 p++;
1401 if ('>' == *p) {
1402 creationDisposition = OPEN_ALWAYS;
1403 p++;
1405 else {
1406 creationDisposition = CREATE_ALWAYS;
1409 /* Add support for 2>&1 */
1410 redir = p;
1411 if (*p == '&') {
1412 int idx = *(p+1) - '0';
1414 if (DuplicateHandle(GetCurrentProcess(),
1415 GetStdHandle(idx_stdhandles[idx]),
1416 GetCurrentProcess(),
1418 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1419 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1421 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1423 } else {
1424 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1425 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1426 FILE_ATTRIBUTE_NORMAL, NULL);
1427 if (h == INVALID_HANDLE_VALUE) {
1428 WCMD_print_error ();
1429 HeapFree( GetProcessHeap(), 0, cmd );
1430 HeapFree( GetProcessHeap(), 0, new_redir );
1431 return;
1433 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1434 INVALID_SET_FILE_POINTER) {
1435 WCMD_print_error ();
1437 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1440 SetStdHandle (idx_stdhandles[handle], h);
1444 * Strip leading whitespaces, and a '@' if supplied
1446 whichcmd = WCMD_skip_leading_spaces(cmd);
1447 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1448 if (whichcmd[0] == '@') whichcmd++;
1451 * Check if the command entered is internal. If it is, pass the rest of the
1452 * line down to the command. If not try to run a program.
1455 count = 0;
1456 while (IsCharAlphaNumericW(whichcmd[count])) {
1457 count++;
1459 for (i=0; i<=WCMD_EXIT; i++) {
1460 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1461 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1463 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1464 WCMD_parse (p, quals, param1, param2);
1465 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1467 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1468 /* this is a help request for a builtin program */
1469 i = WCMD_HELP;
1470 memcpy(p, whichcmd, count * sizeof(WCHAR));
1471 p[count] = '\0';
1475 switch (i) {
1477 case WCMD_CALL:
1478 WCMD_call (p);
1479 break;
1480 case WCMD_CD:
1481 case WCMD_CHDIR:
1482 WCMD_setshow_default (p);
1483 break;
1484 case WCMD_CLS:
1485 WCMD_clear_screen ();
1486 break;
1487 case WCMD_COPY:
1488 WCMD_copy ();
1489 break;
1490 case WCMD_CTTY:
1491 WCMD_change_tty ();
1492 break;
1493 case WCMD_DATE:
1494 WCMD_setshow_date ();
1495 break;
1496 case WCMD_DEL:
1497 case WCMD_ERASE:
1498 WCMD_delete (p);
1499 break;
1500 case WCMD_DIR:
1501 WCMD_directory (p);
1502 break;
1503 case WCMD_ECHO:
1504 WCMD_echo(&whichcmd[count]);
1505 break;
1506 case WCMD_FOR:
1507 WCMD_for (p, cmdList);
1508 break;
1509 case WCMD_GOTO:
1510 WCMD_goto (cmdList);
1511 break;
1512 case WCMD_HELP:
1513 WCMD_give_help (p);
1514 break;
1515 case WCMD_IF:
1516 WCMD_if (p, cmdList);
1517 break;
1518 case WCMD_LABEL:
1519 WCMD_volume (TRUE, p);
1520 break;
1521 case WCMD_MD:
1522 case WCMD_MKDIR:
1523 WCMD_create_dir (p);
1524 break;
1525 case WCMD_MOVE:
1526 WCMD_move ();
1527 break;
1528 case WCMD_PATH:
1529 WCMD_setshow_path (p);
1530 break;
1531 case WCMD_PAUSE:
1532 WCMD_pause ();
1533 break;
1534 case WCMD_PROMPT:
1535 WCMD_setshow_prompt ();
1536 break;
1537 case WCMD_REM:
1538 break;
1539 case WCMD_REN:
1540 case WCMD_RENAME:
1541 WCMD_rename ();
1542 break;
1543 case WCMD_RD:
1544 case WCMD_RMDIR:
1545 WCMD_remove_dir (p);
1546 break;
1547 case WCMD_SETLOCAL:
1548 WCMD_setlocal(p);
1549 break;
1550 case WCMD_ENDLOCAL:
1551 WCMD_endlocal();
1552 break;
1553 case WCMD_SET:
1554 WCMD_setshow_env (p);
1555 break;
1556 case WCMD_SHIFT:
1557 WCMD_shift (p);
1558 break;
1559 case WCMD_TIME:
1560 WCMD_setshow_time ();
1561 break;
1562 case WCMD_TITLE:
1563 if (strlenW(&whichcmd[count]) > 0)
1564 WCMD_title(&whichcmd[count+1]);
1565 break;
1566 case WCMD_TYPE:
1567 WCMD_type (p);
1568 break;
1569 case WCMD_VER:
1570 WCMD_output(newline);
1571 WCMD_version ();
1572 break;
1573 case WCMD_VERIFY:
1574 WCMD_verify (p);
1575 break;
1576 case WCMD_VOL:
1577 WCMD_volume (FALSE, p);
1578 break;
1579 case WCMD_PUSHD:
1580 WCMD_pushd(p);
1581 break;
1582 case WCMD_POPD:
1583 WCMD_popd();
1584 break;
1585 case WCMD_ASSOC:
1586 WCMD_assoc(p, TRUE);
1587 break;
1588 case WCMD_COLOR:
1589 WCMD_color();
1590 break;
1591 case WCMD_FTYPE:
1592 WCMD_assoc(p, FALSE);
1593 break;
1594 case WCMD_MORE:
1595 WCMD_more(p);
1596 break;
1597 case WCMD_CHOICE:
1598 WCMD_choice(p);
1599 break;
1600 case WCMD_EXIT:
1601 WCMD_exit (cmdList);
1602 break;
1603 default:
1604 prev_echo_mode = echo_mode;
1605 WCMD_run_program (whichcmd, 0);
1606 echo_mode = prev_echo_mode;
1608 HeapFree( GetProcessHeap(), 0, cmd );
1609 HeapFree( GetProcessHeap(), 0, new_redir );
1611 /* Restore old handles */
1612 for (i=0; i<3; i++) {
1613 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1614 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1615 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1620 /*************************************************************************
1621 * WCMD_LoadMessage
1622 * Load a string from the resource file, handling any error
1623 * Returns string retrieved from resource file
1625 WCHAR *WCMD_LoadMessage(UINT id) {
1626 static WCHAR msg[2048];
1627 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1629 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1630 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1631 strcpyW(msg, failedMsg);
1633 return msg;
1636 /***************************************************************************
1637 * WCMD_DumpCommands
1639 * Dumps out the parsed command line to ensure syntax is correct
1641 static void WCMD_DumpCommands(CMD_LIST *commands) {
1642 CMD_LIST *thisCmd = commands;
1644 WINE_TRACE("Parsed line:\n");
1645 while (thisCmd != NULL) {
1646 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1647 thisCmd,
1648 thisCmd->prevDelim,
1649 thisCmd->bracketDepth,
1650 thisCmd->nextcommand,
1651 wine_dbgstr_w(thisCmd->command),
1652 wine_dbgstr_w(thisCmd->redirects));
1653 thisCmd = thisCmd->nextcommand;
1657 /***************************************************************************
1658 * WCMD_addCommand
1660 * Adds a command to the current command list
1662 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1663 WCHAR *redirs, int *redirLen,
1664 WCHAR **copyTo, int **copyToLen,
1665 CMD_DELIMITERS prevDelim, int curDepth,
1666 CMD_LIST **lastEntry, CMD_LIST **output) {
1668 CMD_LIST *thisEntry = NULL;
1670 /* Allocate storage for command */
1671 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1673 /* Copy in the command */
1674 if (command) {
1675 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1676 (*commandLen+1) * sizeof(WCHAR));
1677 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1678 thisEntry->command[*commandLen] = 0x00;
1680 /* Copy in the redirects */
1681 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1682 (*redirLen+1) * sizeof(WCHAR));
1683 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1684 thisEntry->redirects[*redirLen] = 0x00;
1685 thisEntry->pipeFile[0] = 0x00;
1687 /* Reset the lengths */
1688 *commandLen = 0;
1689 *redirLen = 0;
1690 *copyToLen = commandLen;
1691 *copyTo = command;
1693 } else {
1694 thisEntry->command = NULL;
1695 thisEntry->redirects = NULL;
1696 thisEntry->pipeFile[0] = 0x00;
1699 /* Fill in other fields */
1700 thisEntry->nextcommand = NULL;
1701 thisEntry->prevDelim = prevDelim;
1702 thisEntry->bracketDepth = curDepth;
1703 if (*lastEntry) {
1704 (*lastEntry)->nextcommand = thisEntry;
1705 } else {
1706 *output = thisEntry;
1708 *lastEntry = thisEntry;
1712 /***************************************************************************
1713 * WCMD_IsEndQuote
1715 * Checks if the quote pointed to is the end-quote.
1717 * Quotes end if:
1719 * 1) The current parameter ends at EOL or at the beginning
1720 * of a redirection or pipe and not in a quote section.
1722 * 2) If the next character is a space and not in a quote section.
1724 * Returns TRUE if this is an end quote, and FALSE if it is not.
1727 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1729 int quoteCount = quoteIndex;
1730 int i;
1732 /* If we are not in a quoted section, then we are not an end-quote */
1733 if(quoteIndex == 0)
1735 return FALSE;
1738 /* Check how many quotes are left for this parameter */
1739 for(i=0;quote[i];i++)
1741 if(quote[i] == '"')
1743 quoteCount++;
1746 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1747 else if(((quoteCount % 2) == 0)
1748 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1750 break;
1754 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1755 be an end-quote */
1756 if(quoteIndex >= (quoteCount / 2))
1758 return TRUE;
1761 /* No cigar */
1762 return FALSE;
1765 /***************************************************************************
1766 * WCMD_ReadAndParseLine
1768 * Either uses supplied input or
1769 * Reads a file from the handle, and then...
1770 * Parse the text buffer, splitting into separate commands
1771 * - unquoted && strings split 2 commands but the 2nd is flagged as
1772 * following an &&
1773 * - ( as the first character just ups the bracket depth
1774 * - unquoted ) when bracket depth > 0 terminates a bracket and
1775 * adds a CMD_LIST structure with null command
1776 * - Anything else gets put into the command string (including
1777 * redirects)
1779 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1781 WCHAR *curPos;
1782 int inQuotes = 0;
1783 WCHAR curString[MAXSTRING];
1784 int curStringLen = 0;
1785 WCHAR curRedirs[MAXSTRING];
1786 int curRedirsLen = 0;
1787 WCHAR *curCopyTo;
1788 int *curLen;
1789 int curDepth = 0;
1790 CMD_LIST *lastEntry = NULL;
1791 CMD_DELIMITERS prevDelim = CMD_NONE;
1792 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1793 static const WCHAR remCmd[] = {'r','e','m'};
1794 static const WCHAR forCmd[] = {'f','o','r'};
1795 static const WCHAR ifCmd[] = {'i','f'};
1796 static const WCHAR ifElse[] = {'e','l','s','e'};
1797 BOOL inRem = FALSE;
1798 BOOL inFor = FALSE;
1799 BOOL inIn = FALSE;
1800 BOOL inIf = FALSE;
1801 BOOL inElse= FALSE;
1802 BOOL onlyWhiteSpace = FALSE;
1803 BOOL lastWasWhiteSpace = FALSE;
1804 BOOL lastWasDo = FALSE;
1805 BOOL lastWasIn = FALSE;
1806 BOOL lastWasElse = FALSE;
1807 BOOL lastWasRedirect = TRUE;
1809 /* Allocate working space for a command read from keyboard, file etc */
1810 if (!extraSpace)
1811 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1812 if (!extraSpace)
1814 WINE_ERR("Could not allocate memory for extraSpace\n");
1815 return NULL;
1818 /* If initial command read in, use that, otherwise get input from handle */
1819 if (optionalcmd != NULL) {
1820 strcpyW(extraSpace, optionalcmd);
1821 } else if (readFrom == INVALID_HANDLE_VALUE) {
1822 WINE_FIXME("No command nor handle supplied\n");
1823 } else {
1824 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1825 return NULL;
1827 curPos = extraSpace;
1829 /* Handle truncated input - issue warning */
1830 if (strlenW(extraSpace) == MAXSTRING -1) {
1831 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1832 WCMD_output_asis_stderr(extraSpace);
1833 WCMD_output_asis_stderr(newline);
1836 /* Replace env vars if in a batch context */
1837 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1838 /* Show prompt before batch line IF echo is on and in batch program */
1839 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1840 static const WCHAR spc[]={' ','\0'};
1841 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1842 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1843 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1844 DWORD curr_size = strlenW(extraSpace);
1845 DWORD min_len = (curr_size < len ? curr_size : len);
1846 WCMD_show_prompt();
1847 WCMD_output_asis(extraSpace);
1848 /* I don't know why Windows puts a space here but it does */
1849 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1850 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1851 extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1852 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1853 extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1855 WCMD_output_asis(spc);
1857 WCMD_output_asis(newline);
1860 /* Start with an empty string, copying to the command string */
1861 curStringLen = 0;
1862 curRedirsLen = 0;
1863 curCopyTo = curString;
1864 curLen = &curStringLen;
1865 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1867 /* Parse every character on the line being processed */
1868 while (*curPos != 0x00) {
1870 WCHAR thisChar;
1872 /* Debugging AID:
1873 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1874 lastWasWhiteSpace, onlyWhiteSpace);
1877 /* Certain commands need special handling */
1878 if (curStringLen == 0 && curCopyTo == curString) {
1879 static const WCHAR forDO[] = {'d','o'};
1881 /* If command starts with 'rem ', ignore any &&, ( etc. */
1882 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1883 inRem = TRUE;
1885 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1886 inFor = TRUE;
1888 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1889 is only true in the command portion of the IF statement, but this
1890 should suffice for now
1891 FIXME: Silly syntax like "if 1(==1( (
1892 echo they equal
1893 )" will be parsed wrong */
1894 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1895 inIf = TRUE;
1897 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1898 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1899 inElse = TRUE;
1900 lastWasElse = TRUE;
1901 onlyWhiteSpace = TRUE;
1902 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1903 (*curLen)+=keyw_len;
1904 curPos+=keyw_len;
1905 continue;
1907 /* In a for loop, the DO command will follow a close bracket followed by
1908 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1909 is then 0, and all whitespace is skipped */
1910 } else if (inFor &&
1911 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1912 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1913 WINE_TRACE("Found 'DO '\n");
1914 lastWasDo = TRUE;
1915 onlyWhiteSpace = TRUE;
1916 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1917 (*curLen)+=keyw_len;
1918 curPos+=keyw_len;
1919 continue;
1921 } else if (curCopyTo == curString) {
1923 /* Special handling for the 'FOR' command */
1924 if (inFor && lastWasWhiteSpace) {
1925 static const WCHAR forIN[] = {'i','n'};
1927 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1929 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1930 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1931 WINE_TRACE("Found 'IN '\n");
1932 lastWasIn = TRUE;
1933 onlyWhiteSpace = TRUE;
1934 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1935 (*curLen)+=keyw_len;
1936 curPos+=keyw_len;
1937 continue;
1942 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1943 so just use the default processing ie skip character specific
1944 matching below */
1945 if (!inRem) thisChar = *curPos;
1946 else thisChar = 'X'; /* Character with no special processing */
1948 lastWasWhiteSpace = FALSE; /* Will be reset below */
1950 switch (thisChar) {
1952 case '=': /* drop through - ignore token delimiters at the start of a command */
1953 case ',': /* drop through - ignore token delimiters at the start of a command */
1954 case '\t':/* drop through - ignore token delimiters at the start of a command */
1955 case ' ':
1956 /* If a redirect in place, it ends here */
1957 if (!inQuotes && !lastWasRedirect) {
1959 /* If finishing off a redirect, add a whitespace delimiter */
1960 if (curCopyTo == curRedirs) {
1961 curCopyTo[(*curLen)++] = ' ';
1963 curCopyTo = curString;
1964 curLen = &curStringLen;
1966 if (*curLen > 0) {
1967 curCopyTo[(*curLen)++] = *curPos;
1970 /* Remember just processed whitespace */
1971 lastWasWhiteSpace = TRUE;
1973 break;
1975 case '>': /* drop through - handle redirect chars the same */
1976 case '<':
1977 /* Make a redirect start here */
1978 if (!inQuotes) {
1979 curCopyTo = curRedirs;
1980 curLen = &curRedirsLen;
1981 lastWasRedirect = TRUE;
1984 /* See if 1>, 2> etc, in which case we have some patching up
1985 to do (provided there's a preceding whitespace, and enough
1986 chars read so far) */
1987 if (curStringLen > 2
1988 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1989 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1990 curStringLen--;
1991 curString[curStringLen] = 0x00;
1992 curCopyTo[(*curLen)++] = *(curPos-1);
1995 curCopyTo[(*curLen)++] = *curPos;
1997 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1998 do not process that ampersand as an AND operator */
1999 if (thisChar == '>' && *(curPos+1) == '&') {
2000 curCopyTo[(*curLen)++] = *(curPos+1);
2001 curPos++;
2003 break;
2005 case '|': /* Pipe character only if not || */
2006 if (!inQuotes) {
2007 lastWasRedirect = FALSE;
2009 /* Add an entry to the command list */
2010 if (curStringLen > 0) {
2012 /* Add the current command */
2013 WCMD_addCommand(curString, &curStringLen,
2014 curRedirs, &curRedirsLen,
2015 &curCopyTo, &curLen,
2016 prevDelim, curDepth,
2017 &lastEntry, output);
2021 if (*(curPos+1) == '|') {
2022 curPos++; /* Skip other | */
2023 prevDelim = CMD_ONFAILURE;
2024 } else {
2025 prevDelim = CMD_PIPE;
2027 } else {
2028 curCopyTo[(*curLen)++] = *curPos;
2030 break;
2032 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2033 inQuotes--;
2034 } else {
2035 inQuotes++; /* Quotes within quotes are fun! */
2037 curCopyTo[(*curLen)++] = *curPos;
2038 lastWasRedirect = FALSE;
2039 break;
2041 case '(': /* If a '(' is the first non whitespace in a command portion
2042 ie start of line or just after &&, then we read until an
2043 unquoted ) is found */
2044 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2045 ", for(%d, In:%d, Do:%d)"
2046 ", if(%d, else:%d, lwe:%d)\n",
2047 *curLen, inQuotes,
2048 onlyWhiteSpace,
2049 inFor, lastWasIn, lastWasDo,
2050 inIf, inElse, lastWasElse);
2051 lastWasRedirect = FALSE;
2053 /* Ignore open brackets inside the for set */
2054 if (*curLen == 0 && !inIn) {
2055 curDepth++;
2057 /* If in quotes, ignore brackets */
2058 } else if (inQuotes) {
2059 curCopyTo[(*curLen)++] = *curPos;
2061 /* In a FOR loop, an unquoted '(' may occur straight after
2062 IN or DO
2063 In an IF statement just handle it regardless as we don't
2064 parse the operands
2065 In an ELSE statement, only allow it straight away after
2066 the ELSE and whitespace
2068 } else if (inIf ||
2069 (inElse && lastWasElse && onlyWhiteSpace) ||
2070 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2072 /* If entering into an 'IN', set inIn */
2073 if (inFor && lastWasIn && onlyWhiteSpace) {
2074 WINE_TRACE("Inside an IN\n");
2075 inIn = TRUE;
2078 /* Add the current command */
2079 WCMD_addCommand(curString, &curStringLen,
2080 curRedirs, &curRedirsLen,
2081 &curCopyTo, &curLen,
2082 prevDelim, curDepth,
2083 &lastEntry, output);
2085 curDepth++;
2086 } else {
2087 curCopyTo[(*curLen)++] = *curPos;
2089 break;
2091 case '&': if (!inQuotes) {
2092 lastWasRedirect = FALSE;
2094 /* Add an entry to the command list */
2095 if (curStringLen > 0) {
2097 /* Add the current command */
2098 WCMD_addCommand(curString, &curStringLen,
2099 curRedirs, &curRedirsLen,
2100 &curCopyTo, &curLen,
2101 prevDelim, curDepth,
2102 &lastEntry, output);
2106 if (*(curPos+1) == '&') {
2107 curPos++; /* Skip other & */
2108 prevDelim = CMD_ONSUCCESS;
2109 } else {
2110 prevDelim = CMD_NONE;
2112 } else {
2113 curCopyTo[(*curLen)++] = *curPos;
2115 break;
2117 case ')': if (!inQuotes && curDepth > 0) {
2118 lastWasRedirect = FALSE;
2120 /* Add the current command if there is one */
2121 if (curStringLen) {
2123 /* Add the current command */
2124 WCMD_addCommand(curString, &curStringLen,
2125 curRedirs, &curRedirsLen,
2126 &curCopyTo, &curLen,
2127 prevDelim, curDepth,
2128 &lastEntry, output);
2131 /* Add an empty entry to the command list */
2132 prevDelim = CMD_NONE;
2133 WCMD_addCommand(NULL, &curStringLen,
2134 curRedirs, &curRedirsLen,
2135 &curCopyTo, &curLen,
2136 prevDelim, curDepth,
2137 &lastEntry, output);
2138 curDepth--;
2140 /* Leave inIn if necessary */
2141 if (inIn) inIn = FALSE;
2142 } else {
2143 curCopyTo[(*curLen)++] = *curPos;
2145 break;
2146 default:
2147 lastWasRedirect = FALSE;
2148 curCopyTo[(*curLen)++] = *curPos;
2151 curPos++;
2153 /* At various times we need to know if we have only skipped whitespace,
2154 so reset this variable and then it will remain true until a non
2155 whitespace is found */
2156 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2157 onlyWhiteSpace = FALSE;
2159 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2160 if (!lastWasWhiteSpace) {
2161 lastWasIn = lastWasDo = FALSE;
2164 /* If we have reached the end, add this command into the list */
2165 if (*curPos == 0x00 && *curLen > 0) {
2167 /* Add an entry to the command list */
2168 WCMD_addCommand(curString, &curStringLen,
2169 curRedirs, &curRedirsLen,
2170 &curCopyTo, &curLen,
2171 prevDelim, curDepth,
2172 &lastEntry, output);
2175 /* If we have reached the end of the string, see if bracketing outstanding */
2176 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2177 inRem = FALSE;
2178 prevDelim = CMD_NONE;
2179 inQuotes = 0;
2180 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2182 /* Read more, skipping any blank lines */
2183 while (*extraSpace == 0x00) {
2184 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2185 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
2186 break;
2188 curPos = extraSpace;
2189 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2190 /* Continue to echo commands IF echo is on and in batch program */
2191 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2192 WCMD_output_asis(extraSpace);
2193 WCMD_output_asis(newline);
2198 /* Dump out the parsed output */
2199 WCMD_DumpCommands(*output);
2201 return extraSpace;
2204 /***************************************************************************
2205 * WCMD_process_commands
2207 * Process all the commands read in so far
2209 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2210 const WCHAR *var, const WCHAR *val) {
2212 int bdepth = -1;
2214 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2216 /* Loop through the commands, processing them one by one */
2217 while (thisCmd) {
2219 CMD_LIST *origCmd = thisCmd;
2221 /* If processing one bracket only, and we find the end bracket
2222 entry (or less), return */
2223 if (oneBracket && !thisCmd->command &&
2224 bdepth <= thisCmd->bracketDepth) {
2225 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2226 thisCmd, thisCmd->nextcommand);
2227 return thisCmd->nextcommand;
2230 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2231 about them and it will be handled in there)
2232 Also, skip over any batch labels (eg. :fred) */
2233 if (thisCmd->command && thisCmd->command[0] != ':') {
2234 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2235 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2238 /* Step on unless the command itself already stepped on */
2239 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2241 return NULL;
2244 /***************************************************************************
2245 * WCMD_free_commands
2247 * Frees the storage held for a parsed command line
2248 * - This is not done in the process_commands, as eventually the current
2249 * pointer will be modified within the commands, and hence a single free
2250 * routine is simpler
2252 void WCMD_free_commands(CMD_LIST *cmds) {
2254 /* Loop through the commands, freeing them one by one */
2255 while (cmds) {
2256 CMD_LIST *thisCmd = cmds;
2257 cmds = cmds->nextcommand;
2258 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2259 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2260 HeapFree(GetProcessHeap(), 0, thisCmd);
2265 /*****************************************************************************
2266 * Main entry point. This is a console application so we have a main() not a
2267 * winmain().
2270 int wmain (int argc, WCHAR *argvW[])
2272 int args;
2273 WCHAR *cmd = NULL;
2274 WCHAR string[1024];
2275 WCHAR envvar[4];
2276 int opt_q;
2277 int opt_t = 0;
2278 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2279 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2280 char ansiVersion[100];
2281 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2283 srand(time(NULL));
2285 /* Pre initialize some messages */
2286 strcpy(ansiVersion, PACKAGE_VERSION);
2287 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2288 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2289 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2291 args = argc;
2292 opt_c=opt_k=opt_q=opt_s=0;
2293 while (args > 0)
2295 WCHAR c;
2296 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2297 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2298 argvW++;
2299 args--;
2300 continue;
2303 c=(*argvW)[1];
2304 if (tolowerW(c)=='c') {
2305 opt_c=1;
2306 } else if (tolowerW(c)=='q') {
2307 opt_q=1;
2308 } else if (tolowerW(c)=='k') {
2309 opt_k=1;
2310 } else if (tolowerW(c)=='s') {
2311 opt_s=1;
2312 } else if (tolowerW(c)=='a') {
2313 unicodePipes=FALSE;
2314 } else if (tolowerW(c)=='u') {
2315 unicodePipes=TRUE;
2316 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2317 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2318 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2319 /* Ignored for compatibility with Windows */
2322 if ((*argvW)[2]==0) {
2323 argvW++;
2324 args--;
2326 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2328 *argvW+=2;
2331 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2332 break;
2335 if (opt_q) {
2336 static const WCHAR eoff[] = {'O','F','F','\0'};
2337 WCMD_echo(eoff);
2340 if (opt_c || opt_k) {
2341 int len,qcount;
2342 WCHAR** arg;
2343 int argsLeft;
2344 WCHAR* p;
2346 /* opt_s left unflagged if the command starts with and contains exactly
2347 * one quoted string (exactly two quote characters). The quoted string
2348 * must be an executable name that has whitespace and must not have the
2349 * following characters: &<>()@^| */
2351 /* Build the command to execute */
2352 len = 0;
2353 qcount = 0;
2354 argsLeft = args;
2355 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2357 int has_space,bcount;
2358 WCHAR* a;
2360 has_space=0;
2361 bcount=0;
2362 a=*arg;
2363 if( !*a ) has_space=1;
2364 while (*a!='\0') {
2365 if (*a=='\\') {
2366 bcount++;
2367 } else {
2368 if (*a==' ' || *a=='\t') {
2369 has_space=1;
2370 } else if (*a=='"') {
2371 /* doubling of '\' preceding a '"',
2372 * plus escaping of said '"'
2374 len+=2*bcount+1;
2375 qcount++;
2377 bcount=0;
2379 a++;
2381 len+=(a-*arg) + 1; /* for the separating space */
2382 if (has_space)
2384 len+=2; /* for the quotes */
2385 qcount+=2;
2389 if (qcount!=2)
2390 opt_s=1;
2392 /* check argvW[0] for a space and invalid characters */
2393 if (!opt_s) {
2394 opt_s=1;
2395 p=*argvW;
2396 while (*p!='\0') {
2397 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2398 || *p=='@' || *p=='^' || *p=='|') {
2399 opt_s=1;
2400 break;
2402 if (*p==' ')
2403 opt_s=0;
2404 p++;
2408 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2409 if (!cmd)
2410 exit(1);
2412 p = cmd;
2413 argsLeft = args;
2414 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2416 int has_space,has_quote;
2417 WCHAR* a;
2419 /* Check for quotes and spaces in this argument */
2420 has_space=has_quote=0;
2421 a=*arg;
2422 if( !*a ) has_space=1;
2423 while (*a!='\0') {
2424 if (*a==' ' || *a=='\t') {
2425 has_space=1;
2426 if (has_quote)
2427 break;
2428 } else if (*a=='"') {
2429 has_quote=1;
2430 if (has_space)
2431 break;
2433 a++;
2436 /* Now transfer it to the command line */
2437 if (has_space)
2438 *p++='"';
2439 if (has_quote) {
2440 int bcount;
2441 WCHAR* a;
2443 bcount=0;
2444 a=*arg;
2445 while (*a!='\0') {
2446 if (*a=='\\') {
2447 *p++=*a;
2448 bcount++;
2449 } else {
2450 if (*a=='"') {
2451 int i;
2453 /* Double all the '\\' preceding this '"', plus one */
2454 for (i=0;i<=bcount;i++)
2455 *p++='\\';
2456 *p++='"';
2457 } else {
2458 *p++=*a;
2460 bcount=0;
2462 a++;
2464 } else {
2465 strcpyW(p,*arg);
2466 p+=strlenW(*arg);
2468 if (has_space)
2469 *p++='"';
2470 *p++=' ';
2472 if (p > cmd)
2473 p--; /* remove last space */
2474 *p = '\0';
2476 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2478 /* strip first and last quote characters if opt_s; check for invalid
2479 * executable is done later */
2480 if (opt_s && *cmd=='\"')
2481 WCMD_opt_s_strip_quotes(cmd);
2484 if (opt_c) {
2485 /* If we do a "cmd /c command", we don't want to allocate a new
2486 * console since the command returns immediately. Rather, we use
2487 * the currently allocated input and output handles. This allows
2488 * us to pipe to and read from the command interpreter.
2491 /* Parse the command string, without reading any more input */
2492 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2493 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2494 WCMD_free_commands(toExecute);
2495 toExecute = NULL;
2497 HeapFree(GetProcessHeap(), 0, cmd);
2498 return errorlevel;
2501 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2502 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2503 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2505 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2506 if (opt_t) {
2507 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2508 defaultColor = opt_t & 0xFF;
2509 param1[0] = 0x00;
2510 WCMD_color();
2512 } else {
2513 /* Check HKCU\Software\Microsoft\Command Processor
2514 Then HKLM\Software\Microsoft\Command Processor
2515 for defaultcolour value
2516 Note Can be supplied as DWORD or REG_SZ
2517 Note2 When supplied as REG_SZ it's in decimal!!! */
2518 HKEY key;
2519 DWORD type;
2520 DWORD value=0, size=4;
2521 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2522 'M','i','c','r','o','s','o','f','t','\\',
2523 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2524 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2526 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2527 0, KEY_READ, &key) == ERROR_SUCCESS) {
2528 WCHAR strvalue[4];
2530 /* See if DWORD or REG_SZ */
2531 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2532 NULL, NULL) == ERROR_SUCCESS) {
2533 if (type == REG_DWORD) {
2534 size = sizeof(DWORD);
2535 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2536 (LPBYTE)&value, &size);
2537 } else if (type == REG_SZ) {
2538 size = sizeof(strvalue)/sizeof(WCHAR);
2539 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2540 (LPBYTE)strvalue, &size);
2541 value = strtoulW(strvalue, NULL, 10);
2544 RegCloseKey(key);
2547 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2548 0, KEY_READ, &key) == ERROR_SUCCESS) {
2549 WCHAR strvalue[4];
2551 /* See if DWORD or REG_SZ */
2552 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2553 NULL, NULL) == ERROR_SUCCESS) {
2554 if (type == REG_DWORD) {
2555 size = sizeof(DWORD);
2556 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2557 (LPBYTE)&value, &size);
2558 } else if (type == REG_SZ) {
2559 size = sizeof(strvalue)/sizeof(WCHAR);
2560 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2561 (LPBYTE)strvalue, &size);
2562 value = strtoulW(strvalue, NULL, 10);
2565 RegCloseKey(key);
2568 /* If one found, set the screen to that colour */
2569 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2570 defaultColor = value & 0xFF;
2571 param1[0] = 0x00;
2572 WCMD_color();
2577 /* Save cwd into appropriate env var */
2578 GetCurrentDirectoryW(1024, string);
2579 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2580 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2581 wsprintfW(envvar, fmt, string[0]);
2582 SetEnvironmentVariableW(envvar, string);
2583 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2586 if (opt_k) {
2587 /* Parse the command string, without reading any more input */
2588 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2589 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2590 WCMD_free_commands(toExecute);
2591 toExecute = NULL;
2592 HeapFree(GetProcessHeap(), 0, cmd);
2596 * Loop forever getting commands and executing them.
2599 SetEnvironmentVariableW(promptW, defaultpromptW);
2600 WCMD_version ();
2601 while (TRUE) {
2603 /* Read until EOF (which for std input is never, but if redirect
2604 in place, may occur */
2605 if (echo_mode) WCMD_show_prompt();
2606 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2607 break;
2608 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2609 WCMD_free_commands(toExecute);
2610 toExecute = NULL;
2612 return 0;