cmd: Use boolean types for boolean variables.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
blob32a457eb6e266fdad948768ba19717d3404e81db
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 extern WCHAR inbuilt[][10];
35 HINSTANCE hinst;
36 DWORD errorlevel;
37 int defaultColor = 7;
38 BOOL echo_mode = TRUE;
39 static BOOL opt_c, opt_k, opt_s;
40 const WCHAR newline[] = {'\r','\n','\0'};
41 const WCHAR space[] = {' ','\0'};
42 WCHAR anykey[100];
43 WCHAR version_string[100];
44 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
45 BATCH_CONTEXT *context = NULL;
46 extern struct env_stack *pushd_directories;
47 static const WCHAR *pagedMessage = NULL;
48 static char *output_bufA = NULL;
49 #define MAX_WRITECONSOLE_SIZE 65535
50 static BOOL unicodePipes = FALSE;
53 * Returns a buffer for reading from/writing to file
54 * Never freed
56 static char *get_file_buffer(void)
58 if (!output_bufA) {
59 output_bufA = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
60 if (!output_bufA)
61 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
63 return output_bufA;
66 /*******************************************************************
67 * WCMD_output_asis_len - send output to current standard output
69 * Output a formatted unicode string. Ideally this will go to the console
70 * and hence required WriteConsoleW to output it, however if file i/o is
71 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
73 static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
75 DWORD nOut= 0;
76 DWORD res = 0;
78 /* If nothing to write, return (MORE does this sometimes) */
79 if (!len) return;
81 /* Try to write as unicode assuming it is to a console */
82 res = WriteConsoleW(device, message, len, &nOut, NULL);
84 /* If writing to console fails, assume its file
85 i/o so convert to OEM codepage and output */
86 if (!res) {
87 BOOL usedDefaultChar = FALSE;
88 DWORD convertedChars;
89 char *buffer;
91 if (!unicodePipes) {
93 if (!(buffer = get_file_buffer()))
94 return;
96 /* Convert to OEM, then output */
97 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
98 len, buffer, MAX_WRITECONSOLE_SIZE,
99 "?", &usedDefaultChar);
100 WriteFile(device, buffer, convertedChars,
101 &nOut, FALSE);
102 } else {
103 WriteFile(device, message, len*sizeof(WCHAR),
104 &nOut, FALSE);
107 return;
110 /*******************************************************************
111 * WCMD_output - send output to current standard output device.
115 void WCMD_output (const WCHAR *format, ...) {
117 va_list ap;
118 WCHAR string[1024];
119 DWORD ret;
121 va_start(ap,format);
122 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
123 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
124 WINE_ERR("Output truncated\n" );
125 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
126 string[ret] = '\0';
128 va_end(ap);
129 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
132 /*******************************************************************
133 * WCMD_output_stderr - send output to current standard error device.
137 void WCMD_output_stderr (const WCHAR *format, ...) {
139 va_list ap;
140 WCHAR string[1024];
141 DWORD ret;
143 va_start(ap,format);
144 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
145 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
146 WINE_ERR("Output truncated\n" );
147 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
148 string[ret] = '\0';
150 va_end(ap);
151 WCMD_output_asis_len(string, ret, GetStdHandle(STD_ERROR_HANDLE));
154 static int line_count;
155 static int max_height;
156 static int max_width;
157 static BOOL paged_mode;
158 static int numChars;
160 void WCMD_enter_paged_mode(const WCHAR *msg)
162 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
164 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
165 max_height = consoleInfo.dwSize.Y;
166 max_width = consoleInfo.dwSize.X;
167 } else {
168 max_height = 25;
169 max_width = 80;
171 paged_mode = TRUE;
172 line_count = 0;
173 numChars = 0;
174 pagedMessage = (msg==NULL)? anykey : msg;
177 void WCMD_leave_paged_mode(void)
179 paged_mode = FALSE;
180 pagedMessage = NULL;
183 /***************************************************************************
184 * WCMD_Readfile
186 * Read characters in from a console/file, returning result in Unicode
188 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
190 DWORD numRead;
191 char *buffer;
193 if (WCMD_is_console_handle(hIn))
194 /* Try to read from console as Unicode */
195 return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
197 /* We assume it's a file handle and read then convert from assumed OEM codepage */
198 if (!(buffer = get_file_buffer()))
199 return FALSE;
201 if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
202 return FALSE;
204 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
206 return TRUE;
209 /*******************************************************************
210 * WCMD_output_asis_handle
212 * Send output to specified handle without formatting e.g. when message contains '%'
214 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
215 DWORD count;
216 const WCHAR* ptr;
217 WCHAR string[1024];
218 HANDLE handle = GetStdHandle(std_handle);
220 if (paged_mode) {
221 do {
222 ptr = message;
223 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
224 numChars++;
225 ptr++;
227 if (*ptr == '\n') ptr++;
228 WCMD_output_asis_len(message, ptr - message, handle);
229 numChars = 0;
230 if (++line_count >= max_height - 1) {
231 line_count = 0;
232 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
233 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
235 } while (((message = ptr) != NULL) && (*ptr));
236 } else {
237 WCMD_output_asis_len(message, lstrlenW(message), handle);
241 /*******************************************************************
242 * WCMD_output_asis
244 * Send output to current standard output device, without formatting
245 * e.g. when message contains '%'
247 void WCMD_output_asis (const WCHAR *message) {
248 WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
251 /*******************************************************************
252 * WCMD_output_asis_stderr
254 * Send output to current standard error device, without formatting
255 * e.g. when message contains '%'
257 void WCMD_output_asis_stderr (const WCHAR *message) {
258 WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
261 /****************************************************************************
262 * WCMD_print_error
264 * Print the message for GetLastError
267 void WCMD_print_error (void) {
268 LPVOID lpMsgBuf;
269 DWORD error_code;
270 int status;
272 error_code = GetLastError ();
273 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
274 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
275 if (!status) {
276 WINE_FIXME ("Cannot display message for error %d, status %d\n",
277 error_code, GetLastError());
278 return;
281 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
282 GetStdHandle(STD_ERROR_HANDLE));
283 LocalFree (lpMsgBuf);
284 WCMD_output_asis_len (newline, lstrlenW(newline),
285 GetStdHandle(STD_ERROR_HANDLE));
286 return;
289 /******************************************************************************
290 * WCMD_show_prompt
292 * Display the prompt on STDout
296 static void WCMD_show_prompt (void) {
298 int status;
299 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
300 WCHAR *p, *q;
301 DWORD len;
302 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
304 len = GetEnvironmentVariableW(envPrompt, prompt_string,
305 sizeof(prompt_string)/sizeof(WCHAR));
306 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
307 static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
308 strcpyW (prompt_string, dfltPrompt);
310 p = prompt_string;
311 q = out_string;
312 *q++ = '\r';
313 *q++ = '\n';
314 *q = '\0';
315 while (*p != '\0') {
316 if (*p != '$') {
317 *q++ = *p++;
318 *q = '\0';
320 else {
321 p++;
322 switch (toupper(*p)) {
323 case '$':
324 *q++ = '$';
325 break;
326 case 'A':
327 *q++ = '&';
328 break;
329 case 'B':
330 *q++ = '|';
331 break;
332 case 'C':
333 *q++ = '(';
334 break;
335 case 'D':
336 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
337 while (*q) q++;
338 break;
339 case 'E':
340 *q++ = '\E';
341 break;
342 case 'F':
343 *q++ = ')';
344 break;
345 case 'G':
346 *q++ = '>';
347 break;
348 case 'H':
349 *q++ = '\b';
350 break;
351 case 'L':
352 *q++ = '<';
353 break;
354 case 'N':
355 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
356 if (status) {
357 *q++ = curdir[0];
359 break;
360 case 'P':
361 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
362 if (status) {
363 strcatW (q, curdir);
364 while (*q) q++;
366 break;
367 case 'Q':
368 *q++ = '=';
369 break;
370 case 'S':
371 *q++ = ' ';
372 break;
373 case 'T':
374 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
375 while (*q) q++;
376 break;
377 case 'V':
378 strcatW (q, version_string);
379 while (*q) q++;
380 break;
381 case '_':
382 *q++ = '\n';
383 break;
384 case '+':
385 if (pushd_directories) {
386 memset(q, '+', pushd_directories->u.stackdepth);
387 q = q + pushd_directories->u.stackdepth;
389 break;
391 p++;
392 *q = '\0';
395 WCMD_output_asis (out_string);
399 /*************************************************************************
400 * WCMD_strdupW
401 * A wide version of strdup as its missing from unicode.h
403 WCHAR *WCMD_strdupW(const WCHAR *input) {
404 int len=strlenW(input)+1;
405 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
406 memcpy(result, input, len * sizeof(WCHAR));
407 return result;
410 /*************************************************************************
411 * WCMD_strsubstW
412 * Replaces a portion of a Unicode string with the specified string.
413 * It's up to the caller to ensure there is enough space in the
414 * destination buffer.
416 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
418 if (len < 0)
419 len=insert ? lstrlenW(insert) : 0;
420 if (start+len != next)
421 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
422 if (insert)
423 memcpy(start, insert, len * sizeof(*insert));
426 /***************************************************************************
427 * WCMD_skip_leading_spaces
429 * Return a pointer to the first non-whitespace character of string.
430 * Does not modify the input string.
432 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
434 WCHAR *ptr;
436 ptr = string;
437 while (*ptr == ' ' || *ptr == '\t') ptr++;
438 return ptr;
441 /***************************************************************************
442 * WCMD_keyword_ws_found
444 * Checks if the string located at ptr matches a keyword (of length len)
445 * followed by a whitespace character (space or tab)
447 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
448 return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
449 ptr, len, keyword, len) == CSTR_EQUAL)
450 && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
453 /*************************************************************************
454 * WCMD_strip_quotes
456 * Remove first and last quote WCHARacters, preserving all other text
458 void WCMD_strip_quotes(WCHAR *cmd) {
459 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
460 while((*dest=*src) != '\0') {
461 if (*src=='\"')
462 lastq=dest;
463 dest++, src++;
465 if (lastq) {
466 dest=lastq++;
467 while ((*dest++=*lastq++) != 0)
473 /*************************************************************************
474 * WCMD_is_magic_envvar
475 * Return TRUE if s is '%'magicvar'%'
476 * and is not masked by a real environment variable.
479 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
481 int len;
483 if (s[0] != '%')
484 return FALSE; /* Didn't begin with % */
485 len = strlenW(s);
486 if (len < 2 || s[len-1] != '%')
487 return FALSE; /* Didn't end with another % */
489 if (CompareStringW(LOCALE_USER_DEFAULT,
490 NORM_IGNORECASE | SORT_STRINGSORT,
491 s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
492 /* Name doesn't match. */
493 return FALSE;
496 if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
497 /* Masked by real environment variable. */
498 return FALSE;
501 return TRUE;
504 /*************************************************************************
505 * WCMD_expand_envvar
507 * Expands environment variables, allowing for WCHARacter substitution
509 static WCHAR *WCMD_expand_envvar(WCHAR *start,
510 const WCHAR *forVar, const WCHAR *forVal) {
511 WCHAR *endOfVar = NULL, *s;
512 WCHAR *colonpos = NULL;
513 WCHAR thisVar[MAXSTRING];
514 WCHAR thisVarContents[MAXSTRING];
515 WCHAR savedchar = 0x00;
516 int len;
518 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
519 static const WCHAR Date[] = {'D','A','T','E','\0'};
520 static const WCHAR Time[] = {'T','I','M','E','\0'};
521 static const WCHAR Cd[] = {'C','D','\0'};
522 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
523 static const WCHAR Delims[] = {'%',' ',':','\0'};
525 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
526 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
528 /* Find the end of the environment variable, and extract name */
529 endOfVar = strpbrkW(start+1, Delims);
531 if (endOfVar == NULL || *endOfVar==' ') {
533 /* In batch program, missing terminator for % and no following
534 ':' just removes the '%' */
535 if (context) {
536 WCMD_strsubstW(start, start + 1, NULL, 0);
537 return start;
538 } else {
540 /* In command processing, just ignore it - allows command line
541 syntax like: for %i in (a.a) do echo %i */
542 return start+1;
546 /* If ':' found, process remaining up until '%' (or stop at ':' if
547 a missing '%' */
548 if (*endOfVar==':') {
549 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
550 if (endOfVar2 != NULL) endOfVar = endOfVar2;
553 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
554 thisVar[(endOfVar - start)+1] = 0x00;
555 colonpos = strchrW(thisVar+1, ':');
557 /* If there's complex substitution, just need %var% for now
558 to get the expanded data to play with */
559 if (colonpos) {
560 *colonpos = '%';
561 savedchar = *(colonpos+1);
562 *(colonpos+1) = 0x00;
565 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
567 /* Expand to contents, if unchanged, return */
568 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
569 /* override if existing env var called that name */
570 if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
571 static const WCHAR fmt[] = {'%','d','\0'};
572 wsprintfW(thisVarContents, fmt, errorlevel);
573 len = strlenW(thisVarContents);
574 } else if (WCMD_is_magic_envvar(thisVar, Date)) {
575 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
576 NULL, thisVarContents, MAXSTRING);
577 len = strlenW(thisVarContents);
578 } else if (WCMD_is_magic_envvar(thisVar, Time)) {
579 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
580 NULL, thisVarContents, MAXSTRING);
581 len = strlenW(thisVarContents);
582 } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
583 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
584 len = strlenW(thisVarContents);
585 } else if (WCMD_is_magic_envvar(thisVar, Random)) {
586 static const WCHAR fmt[] = {'%','d','\0'};
587 wsprintfW(thisVarContents, fmt, rand() % 32768);
588 len = strlenW(thisVarContents);
590 /* Look for a matching 'for' variable */
591 } else if (forVar &&
592 (CompareStringW(LOCALE_USER_DEFAULT,
593 SORT_STRINGSORT,
594 thisVar,
595 (colonpos - thisVar) - 1,
596 forVar, -1) == CSTR_EQUAL)) {
597 strcpyW(thisVarContents, forVal);
598 len = strlenW(thisVarContents);
600 } else {
602 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
603 sizeof(thisVarContents)/sizeof(WCHAR));
606 if (len == 0)
607 return endOfVar+1;
609 /* In a batch program, unknown env vars are replaced with nothing,
610 note syntax %garbage:1,3% results in anything after the ':'
611 except the %
612 From the command line, you just get back what you entered */
613 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
615 /* Restore the complex part after the compare */
616 if (colonpos) {
617 *colonpos = ':';
618 *(colonpos+1) = savedchar;
621 /* Command line - just ignore this */
622 if (context == NULL) return endOfVar+1;
625 /* Batch - replace unknown env var with nothing */
626 if (colonpos == NULL) {
627 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
628 } else {
629 len = strlenW(thisVar);
630 thisVar[len-1] = 0x00;
631 /* If %:...% supplied, : is retained */
632 if (colonpos == thisVar+1) {
633 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
634 } else {
635 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
638 return start;
642 /* See if we need to do complex substitution (any ':'s), if not
643 then our work here is done */
644 if (colonpos == NULL) {
645 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
646 return start;
649 /* Restore complex bit */
650 *colonpos = ':';
651 *(colonpos+1) = savedchar;
654 Handle complex substitutions:
655 xxx=yyy (replace xxx with yyy)
656 *xxx=yyy (replace up to and including xxx with yyy)
657 ~x (from x WCHARs in)
658 ~-x (from x WCHARs from the end)
659 ~x,y (from x WCHARs in for y WCHARacters)
660 ~x,-y (from x WCHARs in until y WCHARacters from the end)
663 /* ~ is substring manipulation */
664 if (savedchar == '~') {
666 int substrposition, substrlength = 0;
667 WCHAR *commapos = strchrW(colonpos+2, ',');
668 WCHAR *startCopy;
670 substrposition = atolW(colonpos+2);
671 if (commapos) substrlength = atolW(commapos+1);
673 /* Check bounds */
674 if (substrposition >= 0) {
675 startCopy = &thisVarContents[min(substrposition, len)];
676 } else {
677 startCopy = &thisVarContents[max(0, len+substrposition-1)];
680 if (commapos == NULL) {
681 /* Copy the lot */
682 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
683 } else if (substrlength < 0) {
685 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
686 if (copybytes > len) copybytes = len;
687 else if (copybytes < 0) copybytes = 0;
688 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
689 } else {
690 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
693 return start;
695 /* search and replace manipulation */
696 } else {
697 WCHAR *equalspos = strstrW(colonpos, equalW);
698 WCHAR *replacewith = equalspos+1;
699 WCHAR *found = NULL;
700 WCHAR *searchIn;
701 WCHAR *searchFor;
703 if (equalspos == NULL) return start+1;
704 s = WCMD_strdupW(endOfVar + 1);
706 /* Null terminate both strings */
707 thisVar[strlenW(thisVar)-1] = 0x00;
708 *equalspos = 0x00;
710 /* Since we need to be case insensitive, copy the 2 buffers */
711 searchIn = WCMD_strdupW(thisVarContents);
712 CharUpperBuffW(searchIn, strlenW(thisVarContents));
713 searchFor = WCMD_strdupW(colonpos+1);
714 CharUpperBuffW(searchFor, strlenW(colonpos+1));
716 /* Handle wildcard case */
717 if (*(colonpos+1) == '*') {
718 /* Search for string to replace */
719 found = strstrW(searchIn, searchFor+1);
721 if (found) {
722 /* Do replacement */
723 strcpyW(start, replacewith);
724 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
725 strcatW(start, s);
726 } else {
727 /* Copy as is */
728 strcpyW(start, thisVarContents);
729 strcatW(start, s);
732 } else {
733 /* Loop replacing all instances */
734 WCHAR *lastFound = searchIn;
735 WCHAR *outputposn = start;
737 *start = 0x00;
738 while ((found = strstrW(lastFound, searchFor))) {
739 lstrcpynW(outputposn,
740 thisVarContents + (lastFound-searchIn),
741 (found - lastFound)+1);
742 outputposn = outputposn + (found - lastFound);
743 strcatW(outputposn, replacewith);
744 outputposn = outputposn + strlenW(replacewith);
745 lastFound = found + strlenW(searchFor);
747 strcatW(outputposn,
748 thisVarContents + (lastFound-searchIn));
749 strcatW(outputposn, s);
751 HeapFree(GetProcessHeap(), 0, s);
752 HeapFree(GetProcessHeap(), 0, searchIn);
753 HeapFree(GetProcessHeap(), 0, searchFor);
754 return start;
756 return start+1;
759 /*****************************************************************************
760 * Expand the command. Native expands lines from batch programs as they are
761 * read in and not again, except for 'for' variable substitution.
762 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
764 static void handleExpansion(WCHAR *cmd, BOOL justFors,
765 const WCHAR *forVariable, const WCHAR *forValue) {
767 /* For commands in a context (batch program): */
768 /* Expand environment variables in a batch file %{0-9} first */
769 /* including support for any ~ modifiers */
770 /* Additionally: */
771 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
772 /* names allowing environment variable overrides */
773 /* NOTE: To support the %PATH:xxx% syntax, also perform */
774 /* manual expansion of environment variables here */
776 WCHAR *p = cmd;
777 WCHAR *t;
778 int i;
780 while ((p = strchrW(p, '%'))) {
782 WINE_TRACE("Translate command:%s %d (at: %s)\n",
783 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
784 i = *(p+1) - '0';
786 /* Don't touch %% unless its in Batch */
787 if (!justFors && *(p+1) == '%') {
788 if (context) {
789 WCMD_strsubstW(p, p+1, NULL, 0);
791 p+=1;
793 /* Replace %~ modifications if in batch program */
794 } else if (*(p+1) == '~') {
795 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
796 p++;
798 /* Replace use of %0...%9 if in batch program*/
799 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
800 t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
801 WCMD_strsubstW(p, p+2, t, -1);
803 /* Replace use of %* if in batch program*/
804 } else if (!justFors && context && *(p+1)=='*') {
805 WCHAR *startOfParms = NULL;
806 t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
807 if (startOfParms != NULL)
808 WCMD_strsubstW(p, p+2, startOfParms, -1);
809 else
810 WCMD_strsubstW(p, p+2, NULL, 0);
812 } else if (forVariable &&
813 (CompareStringW(LOCALE_USER_DEFAULT,
814 SORT_STRINGSORT,
816 strlenW(forVariable),
817 forVariable, -1) == CSTR_EQUAL)) {
818 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
820 } else if (!justFors) {
821 p = WCMD_expand_envvar(p, forVariable, forValue);
823 /* In a FOR loop, see if this is the variable to replace */
824 } else { /* Ignore %'s on second pass of batch program */
825 p++;
829 return;
833 /*******************************************************************
834 * WCMD_parse - parse a command into parameters and qualifiers.
836 * On exit, all qualifiers are concatenated into q, the first string
837 * not beginning with "/" is in p1 and the
838 * second in p2. Any subsequent non-qualifier strings are lost.
839 * Parameters in quotes are handled.
841 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
843 int p = 0;
845 *q = *p1 = *p2 = '\0';
846 while (TRUE) {
847 switch (*s) {
848 case '/':
849 *q++ = *s++;
850 while ((*s != '\0') && (*s != ' ') && *s != '/') {
851 *q++ = toupperW (*s++);
853 *q = '\0';
854 break;
855 case ' ':
856 case '\t':
857 s++;
858 break;
859 case '"':
860 s++;
861 while ((*s != '\0') && (*s != '"')) {
862 if (p == 0) *p1++ = *s++;
863 else if (p == 1) *p2++ = *s++;
864 else s++;
866 if (p == 0) *p1 = '\0';
867 if (p == 1) *p2 = '\0';
868 p++;
869 if (*s == '"') s++;
870 break;
871 case '\0':
872 return;
873 default:
874 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
875 && (*s != '=') && (*s != ',') ) {
876 if (p == 0) *p1++ = *s++;
877 else if (p == 1) *p2++ = *s++;
878 else s++;
880 /* Skip concurrent parms */
881 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
883 if (p == 0) *p1 = '\0';
884 if (p == 1) *p2 = '\0';
885 p++;
890 static void init_msvcrt_io_block(STARTUPINFOW* st)
892 STARTUPINFOW st_p;
893 /* fetch the parent MSVCRT info block if any, so that the child can use the
894 * same handles as its grand-father
896 st_p.cb = sizeof(STARTUPINFOW);
897 GetStartupInfoW(&st_p);
898 st->cbReserved2 = st_p.cbReserved2;
899 st->lpReserved2 = st_p.lpReserved2;
900 if (st_p.cbReserved2 && st_p.lpReserved2)
902 /* Override the entries for fd 0,1,2 if we happened
903 * to change those std handles (this depends on the way cmd sets
904 * its new input & output handles)
906 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
907 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
908 if (ptr)
910 unsigned num = *(unsigned*)st_p.lpReserved2;
911 char* flags = (char*)(ptr + sizeof(unsigned));
912 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
914 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
915 st->cbReserved2 = sz;
916 st->lpReserved2 = ptr;
918 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
919 if (num <= 0 || (flags[0] & WX_OPEN))
921 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
922 flags[0] |= WX_OPEN;
924 if (num <= 1 || (flags[1] & WX_OPEN))
926 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
927 flags[1] |= WX_OPEN;
929 if (num <= 2 || (flags[2] & WX_OPEN))
931 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
932 flags[2] |= WX_OPEN;
934 #undef WX_OPEN
939 /******************************************************************************
940 * WCMD_run_program
942 * Execute a command line as an external program. Must allow recursion.
944 * Precedence:
945 * Manual testing under windows shows PATHEXT plays a key part in this,
946 * and the search algorithm and precedence appears to be as follows.
948 * Search locations:
949 * If directory supplied on command, just use that directory
950 * If extension supplied on command, look for that explicit name first
951 * Otherwise, search in each directory on the path
952 * Precedence:
953 * If extension supplied on command, look for that explicit name first
954 * Then look for supplied name .* (even if extension supplied, so
955 * 'garbage.exe' will match 'garbage.exe.cmd')
956 * If any found, cycle through PATHEXT looking for name.exe one by one
957 * Launching
958 * Once a match has been found, it is launched - Code currently uses
959 * findexecutable to achieve this which is left untouched.
962 void WCMD_run_program (WCHAR *command, int called) {
964 WCHAR temp[MAX_PATH];
965 WCHAR pathtosearch[MAXSTRING];
966 WCHAR *pathposn;
967 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
968 MAX_PATH, including null character */
969 WCHAR *lastSlash;
970 WCHAR pathext[MAXSTRING];
971 BOOL extensionsupplied = FALSE;
972 BOOL launched = FALSE;
973 BOOL status;
974 BOOL assumeInternal = FALSE;
975 DWORD len;
976 static const WCHAR envPath[] = {'P','A','T','H','\0'};
977 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
978 static const WCHAR delims[] = {'/','\\',':','\0'};
980 /* Quick way to get the filename
981 * (but handle leading / as part of program name, not qualifier)
983 for (len = 0; command[len] == '/'; len++) param1[len] = '/';
984 WCMD_parse (command + len, quals, param1 + len, param2);
986 if (!(*param1) && !(*param2))
987 return;
989 /* Calculate the search path and stem to search for */
990 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
991 static const WCHAR curDir[] = {'.',';','\0'};
992 strcpyW(pathtosearch, curDir);
993 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
994 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
995 static const WCHAR curDir[] = {'.','\0'};
996 strcpyW (pathtosearch, curDir);
998 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
999 if (strlenW(param1) >= MAX_PATH)
1001 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1002 return;
1005 strcpyW(stemofsearch, param1);
1007 } else {
1009 /* Convert eg. ..\fred to include a directory by removing file part */
1010 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1011 lastSlash = strrchrW(pathtosearch, '\\');
1012 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1013 strcpyW(stemofsearch, lastSlash+1);
1015 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1016 c:\windows\a.bat syntax */
1017 if (lastSlash) *(lastSlash + 1) = 0x00;
1020 /* Now extract PATHEXT */
1021 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1022 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1023 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1024 '.','c','o','m',';',
1025 '.','c','m','d',';',
1026 '.','e','x','e','\0'};
1027 strcpyW (pathext, dfltPathExt);
1030 /* Loop through the search path, dir by dir */
1031 pathposn = pathtosearch;
1032 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1033 wine_dbgstr_w(stemofsearch));
1034 while (!launched && pathposn) {
1036 WCHAR thisDir[MAX_PATH] = {'\0'};
1037 WCHAR *pos = NULL;
1038 BOOL found = FALSE;
1040 /* Work on the first directory on the search path */
1041 pos = strchrW(pathposn, ';');
1042 if (pos) {
1043 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1044 thisDir[(pos-pathposn)] = 0x00;
1045 pathposn = pos+1;
1047 } else {
1048 strcpyW(thisDir, pathposn);
1049 pathposn = NULL;
1052 /* Since you can have eg. ..\.. on the path, need to expand
1053 to full information */
1054 strcpyW(temp, thisDir);
1055 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1057 /* 1. If extension supplied, see if that file exists */
1058 strcatW(thisDir, slashW);
1059 strcatW(thisDir, stemofsearch);
1060 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1062 /* 1. If extension supplied, see if that file exists */
1063 if (extensionsupplied) {
1064 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1065 found = TRUE;
1069 /* 2. Any .* matches? */
1070 if (!found) {
1071 HANDLE h;
1072 WIN32_FIND_DATAW finddata;
1073 static const WCHAR allFiles[] = {'.','*','\0'};
1075 strcatW(thisDir,allFiles);
1076 h = FindFirstFileW(thisDir, &finddata);
1077 FindClose(h);
1078 if (h != INVALID_HANDLE_VALUE) {
1080 WCHAR *thisExt = pathext;
1082 /* 3. Yes - Try each path ext */
1083 while (thisExt) {
1084 WCHAR *nextExt = strchrW(thisExt, ';');
1086 if (nextExt) {
1087 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1088 pos[(nextExt-thisExt)] = 0x00;
1089 thisExt = nextExt+1;
1090 } else {
1091 strcpyW(pos, thisExt);
1092 thisExt = NULL;
1095 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1096 found = TRUE;
1097 thisExt = NULL;
1103 /* Internal programs won't be picked up by this search, so even
1104 though not found, try one last createprocess and wait for it
1105 to complete.
1106 Note: Ideally we could tell between a console app (wait) and a
1107 windows app, but the API's for it fail in this case */
1108 if (!found && pathposn == NULL) {
1109 WINE_TRACE("ASSUMING INTERNAL\n");
1110 assumeInternal = TRUE;
1111 } else {
1112 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1115 /* Once found, launch it */
1116 if (found || assumeInternal) {
1117 STARTUPINFOW st;
1118 PROCESS_INFORMATION pe;
1119 SHFILEINFOW psfi;
1120 DWORD console;
1121 HINSTANCE hinst;
1122 WCHAR *ext = strrchrW( thisDir, '.' );
1123 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1124 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1126 launched = TRUE;
1128 /* Special case BAT and CMD */
1129 if (ext && !strcmpiW(ext, batExt)) {
1130 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1131 return;
1132 } else if (ext && !strcmpiW(ext, cmdExt)) {
1133 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1134 return;
1135 } else {
1137 /* thisDir contains the file to be launched, but with what?
1138 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1139 hinst = FindExecutableW (thisDir, NULL, temp);
1140 if ((INT_PTR)hinst < 32)
1141 console = 0;
1142 else
1143 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1145 ZeroMemory (&st, sizeof(STARTUPINFOW));
1146 st.cb = sizeof(STARTUPINFOW);
1147 init_msvcrt_io_block(&st);
1149 /* Launch the process and if a CUI wait on it to complete
1150 Note: Launching internal wine processes cannot specify a full path to exe */
1151 status = CreateProcessW(assumeInternal?NULL : thisDir,
1152 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1153 if ((opt_c || opt_k) && !opt_s && !status
1154 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1155 /* strip first and last quote WCHARacters and try again */
1156 WCMD_strip_quotes(command);
1157 opt_s = TRUE;
1158 WCMD_run_program(command, called);
1159 return;
1162 if (!status)
1163 break;
1165 if (!assumeInternal && !console) errorlevel = 0;
1166 else
1168 /* Always wait when called in a batch program context */
1169 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1170 GetExitCodeProcess (pe.hProcess, &errorlevel);
1171 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1173 CloseHandle(pe.hProcess);
1174 CloseHandle(pe.hThread);
1175 return;
1180 /* Not found anywhere - give up */
1181 SetLastError(ERROR_FILE_NOT_FOUND);
1182 WCMD_print_error ();
1184 /* If a command fails to launch, it sets errorlevel 9009 - which
1185 does not seem to have any associated constant definition */
1186 errorlevel = 9009;
1187 return;
1191 /*****************************************************************************
1192 * Process one command. If the command is EXIT this routine does not return.
1193 * We will recurse through here executing batch files.
1195 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1196 const WCHAR *forVariable, const WCHAR *forValue,
1197 CMD_LIST **cmdList)
1199 WCHAR *cmd, *p, *redir;
1200 int status, i;
1201 DWORD count, creationDisposition;
1202 HANDLE h;
1203 WCHAR *whichcmd;
1204 SECURITY_ATTRIBUTES sa;
1205 WCHAR *new_cmd = NULL;
1206 WCHAR *new_redir = NULL;
1207 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1208 GetStdHandle (STD_OUTPUT_HANDLE),
1209 GetStdHandle (STD_ERROR_HANDLE)};
1210 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1211 STD_OUTPUT_HANDLE,
1212 STD_ERROR_HANDLE};
1213 BOOL prev_echo_mode, piped = FALSE;
1215 WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1216 wine_dbgstr_w(command), cmdList,
1217 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1219 /* If the next command is a pipe then we implement pipes by redirecting
1220 the output from this command to a temp file and input into the
1221 next command from that temp file.
1222 FIXME: Use of named pipes would make more sense here as currently this
1223 process has to finish before the next one can start but this requires
1224 a change to not wait for the first app to finish but rather the pipe */
1225 if (cmdList && (*cmdList)->nextcommand &&
1226 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1228 WCHAR temp_path[MAX_PATH];
1229 static const WCHAR cmdW[] = {'C','M','D','\0'};
1231 /* Remember piping is in action */
1232 WINE_TRACE("Output needs to be piped\n");
1233 piped = TRUE;
1235 /* Generate a unique temporary filename */
1236 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1237 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1238 WINE_TRACE("Using temporary file of %s\n",
1239 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1242 /* Move copy of the command onto the heap so it can be expanded */
1243 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1244 if (!new_cmd)
1246 WINE_ERR("Could not allocate memory for new_cmd\n");
1247 return;
1249 strcpyW(new_cmd, command);
1251 /* Move copy of the redirects onto the heap so it can be expanded */
1252 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1253 if (!new_redir)
1255 WINE_ERR("Could not allocate memory for new_redir\n");
1256 HeapFree( GetProcessHeap(), 0, new_cmd );
1257 return;
1260 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1261 if (piped) {
1262 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1263 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1264 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1265 } else {
1266 strcpyW(new_redir, redirects);
1269 /* Expand variables in command line mode only (batch mode will
1270 be expanded as the line is read in, except for 'for' loops) */
1271 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1272 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1273 cmd = new_cmd;
1276 * Changing default drive has to be handled as a special case.
1279 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1280 WCHAR envvar[5];
1281 WCHAR dir[MAX_PATH];
1283 /* According to MSDN CreateProcess docs, special env vars record
1284 the current directory on each drive, in the form =C:
1285 so see if one specified, and if so go back to it */
1286 strcpyW(envvar, equalW);
1287 strcatW(envvar, cmd);
1288 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1289 static const WCHAR fmt[] = {'%','s','\\','\0'};
1290 wsprintfW(cmd, fmt, cmd);
1291 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1293 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1294 status = SetCurrentDirectoryW(cmd);
1295 if (!status) WCMD_print_error ();
1296 HeapFree( GetProcessHeap(), 0, cmd );
1297 HeapFree( GetProcessHeap(), 0, new_redir );
1298 return;
1301 sa.nLength = sizeof(sa);
1302 sa.lpSecurityDescriptor = NULL;
1303 sa.bInheritHandle = TRUE;
1306 * Redirect stdin, stdout and/or stderr if required.
1309 /* STDIN could come from a preceding pipe, so delete on close if it does */
1310 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1311 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1312 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1313 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1314 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1315 if (h == INVALID_HANDLE_VALUE) {
1316 WCMD_print_error ();
1317 HeapFree( GetProcessHeap(), 0, cmd );
1318 HeapFree( GetProcessHeap(), 0, new_redir );
1319 return;
1321 SetStdHandle (STD_INPUT_HANDLE, h);
1323 /* No need to remember the temporary name any longer once opened */
1324 (*cmdList)->pipeFile[0] = 0x00;
1326 /* Otherwise STDIN could come from a '<' redirect */
1327 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1328 h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1329 &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1330 if (h == INVALID_HANDLE_VALUE) {
1331 WCMD_print_error ();
1332 HeapFree( GetProcessHeap(), 0, cmd );
1333 HeapFree( GetProcessHeap(), 0, new_redir );
1334 return;
1336 SetStdHandle (STD_INPUT_HANDLE, h);
1339 /* Scan the whole command looking for > and 2> */
1340 redir = new_redir;
1341 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1342 int handle = 0;
1344 if (p > redir && (*(p-1)=='2'))
1345 handle = 2;
1346 else
1347 handle = 1;
1349 p++;
1350 if ('>' == *p) {
1351 creationDisposition = OPEN_ALWAYS;
1352 p++;
1354 else {
1355 creationDisposition = CREATE_ALWAYS;
1358 /* Add support for 2>&1 */
1359 redir = p;
1360 if (*p == '&') {
1361 int idx = *(p+1) - '0';
1363 if (DuplicateHandle(GetCurrentProcess(),
1364 GetStdHandle(idx_stdhandles[idx]),
1365 GetCurrentProcess(),
1367 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1368 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1370 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1372 } else {
1373 WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1374 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1375 FILE_ATTRIBUTE_NORMAL, NULL);
1376 if (h == INVALID_HANDLE_VALUE) {
1377 WCMD_print_error ();
1378 HeapFree( GetProcessHeap(), 0, cmd );
1379 HeapFree( GetProcessHeap(), 0, new_redir );
1380 return;
1382 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1383 INVALID_SET_FILE_POINTER) {
1384 WCMD_print_error ();
1386 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1389 SetStdHandle (idx_stdhandles[handle], h);
1393 * Strip leading whitespaces, and a '@' if supplied
1395 whichcmd = WCMD_skip_leading_spaces(cmd);
1396 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1397 if (whichcmd[0] == '@') whichcmd++;
1400 * Check if the command entered is internal. If it is, pass the rest of the
1401 * line down to the command. If not try to run a program.
1404 count = 0;
1405 while (IsCharAlphaNumericW(whichcmd[count])) {
1406 count++;
1408 for (i=0; i<=WCMD_EXIT; i++) {
1409 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1410 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1412 p = WCMD_skip_leading_spaces (&whichcmd[count]);
1413 WCMD_parse (p, quals, param1, param2);
1414 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1416 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1417 /* this is a help request for a builtin program */
1418 i = WCMD_HELP;
1419 memcpy(p, whichcmd, count * sizeof(WCHAR));
1420 p[count] = '\0';
1424 switch (i) {
1426 case WCMD_CALL:
1427 WCMD_call (p);
1428 break;
1429 case WCMD_CD:
1430 case WCMD_CHDIR:
1431 WCMD_setshow_default (p);
1432 break;
1433 case WCMD_CLS:
1434 WCMD_clear_screen ();
1435 break;
1436 case WCMD_COPY:
1437 WCMD_copy ();
1438 break;
1439 case WCMD_CTTY:
1440 WCMD_change_tty ();
1441 break;
1442 case WCMD_DATE:
1443 WCMD_setshow_date ();
1444 break;
1445 case WCMD_DEL:
1446 case WCMD_ERASE:
1447 WCMD_delete (p);
1448 break;
1449 case WCMD_DIR:
1450 WCMD_directory (p);
1451 break;
1452 case WCMD_ECHO:
1453 WCMD_echo(&whichcmd[count]);
1454 break;
1455 case WCMD_FOR:
1456 WCMD_for (p, cmdList);
1457 break;
1458 case WCMD_GOTO:
1459 WCMD_goto (cmdList);
1460 break;
1461 case WCMD_HELP:
1462 WCMD_give_help (p);
1463 break;
1464 case WCMD_IF:
1465 WCMD_if (p, cmdList);
1466 break;
1467 case WCMD_LABEL:
1468 WCMD_volume (TRUE, p);
1469 break;
1470 case WCMD_MD:
1471 case WCMD_MKDIR:
1472 WCMD_create_dir (p);
1473 break;
1474 case WCMD_MOVE:
1475 WCMD_move ();
1476 break;
1477 case WCMD_PATH:
1478 WCMD_setshow_path (p);
1479 break;
1480 case WCMD_PAUSE:
1481 WCMD_pause ();
1482 break;
1483 case WCMD_PROMPT:
1484 WCMD_setshow_prompt ();
1485 break;
1486 case WCMD_REM:
1487 break;
1488 case WCMD_REN:
1489 case WCMD_RENAME:
1490 WCMD_rename ();
1491 break;
1492 case WCMD_RD:
1493 case WCMD_RMDIR:
1494 WCMD_remove_dir (p);
1495 break;
1496 case WCMD_SETLOCAL:
1497 WCMD_setlocal(p);
1498 break;
1499 case WCMD_ENDLOCAL:
1500 WCMD_endlocal();
1501 break;
1502 case WCMD_SET:
1503 WCMD_setshow_env (p);
1504 break;
1505 case WCMD_SHIFT:
1506 WCMD_shift (p);
1507 break;
1508 case WCMD_TIME:
1509 WCMD_setshow_time ();
1510 break;
1511 case WCMD_TITLE:
1512 if (strlenW(&whichcmd[count]) > 0)
1513 WCMD_title(&whichcmd[count+1]);
1514 break;
1515 case WCMD_TYPE:
1516 WCMD_type (p);
1517 break;
1518 case WCMD_VER:
1519 WCMD_output_asis(newline);
1520 WCMD_version ();
1521 break;
1522 case WCMD_VERIFY:
1523 WCMD_verify (p);
1524 break;
1525 case WCMD_VOL:
1526 WCMD_volume (FALSE, p);
1527 break;
1528 case WCMD_PUSHD:
1529 WCMD_pushd(p);
1530 break;
1531 case WCMD_POPD:
1532 WCMD_popd();
1533 break;
1534 case WCMD_ASSOC:
1535 WCMD_assoc(p, TRUE);
1536 break;
1537 case WCMD_COLOR:
1538 WCMD_color();
1539 break;
1540 case WCMD_FTYPE:
1541 WCMD_assoc(p, FALSE);
1542 break;
1543 case WCMD_MORE:
1544 WCMD_more(p);
1545 break;
1546 case WCMD_CHOICE:
1547 WCMD_choice(p);
1548 break;
1549 case WCMD_EXIT:
1550 WCMD_exit (cmdList);
1551 break;
1552 default:
1553 prev_echo_mode = echo_mode;
1554 WCMD_run_program (whichcmd, 0);
1555 echo_mode = prev_echo_mode;
1557 HeapFree( GetProcessHeap(), 0, cmd );
1558 HeapFree( GetProcessHeap(), 0, new_redir );
1560 /* Restore old handles */
1561 for (i=0; i<3; i++) {
1562 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1563 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1564 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1569 /*************************************************************************
1570 * WCMD_LoadMessage
1571 * Load a string from the resource file, handling any error
1572 * Returns string retrieved from resource file
1574 WCHAR *WCMD_LoadMessage(UINT id) {
1575 static WCHAR msg[2048];
1576 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1578 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1579 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1580 strcpyW(msg, failedMsg);
1582 return msg;
1585 /***************************************************************************
1586 * WCMD_DumpCommands
1588 * Dumps out the parsed command line to ensure syntax is correct
1590 static void WCMD_DumpCommands(CMD_LIST *commands) {
1591 CMD_LIST *thisCmd = commands;
1593 WINE_TRACE("Parsed line:\n");
1594 while (thisCmd != NULL) {
1595 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1596 thisCmd,
1597 thisCmd->prevDelim,
1598 thisCmd->bracketDepth,
1599 thisCmd->nextcommand,
1600 wine_dbgstr_w(thisCmd->command),
1601 wine_dbgstr_w(thisCmd->redirects));
1602 thisCmd = thisCmd->nextcommand;
1606 /***************************************************************************
1607 * WCMD_addCommand
1609 * Adds a command to the current command list
1611 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1612 WCHAR *redirs, int *redirLen,
1613 WCHAR **copyTo, int **copyToLen,
1614 CMD_DELIMITERS prevDelim, int curDepth,
1615 CMD_LIST **lastEntry, CMD_LIST **output) {
1617 CMD_LIST *thisEntry = NULL;
1619 /* Allocate storage for command */
1620 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1622 /* Copy in the command */
1623 if (command) {
1624 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1625 (*commandLen+1) * sizeof(WCHAR));
1626 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1627 thisEntry->command[*commandLen] = 0x00;
1629 /* Copy in the redirects */
1630 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1631 (*redirLen+1) * sizeof(WCHAR));
1632 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1633 thisEntry->redirects[*redirLen] = 0x00;
1634 thisEntry->pipeFile[0] = 0x00;
1636 /* Reset the lengths */
1637 *commandLen = 0;
1638 *redirLen = 0;
1639 *copyToLen = commandLen;
1640 *copyTo = command;
1642 } else {
1643 thisEntry->command = NULL;
1644 thisEntry->redirects = NULL;
1645 thisEntry->pipeFile[0] = 0x00;
1648 /* Fill in other fields */
1649 thisEntry->nextcommand = NULL;
1650 thisEntry->prevDelim = prevDelim;
1651 thisEntry->bracketDepth = curDepth;
1652 if (*lastEntry) {
1653 (*lastEntry)->nextcommand = thisEntry;
1654 } else {
1655 *output = thisEntry;
1657 *lastEntry = thisEntry;
1661 /***************************************************************************
1662 * WCMD_IsEndQuote
1664 * Checks if the quote pointed to is the end-quote.
1666 * Quotes end if:
1668 * 1) The current parameter ends at EOL or at the beginning
1669 * of a redirection or pipe and not in a quote section.
1671 * 2) If the next character is a space and not in a quote section.
1673 * Returns TRUE if this is an end quote, and FALSE if it is not.
1676 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1678 int quoteCount = quoteIndex;
1679 int i;
1681 /* If we are not in a quoted section, then we are not an end-quote */
1682 if(quoteIndex == 0)
1684 return FALSE;
1687 /* Check how many quotes are left for this parameter */
1688 for(i=0;quote[i];i++)
1690 if(quote[i] == '"')
1692 quoteCount++;
1695 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1696 else if(((quoteCount % 2) == 0)
1697 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1699 break;
1703 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1704 be an end-quote */
1705 if(quoteIndex >= (quoteCount / 2))
1707 return TRUE;
1710 /* No cigar */
1711 return FALSE;
1714 /***************************************************************************
1715 * WCMD_ReadAndParseLine
1717 * Either uses supplied input or
1718 * Reads a file from the handle, and then...
1719 * Parse the text buffer, splitting into separate commands
1720 * - unquoted && strings split 2 commands but the 2nd is flagged as
1721 * following an &&
1722 * - ( as the first character just ups the bracket depth
1723 * - unquoted ) when bracket depth > 0 terminates a bracket and
1724 * adds a CMD_LIST structure with null command
1725 * - Anything else gets put into the command string (including
1726 * redirects)
1728 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1730 WCHAR *curPos;
1731 int inQuotes = 0;
1732 WCHAR curString[MAXSTRING];
1733 int curStringLen = 0;
1734 WCHAR curRedirs[MAXSTRING];
1735 int curRedirsLen = 0;
1736 WCHAR *curCopyTo;
1737 int *curLen;
1738 int curDepth = 0;
1739 CMD_LIST *lastEntry = NULL;
1740 CMD_DELIMITERS prevDelim = CMD_NONE;
1741 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1742 static const WCHAR remCmd[] = {'r','e','m'};
1743 static const WCHAR forCmd[] = {'f','o','r'};
1744 static const WCHAR ifCmd[] = {'i','f'};
1745 static const WCHAR ifElse[] = {'e','l','s','e'};
1746 BOOL inRem = FALSE;
1747 BOOL inFor = FALSE;
1748 BOOL inIn = FALSE;
1749 BOOL inIf = FALSE;
1750 BOOL inElse= FALSE;
1751 BOOL onlyWhiteSpace = FALSE;
1752 BOOL lastWasWhiteSpace = FALSE;
1753 BOOL lastWasDo = FALSE;
1754 BOOL lastWasIn = FALSE;
1755 BOOL lastWasElse = FALSE;
1756 BOOL lastWasRedirect = TRUE;
1758 /* Allocate working space for a command read from keyboard, file etc */
1759 if (!extraSpace)
1760 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1761 if (!extraSpace)
1763 WINE_ERR("Could not allocate memory for extraSpace\n");
1764 return NULL;
1767 /* If initial command read in, use that, otherwise get input from handle */
1768 if (optionalcmd != NULL) {
1769 strcpyW(extraSpace, optionalcmd);
1770 } else if (readFrom == INVALID_HANDLE_VALUE) {
1771 WINE_FIXME("No command nor handle supplied\n");
1772 } else {
1773 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1774 return NULL;
1776 curPos = extraSpace;
1778 /* Handle truncated input - issue warning */
1779 if (strlenW(extraSpace) == MAXSTRING -1) {
1780 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1781 WCMD_output_asis_stderr(extraSpace);
1782 WCMD_output_asis_stderr(newline);
1785 /* Replace env vars if in a batch context */
1786 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1787 /* Show prompt before batch line IF echo is on and in batch program */
1788 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1789 static const WCHAR echoDot[] = {'e','c','h','o','.'};
1790 static const WCHAR echoCol[] = {'e','c','h','o',':'};
1791 const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1792 DWORD curr_size = strlenW(extraSpace);
1793 DWORD min_len = (curr_size < len ? curr_size : len);
1794 WCMD_show_prompt();
1795 WCMD_output_asis(extraSpace);
1796 /* I don't know why Windows puts a space here but it does */
1797 /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1798 if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1799 extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1800 && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1801 extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1803 WCMD_output_asis(space);
1805 WCMD_output_asis(newline);
1808 /* Start with an empty string, copying to the command string */
1809 curStringLen = 0;
1810 curRedirsLen = 0;
1811 curCopyTo = curString;
1812 curLen = &curStringLen;
1813 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1815 /* Parse every character on the line being processed */
1816 while (*curPos != 0x00) {
1818 WCHAR thisChar;
1820 /* Debugging AID:
1821 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1822 lastWasWhiteSpace, onlyWhiteSpace);
1825 /* Certain commands need special handling */
1826 if (curStringLen == 0 && curCopyTo == curString) {
1827 static const WCHAR forDO[] = {'d','o'};
1829 /* If command starts with 'rem ', ignore any &&, ( etc. */
1830 if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1831 inRem = TRUE;
1833 } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1834 inFor = TRUE;
1836 /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1837 is only true in the command portion of the IF statement, but this
1838 should suffice for now
1839 FIXME: Silly syntax like "if 1(==1( (
1840 echo they equal
1841 )" will be parsed wrong */
1842 } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1843 inIf = TRUE;
1845 } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1846 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1847 inElse = TRUE;
1848 lastWasElse = TRUE;
1849 onlyWhiteSpace = TRUE;
1850 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1851 (*curLen)+=keyw_len;
1852 curPos+=keyw_len;
1853 continue;
1855 /* In a for loop, the DO command will follow a close bracket followed by
1856 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1857 is then 0, and all whitespace is skipped */
1858 } else if (inFor &&
1859 WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1860 const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1861 WINE_TRACE("Found 'DO '\n");
1862 lastWasDo = TRUE;
1863 onlyWhiteSpace = TRUE;
1864 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1865 (*curLen)+=keyw_len;
1866 curPos+=keyw_len;
1867 continue;
1869 } else if (curCopyTo == curString) {
1871 /* Special handling for the 'FOR' command */
1872 if (inFor && lastWasWhiteSpace) {
1873 static const WCHAR forIN[] = {'i','n'};
1875 WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1877 if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1878 const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1879 WINE_TRACE("Found 'IN '\n");
1880 lastWasIn = TRUE;
1881 onlyWhiteSpace = TRUE;
1882 memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1883 (*curLen)+=keyw_len;
1884 curPos+=keyw_len;
1885 continue;
1890 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1891 so just use the default processing ie skip character specific
1892 matching below */
1893 if (!inRem) thisChar = *curPos;
1894 else thisChar = 'X'; /* Character with no special processing */
1896 lastWasWhiteSpace = FALSE; /* Will be reset below */
1898 switch (thisChar) {
1900 case '=': /* drop through - ignore token delimiters at the start of a command */
1901 case ',': /* drop through - ignore token delimiters at the start of a command */
1902 case '\t':/* drop through - ignore token delimiters at the start of a command */
1903 case ' ':
1904 /* If a redirect in place, it ends here */
1905 if (!inQuotes && !lastWasRedirect) {
1907 /* If finishing off a redirect, add a whitespace delimiter */
1908 if (curCopyTo == curRedirs) {
1909 curCopyTo[(*curLen)++] = ' ';
1911 curCopyTo = curString;
1912 curLen = &curStringLen;
1914 if (*curLen > 0) {
1915 curCopyTo[(*curLen)++] = *curPos;
1918 /* Remember just processed whitespace */
1919 lastWasWhiteSpace = TRUE;
1921 break;
1923 case '>': /* drop through - handle redirect chars the same */
1924 case '<':
1925 /* Make a redirect start here */
1926 if (!inQuotes) {
1927 curCopyTo = curRedirs;
1928 curLen = &curRedirsLen;
1929 lastWasRedirect = TRUE;
1932 /* See if 1>, 2> etc, in which case we have some patching up
1933 to do (provided there's a preceding whitespace, and enough
1934 chars read so far) */
1935 if (curStringLen > 2
1936 && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1937 && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1938 curStringLen--;
1939 curString[curStringLen] = 0x00;
1940 curCopyTo[(*curLen)++] = *(curPos-1);
1943 curCopyTo[(*curLen)++] = *curPos;
1945 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1946 do not process that ampersand as an AND operator */
1947 if (thisChar == '>' && *(curPos+1) == '&') {
1948 curCopyTo[(*curLen)++] = *(curPos+1);
1949 curPos++;
1951 break;
1953 case '|': /* Pipe character only if not || */
1954 if (!inQuotes) {
1955 lastWasRedirect = FALSE;
1957 /* Add an entry to the command list */
1958 if (curStringLen > 0) {
1960 /* Add the current command */
1961 WCMD_addCommand(curString, &curStringLen,
1962 curRedirs, &curRedirsLen,
1963 &curCopyTo, &curLen,
1964 prevDelim, curDepth,
1965 &lastEntry, output);
1969 if (*(curPos+1) == '|') {
1970 curPos++; /* Skip other | */
1971 prevDelim = CMD_ONFAILURE;
1972 } else {
1973 prevDelim = CMD_PIPE;
1975 } else {
1976 curCopyTo[(*curLen)++] = *curPos;
1978 break;
1980 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1981 inQuotes--;
1982 } else {
1983 inQuotes++; /* Quotes within quotes are fun! */
1985 curCopyTo[(*curLen)++] = *curPos;
1986 lastWasRedirect = FALSE;
1987 break;
1989 case '(': /* If a '(' is the first non whitespace in a command portion
1990 ie start of line or just after &&, then we read until an
1991 unquoted ) is found */
1992 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1993 ", for(%d, In:%d, Do:%d)"
1994 ", if(%d, else:%d, lwe:%d)\n",
1995 *curLen, inQuotes,
1996 onlyWhiteSpace,
1997 inFor, lastWasIn, lastWasDo,
1998 inIf, inElse, lastWasElse);
1999 lastWasRedirect = FALSE;
2001 /* Ignore open brackets inside the for set */
2002 if (*curLen == 0 && !inIn) {
2003 curDepth++;
2005 /* If in quotes, ignore brackets */
2006 } else if (inQuotes) {
2007 curCopyTo[(*curLen)++] = *curPos;
2009 /* In a FOR loop, an unquoted '(' may occur straight after
2010 IN or DO
2011 In an IF statement just handle it regardless as we don't
2012 parse the operands
2013 In an ELSE statement, only allow it straight away after
2014 the ELSE and whitespace
2016 } else if (inIf ||
2017 (inElse && lastWasElse && onlyWhiteSpace) ||
2018 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2020 /* If entering into an 'IN', set inIn */
2021 if (inFor && lastWasIn && onlyWhiteSpace) {
2022 WINE_TRACE("Inside an IN\n");
2023 inIn = TRUE;
2026 /* Add the current command */
2027 WCMD_addCommand(curString, &curStringLen,
2028 curRedirs, &curRedirsLen,
2029 &curCopyTo, &curLen,
2030 prevDelim, curDepth,
2031 &lastEntry, output);
2033 curDepth++;
2034 } else {
2035 curCopyTo[(*curLen)++] = *curPos;
2037 break;
2039 case '&': if (!inQuotes) {
2040 lastWasRedirect = FALSE;
2042 /* Add an entry to the command list */
2043 if (curStringLen > 0) {
2045 /* Add the current command */
2046 WCMD_addCommand(curString, &curStringLen,
2047 curRedirs, &curRedirsLen,
2048 &curCopyTo, &curLen,
2049 prevDelim, curDepth,
2050 &lastEntry, output);
2054 if (*(curPos+1) == '&') {
2055 curPos++; /* Skip other & */
2056 prevDelim = CMD_ONSUCCESS;
2057 } else {
2058 prevDelim = CMD_NONE;
2060 } else {
2061 curCopyTo[(*curLen)++] = *curPos;
2063 break;
2065 case ')': if (!inQuotes && curDepth > 0) {
2066 lastWasRedirect = FALSE;
2068 /* Add the current command if there is one */
2069 if (curStringLen) {
2071 /* Add the current command */
2072 WCMD_addCommand(curString, &curStringLen,
2073 curRedirs, &curRedirsLen,
2074 &curCopyTo, &curLen,
2075 prevDelim, curDepth,
2076 &lastEntry, output);
2079 /* Add an empty entry to the command list */
2080 prevDelim = CMD_NONE;
2081 WCMD_addCommand(NULL, &curStringLen,
2082 curRedirs, &curRedirsLen,
2083 &curCopyTo, &curLen,
2084 prevDelim, curDepth,
2085 &lastEntry, output);
2086 curDepth--;
2088 /* Leave inIn if necessary */
2089 if (inIn) inIn = FALSE;
2090 } else {
2091 curCopyTo[(*curLen)++] = *curPos;
2093 break;
2094 default:
2095 lastWasRedirect = FALSE;
2096 curCopyTo[(*curLen)++] = *curPos;
2099 curPos++;
2101 /* At various times we need to know if we have only skipped whitespace,
2102 so reset this variable and then it will remain true until a non
2103 whitespace is found */
2104 if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2105 onlyWhiteSpace = FALSE;
2107 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2108 if (!lastWasWhiteSpace) {
2109 lastWasIn = lastWasDo = FALSE;
2112 /* If we have reached the end, add this command into the list */
2113 if (*curPos == 0x00 && *curLen > 0) {
2115 /* Add an entry to the command list */
2116 WCMD_addCommand(curString, &curStringLen,
2117 curRedirs, &curRedirsLen,
2118 &curCopyTo, &curLen,
2119 prevDelim, curDepth,
2120 &lastEntry, output);
2123 /* If we have reached the end of the string, see if bracketing outstanding */
2124 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2125 inRem = FALSE;
2126 prevDelim = CMD_NONE;
2127 inQuotes = 0;
2128 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2130 /* Read more, skipping any blank lines */
2131 while (*extraSpace == 0x00) {
2132 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2133 if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
2134 break;
2136 curPos = extraSpace;
2137 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2138 /* Continue to echo commands IF echo is on and in batch program */
2139 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2140 WCMD_output_asis(extraSpace);
2141 WCMD_output_asis(newline);
2146 /* Dump out the parsed output */
2147 WCMD_DumpCommands(*output);
2149 return extraSpace;
2152 /***************************************************************************
2153 * WCMD_process_commands
2155 * Process all the commands read in so far
2157 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2158 const WCHAR *var, const WCHAR *val) {
2160 int bdepth = -1;
2162 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2164 /* Loop through the commands, processing them one by one */
2165 while (thisCmd) {
2167 CMD_LIST *origCmd = thisCmd;
2169 /* If processing one bracket only, and we find the end bracket
2170 entry (or less), return */
2171 if (oneBracket && !thisCmd->command &&
2172 bdepth <= thisCmd->bracketDepth) {
2173 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2174 thisCmd, thisCmd->nextcommand);
2175 return thisCmd->nextcommand;
2178 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2179 about them and it will be handled in there)
2180 Also, skip over any batch labels (eg. :fred) */
2181 if (thisCmd->command && thisCmd->command[0] != ':') {
2182 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2183 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2186 /* Step on unless the command itself already stepped on */
2187 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2189 return NULL;
2192 /***************************************************************************
2193 * WCMD_free_commands
2195 * Frees the storage held for a parsed command line
2196 * - This is not done in the process_commands, as eventually the current
2197 * pointer will be modified within the commands, and hence a single free
2198 * routine is simpler
2200 void WCMD_free_commands(CMD_LIST *cmds) {
2202 /* Loop through the commands, freeing them one by one */
2203 while (cmds) {
2204 CMD_LIST *thisCmd = cmds;
2205 cmds = cmds->nextcommand;
2206 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2207 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2208 HeapFree(GetProcessHeap(), 0, thisCmd);
2213 /*****************************************************************************
2214 * Main entry point. This is a console application so we have a main() not a
2215 * winmain().
2218 int wmain (int argc, WCHAR *argvW[])
2220 int args;
2221 WCHAR *cmd = NULL;
2222 WCHAR string[1024];
2223 WCHAR envvar[4];
2224 BOOL opt_q;
2225 int opt_t = 0;
2226 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2227 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2228 char ansiVersion[100];
2229 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2231 srand(time(NULL));
2233 /* Pre initialize some messages */
2234 strcpy(ansiVersion, PACKAGE_VERSION);
2235 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2236 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2237 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2239 args = argc;
2240 opt_c = opt_k = opt_q = opt_s = FALSE;
2241 while (args > 0)
2243 WCHAR c;
2244 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2245 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2246 argvW++;
2247 args--;
2248 continue;
2251 c=(*argvW)[1];
2252 if (tolowerW(c)=='c') {
2253 opt_c = TRUE;
2254 } else if (tolowerW(c)=='q') {
2255 opt_q = TRUE;
2256 } else if (tolowerW(c)=='k') {
2257 opt_k = TRUE;
2258 } else if (tolowerW(c)=='s') {
2259 opt_s = TRUE;
2260 } else if (tolowerW(c)=='a') {
2261 unicodePipes=FALSE;
2262 } else if (tolowerW(c)=='u') {
2263 unicodePipes=TRUE;
2264 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2265 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2266 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2267 /* Ignored for compatibility with Windows */
2270 if ((*argvW)[2]==0) {
2271 argvW++;
2272 args--;
2274 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2276 *argvW+=2;
2279 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2280 break;
2283 if (opt_q) {
2284 static const WCHAR eoff[] = {'O','F','F','\0'};
2285 WCMD_echo(eoff);
2288 if (opt_c || opt_k) {
2289 int len,qcount;
2290 WCHAR** arg;
2291 int argsLeft;
2292 WCHAR* p;
2294 /* opt_s left unflagged if the command starts with and contains exactly
2295 * one quoted string (exactly two quote characters). The quoted string
2296 * must be an executable name that has whitespace and must not have the
2297 * following characters: &<>()@^| */
2299 /* Build the command to execute */
2300 len = 0;
2301 qcount = 0;
2302 argsLeft = args;
2303 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2305 int has_space,bcount;
2306 WCHAR* a;
2308 has_space=0;
2309 bcount=0;
2310 a=*arg;
2311 if( !*a ) has_space=1;
2312 while (*a!='\0') {
2313 if (*a=='\\') {
2314 bcount++;
2315 } else {
2316 if (*a==' ' || *a=='\t') {
2317 has_space=1;
2318 } else if (*a=='"') {
2319 /* doubling of '\' preceding a '"',
2320 * plus escaping of said '"'
2322 len+=2*bcount+1;
2323 qcount++;
2325 bcount=0;
2327 a++;
2329 len+=(a-*arg) + 1; /* for the separating space */
2330 if (has_space)
2332 len+=2; /* for the quotes */
2333 qcount+=2;
2337 if (qcount!=2)
2338 opt_s = TRUE;
2340 /* check argvW[0] for a space and invalid characters */
2341 if (!opt_s) {
2342 opt_s = TRUE;
2343 p=*argvW;
2344 while (*p!='\0') {
2345 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2346 || *p=='@' || *p=='^' || *p=='|') {
2347 opt_s = TRUE;
2348 break;
2350 if (*p==' ')
2351 opt_s = FALSE;
2352 p++;
2356 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2357 if (!cmd)
2358 exit(1);
2360 p = cmd;
2361 argsLeft = args;
2362 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2364 int has_space,has_quote;
2365 WCHAR* a;
2367 /* Check for quotes and spaces in this argument */
2368 has_space=has_quote=0;
2369 a=*arg;
2370 if( !*a ) has_space=1;
2371 while (*a!='\0') {
2372 if (*a==' ' || *a=='\t') {
2373 has_space=1;
2374 if (has_quote)
2375 break;
2376 } else if (*a=='"') {
2377 has_quote=1;
2378 if (has_space)
2379 break;
2381 a++;
2384 /* Now transfer it to the command line */
2385 if (has_space)
2386 *p++='"';
2387 if (has_quote) {
2388 int bcount;
2389 WCHAR* a;
2391 bcount=0;
2392 a=*arg;
2393 while (*a!='\0') {
2394 if (*a=='\\') {
2395 *p++=*a;
2396 bcount++;
2397 } else {
2398 if (*a=='"') {
2399 int i;
2401 /* Double all the '\\' preceding this '"', plus one */
2402 for (i=0;i<=bcount;i++)
2403 *p++='\\';
2404 *p++='"';
2405 } else {
2406 *p++=*a;
2408 bcount=0;
2410 a++;
2412 } else {
2413 strcpyW(p,*arg);
2414 p+=strlenW(*arg);
2416 if (has_space)
2417 *p++='"';
2418 *p++=' ';
2420 if (p > cmd)
2421 p--; /* remove last space */
2422 *p = '\0';
2424 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2426 /* strip first and last quote characters if opt_s; check for invalid
2427 * executable is done later */
2428 if (opt_s && *cmd=='\"')
2429 WCMD_strip_quotes(cmd);
2432 if (opt_c) {
2433 /* If we do a "cmd /c command", we don't want to allocate a new
2434 * console since the command returns immediately. Rather, we use
2435 * the currently allocated input and output handles. This allows
2436 * us to pipe to and read from the command interpreter.
2439 /* Parse the command string, without reading any more input */
2440 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2441 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2442 WCMD_free_commands(toExecute);
2443 toExecute = NULL;
2445 HeapFree(GetProcessHeap(), 0, cmd);
2446 return errorlevel;
2449 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2450 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2451 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2453 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2454 if (opt_t) {
2455 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2456 defaultColor = opt_t & 0xFF;
2457 param1[0] = 0x00;
2458 WCMD_color();
2460 } else {
2461 /* Check HKCU\Software\Microsoft\Command Processor
2462 Then HKLM\Software\Microsoft\Command Processor
2463 for defaultcolour value
2464 Note Can be supplied as DWORD or REG_SZ
2465 Note2 When supplied as REG_SZ it's in decimal!!! */
2466 HKEY key;
2467 DWORD type;
2468 DWORD value=0, size=4;
2469 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2470 'M','i','c','r','o','s','o','f','t','\\',
2471 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2472 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2474 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2475 0, KEY_READ, &key) == ERROR_SUCCESS) {
2476 WCHAR strvalue[4];
2478 /* See if DWORD or REG_SZ */
2479 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2480 NULL, NULL) == ERROR_SUCCESS) {
2481 if (type == REG_DWORD) {
2482 size = sizeof(DWORD);
2483 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2484 (LPBYTE)&value, &size);
2485 } else if (type == REG_SZ) {
2486 size = sizeof(strvalue)/sizeof(WCHAR);
2487 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2488 (LPBYTE)strvalue, &size);
2489 value = strtoulW(strvalue, NULL, 10);
2492 RegCloseKey(key);
2495 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2496 0, KEY_READ, &key) == ERROR_SUCCESS) {
2497 WCHAR strvalue[4];
2499 /* See if DWORD or REG_SZ */
2500 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2501 NULL, NULL) == ERROR_SUCCESS) {
2502 if (type == REG_DWORD) {
2503 size = sizeof(DWORD);
2504 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2505 (LPBYTE)&value, &size);
2506 } else if (type == REG_SZ) {
2507 size = sizeof(strvalue)/sizeof(WCHAR);
2508 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2509 (LPBYTE)strvalue, &size);
2510 value = strtoulW(strvalue, NULL, 10);
2513 RegCloseKey(key);
2516 /* If one found, set the screen to that colour */
2517 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2518 defaultColor = value & 0xFF;
2519 param1[0] = 0x00;
2520 WCMD_color();
2525 /* Save cwd into appropriate env var */
2526 GetCurrentDirectoryW(1024, string);
2527 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2528 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2529 wsprintfW(envvar, fmt, string[0]);
2530 SetEnvironmentVariableW(envvar, string);
2531 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2534 if (opt_k) {
2535 /* Parse the command string, without reading any more input */
2536 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2537 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2538 WCMD_free_commands(toExecute);
2539 toExecute = NULL;
2540 HeapFree(GetProcessHeap(), 0, cmd);
2544 * Loop forever getting commands and executing them.
2547 SetEnvironmentVariableW(promptW, defaultpromptW);
2548 WCMD_version ();
2549 while (TRUE) {
2551 /* Read until EOF (which for std input is never, but if redirect
2552 in place, may occur */
2553 if (echo_mode) WCMD_show_prompt();
2554 if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2555 break;
2556 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2557 WCMD_free_commands(toExecute);
2558 toExecute = NULL;
2560 return 0;