wined3d: Fix some typos.
[wine/multimedia.git] / programs / cmd / wcmdmain.c
blob454bd3ccc9dc4bb1a8209105b6525840a1d8f4e0
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 <time.h>
30 #include "wcmd.h"
31 #include "wine/debug.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
35 const WCHAR inbuilt[][10] = {
36 {'A','T','T','R','I','B','\0'},
37 {'C','A','L','L','\0'},
38 {'C','D','\0'},
39 {'C','H','D','I','R','\0'},
40 {'C','L','S','\0'},
41 {'C','O','P','Y','\0'},
42 {'C','T','T','Y','\0'},
43 {'D','A','T','E','\0'},
44 {'D','E','L','\0'},
45 {'D','I','R','\0'},
46 {'E','C','H','O','\0'},
47 {'E','R','A','S','E','\0'},
48 {'F','O','R','\0'},
49 {'G','O','T','O','\0'},
50 {'H','E','L','P','\0'},
51 {'I','F','\0'},
52 {'L','A','B','E','L','\0'},
53 {'M','D','\0'},
54 {'M','K','D','I','R','\0'},
55 {'M','O','V','E','\0'},
56 {'P','A','T','H','\0'},
57 {'P','A','U','S','E','\0'},
58 {'P','R','O','M','P','T','\0'},
59 {'R','E','M','\0'},
60 {'R','E','N','\0'},
61 {'R','E','N','A','M','E','\0'},
62 {'R','D','\0'},
63 {'R','M','D','I','R','\0'},
64 {'S','E','T','\0'},
65 {'S','H','I','F','T','\0'},
66 {'T','I','M','E','\0'},
67 {'T','I','T','L','E','\0'},
68 {'T','Y','P','E','\0'},
69 {'V','E','R','I','F','Y','\0'},
70 {'V','E','R','\0'},
71 {'V','O','L','\0'},
72 {'E','N','D','L','O','C','A','L','\0'},
73 {'S','E','T','L','O','C','A','L','\0'},
74 {'P','U','S','H','D','\0'},
75 {'P','O','P','D','\0'},
76 {'A','S','S','O','C','\0'},
77 {'C','O','L','O','R','\0'},
78 {'F','T','Y','P','E','\0'},
79 {'M','O','R','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[] = {'\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[MAX_PATH], param2[MAX_PATH];
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;
100 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forvar, WCHAR *forVal);
101 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device);
103 /*****************************************************************************
104 * Main entry point. This is a console application so we have a main() not a
105 * winmain().
108 int wmain (int argc, WCHAR *argvW[])
110 int args;
111 WCHAR *cmd = NULL;
112 WCHAR string[1024];
113 WCHAR envvar[4];
114 HANDLE h;
115 int opt_q;
116 int opt_t = 0;
117 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
118 'b','a','t','\0'};
119 char ansiVersion[100];
120 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
122 srand(time(NULL));
124 /* Pre initialize some messages */
125 strcpy(ansiVersion, PACKAGE_VERSION);
126 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
127 wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
128 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
130 args = argc;
131 opt_c=opt_k=opt_q=opt_s=0;
132 while (args > 0)
134 WCHAR c;
135 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
136 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
137 argvW++;
138 args--;
139 continue;
142 c=(*argvW)[1];
143 if (tolowerW(c)=='c') {
144 opt_c=1;
145 } else if (tolowerW(c)=='q') {
146 opt_q=1;
147 } else if (tolowerW(c)=='k') {
148 opt_k=1;
149 } else if (tolowerW(c)=='s') {
150 opt_s=1;
151 } else if (tolowerW(c)=='a') {
152 unicodePipes=FALSE;
153 } else if (tolowerW(c)=='u') {
154 unicodePipes=TRUE;
155 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
156 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
157 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
158 /* Ignored for compatibility with Windows */
161 if ((*argvW)[2]==0) {
162 argvW++;
163 args--;
165 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
167 *argvW+=2;
170 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
171 break;
174 if (opt_q) {
175 const WCHAR eoff[] = {'O','F','F','\0'};
176 WCMD_echo(eoff);
179 if (opt_c || opt_k) {
180 int len,qcount;
181 WCHAR** arg;
182 int argsLeft;
183 WCHAR* p;
185 /* opt_s left unflagged if the command starts with and contains exactly
186 * one quoted string (exactly two quote characters). The quoted string
187 * must be an executable name that has whitespace and must not have the
188 * following characters: &<>()@^| */
190 /* Build the command to execute */
191 len = 0;
192 qcount = 0;
193 argsLeft = args;
194 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
196 int has_space,bcount;
197 WCHAR* a;
199 has_space=0;
200 bcount=0;
201 a=*arg;
202 if( !*a ) has_space=1;
203 while (*a!='\0') {
204 if (*a=='\\') {
205 bcount++;
206 } else {
207 if (*a==' ' || *a=='\t') {
208 has_space=1;
209 } else if (*a=='"') {
210 /* doubling of '\' preceding a '"',
211 * plus escaping of said '"'
213 len+=2*bcount+1;
214 qcount++;
216 bcount=0;
218 a++;
220 len+=(a-*arg) + 1; /* for the separating space */
221 if (has_space)
223 len+=2; /* for the quotes */
224 qcount+=2;
228 if (qcount!=2)
229 opt_s=1;
231 /* check argvW[0] for a space and invalid characters */
232 if (!opt_s) {
233 opt_s=1;
234 p=*argvW;
235 while (*p!='\0') {
236 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
237 || *p=='@' || *p=='^' || *p=='|') {
238 opt_s=1;
239 break;
241 if (*p==' ')
242 opt_s=0;
243 p++;
247 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
248 if (!cmd)
249 exit(1);
251 p = cmd;
252 argsLeft = args;
253 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
255 int has_space,has_quote;
256 WCHAR* a;
258 /* Check for quotes and spaces in this argument */
259 has_space=has_quote=0;
260 a=*arg;
261 if( !*a ) has_space=1;
262 while (*a!='\0') {
263 if (*a==' ' || *a=='\t') {
264 has_space=1;
265 if (has_quote)
266 break;
267 } else if (*a=='"') {
268 has_quote=1;
269 if (has_space)
270 break;
272 a++;
275 /* Now transfer it to the command line */
276 if (has_space)
277 *p++='"';
278 if (has_quote) {
279 int bcount;
280 WCHAR* a;
282 bcount=0;
283 a=*arg;
284 while (*a!='\0') {
285 if (*a=='\\') {
286 *p++=*a;
287 bcount++;
288 } else {
289 if (*a=='"') {
290 int i;
292 /* Double all the '\\' preceding this '"', plus one */
293 for (i=0;i<=bcount;i++)
294 *p++='\\';
295 *p++='"';
296 } else {
297 *p++=*a;
299 bcount=0;
301 a++;
303 } else {
304 strcpyW(p,*arg);
305 p+=strlenW(*arg);
307 if (has_space)
308 *p++='"';
309 *p++=' ';
311 if (p > cmd)
312 p--; /* remove last space */
313 *p = '\0';
315 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
317 /* strip first and last quote characters if opt_s; check for invalid
318 * executable is done later */
319 if (opt_s && *cmd=='\"')
320 WCMD_opt_s_strip_quotes(cmd);
323 if (opt_c) {
324 /* If we do a "wcmd /c command", we don't want to allocate a new
325 * console since the command returns immediately. Rather, we use
326 * the currently allocated input and output handles. This allows
327 * us to pipe to and read from the command interpreter.
330 /* Parse the command string, without reading any more input */
331 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
332 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
333 WCMD_free_commands(toExecute);
334 toExecute = NULL;
336 HeapFree(GetProcessHeap(), 0, cmd);
337 return errorlevel;
340 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
341 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
342 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
344 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
345 if (opt_t) {
346 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
347 defaultColor = opt_t & 0xFF;
348 param1[0] = 0x00;
349 WCMD_color();
351 } else {
352 /* Check HKCU\Software\Microsoft\Command Processor
353 Then HKLM\Software\Microsoft\Command Processor
354 for defaultcolour value
355 Note Can be supplied as DWORD or REG_SZ
356 Note2 When supplied as REG_SZ it's in decimal!!! */
357 HKEY key;
358 DWORD type;
359 DWORD value=0, size=4;
360 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
361 'M','i','c','r','o','s','o','f','t','\\',
362 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
363 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
365 if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
366 0, KEY_READ, &key) == ERROR_SUCCESS) {
367 WCHAR strvalue[4];
369 /* See if DWORD or REG_SZ */
370 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
371 NULL, NULL) == ERROR_SUCCESS) {
372 if (type == REG_DWORD) {
373 size = sizeof(DWORD);
374 RegQueryValueEx(key, dfltColorW, NULL, NULL,
375 (LPBYTE)&value, &size);
376 } else if (type == REG_SZ) {
377 size = sizeof(strvalue)/sizeof(WCHAR);
378 RegQueryValueEx(key, dfltColorW, NULL, NULL,
379 (LPBYTE)strvalue, &size);
380 value = strtoulW(strvalue, NULL, 10);
383 RegCloseKey(key);
386 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
387 0, KEY_READ, &key) == ERROR_SUCCESS) {
388 WCHAR strvalue[4];
390 /* See if DWORD or REG_SZ */
391 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
392 NULL, NULL) == ERROR_SUCCESS) {
393 if (type == REG_DWORD) {
394 size = sizeof(DWORD);
395 RegQueryValueEx(key, dfltColorW, NULL, NULL,
396 (LPBYTE)&value, &size);
397 } else if (type == REG_SZ) {
398 size = sizeof(strvalue)/sizeof(WCHAR);
399 RegQueryValueEx(key, dfltColorW, NULL, NULL,
400 (LPBYTE)strvalue, &size);
401 value = strtoulW(strvalue, NULL, 10);
404 RegCloseKey(key);
407 /* If one found, set the screen to that colour */
408 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
409 defaultColor = value & 0xFF;
410 param1[0] = 0x00;
411 WCMD_color();
416 /* Save cwd into appropriate env var */
417 GetCurrentDirectory(1024, string);
418 if (IsCharAlpha(string[0]) && string[1] == ':') {
419 static const WCHAR fmt[] = {'=','%','c',':','\0'};
420 wsprintf(envvar, fmt, string[0]);
421 SetEnvironmentVariable(envvar, string);
422 WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
425 if (opt_k) {
426 /* Parse the command string, without reading any more input */
427 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
428 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
429 WCMD_free_commands(toExecute);
430 toExecute = NULL;
431 HeapFree(GetProcessHeap(), 0, cmd);
435 * If there is an AUTOEXEC.BAT file, try to execute it.
438 GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
439 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
440 if (h != INVALID_HANDLE_VALUE) {
441 CloseHandle (h);
442 #if 0
443 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
444 #endif
448 * Loop forever getting commands and executing them.
451 WCMD_version ();
452 while (TRUE) {
454 /* Read until EOF (which for std input is never, but if redirect
455 in place, may occur */
456 WCMD_show_prompt ();
457 if (WCMD_ReadAndParseLine(NULL, &toExecute,
458 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
459 break;
460 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
461 WCMD_free_commands(toExecute);
462 toExecute = NULL;
464 return 0;
467 /*****************************************************************************
468 * Expand the command. Native expands lines from batch programs as they are
469 * read in and not again, except for 'for' variable substitution.
470 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
472 void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
474 /* For commands in a context (batch program): */
475 /* Expand environment variables in a batch file %{0-9} first */
476 /* including support for any ~ modifiers */
477 /* Additionally: */
478 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
479 /* names allowing environment variable overrides */
480 /* NOTE: To support the %PATH:xxx% syntax, also perform */
481 /* manual expansion of environment variables here */
483 WCHAR *p = cmd;
484 WCHAR *s, *t;
485 int i;
487 while ((p = strchrW(p, '%'))) {
489 WINE_TRACE("Translate command:%s %d (at: %s)\n",
490 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
491 i = *(p+1) - '0';
493 /* Don't touch %% unless its in Batch */
494 if (!justFors && *(p+1) == '%') {
495 if (context) {
496 s = WCMD_strdupW(p+1);
497 strcpyW (p, s);
498 free (s);
500 p+=1;
502 /* Replace %~ modifications if in batch program */
503 } else if (*(p+1) == '~') {
504 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
505 p++;
507 /* Replace use of %0...%9 if in batch program*/
508 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
509 s = WCMD_strdupW(p+2);
510 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
511 strcpyW (p, t);
512 strcatW (p, s);
513 free (s);
515 /* Replace use of %* if in batch program*/
516 } else if (!justFors && context && *(p+1)=='*') {
517 WCHAR *startOfParms = NULL;
518 s = WCMD_strdupW(p+2);
519 t = WCMD_parameter (context -> command, 1, &startOfParms);
520 if (startOfParms != NULL) strcpyW (p, startOfParms);
521 else *p = 0x00;
522 strcatW (p, s);
523 free (s);
525 } else if (forVariable &&
526 (CompareString (LOCALE_USER_DEFAULT,
527 SORT_STRINGSORT,
529 strlenW(forVariable),
530 forVariable, -1) == 2)) {
531 s = WCMD_strdupW(p + strlenW(forVariable));
532 strcpyW(p, forValue);
533 strcatW(p, s);
534 free(s);
536 } else if (!justFors) {
537 p = WCMD_expand_envvar(p, forVariable, forValue);
539 /* In a FOR loop, see if this is the variable to replace */
540 } else { /* Ignore %'s on second pass of batch program */
541 p++;
545 return;
549 /*****************************************************************************
550 * Process one command. If the command is EXIT this routine does not return.
551 * We will recurse through here executing batch files.
555 void WCMD_execute (WCHAR *command, WCHAR *redirects,
556 WCHAR *forVariable, WCHAR *forValue,
557 CMD_LIST **cmdList)
559 WCHAR *cmd, *p, *redir;
560 int status, i;
561 DWORD count, creationDisposition;
562 HANDLE h;
563 WCHAR *whichcmd;
564 SECURITY_ATTRIBUTES sa;
565 WCHAR *new_cmd = NULL;
566 WCHAR *new_redir = NULL;
567 HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
568 INVALID_HANDLE_VALUE,
569 INVALID_HANDLE_VALUE};
570 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
571 STD_OUTPUT_HANDLE,
572 STD_ERROR_HANDLE};
573 BOOL piped = FALSE;
575 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
576 wine_dbgstr_w(command), cmdList,
577 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
579 /* If the next command is a pipe then we implement pipes by redirecting
580 the output from this command to a temp file and input into the
581 next command from that temp file.
582 FIXME: Use of named pipes would make more sense here as currently this
583 process has to finish before the next one can start but this requires
584 a change to not wait for the first app to finish but rather the pipe */
585 if (cmdList && (*cmdList)->nextcommand &&
586 (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
588 WCHAR temp_path[MAX_PATH];
589 static const WCHAR cmdW[] = {'C','M','D','\0'};
591 /* Remember piping is in action */
592 WINE_TRACE("Output needs to be piped\n");
593 piped = TRUE;
595 /* Generate a unique temporary filename */
596 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
597 GetTempFileName (temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
598 WINE_TRACE("Using temporary file of %s\n",
599 wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
602 /* Move copy of the command onto the heap so it can be expanded */
603 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
604 if (!new_cmd)
606 WINE_ERR("Could not allocate memory for new_cmd\n");
607 return;
609 strcpyW(new_cmd, command);
611 /* Move copy of the redirects onto the heap so it can be expanded */
612 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
613 if (!new_redir)
615 WINE_ERR("Could not allocate memory for new_redir\n");
616 HeapFree( GetProcessHeap(), 0, new_cmd );
617 return;
620 /* If piped output, send stdout to the pipe by appending >filename to redirects */
621 if (piped) {
622 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
623 wsprintf (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
624 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
625 } else {
626 strcpyW(new_redir, redirects);
629 /* Expand variables in command line mode only (batch mode will
630 be expanded as the line is read in, except for 'for' loops) */
631 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
632 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
633 cmd = new_cmd;
635 /* Show prompt before batch line IF echo is on and in batch program */
636 if (context && echo_mode && (cmd[0] != '@')) {
637 WCMD_show_prompt();
638 WCMD_output_asis ( cmd);
639 WCMD_output_asis ( newline);
643 * Changing default drive has to be handled as a special case.
646 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
647 WCHAR envvar[5];
648 WCHAR dir[MAX_PATH];
650 /* According to MSDN CreateProcess docs, special env vars record
651 the current directory on each drive, in the form =C:
652 so see if one specified, and if so go back to it */
653 strcpyW(envvar, equalsW);
654 strcatW(envvar, cmd);
655 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
656 static const WCHAR fmt[] = {'%','s','\\','\0'};
657 wsprintf(cmd, fmt, cmd);
658 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
660 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
661 status = SetCurrentDirectory (cmd);
662 if (!status) WCMD_print_error ();
663 HeapFree( GetProcessHeap(), 0, cmd );
664 HeapFree( GetProcessHeap(), 0, new_redir );
665 return;
668 sa.nLength = sizeof(sa);
669 sa.lpSecurityDescriptor = NULL;
670 sa.bInheritHandle = TRUE;
673 * Redirect stdin, stdout and/or stderr if required.
676 /* STDIN could come from a preceding pipe, so delete on close if it does */
677 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
678 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
679 h = CreateFile ((*cmdList)->pipeFile, GENERIC_READ,
680 FILE_SHARE_READ, &sa, OPEN_EXISTING,
681 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
682 if (h == INVALID_HANDLE_VALUE) {
683 WCMD_print_error ();
684 HeapFree( GetProcessHeap(), 0, cmd );
685 HeapFree( GetProcessHeap(), 0, new_redir );
686 return;
688 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
689 SetStdHandle (STD_INPUT_HANDLE, h);
691 /* No need to remember the temporary name any longer once opened */
692 (*cmdList)->pipeFile[0] = 0x00;
694 /* Otherwise STDIN could come from a '<' redirect */
695 } else if ((p = strchrW(new_redir,'<')) != NULL) {
696 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
697 FILE_ATTRIBUTE_NORMAL, NULL);
698 if (h == INVALID_HANDLE_VALUE) {
699 WCMD_print_error ();
700 HeapFree( GetProcessHeap(), 0, cmd );
701 HeapFree( GetProcessHeap(), 0, new_redir );
702 return;
704 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
705 SetStdHandle (STD_INPUT_HANDLE, h);
708 /* Scan the whole command looking for > and 2> */
709 redir = new_redir;
710 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
711 int handle = 0;
713 if (*(p-1)!='2') {
714 handle = 1;
715 } else {
716 handle = 2;
719 p++;
720 if ('>' == *p) {
721 creationDisposition = OPEN_ALWAYS;
722 p++;
724 else {
725 creationDisposition = CREATE_ALWAYS;
728 /* Add support for 2>&1 */
729 redir = p;
730 if (*p == '&') {
731 int idx = *(p+1) - '0';
733 if (DuplicateHandle(GetCurrentProcess(),
734 GetStdHandle(idx_stdhandles[idx]),
735 GetCurrentProcess(),
737 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
738 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
740 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
742 } else {
743 WCHAR *param = WCMD_parameter (p, 0, NULL);
744 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
745 FILE_ATTRIBUTE_NORMAL, NULL);
746 if (h == INVALID_HANDLE_VALUE) {
747 WCMD_print_error ();
748 HeapFree( GetProcessHeap(), 0, cmd );
749 HeapFree( GetProcessHeap(), 0, new_redir );
750 return;
752 if (SetFilePointer (h, 0, NULL, FILE_END) ==
753 INVALID_SET_FILE_POINTER) {
754 WCMD_print_error ();
756 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
759 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
760 SetStdHandle (idx_stdhandles[handle], h);
764 * Strip leading whitespaces, and a '@' if supplied
766 whichcmd = WCMD_strtrim_leading_spaces(cmd);
767 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
768 if (whichcmd[0] == '@') whichcmd++;
771 * Check if the command entered is internal. If it is, pass the rest of the
772 * line down to the command. If not try to run a program.
775 count = 0;
776 while (IsCharAlphaNumeric(whichcmd[count])) {
777 count++;
779 for (i=0; i<=WCMD_EXIT; i++) {
780 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
781 whichcmd, count, inbuilt[i], -1) == 2) break;
783 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
784 WCMD_parse (p, quals, param1, param2);
785 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
787 switch (i) {
789 case WCMD_ATTRIB:
790 WCMD_setshow_attrib ();
791 break;
792 case WCMD_CALL:
793 WCMD_call (p);
794 break;
795 case WCMD_CD:
796 case WCMD_CHDIR:
797 WCMD_setshow_default (p);
798 break;
799 case WCMD_CLS:
800 WCMD_clear_screen ();
801 break;
802 case WCMD_COPY:
803 WCMD_copy ();
804 break;
805 case WCMD_CTTY:
806 WCMD_change_tty ();
807 break;
808 case WCMD_DATE:
809 WCMD_setshow_date ();
810 break;
811 case WCMD_DEL:
812 case WCMD_ERASE:
813 WCMD_delete (p, TRUE);
814 break;
815 case WCMD_DIR:
816 WCMD_directory (p);
817 break;
818 case WCMD_ECHO:
819 WCMD_echo(&whichcmd[count]);
820 break;
821 case WCMD_FOR:
822 WCMD_for (p, cmdList);
823 break;
824 case WCMD_GOTO:
825 WCMD_goto (cmdList);
826 break;
827 case WCMD_HELP:
828 WCMD_give_help (p);
829 break;
830 case WCMD_IF:
831 WCMD_if (p, cmdList);
832 break;
833 case WCMD_LABEL:
834 WCMD_volume (1, p);
835 break;
836 case WCMD_MD:
837 case WCMD_MKDIR:
838 WCMD_create_dir ();
839 break;
840 case WCMD_MOVE:
841 WCMD_move ();
842 break;
843 case WCMD_PATH:
844 WCMD_setshow_path (p);
845 break;
846 case WCMD_PAUSE:
847 WCMD_pause ();
848 break;
849 case WCMD_PROMPT:
850 WCMD_setshow_prompt ();
851 break;
852 case WCMD_REM:
853 break;
854 case WCMD_REN:
855 case WCMD_RENAME:
856 WCMD_rename ();
857 break;
858 case WCMD_RD:
859 case WCMD_RMDIR:
860 WCMD_remove_dir (p);
861 break;
862 case WCMD_SETLOCAL:
863 WCMD_setlocal(p);
864 break;
865 case WCMD_ENDLOCAL:
866 WCMD_endlocal();
867 break;
868 case WCMD_SET:
869 WCMD_setshow_env (p);
870 break;
871 case WCMD_SHIFT:
872 WCMD_shift (p);
873 break;
874 case WCMD_TIME:
875 WCMD_setshow_time ();
876 break;
877 case WCMD_TITLE:
878 if (strlenW(&whichcmd[count]) > 0)
879 WCMD_title(&whichcmd[count+1]);
880 break;
881 case WCMD_TYPE:
882 WCMD_type (p);
883 break;
884 case WCMD_VER:
885 WCMD_version ();
886 break;
887 case WCMD_VERIFY:
888 WCMD_verify (p);
889 break;
890 case WCMD_VOL:
891 WCMD_volume (0, p);
892 break;
893 case WCMD_PUSHD:
894 WCMD_pushd(p);
895 break;
896 case WCMD_POPD:
897 WCMD_popd();
898 break;
899 case WCMD_ASSOC:
900 WCMD_assoc(p, TRUE);
901 break;
902 case WCMD_COLOR:
903 WCMD_color();
904 break;
905 case WCMD_FTYPE:
906 WCMD_assoc(p, FALSE);
907 break;
908 case WCMD_MORE:
909 WCMD_more(p);
910 break;
911 case WCMD_EXIT:
912 WCMD_exit (cmdList);
913 break;
914 default:
915 WCMD_run_program (whichcmd, 0);
917 HeapFree( GetProcessHeap(), 0, cmd );
918 HeapFree( GetProcessHeap(), 0, new_redir );
920 /* Restore old handles */
921 for (i=0; i<3; i++) {
922 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
923 CloseHandle (GetStdHandle (idx_stdhandles[i]));
924 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
929 static void init_msvcrt_io_block(STARTUPINFO* st)
931 STARTUPINFO st_p;
932 /* fetch the parent MSVCRT info block if any, so that the child can use the
933 * same handles as its grand-father
935 st_p.cb = sizeof(STARTUPINFO);
936 GetStartupInfo(&st_p);
937 st->cbReserved2 = st_p.cbReserved2;
938 st->lpReserved2 = st_p.lpReserved2;
939 if (st_p.cbReserved2 && st_p.lpReserved2)
941 /* Override the entries for fd 0,1,2 if we happened
942 * to change those std handles (this depends on the way wcmd sets
943 * it's new input & output handles)
945 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
946 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
947 if (ptr)
949 unsigned num = *(unsigned*)st_p.lpReserved2;
950 char* flags = (char*)(ptr + sizeof(unsigned));
951 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
953 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
954 st->cbReserved2 = sz;
955 st->lpReserved2 = ptr;
957 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
958 if (num <= 0 || (flags[0] & WX_OPEN))
960 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
961 flags[0] |= WX_OPEN;
963 if (num <= 1 || (flags[1] & WX_OPEN))
965 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
966 flags[1] |= WX_OPEN;
968 if (num <= 2 || (flags[2] & WX_OPEN))
970 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
971 flags[2] |= WX_OPEN;
973 #undef WX_OPEN
978 /******************************************************************************
979 * WCMD_run_program
981 * Execute a command line as an external program. Must allow recursion.
983 * Precedence:
984 * Manual testing under windows shows PATHEXT plays a key part in this,
985 * and the search algorithm and precedence appears to be as follows.
987 * Search locations:
988 * If directory supplied on command, just use that directory
989 * If extension supplied on command, look for that explicit name first
990 * Otherwise, search in each directory on the path
991 * Precedence:
992 * If extension supplied on command, look for that explicit name first
993 * Then look for supplied name .* (even if extension supplied, so
994 * 'garbage.exe' will match 'garbage.exe.cmd')
995 * If any found, cycle through PATHEXT looking for name.exe one by one
996 * Launching
997 * Once a match has been found, it is launched - Code currently uses
998 * findexecutable to achieve this which is left untouched.
1001 void WCMD_run_program (WCHAR *command, int called) {
1003 WCHAR temp[MAX_PATH];
1004 WCHAR pathtosearch[MAXSTRING];
1005 WCHAR *pathposn;
1006 WCHAR stemofsearch[MAX_PATH];
1007 WCHAR *lastSlash;
1008 WCHAR pathext[MAXSTRING];
1009 BOOL extensionsupplied = FALSE;
1010 BOOL launched = FALSE;
1011 BOOL status;
1012 BOOL assumeInternal = FALSE;
1013 DWORD len;
1014 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1015 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1016 static const WCHAR delims[] = {'/','\\',':','\0'};
1018 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
1019 if (!(*param1) && !(*param2))
1020 return;
1022 /* Calculate the search path and stem to search for */
1023 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
1024 static const WCHAR curDir[] = {'.',';','\0'};
1025 strcpyW(pathtosearch, curDir);
1026 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1027 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1028 static const WCHAR curDir[] = {'.','\0'};
1029 strcpyW (pathtosearch, curDir);
1031 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1032 strcpyW(stemofsearch, param1);
1034 } else {
1036 /* Convert eg. ..\fred to include a directory by removing file part */
1037 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1038 lastSlash = strrchrW(pathtosearch, '\\');
1039 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1040 strcpyW(stemofsearch, lastSlash+1);
1042 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1043 c:\windows\a.bat syntax */
1044 if (lastSlash) *(lastSlash + 1) = 0x00;
1047 /* Now extract PATHEXT */
1048 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1049 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1050 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1051 '.','c','o','m',';',
1052 '.','c','m','d',';',
1053 '.','e','x','e','\0'};
1054 strcpyW (pathext, dfltPathExt);
1057 /* Loop through the search path, dir by dir */
1058 pathposn = pathtosearch;
1059 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1060 wine_dbgstr_w(stemofsearch));
1061 while (!launched && pathposn) {
1063 WCHAR thisDir[MAX_PATH] = {'\0'};
1064 WCHAR *pos = NULL;
1065 BOOL found = FALSE;
1066 const WCHAR slashW[] = {'\\','\0'};
1068 /* Work on the first directory on the search path */
1069 pos = strchrW(pathposn, ';');
1070 if (pos) {
1071 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1072 thisDir[(pos-pathposn)] = 0x00;
1073 pathposn = pos+1;
1075 } else {
1076 strcpyW(thisDir, pathposn);
1077 pathposn = NULL;
1080 /* Since you can have eg. ..\.. on the path, need to expand
1081 to full information */
1082 strcpyW(temp, thisDir);
1083 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
1085 /* 1. If extension supplied, see if that file exists */
1086 strcatW(thisDir, slashW);
1087 strcatW(thisDir, stemofsearch);
1088 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1090 /* 1. If extension supplied, see if that file exists */
1091 if (extensionsupplied) {
1092 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1093 found = TRUE;
1097 /* 2. Any .* matches? */
1098 if (!found) {
1099 HANDLE h;
1100 WIN32_FIND_DATA finddata;
1101 static const WCHAR allFiles[] = {'.','*','\0'};
1103 strcatW(thisDir,allFiles);
1104 h = FindFirstFile(thisDir, &finddata);
1105 FindClose(h);
1106 if (h != INVALID_HANDLE_VALUE) {
1108 WCHAR *thisExt = pathext;
1110 /* 3. Yes - Try each path ext */
1111 while (thisExt) {
1112 WCHAR *nextExt = strchrW(thisExt, ';');
1114 if (nextExt) {
1115 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1116 pos[(nextExt-thisExt)] = 0x00;
1117 thisExt = nextExt+1;
1118 } else {
1119 strcpyW(pos, thisExt);
1120 thisExt = NULL;
1123 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1124 found = TRUE;
1125 thisExt = NULL;
1131 /* Internal programs won't be picked up by this search, so even
1132 though not found, try one last createprocess and wait for it
1133 to complete.
1134 Note: Ideally we could tell between a console app (wait) and a
1135 windows app, but the API's for it fail in this case */
1136 if (!found && pathposn == NULL) {
1137 WINE_TRACE("ASSUMING INTERNAL\n");
1138 assumeInternal = TRUE;
1139 } else {
1140 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1143 /* Once found, launch it */
1144 if (found || assumeInternal) {
1145 STARTUPINFO st;
1146 PROCESS_INFORMATION pe;
1147 SHFILEINFO psfi;
1148 DWORD console;
1149 HINSTANCE hinst;
1150 WCHAR *ext = strrchrW( thisDir, '.' );
1151 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1152 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1154 launched = TRUE;
1156 /* Special case BAT and CMD */
1157 if (ext && !strcmpiW(ext, batExt)) {
1158 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1159 return;
1160 } else if (ext && !strcmpiW(ext, cmdExt)) {
1161 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1162 return;
1163 } else {
1165 /* thisDir contains the file to be launched, but with what?
1166 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1167 hinst = FindExecutable (thisDir, NULL, temp);
1168 if ((INT_PTR)hinst < 32)
1169 console = 0;
1170 else
1171 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1173 ZeroMemory (&st, sizeof(STARTUPINFO));
1174 st.cb = sizeof(STARTUPINFO);
1175 init_msvcrt_io_block(&st);
1177 /* Launch the process and if a CUI wait on it to complete
1178 Note: Launching internal wine processes cannot specify a full path to exe */
1179 status = CreateProcess (assumeInternal?NULL : thisDir,
1180 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1181 if ((opt_c || opt_k) && !opt_s && !status
1182 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1183 /* strip first and last quote WCHARacters and try again */
1184 WCMD_opt_s_strip_quotes(command);
1185 opt_s=1;
1186 WCMD_run_program(command, called);
1187 return;
1189 if (!status) {
1190 WCMD_print_error ();
1191 /* If a command fails to launch, it sets errorlevel 9009 - which
1192 does not seem to have any associated constant definition */
1193 errorlevel = 9009;
1194 return;
1196 if (!assumeInternal && !console) errorlevel = 0;
1197 else
1199 /* Always wait when called in a batch program context */
1200 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1201 GetExitCodeProcess (pe.hProcess, &errorlevel);
1202 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1204 CloseHandle(pe.hProcess);
1205 CloseHandle(pe.hThread);
1206 return;
1211 /* Not found anywhere - give up */
1212 SetLastError(ERROR_FILE_NOT_FOUND);
1213 WCMD_print_error ();
1215 /* If a command fails to launch, it sets errorlevel 9009 - which
1216 does not seem to have any associated constant definition */
1217 errorlevel = 9009;
1218 return;
1222 /******************************************************************************
1223 * WCMD_show_prompt
1225 * Display the prompt on STDout
1229 void WCMD_show_prompt (void) {
1231 int status;
1232 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1233 WCHAR *p, *q;
1234 DWORD len;
1235 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1237 len = GetEnvironmentVariable (envPrompt, prompt_string,
1238 sizeof(prompt_string)/sizeof(WCHAR));
1239 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1240 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1241 strcpyW (prompt_string, dfltPrompt);
1243 p = prompt_string;
1244 q = out_string;
1245 *q = '\0';
1246 while (*p != '\0') {
1247 if (*p != '$') {
1248 *q++ = *p++;
1249 *q = '\0';
1251 else {
1252 p++;
1253 switch (toupper(*p)) {
1254 case '$':
1255 *q++ = '$';
1256 break;
1257 case 'A':
1258 *q++ = '&';
1259 break;
1260 case 'B':
1261 *q++ = '|';
1262 break;
1263 case 'C':
1264 *q++ = '(';
1265 break;
1266 case 'D':
1267 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1268 while (*q) q++;
1269 break;
1270 case 'E':
1271 *q++ = '\E';
1272 break;
1273 case 'F':
1274 *q++ = ')';
1275 break;
1276 case 'G':
1277 *q++ = '>';
1278 break;
1279 case 'H':
1280 *q++ = '\b';
1281 break;
1282 case 'L':
1283 *q++ = '<';
1284 break;
1285 case 'N':
1286 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1287 if (status) {
1288 *q++ = curdir[0];
1290 break;
1291 case 'P':
1292 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1293 if (status) {
1294 strcatW (q, curdir);
1295 while (*q) q++;
1297 break;
1298 case 'Q':
1299 *q++ = '=';
1300 break;
1301 case 'S':
1302 *q++ = ' ';
1303 break;
1304 case 'T':
1305 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1306 while (*q) q++;
1307 break;
1308 case 'V':
1309 strcatW (q, version_string);
1310 while (*q) q++;
1311 break;
1312 case '_':
1313 *q++ = '\n';
1314 break;
1315 case '+':
1316 if (pushd_directories) {
1317 memset(q, '+', pushd_directories->u.stackdepth);
1318 q = q + pushd_directories->u.stackdepth;
1320 break;
1322 p++;
1323 *q = '\0';
1326 WCMD_output_asis (out_string);
1329 /****************************************************************************
1330 * WCMD_print_error
1332 * Print the message for GetLastError
1335 void WCMD_print_error (void) {
1336 LPVOID lpMsgBuf;
1337 DWORD error_code;
1338 int status;
1340 error_code = GetLastError ();
1341 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1342 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1343 if (!status) {
1344 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1345 error_code, GetLastError());
1346 return;
1349 WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
1350 GetStdHandle(STD_ERROR_HANDLE));
1351 LocalFree ((HLOCAL)lpMsgBuf);
1352 WCMD_output_asis_len (newline, lstrlen(newline),
1353 GetStdHandle(STD_ERROR_HANDLE));
1354 return;
1357 /*******************************************************************
1358 * WCMD_parse - parse a command into parameters and qualifiers.
1360 * On exit, all qualifiers are concatenated into q, the first string
1361 * not beginning with "/" is in p1 and the
1362 * second in p2. Any subsequent non-qualifier strings are lost.
1363 * Parameters in quotes are handled.
1366 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1368 int p = 0;
1370 *q = *p1 = *p2 = '\0';
1371 while (TRUE) {
1372 switch (*s) {
1373 case '/':
1374 *q++ = *s++;
1375 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1376 *q++ = toupperW (*s++);
1378 *q = '\0';
1379 break;
1380 case ' ':
1381 case '\t':
1382 s++;
1383 break;
1384 case '"':
1385 s++;
1386 while ((*s != '\0') && (*s != '"')) {
1387 if (p == 0) *p1++ = *s++;
1388 else if (p == 1) *p2++ = *s++;
1389 else s++;
1391 if (p == 0) *p1 = '\0';
1392 if (p == 1) *p2 = '\0';
1393 p++;
1394 if (*s == '"') s++;
1395 break;
1396 case '\0':
1397 return;
1398 default:
1399 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
1400 && (*s != '=') && (*s != ',') ) {
1401 if (p == 0) *p1++ = *s++;
1402 else if (p == 1) *p2++ = *s++;
1403 else s++;
1405 /* Skip concurrent parms */
1406 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
1408 if (p == 0) *p1 = '\0';
1409 if (p == 1) *p2 = '\0';
1410 p++;
1415 /*******************************************************************
1416 * WCMD_output_asis_len - send output to current standard output
1418 * Output a formatted unicode string. Ideally this will go to the console
1419 * and hence required WriteConsoleW to output it, however if file i/o is
1420 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1422 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
1424 DWORD nOut= 0;
1425 DWORD res = 0;
1427 /* If nothing to write, return (MORE does this sometimes) */
1428 if (!len) return;
1430 /* Try to write as unicode assuming it is to a console */
1431 res = WriteConsoleW(device, message, len, &nOut, NULL);
1433 /* If writing to console fails, assume its file
1434 i/o so convert to OEM codepage and output */
1435 if (!res) {
1436 BOOL usedDefaultChar = FALSE;
1437 DWORD convertedChars;
1439 if (!unicodePipes) {
1441 * Allocate buffer to use when writing to file. (Not freed, as one off)
1443 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1444 MAX_WRITECONSOLE_SIZE);
1445 if (!output_bufA) {
1446 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1447 return;
1450 /* Convert to OEM, then output */
1451 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1452 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1453 "?", &usedDefaultChar);
1454 WriteFile(device, output_bufA, convertedChars,
1455 &nOut, FALSE);
1456 } else {
1457 WriteFile(device, message, len*sizeof(WCHAR),
1458 &nOut, FALSE);
1461 return;
1464 /*******************************************************************
1465 * WCMD_output - send output to current standard output device.
1469 void WCMD_output (const WCHAR *format, ...) {
1471 va_list ap;
1472 WCHAR string[1024];
1473 int ret;
1475 va_start(ap,format);
1476 ret = wvsprintf (string, format, ap);
1477 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1478 WINE_ERR("Output truncated in WCMD_output\n" );
1479 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1480 string[ret] = '\0';
1482 va_end(ap);
1483 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
1487 static int line_count;
1488 static int max_height;
1489 static int max_width;
1490 static BOOL paged_mode;
1491 static int numChars;
1493 void WCMD_enter_paged_mode(const WCHAR *msg)
1495 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1497 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1498 max_height = consoleInfo.dwSize.Y;
1499 max_width = consoleInfo.dwSize.X;
1500 } else {
1501 max_height = 25;
1502 max_width = 80;
1504 paged_mode = TRUE;
1505 line_count = 0;
1506 numChars = 0;
1507 pagedMessage = (msg==NULL)? anykey : msg;
1510 void WCMD_leave_paged_mode(void)
1512 paged_mode = FALSE;
1513 pagedMessage = NULL;
1516 /*******************************************************************
1517 * WCMD_output_asis - send output to current standard output device.
1518 * without formatting eg. when message contains '%'
1521 void WCMD_output_asis (const WCHAR *message) {
1522 DWORD count;
1523 const WCHAR* ptr;
1524 WCHAR string[1024];
1526 if (paged_mode) {
1527 do {
1528 ptr = message;
1529 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1530 numChars++;
1531 ptr++;
1533 if (*ptr == '\n') ptr++;
1534 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
1535 GetStdHandle(STD_OUTPUT_HANDLE));
1536 if (ptr) {
1537 numChars = 0;
1538 if (++line_count >= max_height - 1) {
1539 line_count = 0;
1540 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
1541 GetStdHandle(STD_OUTPUT_HANDLE));
1542 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1543 sizeof(string)/sizeof(WCHAR), &count, NULL);
1546 } while (((message = ptr) != NULL) && (*ptr));
1547 } else {
1548 WCMD_output_asis_len(message, lstrlen(message),
1549 GetStdHandle(STD_OUTPUT_HANDLE));
1554 /***************************************************************************
1555 * WCMD_strtrim_leading_spaces
1557 * Remove leading spaces from a string. Return a pointer to the first
1558 * non-space character. Does not modify the input string
1561 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1563 WCHAR *ptr;
1565 ptr = string;
1566 while (*ptr == ' ') ptr++;
1567 return ptr;
1570 /*************************************************************************
1571 * WCMD_strtrim_trailing_spaces
1573 * Remove trailing spaces from a string. This routine modifies the input
1574 * string by placing a null after the last non-space WCHARacter
1577 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1579 WCHAR *ptr;
1581 ptr = string + strlenW (string) - 1;
1582 while ((*ptr == ' ') && (ptr >= string)) {
1583 *ptr = '\0';
1584 ptr--;
1588 /*************************************************************************
1589 * WCMD_opt_s_strip_quotes
1591 * Remove first and last quote WCHARacters, preserving all other text
1594 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1595 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1596 while((*dest=*src) != '\0') {
1597 if (*src=='\"')
1598 lastq=dest;
1599 dest++, src++;
1601 if (lastq) {
1602 dest=lastq++;
1603 while ((*dest++=*lastq++) != 0)
1608 /*************************************************************************
1609 * WCMD_expand_envvar
1611 * Expands environment variables, allowing for WCHARacter substitution
1613 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
1614 WCHAR *endOfVar = NULL, *s;
1615 WCHAR *colonpos = NULL;
1616 WCHAR thisVar[MAXSTRING];
1617 WCHAR thisVarContents[MAXSTRING];
1618 WCHAR savedchar = 0x00;
1619 int len;
1621 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1622 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1623 static const WCHAR Date[] = {'D','A','T','E','\0'};
1624 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1625 static const WCHAR Time[] = {'T','I','M','E','\0'};
1626 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1627 static const WCHAR Cd[] = {'C','D','\0'};
1628 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1629 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1630 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1631 static const WCHAR Delims[] = {'%',' ',':','\0'};
1633 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
1634 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
1636 /* Find the end of the environment variable, and extract name */
1637 endOfVar = strpbrkW(start+1, Delims);
1639 if (endOfVar == NULL || *endOfVar==' ') {
1641 /* In batch program, missing terminator for % and no following
1642 ':' just removes the '%' */
1643 if (context) {
1644 s = WCMD_strdupW(start + 1);
1645 strcpyW (start, s);
1646 free(s);
1647 return start;
1648 } else {
1650 /* In command processing, just ignore it - allows command line
1651 syntax like: for %i in (a.a) do echo %i */
1652 return start+1;
1656 /* If ':' found, process remaining up until '%' (or stop at ':' if
1657 a missing '%' */
1658 if (*endOfVar==':') {
1659 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
1660 if (endOfVar2 != NULL) endOfVar = endOfVar2;
1663 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1664 thisVar[(endOfVar - start)+1] = 0x00;
1665 colonpos = strchrW(thisVar+1, ':');
1667 /* If there's complex substitution, just need %var% for now
1668 to get the expanded data to play with */
1669 if (colonpos) {
1670 *colonpos = '%';
1671 savedchar = *(colonpos+1);
1672 *(colonpos+1) = 0x00;
1675 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1677 /* Expand to contents, if unchanged, return */
1678 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1679 /* override if existing env var called that name */
1680 if ((CompareString (LOCALE_USER_DEFAULT,
1681 NORM_IGNORECASE | SORT_STRINGSORT,
1682 thisVar, 12, ErrorLvlP, -1) == 2) &&
1683 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1684 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1685 static const WCHAR fmt[] = {'%','d','\0'};
1686 wsprintf(thisVarContents, fmt, errorlevel);
1687 len = strlenW(thisVarContents);
1689 } else if ((CompareString (LOCALE_USER_DEFAULT,
1690 NORM_IGNORECASE | SORT_STRINGSORT,
1691 thisVar, 6, DateP, -1) == 2) &&
1692 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1693 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1695 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1696 NULL, thisVarContents, MAXSTRING);
1697 len = strlenW(thisVarContents);
1699 } else if ((CompareString (LOCALE_USER_DEFAULT,
1700 NORM_IGNORECASE | SORT_STRINGSORT,
1701 thisVar, 6, TimeP, -1) == 2) &&
1702 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1703 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1704 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1705 NULL, thisVarContents, MAXSTRING);
1706 len = strlenW(thisVarContents);
1708 } else if ((CompareString (LOCALE_USER_DEFAULT,
1709 NORM_IGNORECASE | SORT_STRINGSORT,
1710 thisVar, 4, CdP, -1) == 2) &&
1711 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1712 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1713 GetCurrentDirectory (MAXSTRING, thisVarContents);
1714 len = strlenW(thisVarContents);
1716 } else if ((CompareString (LOCALE_USER_DEFAULT,
1717 NORM_IGNORECASE | SORT_STRINGSORT,
1718 thisVar, 8, RandomP, -1) == 2) &&
1719 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1720 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1721 static const WCHAR fmt[] = {'%','d','\0'};
1722 wsprintf(thisVarContents, fmt, rand() % 32768);
1723 len = strlenW(thisVarContents);
1725 /* Look for a matching 'for' variable */
1726 } else if (forVar &&
1727 (CompareString (LOCALE_USER_DEFAULT,
1728 SORT_STRINGSORT,
1729 thisVar,
1730 (colonpos - thisVar) - 1,
1731 forVar, -1) == 2)) {
1732 strcpyW(thisVarContents, forVal);
1733 len = strlenW(thisVarContents);
1735 } else {
1737 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1738 sizeof(thisVarContents)/sizeof(WCHAR));
1741 if (len == 0)
1742 return endOfVar+1;
1744 /* In a batch program, unknown env vars are replaced with nothing,
1745 note syntax %garbage:1,3% results in anything after the ':'
1746 except the %
1747 From the command line, you just get back what you entered */
1748 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1750 /* Restore the complex part after the compare */
1751 if (colonpos) {
1752 *colonpos = ':';
1753 *(colonpos+1) = savedchar;
1756 /* Command line - just ignore this */
1757 if (context == NULL) return endOfVar+1;
1759 s = WCMD_strdupW(endOfVar + 1);
1761 /* Batch - replace unknown env var with nothing */
1762 if (colonpos == NULL) {
1763 strcpyW (start, s);
1765 } else {
1766 len = strlenW(thisVar);
1767 thisVar[len-1] = 0x00;
1768 /* If %:...% supplied, : is retained */
1769 if (colonpos == thisVar+1) {
1770 strcpyW (start, colonpos);
1771 } else {
1772 strcpyW (start, colonpos+1);
1774 strcatW (start, s);
1776 free (s);
1777 return start;
1781 /* See if we need to do complex substitution (any ':'s), if not
1782 then our work here is done */
1783 if (colonpos == NULL) {
1784 s = WCMD_strdupW(endOfVar + 1);
1785 strcpyW (start, thisVarContents);
1786 strcatW (start, s);
1787 free(s);
1788 return start;
1791 /* Restore complex bit */
1792 *colonpos = ':';
1793 *(colonpos+1) = savedchar;
1796 Handle complex substitutions:
1797 xxx=yyy (replace xxx with yyy)
1798 *xxx=yyy (replace up to and including xxx with yyy)
1799 ~x (from x WCHARs in)
1800 ~-x (from x WCHARs from the end)
1801 ~x,y (from x WCHARs in for y WCHARacters)
1802 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1805 /* ~ is substring manipulation */
1806 if (savedchar == '~') {
1808 int substrposition, substrlength = 0;
1809 WCHAR *commapos = strchrW(colonpos+2, ',');
1810 WCHAR *startCopy;
1812 substrposition = atolW(colonpos+2);
1813 if (commapos) substrlength = atolW(commapos+1);
1815 s = WCMD_strdupW(endOfVar + 1);
1817 /* Check bounds */
1818 if (substrposition >= 0) {
1819 startCopy = &thisVarContents[min(substrposition, len)];
1820 } else {
1821 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1824 if (commapos == NULL) {
1825 strcpyW (start, startCopy); /* Copy the lot */
1826 } else if (substrlength < 0) {
1828 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1829 if (copybytes > len) copybytes = len;
1830 else if (copybytes < 0) copybytes = 0;
1831 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1832 start[copybytes] = 0x00;
1833 } else {
1834 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1835 start[substrlength] = 0x00;
1838 strcatW (start, s);
1839 free(s);
1840 return start;
1842 /* search and replace manipulation */
1843 } else {
1844 WCHAR *equalspos = strstrW(colonpos, equalsW);
1845 WCHAR *replacewith = equalspos+1;
1846 WCHAR *found = NULL;
1847 WCHAR *searchIn;
1848 WCHAR *searchFor;
1850 s = WCMD_strdupW(endOfVar + 1);
1851 if (equalspos == NULL) return start+1;
1853 /* Null terminate both strings */
1854 thisVar[strlenW(thisVar)-1] = 0x00;
1855 *equalspos = 0x00;
1857 /* Since we need to be case insensitive, copy the 2 buffers */
1858 searchIn = WCMD_strdupW(thisVarContents);
1859 CharUpperBuff(searchIn, strlenW(thisVarContents));
1860 searchFor = WCMD_strdupW(colonpos+1);
1861 CharUpperBuff(searchFor, strlenW(colonpos+1));
1864 /* Handle wildcard case */
1865 if (*(colonpos+1) == '*') {
1866 /* Search for string to replace */
1867 found = strstrW(searchIn, searchFor+1);
1869 if (found) {
1870 /* Do replacement */
1871 strcpyW(start, replacewith);
1872 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1873 strcatW(start, s);
1874 free(s);
1875 } else {
1876 /* Copy as it */
1877 strcpyW(start, thisVarContents);
1878 strcatW(start, s);
1881 } else {
1882 /* Loop replacing all instances */
1883 WCHAR *lastFound = searchIn;
1884 WCHAR *outputposn = start;
1886 *start = 0x00;
1887 while ((found = strstrW(lastFound, searchFor))) {
1888 lstrcpynW(outputposn,
1889 thisVarContents + (lastFound-searchIn),
1890 (found - lastFound)+1);
1891 outputposn = outputposn + (found - lastFound);
1892 strcatW(outputposn, replacewith);
1893 outputposn = outputposn + strlenW(replacewith);
1894 lastFound = found + strlenW(searchFor);
1896 strcatW(outputposn,
1897 thisVarContents + (lastFound-searchIn));
1898 strcatW(outputposn, s);
1900 free(searchIn);
1901 free(searchFor);
1902 return start;
1904 return start+1;
1907 /*************************************************************************
1908 * WCMD_LoadMessage
1909 * Load a string from the resource file, handling any error
1910 * Returns string retrieved from resource file
1912 WCHAR *WCMD_LoadMessage(UINT id) {
1913 static WCHAR msg[2048];
1914 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1916 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1917 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1918 strcpyW(msg, failedMsg);
1920 return msg;
1923 /*************************************************************************
1924 * WCMD_strdupW
1925 * A wide version of strdup as its missing from unicode.h
1927 WCHAR *WCMD_strdupW(WCHAR *input) {
1928 int len=strlenW(input)+1;
1929 /* Note: Use malloc not HeapAlloc to emulate strdup */
1930 WCHAR *result = malloc(len * sizeof(WCHAR));
1931 memcpy(result, input, len * sizeof(WCHAR));
1932 return result;
1935 /***************************************************************************
1936 * WCMD_Readfile
1938 * Read characters in from a console/file, returning result in Unicode
1939 * with signature identical to ReadFile
1941 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1942 LPDWORD charsRead, const LPOVERLAPPED unused) {
1944 BOOL res;
1946 /* Try to read from console as Unicode */
1947 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1949 /* If reading from console has failed we assume its file
1950 i/o so read in and convert from OEM codepage */
1951 if (!res) {
1953 DWORD numRead;
1955 * Allocate buffer to use when reading from file. Not freed
1957 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1958 MAX_WRITECONSOLE_SIZE);
1959 if (!output_bufA) {
1960 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1961 return 0;
1964 /* Read from file (assume OEM codepage) */
1965 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1967 /* Convert from OEM */
1968 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1969 intoBuf, maxChars);
1972 return res;
1975 /***************************************************************************
1976 * WCMD_DumpCommands
1978 * Domps out the parsed command line to ensure syntax is correct
1980 void WCMD_DumpCommands(CMD_LIST *commands) {
1981 WCHAR buffer[MAXSTRING];
1982 CMD_LIST *thisCmd = commands;
1983 const WCHAR fmt[] = {'%','p',' ','%','d',' ','%','2','.','2','d',' ',
1984 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1985 '%','s','\0'};
1987 WINE_TRACE("Parsed line:\n");
1988 while (thisCmd != NULL) {
1989 sprintfW(buffer, fmt,
1990 thisCmd,
1991 thisCmd->prevDelim,
1992 thisCmd->bracketDepth,
1993 thisCmd->nextcommand,
1994 thisCmd->command,
1995 thisCmd->redirects);
1996 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1997 thisCmd = thisCmd->nextcommand;
2001 /***************************************************************************
2002 * WCMD_addCommand
2004 * Adds a command to the current command list
2006 void WCMD_addCommand(WCHAR *command, int *commandLen,
2007 WCHAR *redirs, int *redirLen,
2008 WCHAR **copyTo, int **copyToLen,
2009 CMD_DELIMITERS prevDelim, int curDepth,
2010 CMD_LIST **lastEntry, CMD_LIST **output) {
2012 CMD_LIST *thisEntry = NULL;
2014 /* Allocate storage for command */
2015 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2017 /* Copy in the command */
2018 if (command) {
2019 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2020 (*commandLen+1) * sizeof(WCHAR));
2021 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
2022 thisEntry->command[*commandLen] = 0x00;
2024 /* Copy in the redirects */
2025 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
2026 (*redirLen+1) * sizeof(WCHAR));
2027 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
2028 thisEntry->redirects[*redirLen] = 0x00;
2029 thisEntry->pipeFile[0] = 0x00;
2031 /* Reset the lengths */
2032 *commandLen = 0;
2033 *redirLen = 0;
2034 *copyToLen = commandLen;
2035 *copyTo = command;
2037 } else {
2038 thisEntry->command = NULL;
2041 /* Fill in other fields */
2042 thisEntry->nextcommand = NULL;
2043 thisEntry->prevDelim = prevDelim;
2044 thisEntry->bracketDepth = curDepth;
2045 if (*lastEntry) {
2046 (*lastEntry)->nextcommand = thisEntry;
2047 } else {
2048 *output = thisEntry;
2050 *lastEntry = thisEntry;
2053 /***************************************************************************
2054 * WCMD_ReadAndParseLine
2056 * Either uses supplied input or
2057 * Reads a file from the handle, and then...
2058 * Parse the text buffer, spliting into separate commands
2059 * - unquoted && strings split 2 commands but the 2nd is flagged as
2060 * following an &&
2061 * - ( as the first character just ups the bracket depth
2062 * - unquoted ) when bracket depth > 0 terminates a bracket and
2063 * adds a CMD_LIST structure with null command
2064 * - Anything else gets put into the command string (including
2065 * redirects)
2067 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
2069 WCHAR *curPos;
2070 BOOL inQuotes = FALSE;
2071 WCHAR curString[MAXSTRING];
2072 int curStringLen = 0;
2073 WCHAR curRedirs[MAXSTRING];
2074 int curRedirsLen = 0;
2075 WCHAR *curCopyTo;
2076 int *curLen;
2077 int curDepth = 0;
2078 CMD_LIST *lastEntry = NULL;
2079 CMD_DELIMITERS prevDelim = CMD_NONE;
2080 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
2081 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
2082 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
2083 const WCHAR ifCmd[] = {'i','f',' ','\0'};
2084 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
2085 BOOL inRem = FALSE;
2086 BOOL inFor = FALSE;
2087 BOOL inIn = FALSE;
2088 BOOL inIf = FALSE;
2089 BOOL inElse= FALSE;
2090 BOOL onlyWhiteSpace = FALSE;
2091 BOOL lastWasWhiteSpace = FALSE;
2092 BOOL lastWasDo = FALSE;
2093 BOOL lastWasIn = FALSE;
2094 BOOL lastWasElse = FALSE;
2095 BOOL lastWasRedirect = TRUE;
2097 /* Allocate working space for a command read from keyboard, file etc */
2098 if (!extraSpace)
2099 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
2100 if (!extraSpace)
2102 WINE_ERR("Could not allocate memory for extraSpace\n");
2103 return NULL;
2106 /* If initial command read in, use that, otherwise get input from handle */
2107 if (optionalcmd != NULL) {
2108 strcpyW(extraSpace, optionalcmd);
2109 } else if (readFrom == INVALID_HANDLE_VALUE) {
2110 WINE_FIXME("No command nor handle supplied\n");
2111 } else {
2112 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
2114 curPos = extraSpace;
2116 /* Handle truncated input - issue warning */
2117 if (strlenW(extraSpace) == MAXSTRING -1) {
2118 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
2119 WCMD_output_asis(extraSpace);
2120 WCMD_output_asis(newline);
2123 /* Replace env vars if in a batch context */
2124 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2126 /* Start with an empty string, copying to the command string */
2127 curStringLen = 0;
2128 curRedirsLen = 0;
2129 curCopyTo = curString;
2130 curLen = &curStringLen;
2131 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
2133 /* Parse every character on the line being processed */
2134 while (*curPos != 0x00) {
2136 WCHAR thisChar;
2138 /* Debugging AID:
2139 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2140 lastWasWhiteSpace, onlyWhiteSpace);
2143 /* Certain commands need special handling */
2144 if (curStringLen == 0 && curCopyTo == curString) {
2145 const WCHAR forDO[] = {'d','o',' ','\0'};
2147 /* If command starts with 'rem', ignore any &&, ( etc */
2148 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2149 curPos, 4, remCmd, -1) == 2) {
2150 inRem = TRUE;
2152 /* If command starts with 'for', handle ('s mid line after IN or DO */
2153 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2154 curPos, 4, forCmd, -1) == 2) {
2155 inFor = TRUE;
2157 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2158 is only true in the command portion of the IF statement, but this
2159 should suffice for now
2160 FIXME: Silly syntax like "if 1(==1( (
2161 echo they equal
2162 )" will be parsed wrong */
2163 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2164 curPos, 3, ifCmd, -1) == 2) {
2165 inIf = TRUE;
2167 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2168 curPos, 5, ifElse, -1) == 2) {
2169 inElse = TRUE;
2170 lastWasElse = TRUE;
2171 onlyWhiteSpace = TRUE;
2172 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
2173 (*curLen)+=5;
2174 curPos+=5;
2175 continue;
2177 /* In a for loop, the DO command will follow a close bracket followed by
2178 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2179 is then 0, and all whitespace is skipped */
2180 } else if (inFor &&
2181 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2182 curPos, 3, forDO, -1) == 2)) {
2183 WINE_TRACE("Found DO\n");
2184 lastWasDo = TRUE;
2185 onlyWhiteSpace = TRUE;
2186 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2187 (*curLen)+=3;
2188 curPos+=3;
2189 continue;
2191 } else if (curCopyTo == curString) {
2193 /* Special handling for the 'FOR' command */
2194 if (inFor && lastWasWhiteSpace) {
2195 const WCHAR forIN[] = {'i','n',' ','\0'};
2197 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2199 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2200 curPos, 3, forIN, -1) == 2) {
2201 WINE_TRACE("Found IN\n");
2202 lastWasIn = TRUE;
2203 onlyWhiteSpace = TRUE;
2204 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2205 (*curLen)+=3;
2206 curPos+=3;
2207 continue;
2212 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2213 so just use the default processing ie skip character specific
2214 matching below */
2215 if (!inRem) thisChar = *curPos;
2216 else thisChar = 'X'; /* Character with no special processing */
2218 lastWasWhiteSpace = FALSE; /* Will be reset below */
2220 switch (thisChar) {
2222 case '=': /* drop through - ignore token delimiters at the start of a command */
2223 case ',': /* drop through - ignore token delimiters at the start of a command */
2224 case '\t':/* drop through - ignore token delimiters at the start of a command */
2225 case ' ':
2226 /* If a redirect in place, it ends here */
2227 if (!inQuotes && !lastWasRedirect) {
2229 /* If finishing off a redirect, add a whitespace delimiter */
2230 if (curCopyTo == curRedirs) {
2231 curCopyTo[(*curLen)++] = ' ';
2233 curCopyTo = curString;
2234 curLen = &curStringLen;
2236 if (*curLen > 0) {
2237 curCopyTo[(*curLen)++] = *curPos;
2240 /* Remember just processed whitespace */
2241 lastWasWhiteSpace = TRUE;
2243 break;
2245 case '>': /* drop through - handle redirect chars the same */
2246 case '<':
2247 /* Make a redirect start here */
2248 if (!inQuotes) {
2249 curCopyTo = curRedirs;
2250 curLen = &curRedirsLen;
2251 lastWasRedirect = TRUE;
2254 /* See if 1>, 2> etc, in which case we have some patching up
2255 to do */
2256 if (curPos != extraSpace &&
2257 *(curPos-1)>='1' && *(curPos-1)<='9') {
2259 curStringLen--;
2260 curString[curStringLen] = 0x00;
2261 curCopyTo[(*curLen)++] = *(curPos-1);
2264 curCopyTo[(*curLen)++] = *curPos;
2265 break;
2267 case '|': /* Pipe character only if not || */
2268 if (!inQuotes) {
2269 lastWasRedirect = FALSE;
2271 /* Add an entry to the command list */
2272 if (curStringLen > 0) {
2274 /* Add the current command */
2275 WCMD_addCommand(curString, &curStringLen,
2276 curRedirs, &curRedirsLen,
2277 &curCopyTo, &curLen,
2278 prevDelim, curDepth,
2279 &lastEntry, output);
2283 if (*(curPos+1) == '|') {
2284 curPos++; /* Skip other | */
2285 prevDelim = CMD_ONFAILURE;
2286 } else {
2287 prevDelim = CMD_PIPE;
2289 } else {
2290 curCopyTo[(*curLen)++] = *curPos;
2292 break;
2294 case '"': inQuotes = !inQuotes;
2295 curCopyTo[(*curLen)++] = *curPos;
2296 lastWasRedirect = FALSE;
2297 break;
2299 case '(': /* If a '(' is the first non whitespace in a command portion
2300 ie start of line or just after &&, then we read until an
2301 unquoted ) is found */
2302 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2303 ", for(%d, In:%d, Do:%d)"
2304 ", if(%d, else:%d, lwe:%d)\n",
2305 *curLen, inQuotes,
2306 onlyWhiteSpace,
2307 inFor, lastWasIn, lastWasDo,
2308 inIf, inElse, lastWasElse);
2309 lastWasRedirect = FALSE;
2311 /* Ignore open brackets inside the for set */
2312 if (*curLen == 0 && !inIn) {
2313 curDepth++;
2315 /* If in quotes, ignore brackets */
2316 } else if (inQuotes) {
2317 curCopyTo[(*curLen)++] = *curPos;
2319 /* In a FOR loop, an unquoted '(' may occur straight after
2320 IN or DO
2321 In an IF statement just handle it regardless as we don't
2322 parse the operands
2323 In an ELSE statement, only allow it straight away after
2324 the ELSE and whitespace
2326 } else if (inIf ||
2327 (inElse && lastWasElse && onlyWhiteSpace) ||
2328 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2330 /* If entering into an 'IN', set inIn */
2331 if (inFor && lastWasIn && onlyWhiteSpace) {
2332 WINE_TRACE("Inside an IN\n");
2333 inIn = TRUE;
2336 /* Add the current command */
2337 WCMD_addCommand(curString, &curStringLen,
2338 curRedirs, &curRedirsLen,
2339 &curCopyTo, &curLen,
2340 prevDelim, curDepth,
2341 &lastEntry, output);
2343 curDepth++;
2344 } else {
2345 curCopyTo[(*curLen)++] = *curPos;
2347 break;
2349 case '&': if (!inQuotes) {
2350 lastWasRedirect = FALSE;
2352 /* Add an entry to the command list */
2353 if (curStringLen > 0) {
2355 /* Add the current command */
2356 WCMD_addCommand(curString, &curStringLen,
2357 curRedirs, &curRedirsLen,
2358 &curCopyTo, &curLen,
2359 prevDelim, curDepth,
2360 &lastEntry, output);
2364 if (*(curPos+1) == '&') {
2365 curPos++; /* Skip other & */
2366 prevDelim = CMD_ONSUCCESS;
2367 } else {
2368 prevDelim = CMD_NONE;
2370 } else {
2371 curCopyTo[(*curLen)++] = *curPos;
2373 break;
2375 case ')': if (!inQuotes && curDepth > 0) {
2376 lastWasRedirect = FALSE;
2378 /* Add the current command if there is one */
2379 if (curStringLen) {
2381 /* Add the current command */
2382 WCMD_addCommand(curString, &curStringLen,
2383 curRedirs, &curRedirsLen,
2384 &curCopyTo, &curLen,
2385 prevDelim, curDepth,
2386 &lastEntry, output);
2389 /* Add an empty entry to the command list */
2390 prevDelim = CMD_NONE;
2391 WCMD_addCommand(NULL, &curStringLen,
2392 curRedirs, &curRedirsLen,
2393 &curCopyTo, &curLen,
2394 prevDelim, curDepth,
2395 &lastEntry, output);
2396 curDepth--;
2398 /* Leave inIn if necessary */
2399 if (inIn) inIn = FALSE;
2400 } else {
2401 curCopyTo[(*curLen)++] = *curPos;
2403 break;
2404 default:
2405 lastWasRedirect = FALSE;
2406 curCopyTo[(*curLen)++] = *curPos;
2409 curPos++;
2411 /* At various times we need to know if we have only skipped whitespace,
2412 so reset this variable and then it will remain true until a non
2413 whitespace is found */
2414 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2416 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2417 if (!lastWasWhiteSpace) {
2418 lastWasIn = lastWasDo = FALSE;
2421 /* If we have reached the end, add this command into the list */
2422 if (*curPos == 0x00 && *curLen > 0) {
2424 /* Add an entry to the command list */
2425 WCMD_addCommand(curString, &curStringLen,
2426 curRedirs, &curRedirsLen,
2427 &curCopyTo, &curLen,
2428 prevDelim, curDepth,
2429 &lastEntry, output);
2432 /* If we have reached the end of the string, see if bracketing outstanding */
2433 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2434 inRem = FALSE;
2435 prevDelim = CMD_NONE;
2436 inQuotes = FALSE;
2437 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2439 /* Read more, skipping any blank lines */
2440 while (*extraSpace == 0x00) {
2441 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2442 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2444 curPos = extraSpace;
2445 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2449 /* Dump out the parsed output */
2450 WCMD_DumpCommands(*output);
2452 return extraSpace;
2455 /***************************************************************************
2456 * WCMD_process_commands
2458 * Process all the commands read in so far
2460 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2461 WCHAR *var, WCHAR *val) {
2463 int bdepth = -1;
2465 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2467 /* Loop through the commands, processing them one by one */
2468 while (thisCmd) {
2470 CMD_LIST *origCmd = thisCmd;
2472 /* If processing one bracket only, and we find the end bracket
2473 entry (or less), return */
2474 if (oneBracket && !thisCmd->command &&
2475 bdepth <= thisCmd->bracketDepth) {
2476 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2477 thisCmd, thisCmd->nextcommand);
2478 return thisCmd->nextcommand;
2481 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2482 about them and it will be handled in there)
2483 Also, skip over any batch labels (eg. :fred) */
2484 if (thisCmd->command && thisCmd->command[0] != ':') {
2485 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2486 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2489 /* Step on unless the command itself already stepped on */
2490 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2492 return NULL;
2495 /***************************************************************************
2496 * WCMD_free_commands
2498 * Frees the storage held for a parsed command line
2499 * - This is not done in the process_commands, as eventually the current
2500 * pointer will be modified within the commands, and hence a single free
2501 * routine is simpler
2503 void WCMD_free_commands(CMD_LIST *cmds) {
2505 /* Loop through the commands, freeing them one by one */
2506 while (cmds) {
2507 CMD_LIST *thisCmd = cmds;
2508 cmds = cmds->nextcommand;
2509 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2510 HeapFree(GetProcessHeap(), 0, thisCmd);