winecoreaudio: Fix deprecation warnings in mixer.c.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
blob7b7ce8ffde88137f8676c0d5e44f88b9c7b025ef
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
34 const WCHAR inbuilt[][10] = {
35 {'A','T','T','R','I','B','\0'},
36 {'C','A','L','L','\0'},
37 {'C','D','\0'},
38 {'C','H','D','I','R','\0'},
39 {'C','L','S','\0'},
40 {'C','O','P','Y','\0'},
41 {'C','T','T','Y','\0'},
42 {'D','A','T','E','\0'},
43 {'D','E','L','\0'},
44 {'D','I','R','\0'},
45 {'E','C','H','O','\0'},
46 {'E','R','A','S','E','\0'},
47 {'F','O','R','\0'},
48 {'G','O','T','O','\0'},
49 {'H','E','L','P','\0'},
50 {'I','F','\0'},
51 {'L','A','B','E','L','\0'},
52 {'M','D','\0'},
53 {'M','K','D','I','R','\0'},
54 {'M','O','V','E','\0'},
55 {'P','A','T','H','\0'},
56 {'P','A','U','S','E','\0'},
57 {'P','R','O','M','P','T','\0'},
58 {'R','E','M','\0'},
59 {'R','E','N','\0'},
60 {'R','E','N','A','M','E','\0'},
61 {'R','D','\0'},
62 {'R','M','D','I','R','\0'},
63 {'S','E','T','\0'},
64 {'S','H','I','F','T','\0'},
65 {'T','I','M','E','\0'},
66 {'T','I','T','L','E','\0'},
67 {'T','Y','P','E','\0'},
68 {'V','E','R','I','F','Y','\0'},
69 {'V','E','R','\0'},
70 {'V','O','L','\0'},
71 {'E','N','D','L','O','C','A','L','\0'},
72 {'S','E','T','L','O','C','A','L','\0'},
73 {'P','U','S','H','D','\0'},
74 {'P','O','P','D','\0'},
75 {'A','S','S','O','C','\0'},
76 {'C','O','L','O','R','\0'},
77 {'F','T','Y','P','E','\0'},
78 {'M','O','R','E','\0'},
79 {'C','H','O','I','C','E','\0'},
80 {'E','X','I','T','\0'}
83 HINSTANCE hinst;
84 DWORD errorlevel;
85 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
86 static int opt_c, opt_k, opt_s;
87 const WCHAR newline[] = {'\r','\n','\0'};
88 static const WCHAR equalsW[] = {'=','\0'};
89 static const WCHAR closeBW[] = {')','\0'};
90 WCHAR anykey[100];
91 WCHAR version_string[100];
92 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
93 BATCH_CONTEXT *context = NULL;
94 extern struct env_stack *pushd_directories;
95 static const WCHAR *pagedMessage = NULL;
96 static char *output_bufA = NULL;
97 #define MAX_WRITECONSOLE_SIZE 65535
98 BOOL unicodePipes = FALSE;
101 /*******************************************************************
102 * WCMD_output_asis_len - send output to current standard output
104 * Output a formatted unicode string. Ideally this will go to the console
105 * and hence required WriteConsoleW to output it, however if file i/o is
106 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
108 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
110 DWORD nOut= 0;
111 DWORD res = 0;
113 /* If nothing to write, return (MORE does this sometimes) */
114 if (!len) return;
116 /* Try to write as unicode assuming it is to a console */
117 res = WriteConsoleW(device, message, len, &nOut, NULL);
119 /* If writing to console fails, assume its file
120 i/o so convert to OEM codepage and output */
121 if (!res) {
122 BOOL usedDefaultChar = FALSE;
123 DWORD convertedChars;
125 if (!unicodePipes) {
127 * Allocate buffer to use when writing to file. (Not freed, as one off)
129 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
130 MAX_WRITECONSOLE_SIZE);
131 if (!output_bufA) {
132 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
133 return;
136 /* Convert to OEM, then output */
137 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
138 len, output_bufA, MAX_WRITECONSOLE_SIZE,
139 "?", &usedDefaultChar);
140 WriteFile(device, output_bufA, convertedChars,
141 &nOut, FALSE);
142 } else {
143 WriteFile(device, message, len*sizeof(WCHAR),
144 &nOut, FALSE);
147 return;
150 /*******************************************************************
151 * WCMD_output - send output to current standard output device.
155 void WCMD_output (const WCHAR *format, ...) {
157 va_list ap;
158 WCHAR string[1024];
159 int ret;
161 va_start(ap,format);
162 ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
163 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
164 WINE_ERR("Output truncated in WCMD_output\n" );
165 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
166 string[ret] = '\0';
168 va_end(ap);
169 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
173 static int line_count;
174 static int max_height;
175 static int max_width;
176 static BOOL paged_mode;
177 static int numChars;
179 void WCMD_enter_paged_mode(const WCHAR *msg)
181 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
183 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
184 max_height = consoleInfo.dwSize.Y;
185 max_width = consoleInfo.dwSize.X;
186 } else {
187 max_height = 25;
188 max_width = 80;
190 paged_mode = TRUE;
191 line_count = 0;
192 numChars = 0;
193 pagedMessage = (msg==NULL)? anykey : msg;
196 void WCMD_leave_paged_mode(void)
198 paged_mode = FALSE;
199 pagedMessage = NULL;
202 /***************************************************************************
203 * WCMD_Readfile
205 * Read characters in from a console/file, returning result in Unicode
206 * with signature identical to ReadFile
208 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
209 LPDWORD charsRead, const LPOVERLAPPED unused) {
211 BOOL res;
213 /* Try to read from console as Unicode */
214 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
216 /* If reading from console has failed we assume its file
217 i/o so read in and convert from OEM codepage */
218 if (!res) {
220 DWORD numRead;
222 * Allocate buffer to use when reading from file. Not freed
224 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
225 MAX_WRITECONSOLE_SIZE);
226 if (!output_bufA) {
227 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
228 return 0;
231 /* Read from file (assume OEM codepage) */
232 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
234 /* Convert from OEM */
235 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
236 intoBuf, maxChars);
239 return res;
242 /*******************************************************************
243 * WCMD_output_asis - send output to current standard output device.
244 * without formatting eg. when message contains '%'
246 void WCMD_output_asis (const WCHAR *message) {
247 DWORD count;
248 const WCHAR* ptr;
249 WCHAR string[1024];
251 if (paged_mode) {
252 do {
253 ptr = message;
254 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
255 numChars++;
256 ptr++;
258 if (*ptr == '\n') ptr++;
259 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
260 GetStdHandle(STD_OUTPUT_HANDLE));
261 if (ptr) {
262 numChars = 0;
263 if (++line_count >= max_height - 1) {
264 line_count = 0;
265 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
266 GetStdHandle(STD_OUTPUT_HANDLE));
267 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
268 sizeof(string)/sizeof(WCHAR), &count, NULL);
271 } while (((message = ptr) != NULL) && (*ptr));
272 } else {
273 WCMD_output_asis_len(message, lstrlenW(message),
274 GetStdHandle(STD_OUTPUT_HANDLE));
278 /****************************************************************************
279 * WCMD_print_error
281 * Print the message for GetLastError
284 void WCMD_print_error (void) {
285 LPVOID lpMsgBuf;
286 DWORD error_code;
287 int status;
289 error_code = GetLastError ();
290 status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
291 NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
292 if (!status) {
293 WINE_FIXME ("Cannot display message for error %d, status %d\n",
294 error_code, GetLastError());
295 return;
298 WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
299 GetStdHandle(STD_ERROR_HANDLE));
300 LocalFree (lpMsgBuf);
301 WCMD_output_asis_len (newline, lstrlenW(newline),
302 GetStdHandle(STD_ERROR_HANDLE));
303 return;
306 /******************************************************************************
307 * WCMD_show_prompt
309 * Display the prompt on STDout
313 static void WCMD_show_prompt (void) {
315 int status;
316 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
317 WCHAR *p, *q;
318 DWORD len;
319 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
321 len = GetEnvironmentVariableW(envPrompt, prompt_string,
322 sizeof(prompt_string)/sizeof(WCHAR));
323 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
324 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
325 strcpyW (prompt_string, dfltPrompt);
327 p = prompt_string;
328 q = out_string;
329 *q++ = '\r';
330 *q++ = '\n';
331 *q = '\0';
332 while (*p != '\0') {
333 if (*p != '$') {
334 *q++ = *p++;
335 *q = '\0';
337 else {
338 p++;
339 switch (toupper(*p)) {
340 case '$':
341 *q++ = '$';
342 break;
343 case 'A':
344 *q++ = '&';
345 break;
346 case 'B':
347 *q++ = '|';
348 break;
349 case 'C':
350 *q++ = '(';
351 break;
352 case 'D':
353 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
354 while (*q) q++;
355 break;
356 case 'E':
357 *q++ = '\E';
358 break;
359 case 'F':
360 *q++ = ')';
361 break;
362 case 'G':
363 *q++ = '>';
364 break;
365 case 'H':
366 *q++ = '\b';
367 break;
368 case 'L':
369 *q++ = '<';
370 break;
371 case 'N':
372 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
373 if (status) {
374 *q++ = curdir[0];
376 break;
377 case 'P':
378 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
379 if (status) {
380 strcatW (q, curdir);
381 while (*q) q++;
383 break;
384 case 'Q':
385 *q++ = '=';
386 break;
387 case 'S':
388 *q++ = ' ';
389 break;
390 case 'T':
391 GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
392 while (*q) q++;
393 break;
394 case 'V':
395 strcatW (q, version_string);
396 while (*q) q++;
397 break;
398 case '_':
399 *q++ = '\n';
400 break;
401 case '+':
402 if (pushd_directories) {
403 memset(q, '+', pushd_directories->u.stackdepth);
404 q = q + pushd_directories->u.stackdepth;
406 break;
408 p++;
409 *q = '\0';
412 WCMD_output_asis (out_string);
416 /*************************************************************************
417 * WCMD_strdupW
418 * A wide version of strdup as its missing from unicode.h
420 WCHAR *WCMD_strdupW(WCHAR *input) {
421 int len=strlenW(input)+1;
422 WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
423 memcpy(result, input, len * sizeof(WCHAR));
424 return result;
427 /*************************************************************************
428 * WCMD_strsubstW
429 * Replaces a portion of a Unicode string with the specified string.
430 * It's up to the caller to ensure there is enough space in the
431 * destination buffer.
433 void WCMD_strsubstW(WCHAR* start, WCHAR* next, WCHAR* insert, int len) {
435 if (len < 0)
436 len=insert ? lstrlenW(insert) : 0;
437 if (start+len != next)
438 memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
439 if (insert)
440 memcpy(start, insert, len * sizeof(*insert));
443 /***************************************************************************
444 * WCMD_strtrim_leading_spaces
446 * Remove leading spaces from a string. Return a pointer to the first
447 * non-space character. Does not modify the input string
449 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
451 WCHAR *ptr;
453 ptr = string;
454 while (*ptr == ' ') ptr++;
455 return ptr;
458 /*************************************************************************
459 * WCMD_opt_s_strip_quotes
461 * Remove first and last quote WCHARacters, preserving all other text
463 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
464 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
465 while((*dest=*src) != '\0') {
466 if (*src=='\"')
467 lastq=dest;
468 dest++, src++;
470 if (lastq) {
471 dest=lastq++;
472 while ((*dest++=*lastq++) != 0)
478 /*************************************************************************
479 * WCMD_expand_envvar
481 * Expands environment variables, allowing for WCHARacter substitution
483 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
484 WCHAR *endOfVar = NULL, *s;
485 WCHAR *colonpos = NULL;
486 WCHAR thisVar[MAXSTRING];
487 WCHAR thisVarContents[MAXSTRING];
488 WCHAR savedchar = 0x00;
489 int len;
491 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
492 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
493 static const WCHAR Date[] = {'D','A','T','E','\0'};
494 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
495 static const WCHAR Time[] = {'T','I','M','E','\0'};
496 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
497 static const WCHAR Cd[] = {'C','D','\0'};
498 static const WCHAR CdP[] = {'%','C','D','%','\0'};
499 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
500 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
501 static const WCHAR Delims[] = {'%',' ',':','\0'};
503 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
504 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
506 /* Find the end of the environment variable, and extract name */
507 endOfVar = strpbrkW(start+1, Delims);
509 if (endOfVar == NULL || *endOfVar==' ') {
511 /* In batch program, missing terminator for % and no following
512 ':' just removes the '%' */
513 if (context) {
514 WCMD_strsubstW(start, start + 1, NULL, 0);
515 return start;
516 } else {
518 /* In command processing, just ignore it - allows command line
519 syntax like: for %i in (a.a) do echo %i */
520 return start+1;
524 /* If ':' found, process remaining up until '%' (or stop at ':' if
525 a missing '%' */
526 if (*endOfVar==':') {
527 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
528 if (endOfVar2 != NULL) endOfVar = endOfVar2;
531 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
532 thisVar[(endOfVar - start)+1] = 0x00;
533 colonpos = strchrW(thisVar+1, ':');
535 /* If there's complex substitution, just need %var% for now
536 to get the expanded data to play with */
537 if (colonpos) {
538 *colonpos = '%';
539 savedchar = *(colonpos+1);
540 *(colonpos+1) = 0x00;
543 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
545 /* Expand to contents, if unchanged, return */
546 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
547 /* override if existing env var called that name */
548 if ((CompareStringW(LOCALE_USER_DEFAULT,
549 NORM_IGNORECASE | SORT_STRINGSORT,
550 thisVar, 12, ErrorLvlP, -1) == 2) &&
551 (GetEnvironmentVariableW(ErrorLvl, thisVarContents, 1) == 0) &&
552 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
553 static const WCHAR fmt[] = {'%','d','\0'};
554 wsprintfW(thisVarContents, fmt, errorlevel);
555 len = strlenW(thisVarContents);
557 } else if ((CompareStringW(LOCALE_USER_DEFAULT,
558 NORM_IGNORECASE | SORT_STRINGSORT,
559 thisVar, 6, DateP, -1) == 2) &&
560 (GetEnvironmentVariableW(Date, thisVarContents, 1) == 0) &&
561 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
563 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
564 NULL, thisVarContents, MAXSTRING);
565 len = strlenW(thisVarContents);
567 } else if ((CompareStringW(LOCALE_USER_DEFAULT,
568 NORM_IGNORECASE | SORT_STRINGSORT,
569 thisVar, 6, TimeP, -1) == 2) &&
570 (GetEnvironmentVariableW(Time, thisVarContents, 1) == 0) &&
571 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
572 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
573 NULL, thisVarContents, MAXSTRING);
574 len = strlenW(thisVarContents);
576 } else if ((CompareStringW(LOCALE_USER_DEFAULT,
577 NORM_IGNORECASE | SORT_STRINGSORT,
578 thisVar, 4, CdP, -1) == 2) &&
579 (GetEnvironmentVariableW(Cd, thisVarContents, 1) == 0) &&
580 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
581 GetCurrentDirectoryW(MAXSTRING, thisVarContents);
582 len = strlenW(thisVarContents);
584 } else if ((CompareStringW(LOCALE_USER_DEFAULT,
585 NORM_IGNORECASE | SORT_STRINGSORT,
586 thisVar, 8, RandomP, -1) == 2) &&
587 (GetEnvironmentVariableW(Random, thisVarContents, 1) == 0) &&
588 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
589 static const WCHAR fmt[] = {'%','d','\0'};
590 wsprintfW(thisVarContents, fmt, rand() % 32768);
591 len = strlenW(thisVarContents);
593 /* Look for a matching 'for' variable */
594 } else if (forVar &&
595 (CompareStringW(LOCALE_USER_DEFAULT,
596 SORT_STRINGSORT,
597 thisVar,
598 (colonpos - thisVar) - 1,
599 forVar, -1) == 2)) {
600 strcpyW(thisVarContents, forVal);
601 len = strlenW(thisVarContents);
603 } else {
605 len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
606 sizeof(thisVarContents)/sizeof(WCHAR));
609 if (len == 0)
610 return endOfVar+1;
612 /* In a batch program, unknown env vars are replaced with nothing,
613 note syntax %garbage:1,3% results in anything after the ':'
614 except the %
615 From the command line, you just get back what you entered */
616 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
618 /* Restore the complex part after the compare */
619 if (colonpos) {
620 *colonpos = ':';
621 *(colonpos+1) = savedchar;
624 /* Command line - just ignore this */
625 if (context == NULL) return endOfVar+1;
628 /* Batch - replace unknown env var with nothing */
629 if (colonpos == NULL) {
630 WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
631 } else {
632 len = strlenW(thisVar);
633 thisVar[len-1] = 0x00;
634 /* If %:...% supplied, : is retained */
635 if (colonpos == thisVar+1) {
636 WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
637 } else {
638 WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
641 return start;
645 /* See if we need to do complex substitution (any ':'s), if not
646 then our work here is done */
647 if (colonpos == NULL) {
648 WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
649 return start;
652 /* Restore complex bit */
653 *colonpos = ':';
654 *(colonpos+1) = savedchar;
657 Handle complex substitutions:
658 xxx=yyy (replace xxx with yyy)
659 *xxx=yyy (replace up to and including xxx with yyy)
660 ~x (from x WCHARs in)
661 ~-x (from x WCHARs from the end)
662 ~x,y (from x WCHARs in for y WCHARacters)
663 ~x,-y (from x WCHARs in until y WCHARacters from the end)
666 /* ~ is substring manipulation */
667 if (savedchar == '~') {
669 int substrposition, substrlength = 0;
670 WCHAR *commapos = strchrW(colonpos+2, ',');
671 WCHAR *startCopy;
673 substrposition = atolW(colonpos+2);
674 if (commapos) substrlength = atolW(commapos+1);
676 /* Check bounds */
677 if (substrposition >= 0) {
678 startCopy = &thisVarContents[min(substrposition, len)];
679 } else {
680 startCopy = &thisVarContents[max(0, len+substrposition-1)];
683 if (commapos == NULL) {
684 /* Copy the lot */
685 WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
686 } else if (substrlength < 0) {
688 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
689 if (copybytes > len) copybytes = len;
690 else if (copybytes < 0) copybytes = 0;
691 WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
692 } else {
693 WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
696 return start;
698 /* search and replace manipulation */
699 } else {
700 WCHAR *equalspos = strstrW(colonpos, equalsW);
701 WCHAR *replacewith = equalspos+1;
702 WCHAR *found = NULL;
703 WCHAR *searchIn;
704 WCHAR *searchFor;
706 if (equalspos == NULL) return start+1;
707 s = WCMD_strdupW(endOfVar + 1);
709 /* Null terminate both strings */
710 thisVar[strlenW(thisVar)-1] = 0x00;
711 *equalspos = 0x00;
713 /* Since we need to be case insensitive, copy the 2 buffers */
714 searchIn = WCMD_strdupW(thisVarContents);
715 CharUpperBuffW(searchIn, strlenW(thisVarContents));
716 searchFor = WCMD_strdupW(colonpos+1);
717 CharUpperBuffW(searchFor, strlenW(colonpos+1));
719 /* Handle wildcard case */
720 if (*(colonpos+1) == '*') {
721 /* Search for string to replace */
722 found = strstrW(searchIn, searchFor+1);
724 if (found) {
725 /* Do replacement */
726 strcpyW(start, replacewith);
727 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
728 strcatW(start, s);
729 } else {
730 /* Copy as is */
731 strcpyW(start, thisVarContents);
732 strcatW(start, s);
735 } else {
736 /* Loop replacing all instances */
737 WCHAR *lastFound = searchIn;
738 WCHAR *outputposn = start;
740 *start = 0x00;
741 while ((found = strstrW(lastFound, searchFor))) {
742 lstrcpynW(outputposn,
743 thisVarContents + (lastFound-searchIn),
744 (found - lastFound)+1);
745 outputposn = outputposn + (found - lastFound);
746 strcatW(outputposn, replacewith);
747 outputposn = outputposn + strlenW(replacewith);
748 lastFound = found + strlenW(searchFor);
750 strcatW(outputposn,
751 thisVarContents + (lastFound-searchIn));
752 strcatW(outputposn, s);
754 HeapFree(GetProcessHeap(), 0, s);
755 HeapFree(GetProcessHeap(), 0, searchIn);
756 HeapFree(GetProcessHeap(), 0, searchFor);
757 return start;
759 return start+1;
762 /*****************************************************************************
763 * Expand the command. Native expands lines from batch programs as they are
764 * read in and not again, except for 'for' variable substitution.
765 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
767 static void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
769 /* For commands in a context (batch program): */
770 /* Expand environment variables in a batch file %{0-9} first */
771 /* including support for any ~ modifiers */
772 /* Additionally: */
773 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
774 /* names allowing environment variable overrides */
775 /* NOTE: To support the %PATH:xxx% syntax, also perform */
776 /* manual expansion of environment variables here */
778 WCHAR *p = cmd;
779 WCHAR *t;
780 int i;
782 while ((p = strchrW(p, '%'))) {
784 WINE_TRACE("Translate command:%s %d (at: %s)\n",
785 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
786 i = *(p+1) - '0';
788 /* Don't touch %% unless its in Batch */
789 if (!justFors && *(p+1) == '%') {
790 if (context) {
791 WCMD_strsubstW(p, p+1, NULL, 0);
793 p+=1;
795 /* Replace %~ modifications if in batch program */
796 } else if (*(p+1) == '~') {
797 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
798 p++;
800 /* Replace use of %0...%9 if in batch program*/
801 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
802 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
803 WCMD_strsubstW(p, p+2, t, -1);
805 /* Replace use of %* if in batch program*/
806 } else if (!justFors && context && *(p+1)=='*') {
807 WCHAR *startOfParms = NULL;
808 t = WCMD_parameter (context -> command, 1, &startOfParms);
809 if (startOfParms != NULL)
810 WCMD_strsubstW(p, p+2, startOfParms, -1);
811 else
812 WCMD_strsubstW(p, p+2, NULL, 0);
814 } else if (forVariable &&
815 (CompareStringW(LOCALE_USER_DEFAULT,
816 SORT_STRINGSORT,
818 strlenW(forVariable),
819 forVariable, -1) == 2)) {
820 WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
822 } else if (!justFors) {
823 p = WCMD_expand_envvar(p, forVariable, forValue);
825 /* In a FOR loop, see if this is the variable to replace */
826 } else { /* Ignore %'s on second pass of batch program */
827 p++;
831 return;
835 /*******************************************************************
836 * WCMD_parse - parse a command into parameters and qualifiers.
838 * On exit, all qualifiers are concatenated into q, the first string
839 * not beginning with "/" is in p1 and the
840 * second in p2. Any subsequent non-qualifier strings are lost.
841 * Parameters in quotes are handled.
843 static void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
845 int p = 0;
847 *q = *p1 = *p2 = '\0';
848 while (TRUE) {
849 switch (*s) {
850 case '/':
851 *q++ = *s++;
852 while ((*s != '\0') && (*s != ' ') && *s != '/') {
853 *q++ = toupperW (*s++);
855 *q = '\0';
856 break;
857 case ' ':
858 case '\t':
859 s++;
860 break;
861 case '"':
862 s++;
863 while ((*s != '\0') && (*s != '"')) {
864 if (p == 0) *p1++ = *s++;
865 else if (p == 1) *p2++ = *s++;
866 else s++;
868 if (p == 0) *p1 = '\0';
869 if (p == 1) *p2 = '\0';
870 p++;
871 if (*s == '"') s++;
872 break;
873 case '\0':
874 return;
875 default:
876 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
877 && (*s != '=') && (*s != ',') ) {
878 if (p == 0) *p1++ = *s++;
879 else if (p == 1) *p2++ = *s++;
880 else s++;
882 /* Skip concurrent parms */
883 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
885 if (p == 0) *p1 = '\0';
886 if (p == 1) *p2 = '\0';
887 p++;
892 static void init_msvcrt_io_block(STARTUPINFOW* st)
894 STARTUPINFOW st_p;
895 /* fetch the parent MSVCRT info block if any, so that the child can use the
896 * same handles as its grand-father
898 st_p.cb = sizeof(STARTUPINFOW);
899 GetStartupInfoW(&st_p);
900 st->cbReserved2 = st_p.cbReserved2;
901 st->lpReserved2 = st_p.lpReserved2;
902 if (st_p.cbReserved2 && st_p.lpReserved2)
904 /* Override the entries for fd 0,1,2 if we happened
905 * to change those std handles (this depends on the way wcmd sets
906 * it's new input & output handles)
908 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
909 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
910 if (ptr)
912 unsigned num = *(unsigned*)st_p.lpReserved2;
913 char* flags = (char*)(ptr + sizeof(unsigned));
914 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
916 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
917 st->cbReserved2 = sz;
918 st->lpReserved2 = ptr;
920 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
921 if (num <= 0 || (flags[0] & WX_OPEN))
923 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
924 flags[0] |= WX_OPEN;
926 if (num <= 1 || (flags[1] & WX_OPEN))
928 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
929 flags[1] |= WX_OPEN;
931 if (num <= 2 || (flags[2] & WX_OPEN))
933 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
934 flags[2] |= WX_OPEN;
936 #undef WX_OPEN
941 /******************************************************************************
942 * WCMD_run_program
944 * Execute a command line as an external program. Must allow recursion.
946 * Precedence:
947 * Manual testing under windows shows PATHEXT plays a key part in this,
948 * and the search algorithm and precedence appears to be as follows.
950 * Search locations:
951 * If directory supplied on command, just use that directory
952 * If extension supplied on command, look for that explicit name first
953 * Otherwise, search in each directory on the path
954 * Precedence:
955 * If extension supplied on command, look for that explicit name first
956 * Then look for supplied name .* (even if extension supplied, so
957 * 'garbage.exe' will match 'garbage.exe.cmd')
958 * If any found, cycle through PATHEXT looking for name.exe one by one
959 * Launching
960 * Once a match has been found, it is launched - Code currently uses
961 * findexecutable to achieve this which is left untouched.
964 void WCMD_run_program (WCHAR *command, int called) {
966 WCHAR temp[MAX_PATH];
967 WCHAR pathtosearch[MAXSTRING];
968 WCHAR *pathposn;
969 WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
970 MAX_PATH, including null character */
971 WCHAR *lastSlash;
972 WCHAR pathext[MAXSTRING];
973 BOOL extensionsupplied = FALSE;
974 BOOL launched = FALSE;
975 BOOL status;
976 BOOL assumeInternal = FALSE;
977 DWORD len;
978 static const WCHAR envPath[] = {'P','A','T','H','\0'};
979 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
980 static const WCHAR delims[] = {'/','\\',':','\0'};
982 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
983 if (!(*param1) && !(*param2))
984 return;
986 /* Calculate the search path and stem to search for */
987 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
988 static const WCHAR curDir[] = {'.',';','\0'};
989 strcpyW(pathtosearch, curDir);
990 len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
991 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
992 static const WCHAR curDir[] = {'.','\0'};
993 strcpyW (pathtosearch, curDir);
995 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
996 if (strlenW(param1) >= MAX_PATH)
998 WCMD_output_asis(WCMD_LoadMessage(WCMD_LINETOOLONG));
999 return;
1002 strcpyW(stemofsearch, param1);
1004 } else {
1006 /* Convert eg. ..\fred to include a directory by removing file part */
1007 GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1008 lastSlash = strrchrW(pathtosearch, '\\');
1009 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1010 strcpyW(stemofsearch, lastSlash+1);
1012 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1013 c:\windows\a.bat syntax */
1014 if (lastSlash) *(lastSlash + 1) = 0x00;
1017 /* Now extract PATHEXT */
1018 len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1019 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1020 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1021 '.','c','o','m',';',
1022 '.','c','m','d',';',
1023 '.','e','x','e','\0'};
1024 strcpyW (pathext, dfltPathExt);
1027 /* Loop through the search path, dir by dir */
1028 pathposn = pathtosearch;
1029 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1030 wine_dbgstr_w(stemofsearch));
1031 while (!launched && pathposn) {
1033 WCHAR thisDir[MAX_PATH] = {'\0'};
1034 WCHAR *pos = NULL;
1035 BOOL found = FALSE;
1036 const WCHAR slashW[] = {'\\','\0'};
1038 /* Work on the first directory on the search path */
1039 pos = strchrW(pathposn, ';');
1040 if (pos) {
1041 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1042 thisDir[(pos-pathposn)] = 0x00;
1043 pathposn = pos+1;
1045 } else {
1046 strcpyW(thisDir, pathposn);
1047 pathposn = NULL;
1050 /* Since you can have eg. ..\.. on the path, need to expand
1051 to full information */
1052 strcpyW(temp, thisDir);
1053 GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1055 /* 1. If extension supplied, see if that file exists */
1056 strcatW(thisDir, slashW);
1057 strcatW(thisDir, stemofsearch);
1058 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1060 /* 1. If extension supplied, see if that file exists */
1061 if (extensionsupplied) {
1062 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1063 found = TRUE;
1067 /* 2. Any .* matches? */
1068 if (!found) {
1069 HANDLE h;
1070 WIN32_FIND_DATAW finddata;
1071 static const WCHAR allFiles[] = {'.','*','\0'};
1073 strcatW(thisDir,allFiles);
1074 h = FindFirstFileW(thisDir, &finddata);
1075 FindClose(h);
1076 if (h != INVALID_HANDLE_VALUE) {
1078 WCHAR *thisExt = pathext;
1080 /* 3. Yes - Try each path ext */
1081 while (thisExt) {
1082 WCHAR *nextExt = strchrW(thisExt, ';');
1084 if (nextExt) {
1085 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1086 pos[(nextExt-thisExt)] = 0x00;
1087 thisExt = nextExt+1;
1088 } else {
1089 strcpyW(pos, thisExt);
1090 thisExt = NULL;
1093 if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1094 found = TRUE;
1095 thisExt = NULL;
1101 /* Internal programs won't be picked up by this search, so even
1102 though not found, try one last createprocess and wait for it
1103 to complete.
1104 Note: Ideally we could tell between a console app (wait) and a
1105 windows app, but the API's for it fail in this case */
1106 if (!found && pathposn == NULL) {
1107 WINE_TRACE("ASSUMING INTERNAL\n");
1108 assumeInternal = TRUE;
1109 } else {
1110 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1113 /* Once found, launch it */
1114 if (found || assumeInternal) {
1115 STARTUPINFOW st;
1116 PROCESS_INFORMATION pe;
1117 SHFILEINFOW psfi;
1118 DWORD console;
1119 HINSTANCE hinst;
1120 WCHAR *ext = strrchrW( thisDir, '.' );
1121 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1122 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1124 launched = TRUE;
1126 /* Special case BAT and CMD */
1127 if (ext && !strcmpiW(ext, batExt)) {
1128 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1129 return;
1130 } else if (ext && !strcmpiW(ext, cmdExt)) {
1131 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1132 return;
1133 } else {
1135 /* thisDir contains the file to be launched, but with what?
1136 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1137 hinst = FindExecutableW (thisDir, NULL, temp);
1138 if ((INT_PTR)hinst < 32)
1139 console = 0;
1140 else
1141 console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1143 ZeroMemory (&st, sizeof(STARTUPINFOW));
1144 st.cb = sizeof(STARTUPINFOW);
1145 init_msvcrt_io_block(&st);
1147 /* Launch the process and if a CUI wait on it to complete
1148 Note: Launching internal wine processes cannot specify a full path to exe */
1149 status = CreateProcessW(assumeInternal?NULL : thisDir,
1150 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1151 if ((opt_c || opt_k) && !opt_s && !status
1152 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1153 /* strip first and last quote WCHARacters and try again */
1154 WCMD_opt_s_strip_quotes(command);
1155 opt_s=1;
1156 WCMD_run_program(command, called);
1157 return;
1160 if (!status)
1161 break;
1163 if (!assumeInternal && !console) errorlevel = 0;
1164 else
1166 /* Always wait when called in a batch program context */
1167 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1168 GetExitCodeProcess (pe.hProcess, &errorlevel);
1169 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1171 CloseHandle(pe.hProcess);
1172 CloseHandle(pe.hThread);
1173 return;
1178 /* Not found anywhere - give up */
1179 SetLastError(ERROR_FILE_NOT_FOUND);
1180 WCMD_print_error ();
1182 /* If a command fails to launch, it sets errorlevel 9009 - which
1183 does not seem to have any associated constant definition */
1184 errorlevel = 9009;
1185 return;
1189 /*****************************************************************************
1190 * Process one command. If the command is EXIT this routine does not return.
1191 * We will recurse through here executing batch files.
1193 void WCMD_execute (WCHAR *command, WCHAR *redirects,
1194 WCHAR *forVariable, WCHAR *forValue,
1195 CMD_LIST **cmdList)
1197 WCHAR *cmd, *p, *redir;
1198 int status, i;
1199 DWORD count, creationDisposition;
1200 HANDLE h;
1201 WCHAR *whichcmd;
1202 SECURITY_ATTRIBUTES sa;
1203 WCHAR *new_cmd = NULL;
1204 WCHAR *new_redir = NULL;
1205 HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1206 GetStdHandle (STD_OUTPUT_HANDLE),
1207 GetStdHandle (STD_ERROR_HANDLE)};
1208 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
1209 STD_OUTPUT_HANDLE,
1210 STD_ERROR_HANDLE};
1211 BOOL piped = FALSE;
1213 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
1214 wine_dbgstr_w(command), cmdList,
1215 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1217 /* If the next command is a pipe then we implement pipes by redirecting
1218 the output from this command to a temp file and input into the
1219 next command from that temp file.
1220 FIXME: Use of named pipes would make more sense here as currently this
1221 process has to finish before the next one can start but this requires
1222 a change to not wait for the first app to finish but rather the pipe */
1223 if (cmdList && (*cmdList)->nextcommand &&
1224 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1226 WCHAR temp_path[MAX_PATH];
1227 static const WCHAR cmdW[] = {'C','M','D','\0'};
1229 /* Remember piping is in action */
1230 WINE_TRACE("Output needs to be piped\n");
1231 piped = TRUE;
1233 /* Generate a unique temporary filename */
1234 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1235 GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1236 WINE_TRACE("Using temporary file of %s\n",
1237 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1240 /* Move copy of the command onto the heap so it can be expanded */
1241 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1242 if (!new_cmd)
1244 WINE_ERR("Could not allocate memory for new_cmd\n");
1245 return;
1247 strcpyW(new_cmd, command);
1249 /* Move copy of the redirects onto the heap so it can be expanded */
1250 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1251 if (!new_redir)
1253 WINE_ERR("Could not allocate memory for new_redir\n");
1254 HeapFree( GetProcessHeap(), 0, new_cmd );
1255 return;
1258 /* If piped output, send stdout to the pipe by appending >filename to redirects */
1259 if (piped) {
1260 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1261 wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1262 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1263 } else {
1264 strcpyW(new_redir, redirects);
1267 /* Expand variables in command line mode only (batch mode will
1268 be expanded as the line is read in, except for 'for' loops) */
1269 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1270 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1271 cmd = new_cmd;
1274 * Changing default drive has to be handled as a special case.
1277 if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1278 WCHAR envvar[5];
1279 WCHAR dir[MAX_PATH];
1281 /* According to MSDN CreateProcess docs, special env vars record
1282 the current directory on each drive, in the form =C:
1283 so see if one specified, and if so go back to it */
1284 strcpyW(envvar, equalsW);
1285 strcatW(envvar, cmd);
1286 if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1287 static const WCHAR fmt[] = {'%','s','\\','\0'};
1288 wsprintfW(cmd, fmt, cmd);
1289 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1291 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1292 status = SetCurrentDirectoryW(cmd);
1293 if (!status) WCMD_print_error ();
1294 HeapFree( GetProcessHeap(), 0, cmd );
1295 HeapFree( GetProcessHeap(), 0, new_redir );
1296 return;
1299 sa.nLength = sizeof(sa);
1300 sa.lpSecurityDescriptor = NULL;
1301 sa.bInheritHandle = TRUE;
1304 * Redirect stdin, stdout and/or stderr if required.
1307 /* STDIN could come from a preceding pipe, so delete on close if it does */
1308 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1309 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1310 h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1311 FILE_SHARE_READ, &sa, OPEN_EXISTING,
1312 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1313 if (h == INVALID_HANDLE_VALUE) {
1314 WCMD_print_error ();
1315 HeapFree( GetProcessHeap(), 0, cmd );
1316 HeapFree( GetProcessHeap(), 0, new_redir );
1317 return;
1319 SetStdHandle (STD_INPUT_HANDLE, h);
1321 /* No need to remember the temporary name any longer once opened */
1322 (*cmdList)->pipeFile[0] = 0x00;
1324 /* Otherwise STDIN could come from a '<' redirect */
1325 } else if ((p = strchrW(new_redir,'<')) != NULL) {
1326 h = CreateFileW(WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
1327 FILE_ATTRIBUTE_NORMAL, NULL);
1328 if (h == INVALID_HANDLE_VALUE) {
1329 WCMD_print_error ();
1330 HeapFree( GetProcessHeap(), 0, cmd );
1331 HeapFree( GetProcessHeap(), 0, new_redir );
1332 return;
1334 SetStdHandle (STD_INPUT_HANDLE, h);
1337 /* Scan the whole command looking for > and 2> */
1338 redir = new_redir;
1339 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1340 int handle = 0;
1342 if (*(p-1)!='2') {
1343 handle = 1;
1344 } else {
1345 handle = 2;
1348 p++;
1349 if ('>' == *p) {
1350 creationDisposition = OPEN_ALWAYS;
1351 p++;
1353 else {
1354 creationDisposition = CREATE_ALWAYS;
1357 /* Add support for 2>&1 */
1358 redir = p;
1359 if (*p == '&') {
1360 int idx = *(p+1) - '0';
1362 if (DuplicateHandle(GetCurrentProcess(),
1363 GetStdHandle(idx_stdhandles[idx]),
1364 GetCurrentProcess(),
1366 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1367 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1369 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1371 } else {
1372 WCHAR *param = WCMD_parameter (p, 0, NULL);
1373 h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1374 FILE_ATTRIBUTE_NORMAL, NULL);
1375 if (h == INVALID_HANDLE_VALUE) {
1376 WCMD_print_error ();
1377 HeapFree( GetProcessHeap(), 0, cmd );
1378 HeapFree( GetProcessHeap(), 0, new_redir );
1379 return;
1381 if (SetFilePointer (h, 0, NULL, FILE_END) ==
1382 INVALID_SET_FILE_POINTER) {
1383 WCMD_print_error ();
1385 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1388 SetStdHandle (idx_stdhandles[handle], h);
1392 * Strip leading whitespaces, and a '@' if supplied
1394 whichcmd = WCMD_strtrim_leading_spaces(cmd);
1395 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1396 if (whichcmd[0] == '@') whichcmd++;
1399 * Check if the command entered is internal. If it is, pass the rest of the
1400 * line down to the command. If not try to run a program.
1403 count = 0;
1404 while (IsCharAlphaNumericW(whichcmd[count])) {
1405 count++;
1407 for (i=0; i<=WCMD_EXIT; i++) {
1408 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1409 whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1411 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
1412 WCMD_parse (p, quals, param1, param2);
1413 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1415 if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1416 /* this is a help request for a builtin program */
1417 i = WCMD_HELP;
1418 memcpy(p, whichcmd, count * sizeof(WCHAR));
1419 p[count] = '\0';
1423 switch (i) {
1425 case WCMD_ATTRIB:
1426 WCMD_setshow_attrib ();
1427 break;
1428 case WCMD_CALL:
1429 WCMD_call (p);
1430 break;
1431 case WCMD_CD:
1432 case WCMD_CHDIR:
1433 WCMD_setshow_default (p);
1434 break;
1435 case WCMD_CLS:
1436 WCMD_clear_screen ();
1437 break;
1438 case WCMD_COPY:
1439 WCMD_copy ();
1440 break;
1441 case WCMD_CTTY:
1442 WCMD_change_tty ();
1443 break;
1444 case WCMD_DATE:
1445 WCMD_setshow_date ();
1446 break;
1447 case WCMD_DEL:
1448 case WCMD_ERASE:
1449 WCMD_delete (p, TRUE);
1450 break;
1451 case WCMD_DIR:
1452 WCMD_directory (p);
1453 break;
1454 case WCMD_ECHO:
1455 WCMD_echo(&whichcmd[count]);
1456 break;
1457 case WCMD_FOR:
1458 WCMD_for (p, cmdList);
1459 break;
1460 case WCMD_GOTO:
1461 WCMD_goto (cmdList);
1462 break;
1463 case WCMD_HELP:
1464 WCMD_give_help (p);
1465 break;
1466 case WCMD_IF:
1467 WCMD_if (p, cmdList);
1468 break;
1469 case WCMD_LABEL:
1470 WCMD_volume (1, p);
1471 break;
1472 case WCMD_MD:
1473 case WCMD_MKDIR:
1474 WCMD_create_dir ();
1475 break;
1476 case WCMD_MOVE:
1477 WCMD_move ();
1478 break;
1479 case WCMD_PATH:
1480 WCMD_setshow_path (p);
1481 break;
1482 case WCMD_PAUSE:
1483 WCMD_pause ();
1484 break;
1485 case WCMD_PROMPT:
1486 WCMD_setshow_prompt ();
1487 break;
1488 case WCMD_REM:
1489 break;
1490 case WCMD_REN:
1491 case WCMD_RENAME:
1492 WCMD_rename ();
1493 break;
1494 case WCMD_RD:
1495 case WCMD_RMDIR:
1496 WCMD_remove_dir (p);
1497 break;
1498 case WCMD_SETLOCAL:
1499 WCMD_setlocal(p);
1500 break;
1501 case WCMD_ENDLOCAL:
1502 WCMD_endlocal();
1503 break;
1504 case WCMD_SET:
1505 WCMD_setshow_env (p);
1506 break;
1507 case WCMD_SHIFT:
1508 WCMD_shift (p);
1509 break;
1510 case WCMD_TIME:
1511 WCMD_setshow_time ();
1512 break;
1513 case WCMD_TITLE:
1514 if (strlenW(&whichcmd[count]) > 0)
1515 WCMD_title(&whichcmd[count+1]);
1516 break;
1517 case WCMD_TYPE:
1518 WCMD_type (p);
1519 break;
1520 case WCMD_VER:
1521 WCMD_version ();
1522 break;
1523 case WCMD_VERIFY:
1524 WCMD_verify (p);
1525 break;
1526 case WCMD_VOL:
1527 WCMD_volume (0, p);
1528 break;
1529 case WCMD_PUSHD:
1530 WCMD_pushd(p);
1531 break;
1532 case WCMD_POPD:
1533 WCMD_popd();
1534 break;
1535 case WCMD_ASSOC:
1536 WCMD_assoc(p, TRUE);
1537 break;
1538 case WCMD_COLOR:
1539 WCMD_color();
1540 break;
1541 case WCMD_FTYPE:
1542 WCMD_assoc(p, FALSE);
1543 break;
1544 case WCMD_MORE:
1545 WCMD_more(p);
1546 break;
1547 case WCMD_CHOICE:
1548 WCMD_choice(p);
1549 break;
1550 case WCMD_EXIT:
1551 WCMD_exit (cmdList);
1552 break;
1553 default:
1554 WCMD_run_program (whichcmd, 0);
1556 HeapFree( GetProcessHeap(), 0, cmd );
1557 HeapFree( GetProcessHeap(), 0, new_redir );
1559 /* Restore old handles */
1560 for (i=0; i<3; i++) {
1561 if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1562 CloseHandle (GetStdHandle (idx_stdhandles[i]));
1563 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1567 /*************************************************************************
1568 * WCMD_LoadMessage
1569 * Load a string from the resource file, handling any error
1570 * Returns string retrieved from resource file
1572 WCHAR *WCMD_LoadMessage(UINT id) {
1573 static WCHAR msg[2048];
1574 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1576 if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1577 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1578 strcpyW(msg, failedMsg);
1580 return msg;
1583 /***************************************************************************
1584 * WCMD_DumpCommands
1586 * Dumps out the parsed command line to ensure syntax is correct
1588 static void WCMD_DumpCommands(CMD_LIST *commands) {
1589 CMD_LIST *thisCmd = commands;
1591 WINE_TRACE("Parsed line:\n");
1592 while (thisCmd != NULL) {
1593 WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1594 thisCmd,
1595 thisCmd->prevDelim,
1596 thisCmd->bracketDepth,
1597 thisCmd->nextcommand,
1598 wine_dbgstr_w(thisCmd->command),
1599 wine_dbgstr_w(thisCmd->redirects));
1600 thisCmd = thisCmd->nextcommand;
1604 /***************************************************************************
1605 * WCMD_addCommand
1607 * Adds a command to the current command list
1609 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1610 WCHAR *redirs, int *redirLen,
1611 WCHAR **copyTo, int **copyToLen,
1612 CMD_DELIMITERS prevDelim, int curDepth,
1613 CMD_LIST **lastEntry, CMD_LIST **output) {
1615 CMD_LIST *thisEntry = NULL;
1617 /* Allocate storage for command */
1618 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1620 /* Copy in the command */
1621 if (command) {
1622 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1623 (*commandLen+1) * sizeof(WCHAR));
1624 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1625 thisEntry->command[*commandLen] = 0x00;
1627 /* Copy in the redirects */
1628 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1629 (*redirLen+1) * sizeof(WCHAR));
1630 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1631 thisEntry->redirects[*redirLen] = 0x00;
1632 thisEntry->pipeFile[0] = 0x00;
1634 /* Reset the lengths */
1635 *commandLen = 0;
1636 *redirLen = 0;
1637 *copyToLen = commandLen;
1638 *copyTo = command;
1640 } else {
1641 thisEntry->command = NULL;
1642 thisEntry->redirects = NULL;
1643 thisEntry->pipeFile[0] = 0x00;
1646 /* Fill in other fields */
1647 thisEntry->nextcommand = NULL;
1648 thisEntry->prevDelim = prevDelim;
1649 thisEntry->bracketDepth = curDepth;
1650 if (*lastEntry) {
1651 (*lastEntry)->nextcommand = thisEntry;
1652 } else {
1653 *output = thisEntry;
1655 *lastEntry = thisEntry;
1659 /***************************************************************************
1660 * WCMD_IsEndQuote
1662 * Checks if the quote pointet to is the end-quote.
1664 * Quotes end if:
1666 * 1) The current parameter ends at EOL or at the beginning
1667 * of a redirection or pipe and not in a quote section.
1669 * 2) If the next character is a space and not in a quote section.
1671 * Returns TRUE if this is an end quote, and FALSE if it is not.
1674 static BOOL WCMD_IsEndQuote(WCHAR *quote, int quoteIndex)
1676 int quoteCount = quoteIndex;
1677 int i;
1679 /* If we are not in a quoted section, then we are not an end-quote */
1680 if(quoteIndex == 0)
1682 return FALSE;
1685 /* Check how many quotes are left for this parameter */
1686 for(i=0;quote[i];i++)
1688 if(quote[i] == '"')
1690 quoteCount++;
1693 /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1694 else if(((quoteCount % 2) == 0)
1695 && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1697 break;
1701 /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1702 be an end-quote */
1703 if(quoteIndex >= (quoteCount / 2))
1705 return TRUE;
1708 /* No cigar */
1709 return FALSE;
1712 /***************************************************************************
1713 * WCMD_ReadAndParseLine
1715 * Either uses supplied input or
1716 * Reads a file from the handle, and then...
1717 * Parse the text buffer, spliting into separate commands
1718 * - unquoted && strings split 2 commands but the 2nd is flagged as
1719 * following an &&
1720 * - ( as the first character just ups the bracket depth
1721 * - unquoted ) when bracket depth > 0 terminates a bracket and
1722 * adds a CMD_LIST structure with null command
1723 * - Anything else gets put into the command string (including
1724 * redirects)
1726 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1728 WCHAR *curPos;
1729 int inQuotes = 0;
1730 WCHAR curString[MAXSTRING];
1731 int curStringLen = 0;
1732 WCHAR curRedirs[MAXSTRING];
1733 int curRedirsLen = 0;
1734 WCHAR *curCopyTo;
1735 int *curLen;
1736 int curDepth = 0;
1737 CMD_LIST *lastEntry = NULL;
1738 CMD_DELIMITERS prevDelim = CMD_NONE;
1739 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1740 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1741 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1742 const WCHAR ifCmd[] = {'i','f',' ','\0'};
1743 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1744 BOOL inRem = FALSE;
1745 BOOL inFor = FALSE;
1746 BOOL inIn = FALSE;
1747 BOOL inIf = FALSE;
1748 BOOL inElse= FALSE;
1749 BOOL onlyWhiteSpace = FALSE;
1750 BOOL lastWasWhiteSpace = FALSE;
1751 BOOL lastWasDo = FALSE;
1752 BOOL lastWasIn = FALSE;
1753 BOOL lastWasElse = FALSE;
1754 BOOL lastWasRedirect = TRUE;
1756 /* Allocate working space for a command read from keyboard, file etc */
1757 if (!extraSpace)
1758 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1759 if (!extraSpace)
1761 WINE_ERR("Could not allocate memory for extraSpace\n");
1762 return NULL;
1765 /* If initial command read in, use that, otherwise get input from handle */
1766 if (optionalcmd != NULL) {
1767 strcpyW(extraSpace, optionalcmd);
1768 } else if (readFrom == INVALID_HANDLE_VALUE) {
1769 WINE_FIXME("No command nor handle supplied\n");
1770 } else {
1771 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1773 curPos = extraSpace;
1775 /* Handle truncated input - issue warning */
1776 if (strlenW(extraSpace) == MAXSTRING -1) {
1777 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1778 WCMD_output_asis(extraSpace);
1779 WCMD_output_asis(newline);
1782 /* Replace env vars if in a batch context */
1783 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1784 /* Show prompt before batch line IF echo is on and in batch program */
1785 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1786 const WCHAR spc[]={' ','\0'};
1787 WCMD_show_prompt();
1788 WCMD_output_asis(extraSpace);
1789 /* I don't know why Windows puts a space here but it does */
1790 WCMD_output_asis(spc);
1791 WCMD_output_asis(newline);
1794 /* Start with an empty string, copying to the command string */
1795 curStringLen = 0;
1796 curRedirsLen = 0;
1797 curCopyTo = curString;
1798 curLen = &curStringLen;
1799 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
1801 /* Parse every character on the line being processed */
1802 while (*curPos != 0x00) {
1804 WCHAR thisChar;
1806 /* Debugging AID:
1807 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1808 lastWasWhiteSpace, onlyWhiteSpace);
1811 /* Certain commands need special handling */
1812 if (curStringLen == 0 && curCopyTo == curString) {
1813 const WCHAR forDO[] = {'d','o',' ','\0'};
1815 /* If command starts with 'rem', ignore any &&, ( etc */
1816 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1817 curPos, 4, remCmd, -1) == 2) {
1818 inRem = TRUE;
1820 /* If command starts with 'for', handle ('s mid line after IN or DO */
1821 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1822 curPos, 4, forCmd, -1) == 2) {
1823 inFor = TRUE;
1825 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1826 is only true in the command portion of the IF statement, but this
1827 should suffice for now
1828 FIXME: Silly syntax like "if 1(==1( (
1829 echo they equal
1830 )" will be parsed wrong */
1831 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1832 curPos, 3, ifCmd, -1) == 2) {
1833 inIf = TRUE;
1835 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1836 curPos, 5, ifElse, -1) == 2) {
1837 inElse = TRUE;
1838 lastWasElse = TRUE;
1839 onlyWhiteSpace = TRUE;
1840 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
1841 (*curLen)+=5;
1842 curPos+=5;
1843 continue;
1845 /* In a for loop, the DO command will follow a close bracket followed by
1846 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1847 is then 0, and all whitespace is skipped */
1848 } else if (inFor &&
1849 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1850 curPos, 3, forDO, -1) == 2)) {
1851 WINE_TRACE("Found DO\n");
1852 lastWasDo = TRUE;
1853 onlyWhiteSpace = TRUE;
1854 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1855 (*curLen)+=3;
1856 curPos+=3;
1857 continue;
1859 } else if (curCopyTo == curString) {
1861 /* Special handling for the 'FOR' command */
1862 if (inFor && lastWasWhiteSpace) {
1863 const WCHAR forIN[] = {'i','n',' ','\0'};
1865 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1867 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1868 curPos, 3, forIN, -1) == 2) {
1869 WINE_TRACE("Found IN\n");
1870 lastWasIn = TRUE;
1871 onlyWhiteSpace = TRUE;
1872 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
1873 (*curLen)+=3;
1874 curPos+=3;
1875 continue;
1880 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1881 so just use the default processing ie skip character specific
1882 matching below */
1883 if (!inRem) thisChar = *curPos;
1884 else thisChar = 'X'; /* Character with no special processing */
1886 lastWasWhiteSpace = FALSE; /* Will be reset below */
1888 switch (thisChar) {
1890 case '=': /* drop through - ignore token delimiters at the start of a command */
1891 case ',': /* drop through - ignore token delimiters at the start of a command */
1892 case '\t':/* drop through - ignore token delimiters at the start of a command */
1893 case ' ':
1894 /* If a redirect in place, it ends here */
1895 if (!inQuotes && !lastWasRedirect) {
1897 /* If finishing off a redirect, add a whitespace delimiter */
1898 if (curCopyTo == curRedirs) {
1899 curCopyTo[(*curLen)++] = ' ';
1901 curCopyTo = curString;
1902 curLen = &curStringLen;
1904 if (*curLen > 0) {
1905 curCopyTo[(*curLen)++] = *curPos;
1908 /* Remember just processed whitespace */
1909 lastWasWhiteSpace = TRUE;
1911 break;
1913 case '>': /* drop through - handle redirect chars the same */
1914 case '<':
1915 /* Make a redirect start here */
1916 if (!inQuotes) {
1917 curCopyTo = curRedirs;
1918 curLen = &curRedirsLen;
1919 lastWasRedirect = TRUE;
1922 /* See if 1>, 2> etc, in which case we have some patching up
1923 to do */
1924 if (curPos != extraSpace &&
1925 *(curPos-1)>='1' && *(curPos-1)<='9') {
1927 curStringLen--;
1928 curString[curStringLen] = 0x00;
1929 curCopyTo[(*curLen)++] = *(curPos-1);
1932 curCopyTo[(*curLen)++] = *curPos;
1934 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1935 do not process that ampersand as an AND operator */
1936 if (thisChar == '>' && *(curPos+1) == '&') {
1937 curCopyTo[(*curLen)++] = *(curPos+1);
1938 curPos++;
1940 break;
1942 case '|': /* Pipe character only if not || */
1943 if (!inQuotes) {
1944 lastWasRedirect = FALSE;
1946 /* Add an entry to the command list */
1947 if (curStringLen > 0) {
1949 /* Add the current command */
1950 WCMD_addCommand(curString, &curStringLen,
1951 curRedirs, &curRedirsLen,
1952 &curCopyTo, &curLen,
1953 prevDelim, curDepth,
1954 &lastEntry, output);
1958 if (*(curPos+1) == '|') {
1959 curPos++; /* Skip other | */
1960 prevDelim = CMD_ONFAILURE;
1961 } else {
1962 prevDelim = CMD_PIPE;
1964 } else {
1965 curCopyTo[(*curLen)++] = *curPos;
1967 break;
1969 case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
1970 inQuotes--;
1971 } else {
1972 inQuotes++; /* Quotes within quotes are fun! */
1974 curCopyTo[(*curLen)++] = *curPos;
1975 lastWasRedirect = FALSE;
1976 break;
1978 case '(': /* If a '(' is the first non whitespace in a command portion
1979 ie start of line or just after &&, then we read until an
1980 unquoted ) is found */
1981 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
1982 ", for(%d, In:%d, Do:%d)"
1983 ", if(%d, else:%d, lwe:%d)\n",
1984 *curLen, inQuotes,
1985 onlyWhiteSpace,
1986 inFor, lastWasIn, lastWasDo,
1987 inIf, inElse, lastWasElse);
1988 lastWasRedirect = FALSE;
1990 /* Ignore open brackets inside the for set */
1991 if (*curLen == 0 && !inIn) {
1992 curDepth++;
1994 /* If in quotes, ignore brackets */
1995 } else if (inQuotes) {
1996 curCopyTo[(*curLen)++] = *curPos;
1998 /* In a FOR loop, an unquoted '(' may occur straight after
1999 IN or DO
2000 In an IF statement just handle it regardless as we don't
2001 parse the operands
2002 In an ELSE statement, only allow it straight away after
2003 the ELSE and whitespace
2005 } else if (inIf ||
2006 (inElse && lastWasElse && onlyWhiteSpace) ||
2007 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2009 /* If entering into an 'IN', set inIn */
2010 if (inFor && lastWasIn && onlyWhiteSpace) {
2011 WINE_TRACE("Inside an IN\n");
2012 inIn = TRUE;
2015 /* Add the current command */
2016 WCMD_addCommand(curString, &curStringLen,
2017 curRedirs, &curRedirsLen,
2018 &curCopyTo, &curLen,
2019 prevDelim, curDepth,
2020 &lastEntry, output);
2022 curDepth++;
2023 } else {
2024 curCopyTo[(*curLen)++] = *curPos;
2026 break;
2028 case '&': if (!inQuotes) {
2029 lastWasRedirect = FALSE;
2031 /* Add an entry to the command list */
2032 if (curStringLen > 0) {
2034 /* Add the current command */
2035 WCMD_addCommand(curString, &curStringLen,
2036 curRedirs, &curRedirsLen,
2037 &curCopyTo, &curLen,
2038 prevDelim, curDepth,
2039 &lastEntry, output);
2043 if (*(curPos+1) == '&') {
2044 curPos++; /* Skip other & */
2045 prevDelim = CMD_ONSUCCESS;
2046 } else {
2047 prevDelim = CMD_NONE;
2049 } else {
2050 curCopyTo[(*curLen)++] = *curPos;
2052 break;
2054 case ')': if (!inQuotes && curDepth > 0) {
2055 lastWasRedirect = FALSE;
2057 /* Add the current command if there is one */
2058 if (curStringLen) {
2060 /* Add the current command */
2061 WCMD_addCommand(curString, &curStringLen,
2062 curRedirs, &curRedirsLen,
2063 &curCopyTo, &curLen,
2064 prevDelim, curDepth,
2065 &lastEntry, output);
2068 /* Add an empty entry to the command list */
2069 prevDelim = CMD_NONE;
2070 WCMD_addCommand(NULL, &curStringLen,
2071 curRedirs, &curRedirsLen,
2072 &curCopyTo, &curLen,
2073 prevDelim, curDepth,
2074 &lastEntry, output);
2075 curDepth--;
2077 /* Leave inIn if necessary */
2078 if (inIn) inIn = FALSE;
2079 } else {
2080 curCopyTo[(*curLen)++] = *curPos;
2082 break;
2083 default:
2084 lastWasRedirect = FALSE;
2085 curCopyTo[(*curLen)++] = *curPos;
2088 curPos++;
2090 /* At various times we need to know if we have only skipped whitespace,
2091 so reset this variable and then it will remain true until a non
2092 whitespace is found */
2093 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2095 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2096 if (!lastWasWhiteSpace) {
2097 lastWasIn = lastWasDo = FALSE;
2100 /* If we have reached the end, add this command into the list */
2101 if (*curPos == 0x00 && *curLen > 0) {
2103 /* Add an entry to the command list */
2104 WCMD_addCommand(curString, &curStringLen,
2105 curRedirs, &curRedirsLen,
2106 &curCopyTo, &curLen,
2107 prevDelim, curDepth,
2108 &lastEntry, output);
2111 /* If we have reached the end of the string, see if bracketing outstanding */
2112 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2113 inRem = FALSE;
2114 prevDelim = CMD_NONE;
2115 inQuotes = 0;
2116 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2118 /* Read more, skipping any blank lines */
2119 while (*extraSpace == 0x00) {
2120 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2121 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2123 curPos = extraSpace;
2124 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2125 /* Continue to echo commands IF echo is on and in batch program */
2126 if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2127 WCMD_output_asis(extraSpace);
2128 WCMD_output_asis(newline);
2133 /* Dump out the parsed output */
2134 WCMD_DumpCommands(*output);
2136 return extraSpace;
2139 /***************************************************************************
2140 * WCMD_process_commands
2142 * Process all the commands read in so far
2144 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2145 WCHAR *var, WCHAR *val) {
2147 int bdepth = -1;
2149 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2151 /* Loop through the commands, processing them one by one */
2152 while (thisCmd) {
2154 CMD_LIST *origCmd = thisCmd;
2156 /* If processing one bracket only, and we find the end bracket
2157 entry (or less), return */
2158 if (oneBracket && !thisCmd->command &&
2159 bdepth <= thisCmd->bracketDepth) {
2160 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2161 thisCmd, thisCmd->nextcommand);
2162 return thisCmd->nextcommand;
2165 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2166 about them and it will be handled in there)
2167 Also, skip over any batch labels (eg. :fred) */
2168 if (thisCmd->command && thisCmd->command[0] != ':') {
2169 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2170 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2173 /* Step on unless the command itself already stepped on */
2174 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2176 return NULL;
2179 /***************************************************************************
2180 * WCMD_free_commands
2182 * Frees the storage held for a parsed command line
2183 * - This is not done in the process_commands, as eventually the current
2184 * pointer will be modified within the commands, and hence a single free
2185 * routine is simpler
2187 void WCMD_free_commands(CMD_LIST *cmds) {
2189 /* Loop through the commands, freeing them one by one */
2190 while (cmds) {
2191 CMD_LIST *thisCmd = cmds;
2192 cmds = cmds->nextcommand;
2193 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2194 HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2195 HeapFree(GetProcessHeap(), 0, thisCmd);
2200 /*****************************************************************************
2201 * Main entry point. This is a console application so we have a main() not a
2202 * winmain().
2205 int wmain (int argc, WCHAR *argvW[])
2207 int args;
2208 WCHAR *cmd = NULL;
2209 WCHAR string[1024];
2210 WCHAR envvar[4];
2211 HANDLE h;
2212 int opt_q;
2213 int opt_t = 0;
2214 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
2215 'b','a','t','\0'};
2216 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2217 static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2218 char ansiVersion[100];
2219 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
2221 srand(time(NULL));
2223 /* Pre initialize some messages */
2224 strcpy(ansiVersion, PACKAGE_VERSION);
2225 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2226 wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2227 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2229 args = argc;
2230 opt_c=opt_k=opt_q=opt_s=0;
2231 while (args > 0)
2233 WCHAR c;
2234 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2235 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2236 argvW++;
2237 args--;
2238 continue;
2241 c=(*argvW)[1];
2242 if (tolowerW(c)=='c') {
2243 opt_c=1;
2244 } else if (tolowerW(c)=='q') {
2245 opt_q=1;
2246 } else if (tolowerW(c)=='k') {
2247 opt_k=1;
2248 } else if (tolowerW(c)=='s') {
2249 opt_s=1;
2250 } else if (tolowerW(c)=='a') {
2251 unicodePipes=FALSE;
2252 } else if (tolowerW(c)=='u') {
2253 unicodePipes=TRUE;
2254 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2255 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2256 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2257 /* Ignored for compatibility with Windows */
2260 if ((*argvW)[2]==0) {
2261 argvW++;
2262 args--;
2264 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2266 *argvW+=2;
2269 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2270 break;
2273 if (opt_q) {
2274 const WCHAR eoff[] = {'O','F','F','\0'};
2275 WCMD_echo(eoff);
2278 if (opt_c || opt_k) {
2279 int len,qcount;
2280 WCHAR** arg;
2281 int argsLeft;
2282 WCHAR* p;
2284 /* opt_s left unflagged if the command starts with and contains exactly
2285 * one quoted string (exactly two quote characters). The quoted string
2286 * must be an executable name that has whitespace and must not have the
2287 * following characters: &<>()@^| */
2289 /* Build the command to execute */
2290 len = 0;
2291 qcount = 0;
2292 argsLeft = args;
2293 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2295 int has_space,bcount;
2296 WCHAR* a;
2298 has_space=0;
2299 bcount=0;
2300 a=*arg;
2301 if( !*a ) has_space=1;
2302 while (*a!='\0') {
2303 if (*a=='\\') {
2304 bcount++;
2305 } else {
2306 if (*a==' ' || *a=='\t') {
2307 has_space=1;
2308 } else if (*a=='"') {
2309 /* doubling of '\' preceding a '"',
2310 * plus escaping of said '"'
2312 len+=2*bcount+1;
2313 qcount++;
2315 bcount=0;
2317 a++;
2319 len+=(a-*arg) + 1; /* for the separating space */
2320 if (has_space)
2322 len+=2; /* for the quotes */
2323 qcount+=2;
2327 if (qcount!=2)
2328 opt_s=1;
2330 /* check argvW[0] for a space and invalid characters */
2331 if (!opt_s) {
2332 opt_s=1;
2333 p=*argvW;
2334 while (*p!='\0') {
2335 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2336 || *p=='@' || *p=='^' || *p=='|') {
2337 opt_s=1;
2338 break;
2340 if (*p==' ')
2341 opt_s=0;
2342 p++;
2346 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2347 if (!cmd)
2348 exit(1);
2350 p = cmd;
2351 argsLeft = args;
2352 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2354 int has_space,has_quote;
2355 WCHAR* a;
2357 /* Check for quotes and spaces in this argument */
2358 has_space=has_quote=0;
2359 a=*arg;
2360 if( !*a ) has_space=1;
2361 while (*a!='\0') {
2362 if (*a==' ' || *a=='\t') {
2363 has_space=1;
2364 if (has_quote)
2365 break;
2366 } else if (*a=='"') {
2367 has_quote=1;
2368 if (has_space)
2369 break;
2371 a++;
2374 /* Now transfer it to the command line */
2375 if (has_space)
2376 *p++='"';
2377 if (has_quote) {
2378 int bcount;
2379 WCHAR* a;
2381 bcount=0;
2382 a=*arg;
2383 while (*a!='\0') {
2384 if (*a=='\\') {
2385 *p++=*a;
2386 bcount++;
2387 } else {
2388 if (*a=='"') {
2389 int i;
2391 /* Double all the '\\' preceding this '"', plus one */
2392 for (i=0;i<=bcount;i++)
2393 *p++='\\';
2394 *p++='"';
2395 } else {
2396 *p++=*a;
2398 bcount=0;
2400 a++;
2402 } else {
2403 strcpyW(p,*arg);
2404 p+=strlenW(*arg);
2406 if (has_space)
2407 *p++='"';
2408 *p++=' ';
2410 if (p > cmd)
2411 p--; /* remove last space */
2412 *p = '\0';
2414 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2416 /* strip first and last quote characters if opt_s; check for invalid
2417 * executable is done later */
2418 if (opt_s && *cmd=='\"')
2419 WCMD_opt_s_strip_quotes(cmd);
2422 if (opt_c) {
2423 /* If we do a "wcmd /c command", we don't want to allocate a new
2424 * console since the command returns immediately. Rather, we use
2425 * the currently allocated input and output handles. This allows
2426 * us to pipe to and read from the command interpreter.
2429 /* Parse the command string, without reading any more input */
2430 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2431 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2432 WCMD_free_commands(toExecute);
2433 toExecute = NULL;
2435 HeapFree(GetProcessHeap(), 0, cmd);
2436 return errorlevel;
2439 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2440 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2441 SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2443 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2444 if (opt_t) {
2445 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2446 defaultColor = opt_t & 0xFF;
2447 param1[0] = 0x00;
2448 WCMD_color();
2450 } else {
2451 /* Check HKCU\Software\Microsoft\Command Processor
2452 Then HKLM\Software\Microsoft\Command Processor
2453 for defaultcolour value
2454 Note Can be supplied as DWORD or REG_SZ
2455 Note2 When supplied as REG_SZ it's in decimal!!! */
2456 HKEY key;
2457 DWORD type;
2458 DWORD value=0, size=4;
2459 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2460 'M','i','c','r','o','s','o','f','t','\\',
2461 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2462 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2464 if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2465 0, KEY_READ, &key) == ERROR_SUCCESS) {
2466 WCHAR strvalue[4];
2468 /* See if DWORD or REG_SZ */
2469 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2470 NULL, NULL) == ERROR_SUCCESS) {
2471 if (type == REG_DWORD) {
2472 size = sizeof(DWORD);
2473 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2474 (LPBYTE)&value, &size);
2475 } else if (type == REG_SZ) {
2476 size = sizeof(strvalue)/sizeof(WCHAR);
2477 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2478 (LPBYTE)strvalue, &size);
2479 value = strtoulW(strvalue, NULL, 10);
2482 RegCloseKey(key);
2485 if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2486 0, KEY_READ, &key) == ERROR_SUCCESS) {
2487 WCHAR strvalue[4];
2489 /* See if DWORD or REG_SZ */
2490 if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2491 NULL, NULL) == ERROR_SUCCESS) {
2492 if (type == REG_DWORD) {
2493 size = sizeof(DWORD);
2494 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2495 (LPBYTE)&value, &size);
2496 } else if (type == REG_SZ) {
2497 size = sizeof(strvalue)/sizeof(WCHAR);
2498 RegQueryValueExW(key, dfltColorW, NULL, NULL,
2499 (LPBYTE)strvalue, &size);
2500 value = strtoulW(strvalue, NULL, 10);
2503 RegCloseKey(key);
2506 /* If one found, set the screen to that colour */
2507 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2508 defaultColor = value & 0xFF;
2509 param1[0] = 0x00;
2510 WCMD_color();
2515 /* Save cwd into appropriate env var */
2516 GetCurrentDirectoryW(1024, string);
2517 if (IsCharAlphaW(string[0]) && string[1] == ':') {
2518 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2519 wsprintfW(envvar, fmt, string[0]);
2520 SetEnvironmentVariableW(envvar, string);
2521 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2524 if (opt_k) {
2525 /* Parse the command string, without reading any more input */
2526 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2527 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2528 WCMD_free_commands(toExecute);
2529 toExecute = NULL;
2530 HeapFree(GetProcessHeap(), 0, cmd);
2534 * If there is an AUTOEXEC.BAT file, try to execute it.
2537 GetFullPathNameW (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
2538 h = CreateFileW(string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2539 if (h != INVALID_HANDLE_VALUE) {
2540 CloseHandle (h);
2541 #if 0
2542 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
2543 #endif
2547 * Loop forever getting commands and executing them.
2550 SetEnvironmentVariableW(promptW, defaultpromptW);
2551 WCMD_version ();
2552 while (TRUE) {
2554 /* Read until EOF (which for std input is never, but if redirect
2555 in place, may occur */
2556 WCMD_show_prompt ();
2557 if (WCMD_ReadAndParseLine(NULL, &toExecute,
2558 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
2559 break;
2560 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2561 WCMD_free_commands(toExecute);
2562 toExecute = NULL;
2564 return 0;