Updated Norwegian Bokmål Readme file.
[wine/hacks.git] / programs / cmd / wcmdmain.c
blob8d09967f661a11cb2e4fd6cd99da15ba3421fa1d
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 strcpyW(new_cmd, command);
606 /* Move copy of the redirects onto the heap so it can be expanded */
607 new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
609 /* If piped output, send stdout to the pipe by appending >filename to redirects */
610 if (piped) {
611 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
612 wsprintf (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
613 WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
614 } else {
615 strcpyW(new_redir, redirects);
618 /* Expand variables in command line mode only (batch mode will
619 be expanded as the line is read in, except for 'for' loops) */
620 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
621 handleExpansion(new_redir, (context != NULL), forVariable, forValue);
622 cmd = new_cmd;
624 /* Show prompt before batch line IF echo is on and in batch program */
625 if (context && echo_mode && (cmd[0] != '@')) {
626 WCMD_show_prompt();
627 WCMD_output_asis ( cmd);
628 WCMD_output_asis ( newline);
632 * Changing default drive has to be handled as a special case.
635 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
636 WCHAR envvar[5];
637 WCHAR dir[MAX_PATH];
639 /* According to MSDN CreateProcess docs, special env vars record
640 the current directory on each drive, in the form =C:
641 so see if one specified, and if so go back to it */
642 strcpyW(envvar, equalsW);
643 strcatW(envvar, cmd);
644 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
645 static const WCHAR fmt[] = {'%','s','\\','\0'};
646 wsprintf(cmd, fmt, cmd);
647 WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
649 WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
650 status = SetCurrentDirectory (cmd);
651 if (!status) WCMD_print_error ();
652 HeapFree( GetProcessHeap(), 0, cmd );
653 HeapFree( GetProcessHeap(), 0, new_redir );
654 return;
657 sa.nLength = sizeof(sa);
658 sa.lpSecurityDescriptor = NULL;
659 sa.bInheritHandle = TRUE;
662 * Redirect stdin, stdout and/or stderr if required.
665 /* STDIN could come from a preceding pipe, so delete on close if it does */
666 if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
667 WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
668 h = CreateFile ((*cmdList)->pipeFile, GENERIC_READ,
669 FILE_SHARE_READ, &sa, OPEN_EXISTING,
670 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
671 if (h == INVALID_HANDLE_VALUE) {
672 WCMD_print_error ();
673 HeapFree( GetProcessHeap(), 0, cmd );
674 HeapFree( GetProcessHeap(), 0, new_redir );
675 return;
677 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
678 SetStdHandle (STD_INPUT_HANDLE, h);
680 /* No need to remember the temporary name any longer once opened */
681 (*cmdList)->pipeFile[0] = 0x00;
683 /* Otherwise STDIN could come from a '<' redirect */
684 } else if ((p = strchrW(new_redir,'<')) != NULL) {
685 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
686 FILE_ATTRIBUTE_NORMAL, NULL);
687 if (h == INVALID_HANDLE_VALUE) {
688 WCMD_print_error ();
689 HeapFree( GetProcessHeap(), 0, cmd );
690 HeapFree( GetProcessHeap(), 0, new_redir );
691 return;
693 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
694 SetStdHandle (STD_INPUT_HANDLE, h);
697 /* Scan the whole command looking for > and 2> */
698 redir = new_redir;
699 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
700 int handle = 0;
702 if (*(p-1)!='2') {
703 handle = 1;
704 } else {
705 handle = 2;
708 p++;
709 if ('>' == *p) {
710 creationDisposition = OPEN_ALWAYS;
711 p++;
713 else {
714 creationDisposition = CREATE_ALWAYS;
717 /* Add support for 2>&1 */
718 redir = p;
719 if (*p == '&') {
720 int idx = *(p+1) - '0';
722 if (DuplicateHandle(GetCurrentProcess(),
723 GetStdHandle(idx_stdhandles[idx]),
724 GetCurrentProcess(),
726 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
727 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
729 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
731 } else {
732 WCHAR *param = WCMD_parameter (p, 0, NULL);
733 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
734 FILE_ATTRIBUTE_NORMAL, NULL);
735 if (h == INVALID_HANDLE_VALUE) {
736 WCMD_print_error ();
737 HeapFree( GetProcessHeap(), 0, cmd );
738 HeapFree( GetProcessHeap(), 0, new_redir );
739 return;
741 if (SetFilePointer (h, 0, NULL, FILE_END) ==
742 INVALID_SET_FILE_POINTER) {
743 WCMD_print_error ();
745 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
748 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
749 SetStdHandle (idx_stdhandles[handle], h);
753 * Strip leading whitespaces, and a '@' if supplied
755 whichcmd = WCMD_strtrim_leading_spaces(cmd);
756 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
757 if (whichcmd[0] == '@') whichcmd++;
760 * Check if the command entered is internal. If it is, pass the rest of the
761 * line down to the command. If not try to run a program.
764 count = 0;
765 while (IsCharAlphaNumeric(whichcmd[count])) {
766 count++;
768 for (i=0; i<=WCMD_EXIT; i++) {
769 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
770 whichcmd, count, inbuilt[i], -1) == 2) break;
772 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
773 WCMD_parse (p, quals, param1, param2);
774 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
776 switch (i) {
778 case WCMD_ATTRIB:
779 WCMD_setshow_attrib ();
780 break;
781 case WCMD_CALL:
782 WCMD_call (p);
783 break;
784 case WCMD_CD:
785 case WCMD_CHDIR:
786 WCMD_setshow_default (p);
787 break;
788 case WCMD_CLS:
789 WCMD_clear_screen ();
790 break;
791 case WCMD_COPY:
792 WCMD_copy ();
793 break;
794 case WCMD_CTTY:
795 WCMD_change_tty ();
796 break;
797 case WCMD_DATE:
798 WCMD_setshow_date ();
799 break;
800 case WCMD_DEL:
801 case WCMD_ERASE:
802 WCMD_delete (p, TRUE);
803 break;
804 case WCMD_DIR:
805 WCMD_directory (p);
806 break;
807 case WCMD_ECHO:
808 WCMD_echo(&whichcmd[count]);
809 break;
810 case WCMD_FOR:
811 WCMD_for (p, cmdList);
812 break;
813 case WCMD_GOTO:
814 WCMD_goto (cmdList);
815 break;
816 case WCMD_HELP:
817 WCMD_give_help (p);
818 break;
819 case WCMD_IF:
820 WCMD_if (p, cmdList);
821 break;
822 case WCMD_LABEL:
823 WCMD_volume (1, p);
824 break;
825 case WCMD_MD:
826 case WCMD_MKDIR:
827 WCMD_create_dir ();
828 break;
829 case WCMD_MOVE:
830 WCMD_move ();
831 break;
832 case WCMD_PATH:
833 WCMD_setshow_path (p);
834 break;
835 case WCMD_PAUSE:
836 WCMD_pause ();
837 break;
838 case WCMD_PROMPT:
839 WCMD_setshow_prompt ();
840 break;
841 case WCMD_REM:
842 break;
843 case WCMD_REN:
844 case WCMD_RENAME:
845 WCMD_rename ();
846 break;
847 case WCMD_RD:
848 case WCMD_RMDIR:
849 WCMD_remove_dir (p);
850 break;
851 case WCMD_SETLOCAL:
852 WCMD_setlocal(p);
853 break;
854 case WCMD_ENDLOCAL:
855 WCMD_endlocal();
856 break;
857 case WCMD_SET:
858 WCMD_setshow_env (p);
859 break;
860 case WCMD_SHIFT:
861 WCMD_shift (p);
862 break;
863 case WCMD_TIME:
864 WCMD_setshow_time ();
865 break;
866 case WCMD_TITLE:
867 if (strlenW(&whichcmd[count]) > 0)
868 WCMD_title(&whichcmd[count+1]);
869 break;
870 case WCMD_TYPE:
871 WCMD_type (p);
872 break;
873 case WCMD_VER:
874 WCMD_version ();
875 break;
876 case WCMD_VERIFY:
877 WCMD_verify (p);
878 break;
879 case WCMD_VOL:
880 WCMD_volume (0, p);
881 break;
882 case WCMD_PUSHD:
883 WCMD_pushd(p);
884 break;
885 case WCMD_POPD:
886 WCMD_popd();
887 break;
888 case WCMD_ASSOC:
889 WCMD_assoc(p, TRUE);
890 break;
891 case WCMD_COLOR:
892 WCMD_color();
893 break;
894 case WCMD_FTYPE:
895 WCMD_assoc(p, FALSE);
896 break;
897 case WCMD_MORE:
898 WCMD_more(p);
899 break;
900 case WCMD_EXIT:
901 WCMD_exit (cmdList);
902 break;
903 default:
904 WCMD_run_program (whichcmd, 0);
906 HeapFree( GetProcessHeap(), 0, cmd );
907 HeapFree( GetProcessHeap(), 0, new_redir );
909 /* Restore old handles */
910 for (i=0; i<3; i++) {
911 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
912 CloseHandle (GetStdHandle (idx_stdhandles[i]));
913 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
918 static void init_msvcrt_io_block(STARTUPINFO* st)
920 STARTUPINFO st_p;
921 /* fetch the parent MSVCRT info block if any, so that the child can use the
922 * same handles as its grand-father
924 st_p.cb = sizeof(STARTUPINFO);
925 GetStartupInfo(&st_p);
926 st->cbReserved2 = st_p.cbReserved2;
927 st->lpReserved2 = st_p.lpReserved2;
928 if (st_p.cbReserved2 && st_p.lpReserved2)
930 /* Override the entries for fd 0,1,2 if we happened
931 * to change those std handles (this depends on the way wcmd sets
932 * it's new input & output handles)
934 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
935 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
936 if (ptr)
938 unsigned num = *(unsigned*)st_p.lpReserved2;
939 char* flags = (char*)(ptr + sizeof(unsigned));
940 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
942 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
943 st->cbReserved2 = sz;
944 st->lpReserved2 = ptr;
946 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
947 if (num <= 0 || (flags[0] & WX_OPEN))
949 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
950 flags[0] |= WX_OPEN;
952 if (num <= 1 || (flags[1] & WX_OPEN))
954 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
955 flags[1] |= WX_OPEN;
957 if (num <= 2 || (flags[2] & WX_OPEN))
959 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
960 flags[2] |= WX_OPEN;
962 #undef WX_OPEN
967 /******************************************************************************
968 * WCMD_run_program
970 * Execute a command line as an external program. Must allow recursion.
972 * Precedence:
973 * Manual testing under windows shows PATHEXT plays a key part in this,
974 * and the search algorithm and precedence appears to be as follows.
976 * Search locations:
977 * If directory supplied on command, just use that directory
978 * If extension supplied on command, look for that explicit name first
979 * Otherwise, search in each directory on the path
980 * Precedence:
981 * If extension supplied on command, look for that explicit name first
982 * Then look for supplied name .* (even if extension supplied, so
983 * 'garbage.exe' will match 'garbage.exe.cmd')
984 * If any found, cycle through PATHEXT looking for name.exe one by one
985 * Launching
986 * Once a match has been found, it is launched - Code currently uses
987 * findexecutable to achieve this which is left untouched.
990 void WCMD_run_program (WCHAR *command, int called) {
992 WCHAR temp[MAX_PATH];
993 WCHAR pathtosearch[MAXSTRING];
994 WCHAR *pathposn;
995 WCHAR stemofsearch[MAX_PATH];
996 WCHAR *lastSlash;
997 WCHAR pathext[MAXSTRING];
998 BOOL extensionsupplied = FALSE;
999 BOOL launched = FALSE;
1000 BOOL status;
1001 BOOL assumeInternal = FALSE;
1002 DWORD len;
1003 static const WCHAR envPath[] = {'P','A','T','H','\0'};
1004 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1005 static const WCHAR delims[] = {'/','\\',':','\0'};
1007 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
1008 if (!(*param1) && !(*param2))
1009 return;
1011 /* Calculate the search path and stem to search for */
1012 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
1013 static const WCHAR curDir[] = {'.',';','\0'};
1014 strcpyW(pathtosearch, curDir);
1015 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1016 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1017 static const WCHAR curDir[] = {'.','\0'};
1018 strcpyW (pathtosearch, curDir);
1020 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1021 strcpyW(stemofsearch, param1);
1023 } else {
1025 /* Convert eg. ..\fred to include a directory by removing file part */
1026 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1027 lastSlash = strrchrW(pathtosearch, '\\');
1028 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1029 strcpyW(stemofsearch, lastSlash+1);
1031 /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1032 c:\windows\a.bat syntax */
1033 if (lastSlash) *(lastSlash + 1) = 0x00;
1036 /* Now extract PATHEXT */
1037 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1038 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1039 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1040 '.','c','o','m',';',
1041 '.','c','m','d',';',
1042 '.','e','x','e','\0'};
1043 strcpyW (pathext, dfltPathExt);
1046 /* Loop through the search path, dir by dir */
1047 pathposn = pathtosearch;
1048 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1049 wine_dbgstr_w(stemofsearch));
1050 while (!launched && pathposn) {
1052 WCHAR thisDir[MAX_PATH] = {'\0'};
1053 WCHAR *pos = NULL;
1054 BOOL found = FALSE;
1055 const WCHAR slashW[] = {'\\','\0'};
1057 /* Work on the first directory on the search path */
1058 pos = strchrW(pathposn, ';');
1059 if (pos) {
1060 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1061 thisDir[(pos-pathposn)] = 0x00;
1062 pathposn = pos+1;
1064 } else {
1065 strcpyW(thisDir, pathposn);
1066 pathposn = NULL;
1069 /* Since you can have eg. ..\.. on the path, need to expand
1070 to full information */
1071 strcpyW(temp, thisDir);
1072 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
1074 /* 1. If extension supplied, see if that file exists */
1075 strcatW(thisDir, slashW);
1076 strcatW(thisDir, stemofsearch);
1077 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1079 /* 1. If extension supplied, see if that file exists */
1080 if (extensionsupplied) {
1081 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1082 found = TRUE;
1086 /* 2. Any .* matches? */
1087 if (!found) {
1088 HANDLE h;
1089 WIN32_FIND_DATA finddata;
1090 static const WCHAR allFiles[] = {'.','*','\0'};
1092 strcatW(thisDir,allFiles);
1093 h = FindFirstFile(thisDir, &finddata);
1094 FindClose(h);
1095 if (h != INVALID_HANDLE_VALUE) {
1097 WCHAR *thisExt = pathext;
1099 /* 3. Yes - Try each path ext */
1100 while (thisExt) {
1101 WCHAR *nextExt = strchrW(thisExt, ';');
1103 if (nextExt) {
1104 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1105 pos[(nextExt-thisExt)] = 0x00;
1106 thisExt = nextExt+1;
1107 } else {
1108 strcpyW(pos, thisExt);
1109 thisExt = NULL;
1112 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1113 found = TRUE;
1114 thisExt = NULL;
1120 /* Internal programs won't be picked up by this search, so even
1121 though not found, try one last createprocess and wait for it
1122 to complete.
1123 Note: Ideally we could tell between a console app (wait) and a
1124 windows app, but the API's for it fail in this case */
1125 if (!found && pathposn == NULL) {
1126 WINE_TRACE("ASSUMING INTERNAL\n");
1127 assumeInternal = TRUE;
1128 } else {
1129 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1132 /* Once found, launch it */
1133 if (found || assumeInternal) {
1134 STARTUPINFO st;
1135 PROCESS_INFORMATION pe;
1136 SHFILEINFO psfi;
1137 DWORD console;
1138 HINSTANCE hinst;
1139 WCHAR *ext = strrchrW( thisDir, '.' );
1140 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1141 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1143 launched = TRUE;
1145 /* Special case BAT and CMD */
1146 if (ext && !strcmpiW(ext, batExt)) {
1147 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1148 return;
1149 } else if (ext && !strcmpiW(ext, cmdExt)) {
1150 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1151 return;
1152 } else {
1154 /* thisDir contains the file to be launched, but with what?
1155 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1156 hinst = FindExecutable (thisDir, NULL, temp);
1157 if ((INT_PTR)hinst < 32)
1158 console = 0;
1159 else
1160 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1162 ZeroMemory (&st, sizeof(STARTUPINFO));
1163 st.cb = sizeof(STARTUPINFO);
1164 init_msvcrt_io_block(&st);
1166 /* Launch the process and if a CUI wait on it to complete
1167 Note: Launching internal wine processes cannot specify a full path to exe */
1168 status = CreateProcess (assumeInternal?NULL : thisDir,
1169 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1170 if ((opt_c || opt_k) && !opt_s && !status
1171 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1172 /* strip first and last quote WCHARacters and try again */
1173 WCMD_opt_s_strip_quotes(command);
1174 opt_s=1;
1175 WCMD_run_program(command, called);
1176 return;
1178 if (!status) {
1179 WCMD_print_error ();
1180 /* If a command fails to launch, it sets errorlevel 9009 - which
1181 does not seem to have any associated constant definition */
1182 errorlevel = 9009;
1183 return;
1185 if (!assumeInternal && !console) errorlevel = 0;
1186 else
1188 /* Always wait when called in a batch program context */
1189 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1190 GetExitCodeProcess (pe.hProcess, &errorlevel);
1191 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1193 CloseHandle(pe.hProcess);
1194 CloseHandle(pe.hThread);
1195 return;
1200 /* Not found anywhere - give up */
1201 SetLastError(ERROR_FILE_NOT_FOUND);
1202 WCMD_print_error ();
1204 /* If a command fails to launch, it sets errorlevel 9009 - which
1205 does not seem to have any associated constant definition */
1206 errorlevel = 9009;
1207 return;
1211 /******************************************************************************
1212 * WCMD_show_prompt
1214 * Display the prompt on STDout
1218 void WCMD_show_prompt (void) {
1220 int status;
1221 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1222 WCHAR *p, *q;
1223 DWORD len;
1224 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1226 len = GetEnvironmentVariable (envPrompt, prompt_string,
1227 sizeof(prompt_string)/sizeof(WCHAR));
1228 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1229 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1230 strcpyW (prompt_string, dfltPrompt);
1232 p = prompt_string;
1233 q = out_string;
1234 *q = '\0';
1235 while (*p != '\0') {
1236 if (*p != '$') {
1237 *q++ = *p++;
1238 *q = '\0';
1240 else {
1241 p++;
1242 switch (toupper(*p)) {
1243 case '$':
1244 *q++ = '$';
1245 break;
1246 case 'A':
1247 *q++ = '&';
1248 break;
1249 case 'B':
1250 *q++ = '|';
1251 break;
1252 case 'C':
1253 *q++ = '(';
1254 break;
1255 case 'D':
1256 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1257 while (*q) q++;
1258 break;
1259 case 'E':
1260 *q++ = '\E';
1261 break;
1262 case 'F':
1263 *q++ = ')';
1264 break;
1265 case 'G':
1266 *q++ = '>';
1267 break;
1268 case 'H':
1269 *q++ = '\b';
1270 break;
1271 case 'L':
1272 *q++ = '<';
1273 break;
1274 case 'N':
1275 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1276 if (status) {
1277 *q++ = curdir[0];
1279 break;
1280 case 'P':
1281 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1282 if (status) {
1283 strcatW (q, curdir);
1284 while (*q) q++;
1286 break;
1287 case 'Q':
1288 *q++ = '=';
1289 break;
1290 case 'S':
1291 *q++ = ' ';
1292 break;
1293 case 'T':
1294 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1295 while (*q) q++;
1296 break;
1297 case 'V':
1298 strcatW (q, version_string);
1299 while (*q) q++;
1300 break;
1301 case '_':
1302 *q++ = '\n';
1303 break;
1304 case '+':
1305 if (pushd_directories) {
1306 memset(q, '+', pushd_directories->u.stackdepth);
1307 q = q + pushd_directories->u.stackdepth;
1309 break;
1311 p++;
1312 *q = '\0';
1315 WCMD_output_asis (out_string);
1318 /****************************************************************************
1319 * WCMD_print_error
1321 * Print the message for GetLastError
1324 void WCMD_print_error (void) {
1325 LPVOID lpMsgBuf;
1326 DWORD error_code;
1327 int status;
1329 error_code = GetLastError ();
1330 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1331 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1332 if (!status) {
1333 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1334 error_code, GetLastError());
1335 return;
1338 WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
1339 GetStdHandle(STD_ERROR_HANDLE));
1340 LocalFree ((HLOCAL)lpMsgBuf);
1341 WCMD_output_asis_len (newline, lstrlen(newline),
1342 GetStdHandle(STD_ERROR_HANDLE));
1343 return;
1346 /*******************************************************************
1347 * WCMD_parse - parse a command into parameters and qualifiers.
1349 * On exit, all qualifiers are concatenated into q, the first string
1350 * not beginning with "/" is in p1 and the
1351 * second in p2. Any subsequent non-qualifier strings are lost.
1352 * Parameters in quotes are handled.
1355 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1357 int p = 0;
1359 *q = *p1 = *p2 = '\0';
1360 while (TRUE) {
1361 switch (*s) {
1362 case '/':
1363 *q++ = *s++;
1364 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1365 *q++ = toupperW (*s++);
1367 *q = '\0';
1368 break;
1369 case ' ':
1370 case '\t':
1371 s++;
1372 break;
1373 case '"':
1374 s++;
1375 while ((*s != '\0') && (*s != '"')) {
1376 if (p == 0) *p1++ = *s++;
1377 else if (p == 1) *p2++ = *s++;
1378 else s++;
1380 if (p == 0) *p1 = '\0';
1381 if (p == 1) *p2 = '\0';
1382 p++;
1383 if (*s == '"') s++;
1384 break;
1385 case '\0':
1386 return;
1387 default:
1388 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
1389 && (*s != '=') && (*s != ',') ) {
1390 if (p == 0) *p1++ = *s++;
1391 else if (p == 1) *p2++ = *s++;
1392 else s++;
1394 /* Skip concurrent parms */
1395 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
1397 if (p == 0) *p1 = '\0';
1398 if (p == 1) *p2 = '\0';
1399 p++;
1404 /*******************************************************************
1405 * WCMD_output_asis_len - send output to current standard output
1407 * Output a formatted unicode string. Ideally this will go to the console
1408 * and hence required WriteConsoleW to output it, however if file i/o is
1409 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1411 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
1413 DWORD nOut= 0;
1414 DWORD res = 0;
1416 /* If nothing to write, return (MORE does this sometimes) */
1417 if (!len) return;
1419 /* Try to write as unicode assuming it is to a console */
1420 res = WriteConsoleW(device, message, len, &nOut, NULL);
1422 /* If writing to console fails, assume its file
1423 i/o so convert to OEM codepage and output */
1424 if (!res) {
1425 BOOL usedDefaultChar = FALSE;
1426 DWORD convertedChars;
1428 if (!unicodePipes) {
1430 * Allocate buffer to use when writing to file. (Not freed, as one off)
1432 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1433 MAX_WRITECONSOLE_SIZE);
1434 if (!output_bufA) {
1435 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1436 return;
1439 /* Convert to OEM, then output */
1440 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1441 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1442 "?", &usedDefaultChar);
1443 WriteFile(device, output_bufA, convertedChars,
1444 &nOut, FALSE);
1445 } else {
1446 WriteFile(device, message, len*sizeof(WCHAR),
1447 &nOut, FALSE);
1450 return;
1453 /*******************************************************************
1454 * WCMD_output - send output to current standard output device.
1458 void WCMD_output (const WCHAR *format, ...) {
1460 va_list ap;
1461 WCHAR string[1024];
1462 int ret;
1464 va_start(ap,format);
1465 ret = wvsprintf (string, format, ap);
1466 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1467 WINE_ERR("Output truncated in WCMD_output\n" );
1468 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1469 string[ret] = '\0';
1471 va_end(ap);
1472 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
1476 static int line_count;
1477 static int max_height;
1478 static int max_width;
1479 static BOOL paged_mode;
1480 static int numChars;
1482 void WCMD_enter_paged_mode(const WCHAR *msg)
1484 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1486 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1487 max_height = consoleInfo.dwSize.Y;
1488 max_width = consoleInfo.dwSize.X;
1489 } else {
1490 max_height = 25;
1491 max_width = 80;
1493 paged_mode = TRUE;
1494 line_count = 0;
1495 numChars = 0;
1496 pagedMessage = (msg==NULL)? anykey : msg;
1499 void WCMD_leave_paged_mode(void)
1501 paged_mode = FALSE;
1502 pagedMessage = NULL;
1505 /*******************************************************************
1506 * WCMD_output_asis - send output to current standard output device.
1507 * without formatting eg. when message contains '%'
1510 void WCMD_output_asis (const WCHAR *message) {
1511 DWORD count;
1512 const WCHAR* ptr;
1513 WCHAR string[1024];
1515 if (paged_mode) {
1516 do {
1517 ptr = message;
1518 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1519 numChars++;
1520 ptr++;
1522 if (*ptr == '\n') ptr++;
1523 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
1524 GetStdHandle(STD_OUTPUT_HANDLE));
1525 if (ptr) {
1526 numChars = 0;
1527 if (++line_count >= max_height - 1) {
1528 line_count = 0;
1529 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
1530 GetStdHandle(STD_OUTPUT_HANDLE));
1531 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1532 sizeof(string)/sizeof(WCHAR), &count, NULL);
1535 } while (((message = ptr) != NULL) && (*ptr));
1536 } else {
1537 WCMD_output_asis_len(message, lstrlen(message),
1538 GetStdHandle(STD_OUTPUT_HANDLE));
1543 /***************************************************************************
1544 * WCMD_strtrim_leading_spaces
1546 * Remove leading spaces from a string. Return a pointer to the first
1547 * non-space character. Does not modify the input string
1550 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1552 WCHAR *ptr;
1554 ptr = string;
1555 while (*ptr == ' ') ptr++;
1556 return ptr;
1559 /*************************************************************************
1560 * WCMD_strtrim_trailing_spaces
1562 * Remove trailing spaces from a string. This routine modifies the input
1563 * string by placing a null after the last non-space WCHARacter
1566 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1568 WCHAR *ptr;
1570 ptr = string + strlenW (string) - 1;
1571 while ((*ptr == ' ') && (ptr >= string)) {
1572 *ptr = '\0';
1573 ptr--;
1577 /*************************************************************************
1578 * WCMD_opt_s_strip_quotes
1580 * Remove first and last quote WCHARacters, preserving all other text
1583 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1584 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1585 while((*dest=*src) != '\0') {
1586 if (*src=='\"')
1587 lastq=dest;
1588 dest++, src++;
1590 if (lastq) {
1591 dest=lastq++;
1592 while ((*dest++=*lastq++) != 0)
1597 /*************************************************************************
1598 * WCMD_expand_envvar
1600 * Expands environment variables, allowing for WCHARacter substitution
1602 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
1603 WCHAR *endOfVar = NULL, *s;
1604 WCHAR *colonpos = NULL;
1605 WCHAR thisVar[MAXSTRING];
1606 WCHAR thisVarContents[MAXSTRING];
1607 WCHAR savedchar = 0x00;
1608 int len;
1610 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1611 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1612 static const WCHAR Date[] = {'D','A','T','E','\0'};
1613 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1614 static const WCHAR Time[] = {'T','I','M','E','\0'};
1615 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1616 static const WCHAR Cd[] = {'C','D','\0'};
1617 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1618 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1619 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1620 static const WCHAR Delims[] = {'%',' ',':','\0'};
1622 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
1623 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
1625 /* Find the end of the environment variable, and extract name */
1626 endOfVar = strpbrkW(start+1, Delims);
1628 if (endOfVar == NULL || *endOfVar==' ') {
1630 /* In batch program, missing terminator for % and no following
1631 ':' just removes the '%' */
1632 if (context) {
1633 s = WCMD_strdupW(start + 1);
1634 strcpyW (start, s);
1635 free(s);
1636 return start;
1637 } else {
1639 /* In command processing, just ignore it - allows command line
1640 syntax like: for %i in (a.a) do echo %i */
1641 return start+1;
1645 /* If ':' found, process remaining up until '%' (or stop at ':' if
1646 a missing '%' */
1647 if (*endOfVar==':') {
1648 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
1649 if (endOfVar2 != NULL) endOfVar = endOfVar2;
1652 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1653 thisVar[(endOfVar - start)+1] = 0x00;
1654 colonpos = strchrW(thisVar+1, ':');
1656 /* If there's complex substitution, just need %var% for now
1657 to get the expanded data to play with */
1658 if (colonpos) {
1659 *colonpos = '%';
1660 savedchar = *(colonpos+1);
1661 *(colonpos+1) = 0x00;
1664 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1666 /* Expand to contents, if unchanged, return */
1667 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1668 /* override if existing env var called that name */
1669 if ((CompareString (LOCALE_USER_DEFAULT,
1670 NORM_IGNORECASE | SORT_STRINGSORT,
1671 thisVar, 12, ErrorLvlP, -1) == 2) &&
1672 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1673 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1674 static const WCHAR fmt[] = {'%','d','\0'};
1675 wsprintf(thisVarContents, fmt, errorlevel);
1676 len = strlenW(thisVarContents);
1678 } else if ((CompareString (LOCALE_USER_DEFAULT,
1679 NORM_IGNORECASE | SORT_STRINGSORT,
1680 thisVar, 6, DateP, -1) == 2) &&
1681 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1682 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1684 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1685 NULL, thisVarContents, MAXSTRING);
1686 len = strlenW(thisVarContents);
1688 } else if ((CompareString (LOCALE_USER_DEFAULT,
1689 NORM_IGNORECASE | SORT_STRINGSORT,
1690 thisVar, 6, TimeP, -1) == 2) &&
1691 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1692 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1693 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1694 NULL, thisVarContents, MAXSTRING);
1695 len = strlenW(thisVarContents);
1697 } else if ((CompareString (LOCALE_USER_DEFAULT,
1698 NORM_IGNORECASE | SORT_STRINGSORT,
1699 thisVar, 4, CdP, -1) == 2) &&
1700 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1701 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1702 GetCurrentDirectory (MAXSTRING, thisVarContents);
1703 len = strlenW(thisVarContents);
1705 } else if ((CompareString (LOCALE_USER_DEFAULT,
1706 NORM_IGNORECASE | SORT_STRINGSORT,
1707 thisVar, 8, RandomP, -1) == 2) &&
1708 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1709 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1710 static const WCHAR fmt[] = {'%','d','\0'};
1711 wsprintf(thisVarContents, fmt, rand() % 32768);
1712 len = strlenW(thisVarContents);
1714 /* Look for a matching 'for' variable */
1715 } else if (forVar &&
1716 (CompareString (LOCALE_USER_DEFAULT,
1717 SORT_STRINGSORT,
1718 thisVar,
1719 (colonpos - thisVar) - 1,
1720 forVar, -1) == 2)) {
1721 strcpyW(thisVarContents, forVal);
1722 len = strlenW(thisVarContents);
1724 } else {
1726 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1727 sizeof(thisVarContents)/sizeof(WCHAR));
1730 if (len == 0)
1731 return endOfVar+1;
1733 /* In a batch program, unknown env vars are replaced with nothing,
1734 note syntax %garbage:1,3% results in anything after the ':'
1735 except the %
1736 From the command line, you just get back what you entered */
1737 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1739 /* Restore the complex part after the compare */
1740 if (colonpos) {
1741 *colonpos = ':';
1742 *(colonpos+1) = savedchar;
1745 /* Command line - just ignore this */
1746 if (context == NULL) return endOfVar+1;
1748 s = WCMD_strdupW(endOfVar + 1);
1750 /* Batch - replace unknown env var with nothing */
1751 if (colonpos == NULL) {
1752 strcpyW (start, s);
1754 } else {
1755 len = strlenW(thisVar);
1756 thisVar[len-1] = 0x00;
1757 /* If %:...% supplied, : is retained */
1758 if (colonpos == thisVar+1) {
1759 strcpyW (start, colonpos);
1760 } else {
1761 strcpyW (start, colonpos+1);
1763 strcatW (start, s);
1765 free (s);
1766 return start;
1770 /* See if we need to do complex substitution (any ':'s), if not
1771 then our work here is done */
1772 if (colonpos == NULL) {
1773 s = WCMD_strdupW(endOfVar + 1);
1774 strcpyW (start, thisVarContents);
1775 strcatW (start, s);
1776 free(s);
1777 return start;
1780 /* Restore complex bit */
1781 *colonpos = ':';
1782 *(colonpos+1) = savedchar;
1785 Handle complex substitutions:
1786 xxx=yyy (replace xxx with yyy)
1787 *xxx=yyy (replace up to and including xxx with yyy)
1788 ~x (from x WCHARs in)
1789 ~-x (from x WCHARs from the end)
1790 ~x,y (from x WCHARs in for y WCHARacters)
1791 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1794 /* ~ is substring manipulation */
1795 if (savedchar == '~') {
1797 int substrposition, substrlength = 0;
1798 WCHAR *commapos = strchrW(colonpos+2, ',');
1799 WCHAR *startCopy;
1801 substrposition = atolW(colonpos+2);
1802 if (commapos) substrlength = atolW(commapos+1);
1804 s = WCMD_strdupW(endOfVar + 1);
1806 /* Check bounds */
1807 if (substrposition >= 0) {
1808 startCopy = &thisVarContents[min(substrposition, len)];
1809 } else {
1810 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1813 if (commapos == NULL) {
1814 strcpyW (start, startCopy); /* Copy the lot */
1815 } else if (substrlength < 0) {
1817 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1818 if (copybytes > len) copybytes = len;
1819 else if (copybytes < 0) copybytes = 0;
1820 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1821 start[copybytes] = 0x00;
1822 } else {
1823 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1824 start[substrlength] = 0x00;
1827 strcatW (start, s);
1828 free(s);
1829 return start;
1831 /* search and replace manipulation */
1832 } else {
1833 WCHAR *equalspos = strstrW(colonpos, equalsW);
1834 WCHAR *replacewith = equalspos+1;
1835 WCHAR *found = NULL;
1836 WCHAR *searchIn;
1837 WCHAR *searchFor;
1839 s = WCMD_strdupW(endOfVar + 1);
1840 if (equalspos == NULL) return start+1;
1842 /* Null terminate both strings */
1843 thisVar[strlenW(thisVar)-1] = 0x00;
1844 *equalspos = 0x00;
1846 /* Since we need to be case insensitive, copy the 2 buffers */
1847 searchIn = WCMD_strdupW(thisVarContents);
1848 CharUpperBuff(searchIn, strlenW(thisVarContents));
1849 searchFor = WCMD_strdupW(colonpos+1);
1850 CharUpperBuff(searchFor, strlenW(colonpos+1));
1853 /* Handle wildcard case */
1854 if (*(colonpos+1) == '*') {
1855 /* Search for string to replace */
1856 found = strstrW(searchIn, searchFor+1);
1858 if (found) {
1859 /* Do replacement */
1860 strcpyW(start, replacewith);
1861 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1862 strcatW(start, s);
1863 free(s);
1864 } else {
1865 /* Copy as it */
1866 strcpyW(start, thisVarContents);
1867 strcatW(start, s);
1870 } else {
1871 /* Loop replacing all instances */
1872 WCHAR *lastFound = searchIn;
1873 WCHAR *outputposn = start;
1875 *start = 0x00;
1876 while ((found = strstrW(lastFound, searchFor))) {
1877 lstrcpynW(outputposn,
1878 thisVarContents + (lastFound-searchIn),
1879 (found - lastFound)+1);
1880 outputposn = outputposn + (found - lastFound);
1881 strcatW(outputposn, replacewith);
1882 outputposn = outputposn + strlenW(replacewith);
1883 lastFound = found + strlenW(searchFor);
1885 strcatW(outputposn,
1886 thisVarContents + (lastFound-searchIn));
1887 strcatW(outputposn, s);
1889 free(searchIn);
1890 free(searchFor);
1891 return start;
1893 return start+1;
1896 /*************************************************************************
1897 * WCMD_LoadMessage
1898 * Load a string from the resource file, handling any error
1899 * Returns string retrieved from resource file
1901 WCHAR *WCMD_LoadMessage(UINT id) {
1902 static WCHAR msg[2048];
1903 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1905 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1906 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1907 strcpyW(msg, failedMsg);
1909 return msg;
1912 /*************************************************************************
1913 * WCMD_strdupW
1914 * A wide version of strdup as its missing from unicode.h
1916 WCHAR *WCMD_strdupW(WCHAR *input) {
1917 int len=strlenW(input)+1;
1918 /* Note: Use malloc not HeapAlloc to emulate strdup */
1919 WCHAR *result = malloc(len * sizeof(WCHAR));
1920 memcpy(result, input, len * sizeof(WCHAR));
1921 return result;
1924 /***************************************************************************
1925 * WCMD_Readfile
1927 * Read characters in from a console/file, returning result in Unicode
1928 * with signature identical to ReadFile
1930 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1931 LPDWORD charsRead, const LPOVERLAPPED unused) {
1933 BOOL res;
1935 /* Try to read from console as Unicode */
1936 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1938 /* If reading from console has failed we assume its file
1939 i/o so read in and convert from OEM codepage */
1940 if (!res) {
1942 DWORD numRead;
1944 * Allocate buffer to use when reading from file. Not freed
1946 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1947 MAX_WRITECONSOLE_SIZE);
1948 if (!output_bufA) {
1949 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1950 return 0;
1953 /* Read from file (assume OEM codepage) */
1954 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1956 /* Convert from OEM */
1957 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1958 intoBuf, maxChars);
1961 return res;
1964 /***************************************************************************
1965 * WCMD_DumpCommands
1967 * Domps out the parsed command line to ensure syntax is correct
1969 void WCMD_DumpCommands(CMD_LIST *commands) {
1970 WCHAR buffer[MAXSTRING];
1971 CMD_LIST *thisCmd = commands;
1972 const WCHAR fmt[] = {'%','p',' ','%','d',' ','%','2','.','2','d',' ',
1973 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1974 '%','s','\0'};
1976 WINE_TRACE("Parsed line:\n");
1977 while (thisCmd != NULL) {
1978 sprintfW(buffer, fmt,
1979 thisCmd,
1980 thisCmd->prevDelim,
1981 thisCmd->bracketDepth,
1982 thisCmd->nextcommand,
1983 thisCmd->command,
1984 thisCmd->redirects);
1985 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1986 thisCmd = thisCmd->nextcommand;
1990 /***************************************************************************
1991 * WCMD_addCommand
1993 * Adds a command to the current command list
1995 void WCMD_addCommand(WCHAR *command, int *commandLen,
1996 WCHAR *redirs, int *redirLen,
1997 WCHAR **copyTo, int **copyToLen,
1998 CMD_DELIMITERS prevDelim, int curDepth,
1999 CMD_LIST **lastEntry, CMD_LIST **output) {
2001 CMD_LIST *thisEntry = NULL;
2003 /* Allocate storage for command */
2004 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2006 /* Copy in the command */
2007 if (command) {
2008 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2009 (*commandLen+1) * sizeof(WCHAR));
2010 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
2011 thisEntry->command[*commandLen] = 0x00;
2013 /* Copy in the redirects */
2014 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
2015 (*redirLen+1) * sizeof(WCHAR));
2016 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
2017 thisEntry->redirects[*redirLen] = 0x00;
2018 thisEntry->pipeFile[0] = 0x00;
2020 /* Reset the lengths */
2021 *commandLen = 0;
2022 *redirLen = 0;
2023 *copyToLen = commandLen;
2024 *copyTo = command;
2026 } else {
2027 thisEntry->command = NULL;
2030 /* Fill in other fields */
2031 thisEntry->nextcommand = NULL;
2032 thisEntry->prevDelim = prevDelim;
2033 thisEntry->bracketDepth = curDepth;
2034 if (*lastEntry) {
2035 (*lastEntry)->nextcommand = thisEntry;
2036 } else {
2037 *output = thisEntry;
2039 *lastEntry = thisEntry;
2042 /***************************************************************************
2043 * WCMD_ReadAndParseLine
2045 * Either uses supplied input or
2046 * Reads a file from the handle, and then...
2047 * Parse the text buffer, spliting into separate commands
2048 * - unquoted && strings split 2 commands but the 2nd is flagged as
2049 * following an &&
2050 * - ( as the first character just ups the bracket depth
2051 * - unquoted ) when bracket depth > 0 terminates a bracket and
2052 * adds a CMD_LIST structure with null command
2053 * - Anything else gets put into the command string (including
2054 * redirects)
2056 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
2058 WCHAR *curPos;
2059 BOOL inQuotes = FALSE;
2060 WCHAR curString[MAXSTRING];
2061 int curStringLen = 0;
2062 WCHAR curRedirs[MAXSTRING];
2063 int curRedirsLen = 0;
2064 WCHAR *curCopyTo;
2065 int *curLen;
2066 int curDepth = 0;
2067 CMD_LIST *lastEntry = NULL;
2068 CMD_DELIMITERS prevDelim = CMD_NONE;
2069 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
2070 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
2071 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
2072 const WCHAR ifCmd[] = {'i','f',' ','\0'};
2073 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
2074 BOOL inRem = FALSE;
2075 BOOL inFor = FALSE;
2076 BOOL inIn = FALSE;
2077 BOOL inIf = FALSE;
2078 BOOL inElse= FALSE;
2079 BOOL onlyWhiteSpace = FALSE;
2080 BOOL lastWasWhiteSpace = FALSE;
2081 BOOL lastWasDo = FALSE;
2082 BOOL lastWasIn = FALSE;
2083 BOOL lastWasElse = FALSE;
2084 BOOL lastWasRedirect = TRUE;
2086 /* Allocate working space for a command read from keyboard, file etc */
2087 if (!extraSpace)
2088 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
2090 /* If initial command read in, use that, otherwise get input from handle */
2091 if (optionalcmd != NULL) {
2092 strcpyW(extraSpace, optionalcmd);
2093 } else if (readFrom == INVALID_HANDLE_VALUE) {
2094 WINE_FIXME("No command nor handle supplied\n");
2095 } else {
2096 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
2098 curPos = extraSpace;
2100 /* Handle truncated input - issue warning */
2101 if (strlenW(extraSpace) == MAXSTRING -1) {
2102 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
2103 WCMD_output_asis(extraSpace);
2104 WCMD_output_asis(newline);
2107 /* Replace env vars if in a batch context */
2108 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2110 /* Start with an empty string, copying to the command string */
2111 curStringLen = 0;
2112 curRedirsLen = 0;
2113 curCopyTo = curString;
2114 curLen = &curStringLen;
2115 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
2117 /* Parse every character on the line being processed */
2118 while (*curPos != 0x00) {
2120 WCHAR thisChar;
2122 /* Debugging AID:
2123 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2124 lastWasWhiteSpace, onlyWhiteSpace);
2127 /* Certain commands need special handling */
2128 if (curStringLen == 0 && curCopyTo == curString) {
2129 const WCHAR forDO[] = {'d','o',' ','\0'};
2131 /* If command starts with 'rem', ignore any &&, ( etc */
2132 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2133 curPos, 4, remCmd, -1) == 2) {
2134 inRem = TRUE;
2136 /* If command starts with 'for', handle ('s mid line after IN or DO */
2137 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2138 curPos, 4, forCmd, -1) == 2) {
2139 inFor = TRUE;
2141 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2142 is only true in the command portion of the IF statement, but this
2143 should suffice for now
2144 FIXME: Silly syntax like "if 1(==1( (
2145 echo they equal
2146 )" will be parsed wrong */
2147 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2148 curPos, 3, ifCmd, -1) == 2) {
2149 inIf = TRUE;
2151 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2152 curPos, 5, ifElse, -1) == 2) {
2153 inElse = TRUE;
2154 lastWasElse = TRUE;
2155 onlyWhiteSpace = TRUE;
2156 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
2157 (*curLen)+=5;
2158 curPos+=5;
2159 continue;
2161 /* In a for loop, the DO command will follow a close bracket followed by
2162 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2163 is then 0, and all whitespace is skipped */
2164 } else if (inFor &&
2165 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2166 curPos, 3, forDO, -1) == 2)) {
2167 WINE_TRACE("Found DO\n");
2168 lastWasDo = TRUE;
2169 onlyWhiteSpace = TRUE;
2170 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2171 (*curLen)+=3;
2172 curPos+=3;
2173 continue;
2175 } else if (curCopyTo == curString) {
2177 /* Special handling for the 'FOR' command */
2178 if (inFor && lastWasWhiteSpace) {
2179 const WCHAR forIN[] = {'i','n',' ','\0'};
2181 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2183 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2184 curPos, 3, forIN, -1) == 2) {
2185 WINE_TRACE("Found IN\n");
2186 lastWasIn = TRUE;
2187 onlyWhiteSpace = TRUE;
2188 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2189 (*curLen)+=3;
2190 curPos+=3;
2191 continue;
2196 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2197 so just use the default processing ie skip character specific
2198 matching below */
2199 if (!inRem) thisChar = *curPos;
2200 else thisChar = 'X'; /* Character with no special processing */
2202 lastWasWhiteSpace = FALSE; /* Will be reset below */
2204 switch (thisChar) {
2206 case '=': /* drop through - ignore token delimiters at the start of a command */
2207 case ',': /* drop through - ignore token delimiters at the start of a command */
2208 case '\t':/* drop through - ignore token delimiters at the start of a command */
2209 case ' ':
2210 /* If a redirect in place, it ends here */
2211 if (!inQuotes && !lastWasRedirect) {
2213 /* If finishing off a redirect, add a whitespace delimiter */
2214 if (curCopyTo == curRedirs) {
2215 curCopyTo[(*curLen)++] = ' ';
2217 curCopyTo = curString;
2218 curLen = &curStringLen;
2220 if (*curLen > 0) {
2221 curCopyTo[(*curLen)++] = *curPos;
2224 /* Remember just processed whitespace */
2225 lastWasWhiteSpace = TRUE;
2227 break;
2229 case '>': /* drop through - handle redirect chars the same */
2230 case '<':
2231 /* Make a redirect start here */
2232 if (!inQuotes) {
2233 curCopyTo = curRedirs;
2234 curLen = &curRedirsLen;
2235 lastWasRedirect = TRUE;
2238 /* See if 1>, 2> etc, in which case we have some patching up
2239 to do */
2240 if (curPos != extraSpace &&
2241 *(curPos-1)>='1' && *(curPos-1)<='9') {
2243 curStringLen--;
2244 curString[curStringLen] = 0x00;
2245 curCopyTo[(*curLen)++] = *(curPos-1);
2248 curCopyTo[(*curLen)++] = *curPos;
2249 break;
2251 case '|': /* Pipe character only if not || */
2252 if (!inQuotes) {
2253 lastWasRedirect = FALSE;
2255 /* Add an entry to the command list */
2256 if (curStringLen > 0) {
2258 /* Add the current command */
2259 WCMD_addCommand(curString, &curStringLen,
2260 curRedirs, &curRedirsLen,
2261 &curCopyTo, &curLen,
2262 prevDelim, curDepth,
2263 &lastEntry, output);
2267 if (*(curPos+1) == '|') {
2268 curPos++; /* Skip other | */
2269 prevDelim = CMD_ONFAILURE;
2270 } else {
2271 prevDelim = CMD_PIPE;
2273 } else {
2274 curCopyTo[(*curLen)++] = *curPos;
2276 break;
2278 case '"': inQuotes = !inQuotes;
2279 curCopyTo[(*curLen)++] = *curPos;
2280 lastWasRedirect = FALSE;
2281 break;
2283 case '(': /* If a '(' is the first non whitespace in a command portion
2284 ie start of line or just after &&, then we read until an
2285 unquoted ) is found */
2286 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2287 ", for(%d, In:%d, Do:%d)"
2288 ", if(%d, else:%d, lwe:%d)\n",
2289 *curLen, inQuotes,
2290 onlyWhiteSpace,
2291 inFor, lastWasIn, lastWasDo,
2292 inIf, inElse, lastWasElse);
2293 lastWasRedirect = FALSE;
2295 /* Ignore open brackets inside the for set */
2296 if (*curLen == 0 && !inIn) {
2297 curDepth++;
2299 /* If in quotes, ignore brackets */
2300 } else if (inQuotes) {
2301 curCopyTo[(*curLen)++] = *curPos;
2303 /* In a FOR loop, an unquoted '(' may occur straight after
2304 IN or DO
2305 In an IF statement just handle it regardless as we don't
2306 parse the operands
2307 In an ELSE statement, only allow it straight away after
2308 the ELSE and whitespace
2310 } else if (inIf ||
2311 (inElse && lastWasElse && onlyWhiteSpace) ||
2312 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2314 /* If entering into an 'IN', set inIn */
2315 if (inFor && lastWasIn && onlyWhiteSpace) {
2316 WINE_TRACE("Inside an IN\n");
2317 inIn = TRUE;
2320 /* Add the current command */
2321 WCMD_addCommand(curString, &curStringLen,
2322 curRedirs, &curRedirsLen,
2323 &curCopyTo, &curLen,
2324 prevDelim, curDepth,
2325 &lastEntry, output);
2327 curDepth++;
2328 } else {
2329 curCopyTo[(*curLen)++] = *curPos;
2331 break;
2333 case '&': if (!inQuotes) {
2334 lastWasRedirect = FALSE;
2336 /* Add an entry to the command list */
2337 if (curStringLen > 0) {
2339 /* Add the current command */
2340 WCMD_addCommand(curString, &curStringLen,
2341 curRedirs, &curRedirsLen,
2342 &curCopyTo, &curLen,
2343 prevDelim, curDepth,
2344 &lastEntry, output);
2348 if (*(curPos+1) == '&') {
2349 curPos++; /* Skip other & */
2350 prevDelim = CMD_ONSUCCESS;
2351 } else {
2352 prevDelim = CMD_NONE;
2354 } else {
2355 curCopyTo[(*curLen)++] = *curPos;
2357 break;
2359 case ')': if (!inQuotes && curDepth > 0) {
2360 lastWasRedirect = FALSE;
2362 /* Add the current command if there is one */
2363 if (curStringLen) {
2365 /* Add the current command */
2366 WCMD_addCommand(curString, &curStringLen,
2367 curRedirs, &curRedirsLen,
2368 &curCopyTo, &curLen,
2369 prevDelim, curDepth,
2370 &lastEntry, output);
2373 /* Add an empty entry to the command list */
2374 prevDelim = CMD_NONE;
2375 WCMD_addCommand(NULL, &curStringLen,
2376 curRedirs, &curRedirsLen,
2377 &curCopyTo, &curLen,
2378 prevDelim, curDepth,
2379 &lastEntry, output);
2380 curDepth--;
2382 /* Leave inIn if necessary */
2383 if (inIn) inIn = FALSE;
2384 } else {
2385 curCopyTo[(*curLen)++] = *curPos;
2387 break;
2388 default:
2389 lastWasRedirect = FALSE;
2390 curCopyTo[(*curLen)++] = *curPos;
2393 curPos++;
2395 /* At various times we need to know if we have only skipped whitespace,
2396 so reset this variable and then it will remain true until a non
2397 whitespace is found */
2398 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2400 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2401 if (!lastWasWhiteSpace) {
2402 lastWasIn = lastWasDo = FALSE;
2405 /* If we have reached the end, add this command into the list */
2406 if (*curPos == 0x00 && *curLen > 0) {
2408 /* Add an entry to the command list */
2409 WCMD_addCommand(curString, &curStringLen,
2410 curRedirs, &curRedirsLen,
2411 &curCopyTo, &curLen,
2412 prevDelim, curDepth,
2413 &lastEntry, output);
2416 /* If we have reached the end of the string, see if bracketing outstanding */
2417 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2418 inRem = FALSE;
2419 prevDelim = CMD_NONE;
2420 inQuotes = FALSE;
2421 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2423 /* Read more, skipping any blank lines */
2424 while (*extraSpace == 0x00) {
2425 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2426 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2428 curPos = extraSpace;
2429 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2433 /* Dump out the parsed output */
2434 WCMD_DumpCommands(*output);
2436 return extraSpace;
2439 /***************************************************************************
2440 * WCMD_process_commands
2442 * Process all the commands read in so far
2444 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2445 WCHAR *var, WCHAR *val) {
2447 int bdepth = -1;
2449 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2451 /* Loop through the commands, processing them one by one */
2452 while (thisCmd) {
2454 CMD_LIST *origCmd = thisCmd;
2456 /* If processing one bracket only, and we find the end bracket
2457 entry (or less), return */
2458 if (oneBracket && !thisCmd->command &&
2459 bdepth <= thisCmd->bracketDepth) {
2460 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2461 thisCmd, thisCmd->nextcommand);
2462 return thisCmd->nextcommand;
2465 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2466 about them and it will be handled in there)
2467 Also, skip over any batch labels (eg. :fred) */
2468 if (thisCmd->command && thisCmd->command[0] != ':') {
2469 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2470 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2473 /* Step on unless the command itself already stepped on */
2474 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2476 return NULL;
2479 /***************************************************************************
2480 * WCMD_free_commands
2482 * Frees the storage held for a parsed command line
2483 * - This is not done in the process_commands, as eventually the current
2484 * pointer will be modified within the commands, and hence a single free
2485 * routine is simpler
2487 void WCMD_free_commands(CMD_LIST *cmds) {
2489 /* Loop through the commands, freeing them one by one */
2490 while (cmds) {
2491 CMD_LIST *thisCmd = cmds;
2492 cmds = cmds->nextcommand;
2493 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2494 HeapFree(GetProcessHeap(), 0, thisCmd);