ntdll: add test for NtMapViewOfSection / NtCreateSection
[wine/kumbayo.git] / programs / cmd / wcmdmain.c
blob52a310869dcfb9a29161787574062bb5561b9944
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - Cannot handle parameters in quotes
25 * - Lots of functionality missing from builtins
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
34 const WCHAR inbuilt[][10] = {
35 {'A','T','T','R','I','B','\0'},
36 {'C','A','L','L','\0'},
37 {'C','D','\0'},
38 {'C','H','D','I','R','\0'},
39 {'C','L','S','\0'},
40 {'C','O','P','Y','\0'},
41 {'C','T','T','Y','\0'},
42 {'D','A','T','E','\0'},
43 {'D','E','L','\0'},
44 {'D','I','R','\0'},
45 {'E','C','H','O','\0'},
46 {'E','R','A','S','E','\0'},
47 {'F','O','R','\0'},
48 {'G','O','T','O','\0'},
49 {'H','E','L','P','\0'},
50 {'I','F','\0'},
51 {'L','A','B','E','L','\0'},
52 {'M','D','\0'},
53 {'M','K','D','I','R','\0'},
54 {'M','O','V','E','\0'},
55 {'P','A','T','H','\0'},
56 {'P','A','U','S','E','\0'},
57 {'P','R','O','M','P','T','\0'},
58 {'R','E','M','\0'},
59 {'R','E','N','\0'},
60 {'R','E','N','A','M','E','\0'},
61 {'R','D','\0'},
62 {'R','M','D','I','R','\0'},
63 {'S','E','T','\0'},
64 {'S','H','I','F','T','\0'},
65 {'T','I','M','E','\0'},
66 {'T','I','T','L','E','\0'},
67 {'T','Y','P','E','\0'},
68 {'V','E','R','I','F','Y','\0'},
69 {'V','E','R','\0'},
70 {'V','O','L','\0'},
71 {'E','N','D','L','O','C','A','L','\0'},
72 {'S','E','T','L','O','C','A','L','\0'},
73 {'P','U','S','H','D','\0'},
74 {'P','O','P','D','\0'},
75 {'A','S','S','O','C','\0'},
76 {'C','O','L','O','R','\0'},
77 {'F','T','Y','P','E','\0'},
78 {'M','O','R','E','\0'},
79 {'E','X','I','T','\0'}
82 HINSTANCE hinst;
83 DWORD errorlevel;
84 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
85 static int opt_c, opt_k, opt_s;
86 const WCHAR newline[] = {'\n','\0'};
87 static const WCHAR equalsW[] = {'=','\0'};
88 static const WCHAR closeBW[] = {')','\0'};
89 WCHAR anykey[100];
90 WCHAR version_string[100];
91 WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
92 BATCH_CONTEXT *context = NULL;
93 extern struct env_stack *pushd_directories;
94 static const WCHAR *pagedMessage = NULL;
95 static char *output_bufA = NULL;
96 #define MAX_WRITECONSOLE_SIZE 65535
97 BOOL unicodePipes = FALSE;
99 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forvar, WCHAR *forVal);
100 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device);
102 /*****************************************************************************
103 * Main entry point. This is a console application so we have a main() not a
104 * winmain().
107 int wmain (int argc, WCHAR *argvW[])
109 int args;
110 WCHAR *cmd = NULL;
111 WCHAR string[1024];
112 WCHAR envvar[4];
113 HANDLE h;
114 int opt_q;
115 int opt_t = 0;
116 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
117 'b','a','t','\0'};
118 char ansiVersion[100];
119 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
121 /* Pre initialize some messages */
122 strcpy(ansiVersion, PACKAGE_VERSION);
123 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
124 wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
125 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
127 args = argc;
128 opt_c=opt_k=opt_q=opt_s=0;
129 while (args > 0)
131 WCHAR c;
132 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
133 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
134 argvW++;
135 args--;
136 continue;
139 c=(*argvW)[1];
140 if (tolowerW(c)=='c') {
141 opt_c=1;
142 } else if (tolowerW(c)=='q') {
143 opt_q=1;
144 } else if (tolowerW(c)=='k') {
145 opt_k=1;
146 } else if (tolowerW(c)=='s') {
147 opt_s=1;
148 } else if (tolowerW(c)=='a') {
149 unicodePipes=FALSE;
150 } else if (tolowerW(c)=='u') {
151 unicodePipes=TRUE;
152 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
153 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
154 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
155 /* Ignored for compatibility with Windows */
158 if ((*argvW)[2]==0) {
159 argvW++;
160 args--;
162 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
164 *argvW+=2;
167 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
168 break;
171 if (opt_q) {
172 const WCHAR eoff[] = {'O','F','F','\0'};
173 WCMD_echo(eoff);
176 if (opt_c || opt_k) {
177 int len,qcount;
178 WCHAR** arg;
179 int argsLeft;
180 WCHAR* p;
182 /* opt_s left unflagged if the command starts with and contains exactly
183 * one quoted string (exactly two quote characters). The quoted string
184 * must be an executable name that has whitespace and must not have the
185 * following characters: &<>()@^| */
187 /* Build the command to execute */
188 len = 0;
189 qcount = 0;
190 argsLeft = args;
191 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
193 int has_space,bcount;
194 WCHAR* a;
196 has_space=0;
197 bcount=0;
198 a=*arg;
199 if( !*a ) has_space=1;
200 while (*a!='\0') {
201 if (*a=='\\') {
202 bcount++;
203 } else {
204 if (*a==' ' || *a=='\t') {
205 has_space=1;
206 } else if (*a=='"') {
207 /* doubling of '\' preceding a '"',
208 * plus escaping of said '"'
210 len+=2*bcount+1;
211 qcount++;
213 bcount=0;
215 a++;
217 len+=(a-*arg) + 1; /* for the separating space */
218 if (has_space)
220 len+=2; /* for the quotes */
221 qcount+=2;
225 if (qcount!=2)
226 opt_s=1;
228 /* check argvW[0] for a space and invalid characters */
229 if (!opt_s) {
230 opt_s=1;
231 p=*argvW;
232 while (*p!='\0') {
233 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
234 || *p=='@' || *p=='^' || *p=='|') {
235 opt_s=1;
236 break;
238 if (*p==' ')
239 opt_s=0;
240 p++;
244 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
245 if (!cmd)
246 exit(1);
248 p = cmd;
249 argsLeft = args;
250 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
252 int has_space,has_quote;
253 WCHAR* a;
255 /* Check for quotes and spaces in this argument */
256 has_space=has_quote=0;
257 a=*arg;
258 if( !*a ) has_space=1;
259 while (*a!='\0') {
260 if (*a==' ' || *a=='\t') {
261 has_space=1;
262 if (has_quote)
263 break;
264 } else if (*a=='"') {
265 has_quote=1;
266 if (has_space)
267 break;
269 a++;
272 /* Now transfer it to the command line */
273 if (has_space)
274 *p++='"';
275 if (has_quote) {
276 int bcount;
277 WCHAR* a;
279 bcount=0;
280 a=*arg;
281 while (*a!='\0') {
282 if (*a=='\\') {
283 *p++=*a;
284 bcount++;
285 } else {
286 if (*a=='"') {
287 int i;
289 /* Double all the '\\' preceding this '"', plus one */
290 for (i=0;i<=bcount;i++)
291 *p++='\\';
292 *p++='"';
293 } else {
294 *p++=*a;
296 bcount=0;
298 a++;
300 } else {
301 strcpyW(p,*arg);
302 p+=strlenW(*arg);
304 if (has_space)
305 *p++='"';
306 *p++=' ';
308 if (p > cmd)
309 p--; /* remove last space */
310 *p = '\0';
312 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
314 /* strip first and last quote characters if opt_s; check for invalid
315 * executable is done later */
316 if (opt_s && *cmd=='\"')
317 WCMD_opt_s_strip_quotes(cmd);
320 if (opt_c) {
321 /* If we do a "wcmd /c command", we don't want to allocate a new
322 * console since the command returns immediately. Rather, we use
323 * the currently allocated input and output handles. This allows
324 * us to pipe to and read from the command interpreter.
327 /* Parse the command string, without reading any more input */
328 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
329 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
330 WCMD_free_commands(toExecute);
331 toExecute = NULL;
333 HeapFree(GetProcessHeap(), 0, cmd);
334 return errorlevel;
337 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
338 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
339 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
341 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
342 if (opt_t) {
343 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
344 defaultColor = opt_t & 0xFF;
345 param1[0] = 0x00;
346 WCMD_color();
348 } else {
349 /* Check HKCU\Software\Microsoft\Command Processor
350 Then HKLM\Software\Microsoft\Command Processor
351 for defaultcolour value
352 Note Can be supplied as DWORD or REG_SZ
353 Note2 When supplied as REG_SZ it's in decimal!!! */
354 HKEY key;
355 DWORD type;
356 DWORD value=0, size=4;
357 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
358 'M','i','c','r','o','s','o','f','t','\\',
359 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
360 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
362 if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
363 0, KEY_READ, &key) == ERROR_SUCCESS) {
364 WCHAR strvalue[4];
366 /* See if DWORD or REG_SZ */
367 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
368 NULL, NULL) == ERROR_SUCCESS) {
369 if (type == REG_DWORD) {
370 size = sizeof(DWORD);
371 RegQueryValueEx(key, dfltColorW, NULL, NULL,
372 (LPBYTE)&value, &size);
373 } else if (type == REG_SZ) {
374 size = sizeof(strvalue)/sizeof(WCHAR);
375 RegQueryValueEx(key, dfltColorW, NULL, NULL,
376 (LPBYTE)strvalue, &size);
377 value = strtoulW(strvalue, NULL, 10);
382 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
383 0, KEY_READ, &key) == ERROR_SUCCESS) {
384 WCHAR strvalue[4];
386 /* See if DWORD or REG_SZ */
387 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
388 NULL, NULL) == ERROR_SUCCESS) {
389 if (type == REG_DWORD) {
390 size = sizeof(DWORD);
391 RegQueryValueEx(key, dfltColorW, NULL, NULL,
392 (LPBYTE)&value, &size);
393 } else if (type == REG_SZ) {
394 size = sizeof(strvalue)/sizeof(WCHAR);
395 RegQueryValueEx(key, dfltColorW, NULL, NULL,
396 (LPBYTE)strvalue, &size);
397 value = strtoulW(strvalue, NULL, 10);
402 /* If one found, set the screen to that colour */
403 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
404 defaultColor = value & 0xFF;
405 param1[0] = 0x00;
406 WCMD_color();
411 /* Save cwd into appropriate env var */
412 GetCurrentDirectory(1024, string);
413 if (IsCharAlpha(string[0]) && string[1] == ':') {
414 static const WCHAR fmt[] = {'=','%','c',':','\0'};
415 wsprintf(envvar, fmt, string[0]);
416 SetEnvironmentVariable(envvar, string);
419 if (opt_k) {
420 /* Parse the command string, without reading any more input */
421 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
422 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
423 WCMD_free_commands(toExecute);
424 toExecute = NULL;
425 HeapFree(GetProcessHeap(), 0, cmd);
429 * If there is an AUTOEXEC.BAT file, try to execute it.
432 GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
433 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
434 if (h != INVALID_HANDLE_VALUE) {
435 CloseHandle (h);
436 #if 0
437 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
438 #endif
442 * Loop forever getting commands and executing them.
445 WCMD_version ();
446 while (TRUE) {
448 /* Read until EOF (which for std input is never, but if redirect
449 in place, may occur */
450 WCMD_show_prompt ();
451 if (WCMD_ReadAndParseLine(NULL, &toExecute,
452 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
453 break;
454 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
455 WCMD_free_commands(toExecute);
456 toExecute = NULL;
458 return 0;
461 /*****************************************************************************
462 * Expand the command. Native expands lines from batch programs as they are
463 * read in and not again, except for 'for' variable substitution.
464 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
466 void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
468 /* For commands in a context (batch program): */
469 /* Expand environment variables in a batch file %{0-9} first */
470 /* including support for any ~ modifiers */
471 /* Additionally: */
472 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
473 /* names allowing environment variable overrides */
474 /* NOTE: To support the %PATH:xxx% syntax, also perform */
475 /* manual expansion of environment variables here */
477 WCHAR *p = cmd;
478 WCHAR *s, *t;
479 int i;
481 while ((p = strchrW(p, '%'))) {
483 WINE_TRACE("Translate command:%s %d (at: %s)\n",
484 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
485 i = *(p+1) - '0';
487 /* Don't touch %% unless its in Batch */
488 if (!justFors && *(p+1) == '%') {
489 if (context) {
490 s = WCMD_strdupW(p+1);
491 strcpyW (p, s);
492 free (s);
494 p+=1;
496 /* Replace %~ modifications if in batch program */
497 } else if (*(p+1) == '~') {
498 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
499 p++;
501 /* Replace use of %0...%9 if in batch program*/
502 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
503 s = WCMD_strdupW(p+2);
504 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
505 strcpyW (p, t);
506 strcatW (p, s);
507 free (s);
509 /* Replace use of %* if in batch program*/
510 } else if (!justFors && context && *(p+1)=='*') {
511 WCHAR *startOfParms = NULL;
512 s = WCMD_strdupW(p+2);
513 t = WCMD_parameter (context -> command, 1, &startOfParms);
514 if (startOfParms != NULL) strcpyW (p, startOfParms);
515 else *p = 0x00;
516 strcatW (p, s);
517 free (s);
519 } else if (forVariable &&
520 (CompareString (LOCALE_USER_DEFAULT,
521 SORT_STRINGSORT,
523 strlenW(forVariable),
524 forVariable, -1) == 2)) {
525 s = WCMD_strdupW(p + strlenW(forVariable));
526 strcpyW(p, forValue);
527 strcatW(p, s);
528 free(s);
530 } else if (!justFors) {
531 p = WCMD_expand_envvar(p, forVariable, forValue);
533 /* In a FOR loop, see if this is the variable to replace */
534 } else { /* Ignore %'s on second pass of batch program */
535 p++;
539 return;
543 /*****************************************************************************
544 * Process one command. If the command is EXIT this routine does not return.
545 * We will recurse through here executing batch files.
549 void WCMD_execute (WCHAR *command, WCHAR *redirects,
550 WCHAR *forVariable, WCHAR *forValue,
551 CMD_LIST **cmdList)
553 WCHAR *cmd, *p, *redir;
554 int status, i;
555 DWORD count, creationDisposition;
556 HANDLE h;
557 WCHAR *whichcmd;
558 SECURITY_ATTRIBUTES sa;
559 WCHAR *new_cmd;
560 HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
561 INVALID_HANDLE_VALUE,
562 INVALID_HANDLE_VALUE};
563 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
564 STD_OUTPUT_HANDLE,
565 STD_ERROR_HANDLE};
567 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
568 wine_dbgstr_w(command), cmdList,
569 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
571 /* Move copy of the command onto the heap so it can be expanded */
572 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
573 strcpyW(new_cmd, command);
575 /* Expand variables in command line mode only (batch mode will
576 be expanded as the line is read in, except for 'for' loops) */
577 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
578 cmd = new_cmd;
580 /* Show prompt before batch line IF echo is on and in batch program */
581 if (context && echo_mode && (cmd[0] != '@')) {
582 WCMD_show_prompt();
583 WCMD_output_asis ( cmd);
584 WCMD_output_asis ( newline);
588 * Changing default drive has to be handled as a special case.
591 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
592 WCHAR envvar[5];
593 WCHAR dir[MAX_PATH];
595 /* According to MSDN CreateProcess docs, special env vars record
596 the current directory on each drive, in the form =C:
597 so see if one specified, and if so go back to it */
598 strcpyW(envvar, equalsW);
599 strcatW(envvar, cmd);
600 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
601 static const WCHAR fmt[] = {'%','s','\\','\0'};
602 wsprintf(cmd, fmt, cmd);
604 status = SetCurrentDirectory (cmd);
605 if (!status) WCMD_print_error ();
606 HeapFree( GetProcessHeap(), 0, cmd );
607 return;
610 sa.nLength = sizeof(sa);
611 sa.lpSecurityDescriptor = NULL;
612 sa.bInheritHandle = TRUE;
615 * Redirect stdin, stdout and/or stderr if required.
618 if ((p = strchrW(redirects,'<')) != NULL) {
619 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
620 FILE_ATTRIBUTE_NORMAL, NULL);
621 if (h == INVALID_HANDLE_VALUE) {
622 WCMD_print_error ();
623 HeapFree( GetProcessHeap(), 0, cmd );
624 return;
626 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
627 SetStdHandle (STD_INPUT_HANDLE, h);
630 /* Scan the whole command looking for > and 2> */
631 redir = redirects;
632 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
633 int handle = 0;
635 if (*(p-1)!='2') {
636 handle = 1;
637 } else {
638 handle = 2;
641 p++;
642 if ('>' == *p) {
643 creationDisposition = OPEN_ALWAYS;
644 p++;
646 else {
647 creationDisposition = CREATE_ALWAYS;
650 /* Add support for 2>&1 */
651 redir = p;
652 if (*p == '&') {
653 int idx = *(p+1) - '0';
655 if (DuplicateHandle(GetCurrentProcess(),
656 GetStdHandle(idx_stdhandles[idx]),
657 GetCurrentProcess(),
659 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
660 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
662 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
664 } else {
665 WCHAR *param = WCMD_parameter (p, 0, NULL);
666 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
667 FILE_ATTRIBUTE_NORMAL, NULL);
668 if (h == INVALID_HANDLE_VALUE) {
669 WCMD_print_error ();
670 HeapFree( GetProcessHeap(), 0, cmd );
671 return;
673 if (SetFilePointer (h, 0, NULL, FILE_END) ==
674 INVALID_SET_FILE_POINTER) {
675 WCMD_print_error ();
677 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
680 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
681 SetStdHandle (idx_stdhandles[handle], h);
685 * Strip leading whitespaces, and a '@' if supplied
687 whichcmd = WCMD_strtrim_leading_spaces(cmd);
688 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
689 if (whichcmd[0] == '@') whichcmd++;
692 * Check if the command entered is internal. If it is, pass the rest of the
693 * line down to the command. If not try to run a program.
696 count = 0;
697 while (IsCharAlphaNumeric(whichcmd[count])) {
698 count++;
700 for (i=0; i<=WCMD_EXIT; i++) {
701 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
702 whichcmd, count, inbuilt[i], -1) == 2) break;
704 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
705 WCMD_parse (p, quals, param1, param2);
706 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
708 switch (i) {
710 case WCMD_ATTRIB:
711 WCMD_setshow_attrib ();
712 break;
713 case WCMD_CALL:
714 WCMD_call (p);
715 break;
716 case WCMD_CD:
717 case WCMD_CHDIR:
718 WCMD_setshow_default (p);
719 break;
720 case WCMD_CLS:
721 WCMD_clear_screen ();
722 break;
723 case WCMD_COPY:
724 WCMD_copy ();
725 break;
726 case WCMD_CTTY:
727 WCMD_change_tty ();
728 break;
729 case WCMD_DATE:
730 WCMD_setshow_date ();
731 break;
732 case WCMD_DEL:
733 case WCMD_ERASE:
734 WCMD_delete (p, TRUE);
735 break;
736 case WCMD_DIR:
737 WCMD_directory (p);
738 break;
739 case WCMD_ECHO:
740 WCMD_echo(&whichcmd[count]);
741 break;
742 case WCMD_FOR:
743 WCMD_for (p, cmdList);
744 break;
745 case WCMD_GOTO:
746 WCMD_goto (cmdList);
747 break;
748 case WCMD_HELP:
749 WCMD_give_help (p);
750 break;
751 case WCMD_IF:
752 WCMD_if (p, cmdList);
753 break;
754 case WCMD_LABEL:
755 WCMD_volume (1, p);
756 break;
757 case WCMD_MD:
758 case WCMD_MKDIR:
759 WCMD_create_dir ();
760 break;
761 case WCMD_MOVE:
762 WCMD_move ();
763 break;
764 case WCMD_PATH:
765 WCMD_setshow_path (p);
766 break;
767 case WCMD_PAUSE:
768 WCMD_pause ();
769 break;
770 case WCMD_PROMPT:
771 WCMD_setshow_prompt ();
772 break;
773 case WCMD_REM:
774 break;
775 case WCMD_REN:
776 case WCMD_RENAME:
777 WCMD_rename ();
778 break;
779 case WCMD_RD:
780 case WCMD_RMDIR:
781 WCMD_remove_dir (p);
782 break;
783 case WCMD_SETLOCAL:
784 WCMD_setlocal(p);
785 break;
786 case WCMD_ENDLOCAL:
787 WCMD_endlocal();
788 break;
789 case WCMD_SET:
790 WCMD_setshow_env (p);
791 break;
792 case WCMD_SHIFT:
793 WCMD_shift (p);
794 break;
795 case WCMD_TIME:
796 WCMD_setshow_time ();
797 break;
798 case WCMD_TITLE:
799 if (strlenW(&whichcmd[count]) > 0)
800 WCMD_title(&whichcmd[count+1]);
801 break;
802 case WCMD_TYPE:
803 WCMD_type (p);
804 break;
805 case WCMD_VER:
806 WCMD_version ();
807 break;
808 case WCMD_VERIFY:
809 WCMD_verify (p);
810 break;
811 case WCMD_VOL:
812 WCMD_volume (0, p);
813 break;
814 case WCMD_PUSHD:
815 WCMD_pushd(p);
816 break;
817 case WCMD_POPD:
818 WCMD_popd();
819 break;
820 case WCMD_ASSOC:
821 WCMD_assoc(p, TRUE);
822 break;
823 case WCMD_COLOR:
824 WCMD_color();
825 break;
826 case WCMD_FTYPE:
827 WCMD_assoc(p, FALSE);
828 break;
829 case WCMD_MORE:
830 WCMD_more(p);
831 break;
832 case WCMD_EXIT:
833 WCMD_exit (cmdList);
834 break;
835 default:
836 WCMD_run_program (whichcmd, 0);
838 HeapFree( GetProcessHeap(), 0, cmd );
840 /* Restore old handles */
841 for (i=0; i<3; i++) {
842 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
843 CloseHandle (GetStdHandle (idx_stdhandles[i]));
844 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
849 static void init_msvcrt_io_block(STARTUPINFO* st)
851 STARTUPINFO st_p;
852 /* fetch the parent MSVCRT info block if any, so that the child can use the
853 * same handles as its grand-father
855 st_p.cb = sizeof(STARTUPINFO);
856 GetStartupInfo(&st_p);
857 st->cbReserved2 = st_p.cbReserved2;
858 st->lpReserved2 = st_p.lpReserved2;
859 if (st_p.cbReserved2 && st_p.lpReserved2)
861 /* Override the entries for fd 0,1,2 if we happened
862 * to change those std handles (this depends on the way wcmd sets
863 * it's new input & output handles)
865 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
866 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
867 if (ptr)
869 unsigned num = *(unsigned*)st_p.lpReserved2;
870 char* flags = (char*)(ptr + sizeof(unsigned));
871 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
873 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
874 st->cbReserved2 = sz;
875 st->lpReserved2 = ptr;
877 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
878 if (num <= 0 || (flags[0] & WX_OPEN))
880 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
881 flags[0] |= WX_OPEN;
883 if (num <= 1 || (flags[1] & WX_OPEN))
885 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
886 flags[1] |= WX_OPEN;
888 if (num <= 2 || (flags[2] & WX_OPEN))
890 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
891 flags[2] |= WX_OPEN;
893 #undef WX_OPEN
898 /******************************************************************************
899 * WCMD_run_program
901 * Execute a command line as an external program. Must allow recursion.
903 * Precedence:
904 * Manual testing under windows shows PATHEXT plays a key part in this,
905 * and the search algorithm and precedence appears to be as follows.
907 * Search locations:
908 * If directory supplied on command, just use that directory
909 * If extension supplied on command, look for that explicit name first
910 * Otherwise, search in each directory on the path
911 * Precedence:
912 * If extension supplied on command, look for that explicit name first
913 * Then look for supplied name .* (even if extension supplied, so
914 * 'garbage.exe' will match 'garbage.exe.cmd')
915 * If any found, cycle through PATHEXT looking for name.exe one by one
916 * Launching
917 * Once a match has been found, it is launched - Code currently uses
918 * findexecutable to acheive this which is left untouched.
921 void WCMD_run_program (WCHAR *command, int called) {
923 WCHAR temp[MAX_PATH];
924 WCHAR pathtosearch[MAXSTRING];
925 WCHAR *pathposn;
926 WCHAR stemofsearch[MAX_PATH];
927 WCHAR *lastSlash;
928 WCHAR pathext[MAXSTRING];
929 BOOL extensionsupplied = FALSE;
930 BOOL launched = FALSE;
931 BOOL status;
932 BOOL assumeInternal = FALSE;
933 DWORD len;
934 static const WCHAR envPath[] = {'P','A','T','H','\0'};
935 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
936 static const WCHAR delims[] = {'/','\\',':','\0'};
938 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
939 if (!(*param1) && !(*param2))
940 return;
942 /* Calculate the search path and stem to search for */
943 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
944 static const WCHAR curDir[] = {'.',';','\0'};
945 strcpyW(pathtosearch, curDir);
946 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
947 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
948 static const WCHAR curDir[] = {'.','\0'};
949 strcpyW (pathtosearch, curDir);
951 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
952 strcpyW(stemofsearch, param1);
954 } else {
956 /* Convert eg. ..\fred to include a directory by removing file part */
957 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
958 lastSlash = strrchrW(pathtosearch, '\\');
959 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
960 if (lastSlash) *lastSlash = 0x00;
961 strcpyW(stemofsearch, lastSlash+1);
964 /* Now extract PATHEXT */
965 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
966 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
967 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
968 '.','c','o','m',';',
969 '.','c','m','d',';',
970 '.','e','x','e','\0'};
971 strcpyW (pathext, dfltPathExt);
974 /* Loop through the search path, dir by dir */
975 pathposn = pathtosearch;
976 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
977 wine_dbgstr_w(stemofsearch));
978 while (!launched && pathposn) {
980 WCHAR thisDir[MAX_PATH] = {'\0'};
981 WCHAR *pos = NULL;
982 BOOL found = FALSE;
983 const WCHAR slashW[] = {'\\','\0'};
985 /* Work on the first directory on the search path */
986 pos = strchrW(pathposn, ';');
987 if (pos) {
988 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
989 thisDir[(pos-pathposn)] = 0x00;
990 pathposn = pos+1;
992 } else {
993 strcpyW(thisDir, pathposn);
994 pathposn = NULL;
997 /* Since you can have eg. ..\.. on the path, need to expand
998 to full information */
999 strcpyW(temp, thisDir);
1000 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
1002 /* 1. If extension supplied, see if that file exists */
1003 strcatW(thisDir, slashW);
1004 strcatW(thisDir, stemofsearch);
1005 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1007 /* 1. If extension supplied, see if that file exists */
1008 if (extensionsupplied) {
1009 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1010 found = TRUE;
1014 /* 2. Any .* matches? */
1015 if (!found) {
1016 HANDLE h;
1017 WIN32_FIND_DATA finddata;
1018 static const WCHAR allFiles[] = {'.','*','\0'};
1020 strcatW(thisDir,allFiles);
1021 h = FindFirstFile(thisDir, &finddata);
1022 FindClose(h);
1023 if (h != INVALID_HANDLE_VALUE) {
1025 WCHAR *thisExt = pathext;
1027 /* 3. Yes - Try each path ext */
1028 while (thisExt) {
1029 WCHAR *nextExt = strchrW(thisExt, ';');
1031 if (nextExt) {
1032 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1033 pos[(nextExt-thisExt)] = 0x00;
1034 thisExt = nextExt+1;
1035 } else {
1036 strcpyW(pos, thisExt);
1037 thisExt = NULL;
1040 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1041 found = TRUE;
1042 thisExt = NULL;
1048 /* Internal programs won't be picked up by this search, so even
1049 though not found, try one last createprocess and wait for it
1050 to complete.
1051 Note: Ideally we could tell between a console app (wait) and a
1052 windows app, but the API's for it fail in this case */
1053 if (!found && pathposn == NULL) {
1054 WINE_TRACE("ASSUMING INTERNAL\n");
1055 assumeInternal = TRUE;
1056 } else {
1057 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1060 /* Once found, launch it */
1061 if (found || assumeInternal) {
1062 STARTUPINFO st;
1063 PROCESS_INFORMATION pe;
1064 SHFILEINFO psfi;
1065 DWORD console;
1066 HINSTANCE hinst;
1067 WCHAR *ext = strrchrW( thisDir, '.' );
1068 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1069 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1071 launched = TRUE;
1073 /* Special case BAT and CMD */
1074 if (ext && !strcmpiW(ext, batExt)) {
1075 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1076 return;
1077 } else if (ext && !strcmpiW(ext, cmdExt)) {
1078 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1079 return;
1080 } else {
1082 /* thisDir contains the file to be launched, but with what?
1083 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1084 hinst = FindExecutable (thisDir, NULL, temp);
1085 if ((INT_PTR)hinst < 32)
1086 console = 0;
1087 else
1088 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1090 ZeroMemory (&st, sizeof(STARTUPINFO));
1091 st.cb = sizeof(STARTUPINFO);
1092 init_msvcrt_io_block(&st);
1094 /* Launch the process and if a CUI wait on it to complete
1095 Note: Launching internal wine processes cannot specify a full path to exe */
1096 status = CreateProcess (assumeInternal?NULL : thisDir,
1097 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1098 if ((opt_c || opt_k) && !opt_s && !status
1099 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1100 /* strip first and last quote WCHARacters and try again */
1101 WCMD_opt_s_strip_quotes(command);
1102 opt_s=1;
1103 WCMD_run_program(command, called);
1104 return;
1106 if (!status) {
1107 WCMD_print_error ();
1108 /* If a command fails to launch, it sets errorlevel 9009 - which
1109 does not seem to have any associated constant definition */
1110 errorlevel = 9009;
1111 return;
1113 if (!assumeInternal && !console) errorlevel = 0;
1114 else
1116 /* Always wait when called in a batch program context */
1117 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1118 GetExitCodeProcess (pe.hProcess, &errorlevel);
1119 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1121 CloseHandle(pe.hProcess);
1122 CloseHandle(pe.hThread);
1123 return;
1128 /* Not found anywhere - give up */
1129 SetLastError(ERROR_FILE_NOT_FOUND);
1130 WCMD_print_error ();
1132 /* If a command fails to launch, it sets errorlevel 9009 - which
1133 does not seem to have any associated constant definition */
1134 errorlevel = 9009;
1135 return;
1139 /******************************************************************************
1140 * WCMD_show_prompt
1142 * Display the prompt on STDout
1146 void WCMD_show_prompt (void) {
1148 int status;
1149 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1150 WCHAR *p, *q;
1151 DWORD len;
1152 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1154 len = GetEnvironmentVariable (envPrompt, prompt_string,
1155 sizeof(prompt_string)/sizeof(WCHAR));
1156 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1157 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1158 strcpyW (prompt_string, dfltPrompt);
1160 p = prompt_string;
1161 q = out_string;
1162 *q = '\0';
1163 while (*p != '\0') {
1164 if (*p != '$') {
1165 *q++ = *p++;
1166 *q = '\0';
1168 else {
1169 p++;
1170 switch (toupper(*p)) {
1171 case '$':
1172 *q++ = '$';
1173 break;
1174 case 'A':
1175 *q++ = '&';
1176 break;
1177 case 'B':
1178 *q++ = '|';
1179 break;
1180 case 'C':
1181 *q++ = '(';
1182 break;
1183 case 'D':
1184 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1185 while (*q) q++;
1186 break;
1187 case 'E':
1188 *q++ = '\E';
1189 break;
1190 case 'F':
1191 *q++ = ')';
1192 break;
1193 case 'G':
1194 *q++ = '>';
1195 break;
1196 case 'H':
1197 *q++ = '\b';
1198 break;
1199 case 'L':
1200 *q++ = '<';
1201 break;
1202 case 'N':
1203 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1204 if (status) {
1205 *q++ = curdir[0];
1207 break;
1208 case 'P':
1209 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1210 if (status) {
1211 strcatW (q, curdir);
1212 while (*q) q++;
1214 break;
1215 case 'Q':
1216 *q++ = '=';
1217 break;
1218 case 'S':
1219 *q++ = ' ';
1220 break;
1221 case 'T':
1222 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1223 while (*q) q++;
1224 break;
1225 case 'V':
1226 strcatW (q, version_string);
1227 while (*q) q++;
1228 break;
1229 case '_':
1230 *q++ = '\n';
1231 break;
1232 case '+':
1233 if (pushd_directories) {
1234 memset(q, '+', pushd_directories->u.stackdepth);
1235 q = q + pushd_directories->u.stackdepth;
1237 break;
1239 p++;
1240 *q = '\0';
1243 WCMD_output_asis (out_string);
1246 /****************************************************************************
1247 * WCMD_print_error
1249 * Print the message for GetLastError
1252 void WCMD_print_error (void) {
1253 LPVOID lpMsgBuf;
1254 DWORD error_code;
1255 int status;
1257 error_code = GetLastError ();
1258 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1259 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1260 if (!status) {
1261 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1262 error_code, GetLastError());
1263 return;
1266 WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
1267 GetStdHandle(STD_ERROR_HANDLE));
1268 LocalFree ((HLOCAL)lpMsgBuf);
1269 WCMD_output_asis_len (newline, lstrlen(newline),
1270 GetStdHandle(STD_ERROR_HANDLE));
1271 return;
1274 /*******************************************************************
1275 * WCMD_parse - parse a command into parameters and qualifiers.
1277 * On exit, all qualifiers are concatenated into q, the first string
1278 * not beginning with "/" is in p1 and the
1279 * second in p2. Any subsequent non-qualifier strings are lost.
1280 * Parameters in quotes are handled.
1283 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1285 int p = 0;
1287 *q = *p1 = *p2 = '\0';
1288 while (TRUE) {
1289 switch (*s) {
1290 case '/':
1291 *q++ = *s++;
1292 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1293 *q++ = toupperW (*s++);
1295 *q = '\0';
1296 break;
1297 case ' ':
1298 case '\t':
1299 s++;
1300 break;
1301 case '"':
1302 s++;
1303 while ((*s != '\0') && (*s != '"')) {
1304 if (p == 0) *p1++ = *s++;
1305 else if (p == 1) *p2++ = *s++;
1306 else s++;
1308 if (p == 0) *p1 = '\0';
1309 if (p == 1) *p2 = '\0';
1310 p++;
1311 if (*s == '"') s++;
1312 break;
1313 case '\0':
1314 return;
1315 default:
1316 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
1317 && (*s != '=') && (*s != ',') ) {
1318 if (p == 0) *p1++ = *s++;
1319 else if (p == 1) *p2++ = *s++;
1320 else s++;
1322 /* Skip concurrent parms */
1323 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
1325 if (p == 0) *p1 = '\0';
1326 if (p == 1) *p2 = '\0';
1327 p++;
1332 /*******************************************************************
1333 * WCMD_output_asis_len - send output to current standard output
1335 * Output a formatted unicode string. Ideally this will go to the console
1336 * and hence required WriteConsoleW to output it, however if file i/o is
1337 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1339 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
1341 DWORD nOut= 0;
1342 DWORD res = 0;
1344 /* If nothing to write, return (MORE does this sometimes) */
1345 if (!len) return;
1347 /* Try to write as unicode assuming it is to a console */
1348 res = WriteConsoleW(device, message, len, &nOut, NULL);
1350 /* If writing to console fails, assume its file
1351 i/o so convert to OEM codepage and output */
1352 if (!res) {
1353 BOOL usedDefaultChar = FALSE;
1354 DWORD convertedChars;
1356 if (!unicodePipes) {
1358 * Allocate buffer to use when writing to file. (Not freed, as one off)
1360 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1361 MAX_WRITECONSOLE_SIZE);
1362 if (!output_bufA) {
1363 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1364 return;
1367 /* Convert to OEM, then output */
1368 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1369 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1370 "?", &usedDefaultChar);
1371 WriteFile(device, output_bufA, convertedChars,
1372 &nOut, FALSE);
1373 } else {
1374 WriteFile(device, message, len*sizeof(WCHAR),
1375 &nOut, FALSE);
1378 return;
1381 /*******************************************************************
1382 * WCMD_output - send output to current standard output device.
1386 void WCMD_output (const WCHAR *format, ...) {
1388 va_list ap;
1389 WCHAR string[1024];
1390 int ret;
1392 va_start(ap,format);
1393 ret = wvsprintf (string, format, ap);
1394 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1395 WINE_ERR("Output truncated in WCMD_output\n" );
1396 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1397 string[ret] = '\0';
1399 va_end(ap);
1400 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
1404 static int line_count;
1405 static int max_height;
1406 static int max_width;
1407 static BOOL paged_mode;
1408 static int numChars;
1410 void WCMD_enter_paged_mode(const WCHAR *msg)
1412 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1414 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1415 max_height = consoleInfo.dwSize.Y;
1416 max_width = consoleInfo.dwSize.X;
1417 } else {
1418 max_height = 25;
1419 max_width = 80;
1421 paged_mode = TRUE;
1422 line_count = 0;
1423 numChars = 0;
1424 pagedMessage = (msg==NULL)? anykey : msg;
1427 void WCMD_leave_paged_mode(void)
1429 paged_mode = FALSE;
1430 pagedMessage = NULL;
1433 /*******************************************************************
1434 * WCMD_output_asis - send output to current standard output device.
1435 * without formatting eg. when message contains '%'
1438 void WCMD_output_asis (const WCHAR *message) {
1439 DWORD count;
1440 const WCHAR* ptr;
1441 WCHAR string[1024];
1443 if (paged_mode) {
1444 do {
1445 ptr = message;
1446 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1447 numChars++;
1448 ptr++;
1450 if (*ptr == '\n') ptr++;
1451 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
1452 GetStdHandle(STD_OUTPUT_HANDLE));
1453 if (ptr) {
1454 numChars = 0;
1455 if (++line_count >= max_height - 1) {
1456 line_count = 0;
1457 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
1458 GetStdHandle(STD_OUTPUT_HANDLE));
1459 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1460 sizeof(string)/sizeof(WCHAR), &count, NULL);
1463 } while (((message = ptr) != NULL) && (*ptr));
1464 } else {
1465 WCMD_output_asis_len(message, lstrlen(message),
1466 GetStdHandle(STD_OUTPUT_HANDLE));
1471 /***************************************************************************
1472 * WCMD_strtrim_leading_spaces
1474 * Remove leading spaces from a string. Return a pointer to the first
1475 * non-space character. Does not modify the input string
1478 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1480 WCHAR *ptr;
1482 ptr = string;
1483 while (*ptr == ' ') ptr++;
1484 return ptr;
1487 /*************************************************************************
1488 * WCMD_strtrim_trailing_spaces
1490 * Remove trailing spaces from a string. This routine modifies the input
1491 * string by placing a null after the last non-space WCHARacter
1494 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1496 WCHAR *ptr;
1498 ptr = string + strlenW (string) - 1;
1499 while ((*ptr == ' ') && (ptr >= string)) {
1500 *ptr = '\0';
1501 ptr--;
1505 /*************************************************************************
1506 * WCMD_opt_s_strip_quotes
1508 * Remove first and last quote WCHARacters, preserving all other text
1511 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1512 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1513 while((*dest=*src) != '\0') {
1514 if (*src=='\"')
1515 lastq=dest;
1516 dest++, src++;
1518 if (lastq) {
1519 dest=lastq++;
1520 while ((*dest++=*lastq++) != 0)
1525 /*************************************************************************
1526 * WCMD_pipe
1528 * Handle pipes within a command - the DOS way using temporary files.
1531 void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) {
1533 WCHAR *p;
1534 WCHAR *command = (*cmdEntry)->command;
1535 WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1536 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1537 static const WCHAR redirIn[] = {'%','s',' ','<',' ','%','s','\0'};
1538 static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1539 static const WCHAR cmdW[] = {'C','M','D','\0'};
1542 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1543 GetTempFileName (temp_path, cmdW, 0, temp_file);
1544 p = strchrW(command, '|');
1545 *p++ = '\0';
1546 wsprintf (temp_cmd, redirOut, command, temp_file);
1547 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1548 command = p;
1549 while ((p = strchrW(command, '|'))) {
1550 *p++ = '\0';
1551 GetTempFileName (temp_path, cmdW, 0, temp_file2);
1552 wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1553 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1554 DeleteFile (temp_file);
1555 strcpyW (temp_file, temp_file2);
1556 command = p;
1558 wsprintf (temp_cmd, redirIn, command, temp_file);
1559 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1560 DeleteFile (temp_file);
1563 /*************************************************************************
1564 * WCMD_expand_envvar
1566 * Expands environment variables, allowing for WCHARacter substitution
1568 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
1569 WCHAR *endOfVar = NULL, *s;
1570 WCHAR *colonpos = NULL;
1571 WCHAR thisVar[MAXSTRING];
1572 WCHAR thisVarContents[MAXSTRING];
1573 WCHAR savedchar = 0x00;
1574 int len;
1576 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1577 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1578 static const WCHAR Date[] = {'D','A','T','E','\0'};
1579 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1580 static const WCHAR Time[] = {'T','I','M','E','\0'};
1581 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1582 static const WCHAR Cd[] = {'C','D','\0'};
1583 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1584 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1585 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1586 static const WCHAR Delims[] = {'%',' ',':','\0'};
1588 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
1589 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
1591 /* Find the end of the environment variable, and extract name */
1592 endOfVar = strpbrkW(start+1, Delims);
1594 if (endOfVar == NULL || *endOfVar==' ') {
1596 /* In batch program, missing terminator for % and no following
1597 ':' just removes the '%' */
1598 if (context) {
1599 s = WCMD_strdupW(start + 1);
1600 strcpyW (start, s);
1601 free(s);
1602 return start;
1603 } else {
1605 /* In command processing, just ignore it - allows command line
1606 syntax like: for %i in (a.a) do echo %i */
1607 return start+1;
1611 /* If ':' found, process remaining up until '%' (or stop at ':' if
1612 a missing '%' */
1613 if (*endOfVar==':') {
1614 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
1615 if (endOfVar2 != NULL) endOfVar = endOfVar2;
1618 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1619 thisVar[(endOfVar - start)+1] = 0x00;
1620 colonpos = strchrW(thisVar+1, ':');
1622 /* If there's complex substitution, just need %var% for now
1623 to get the expanded data to play with */
1624 if (colonpos) {
1625 *colonpos = '%';
1626 savedchar = *(colonpos+1);
1627 *(colonpos+1) = 0x00;
1630 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1632 /* Expand to contents, if unchanged, return */
1633 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1634 /* override if existing env var called that name */
1635 if ((CompareString (LOCALE_USER_DEFAULT,
1636 NORM_IGNORECASE | SORT_STRINGSORT,
1637 thisVar, 12, ErrorLvlP, -1) == 2) &&
1638 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1639 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1640 static const WCHAR fmt[] = {'%','d','\0'};
1641 wsprintf(thisVarContents, fmt, errorlevel);
1642 len = strlenW(thisVarContents);
1644 } else if ((CompareString (LOCALE_USER_DEFAULT,
1645 NORM_IGNORECASE | SORT_STRINGSORT,
1646 thisVar, 6, DateP, -1) == 2) &&
1647 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1648 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1650 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1651 NULL, thisVarContents, MAXSTRING);
1652 len = strlenW(thisVarContents);
1654 } else if ((CompareString (LOCALE_USER_DEFAULT,
1655 NORM_IGNORECASE | SORT_STRINGSORT,
1656 thisVar, 6, TimeP, -1) == 2) &&
1657 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1658 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1659 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1660 NULL, thisVarContents, MAXSTRING);
1661 len = strlenW(thisVarContents);
1663 } else if ((CompareString (LOCALE_USER_DEFAULT,
1664 NORM_IGNORECASE | SORT_STRINGSORT,
1665 thisVar, 4, CdP, -1) == 2) &&
1666 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1667 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1668 GetCurrentDirectory (MAXSTRING, thisVarContents);
1669 len = strlenW(thisVarContents);
1671 } else if ((CompareString (LOCALE_USER_DEFAULT,
1672 NORM_IGNORECASE | SORT_STRINGSORT,
1673 thisVar, 8, RandomP, -1) == 2) &&
1674 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1675 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1676 static const WCHAR fmt[] = {'%','d','\0'};
1677 wsprintf(thisVarContents, fmt, rand() % 32768);
1678 len = strlenW(thisVarContents);
1680 /* Look for a matching 'for' variable */
1681 } else if (forVar &&
1682 (CompareString (LOCALE_USER_DEFAULT,
1683 SORT_STRINGSORT,
1684 thisVar,
1685 (colonpos - thisVar) - 1,
1686 forVar, -1) == 2)) {
1687 strcpyW(thisVarContents, forVal);
1688 len = strlenW(thisVarContents);
1690 } else {
1692 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1693 sizeof(thisVarContents)/sizeof(WCHAR));
1696 if (len == 0)
1697 return endOfVar+1;
1699 /* In a batch program, unknown env vars are replaced with nothing,
1700 note syntax %garbage:1,3% results in anything after the ':'
1701 except the %
1702 From the command line, you just get back what you entered */
1703 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1705 /* Restore the complex part after the compare */
1706 if (colonpos) {
1707 *colonpos = ':';
1708 *(colonpos+1) = savedchar;
1711 /* Command line - just ignore this */
1712 if (context == NULL) return endOfVar+1;
1714 s = WCMD_strdupW(endOfVar + 1);
1716 /* Batch - replace unknown env var with nothing */
1717 if (colonpos == NULL) {
1718 strcpyW (start, s);
1720 } else {
1721 len = strlenW(thisVar);
1722 thisVar[len-1] = 0x00;
1723 /* If %:...% supplied, : is retained */
1724 if (colonpos == thisVar+1) {
1725 strcpyW (start, colonpos);
1726 } else {
1727 strcpyW (start, colonpos+1);
1729 strcatW (start, s);
1731 free (s);
1732 return start;
1736 /* See if we need to do complex substitution (any ':'s), if not
1737 then our work here is done */
1738 if (colonpos == NULL) {
1739 s = WCMD_strdupW(endOfVar + 1);
1740 strcpyW (start, thisVarContents);
1741 strcatW (start, s);
1742 free(s);
1743 return start;
1746 /* Restore complex bit */
1747 *colonpos = ':';
1748 *(colonpos+1) = savedchar;
1751 Handle complex substitutions:
1752 xxx=yyy (replace xxx with yyy)
1753 *xxx=yyy (replace up to and including xxx with yyy)
1754 ~x (from x WCHARs in)
1755 ~-x (from x WCHARs from the end)
1756 ~x,y (from x WCHARs in for y WCHARacters)
1757 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1760 /* ~ is substring manipulation */
1761 if (savedchar == '~') {
1763 int substrposition, substrlength = 0;
1764 WCHAR *commapos = strchrW(colonpos+2, ',');
1765 WCHAR *startCopy;
1767 substrposition = atolW(colonpos+2);
1768 if (commapos) substrlength = atolW(commapos+1);
1770 s = WCMD_strdupW(endOfVar + 1);
1772 /* Check bounds */
1773 if (substrposition >= 0) {
1774 startCopy = &thisVarContents[min(substrposition, len)];
1775 } else {
1776 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1779 if (commapos == NULL) {
1780 strcpyW (start, startCopy); /* Copy the lot */
1781 } else if (substrlength < 0) {
1783 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1784 if (copybytes > len) copybytes = len;
1785 else if (copybytes < 0) copybytes = 0;
1786 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1787 start[copybytes] = 0x00;
1788 } else {
1789 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1790 start[substrlength] = 0x00;
1793 strcatW (start, s);
1794 free(s);
1795 return start;
1797 /* search and replace manipulation */
1798 } else {
1799 WCHAR *equalspos = strstrW(colonpos, equalsW);
1800 WCHAR *replacewith = equalspos+1;
1801 WCHAR *found = NULL;
1802 WCHAR *searchIn;
1803 WCHAR *searchFor;
1805 s = WCMD_strdupW(endOfVar + 1);
1806 if (equalspos == NULL) return start+1;
1808 /* Null terminate both strings */
1809 thisVar[strlenW(thisVar)-1] = 0x00;
1810 *equalspos = 0x00;
1812 /* Since we need to be case insensitive, copy the 2 buffers */
1813 searchIn = WCMD_strdupW(thisVarContents);
1814 CharUpperBuff(searchIn, strlenW(thisVarContents));
1815 searchFor = WCMD_strdupW(colonpos+1);
1816 CharUpperBuff(searchFor, strlenW(colonpos+1));
1819 /* Handle wildcard case */
1820 if (*(colonpos+1) == '*') {
1821 /* Search for string to replace */
1822 found = strstrW(searchIn, searchFor+1);
1824 if (found) {
1825 /* Do replacement */
1826 strcpyW(start, replacewith);
1827 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1828 strcatW(start, s);
1829 free(s);
1830 } else {
1831 /* Copy as it */
1832 strcpyW(start, thisVarContents);
1833 strcatW(start, s);
1836 } else {
1837 /* Loop replacing all instances */
1838 WCHAR *lastFound = searchIn;
1839 WCHAR *outputposn = start;
1841 *start = 0x00;
1842 while ((found = strstrW(lastFound, searchFor))) {
1843 lstrcpynW(outputposn,
1844 thisVarContents + (lastFound-searchIn),
1845 (found - lastFound)+1);
1846 outputposn = outputposn + (found - lastFound);
1847 strcatW(outputposn, replacewith);
1848 outputposn = outputposn + strlenW(replacewith);
1849 lastFound = found + strlenW(searchFor);
1851 strcatW(outputposn,
1852 thisVarContents + (lastFound-searchIn));
1853 strcatW(outputposn, s);
1855 free(searchIn);
1856 free(searchFor);
1857 return start;
1859 return start+1;
1862 /*************************************************************************
1863 * WCMD_LoadMessage
1864 * Load a string from the resource file, handling any error
1865 * Returns string retrieved from resource file
1867 WCHAR *WCMD_LoadMessage(UINT id) {
1868 static WCHAR msg[2048];
1869 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1871 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1872 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1873 strcpyW(msg, failedMsg);
1875 return msg;
1878 /*************************************************************************
1879 * WCMD_strdupW
1880 * A wide version of strdup as its missing from unicode.h
1882 WCHAR *WCMD_strdupW(WCHAR *input) {
1883 int len=strlenW(input)+1;
1884 /* Note: Use malloc not HeapAlloc to emulate strdup */
1885 WCHAR *result = malloc(len * sizeof(WCHAR));
1886 memcpy(result, input, len * sizeof(WCHAR));
1887 return result;
1890 /***************************************************************************
1891 * WCMD_Readfile
1893 * Read characters in from a console/file, returning result in Unicode
1894 * with signature identical to ReadFile
1896 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1897 LPDWORD charsRead, const LPOVERLAPPED unused) {
1899 BOOL res;
1901 /* Try to read from console as Unicode */
1902 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1904 /* If reading from console has failed we assume its file
1905 i/o so read in and convert from OEM codepage */
1906 if (!res) {
1908 DWORD numRead;
1910 * Allocate buffer to use when reading from file. Not freed
1912 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1913 MAX_WRITECONSOLE_SIZE);
1914 if (!output_bufA) {
1915 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1916 return 0;
1919 /* Read from file (assume OEM codepage) */
1920 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1922 /* Convert from OEM */
1923 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1924 intoBuf, maxChars);
1927 return res;
1930 /***************************************************************************
1931 * WCMD_DumpCommands
1933 * Domps out the parsed command line to ensure syntax is correct
1935 void WCMD_DumpCommands(CMD_LIST *commands) {
1936 WCHAR buffer[MAXSTRING];
1937 CMD_LIST *thisCmd = commands;
1938 const WCHAR fmt[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1939 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1940 '%','s','\0'};
1942 WINE_TRACE("Parsed line:\n");
1943 while (thisCmd != NULL) {
1944 sprintfW(buffer, fmt,
1945 thisCmd,
1946 thisCmd->isAmphersand?'Y':'N',
1947 thisCmd->bracketDepth,
1948 thisCmd->nextcommand,
1949 thisCmd->command,
1950 thisCmd->redirects);
1951 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1952 thisCmd = thisCmd->nextcommand;
1956 /***************************************************************************
1957 * WCMD_addCommand
1959 * Adds a command to the current command list
1961 void WCMD_addCommand(WCHAR *command, int *commandLen,
1962 WCHAR *redirs, int *redirLen,
1963 WCHAR **copyTo, int **copyToLen,
1964 BOOL isAmphersand, int curDepth,
1965 CMD_LIST **lastEntry, CMD_LIST **output) {
1967 CMD_LIST *thisEntry = NULL;
1969 /* Allocate storage for command */
1970 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1972 /* Copy in the command */
1973 if (command) {
1974 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1975 (*commandLen+1) * sizeof(WCHAR));
1976 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1977 thisEntry->command[*commandLen] = 0x00;
1979 /* Copy in the redirects */
1980 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1981 (*redirLen+1) * sizeof(WCHAR));
1982 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1983 thisEntry->redirects[*redirLen] = 0x00;
1985 /* Reset the lengths */
1986 *commandLen = 0;
1987 *redirLen = 0;
1988 *copyToLen = commandLen;
1989 *copyTo = command;
1991 } else {
1992 thisEntry->command = NULL;
1995 /* Fill in other fields */
1996 thisEntry->nextcommand = NULL;
1997 thisEntry->isAmphersand = isAmphersand;
1998 thisEntry->bracketDepth = curDepth;
1999 if (*lastEntry) {
2000 (*lastEntry)->nextcommand = thisEntry;
2001 } else {
2002 *output = thisEntry;
2004 *lastEntry = thisEntry;
2007 /***************************************************************************
2008 * WCMD_ReadAndParseLine
2010 * Either uses supplied input or
2011 * Reads a file from the handle, and then...
2012 * Parse the text buffer, spliting into separate commands
2013 * - unquoted && strings split 2 commands but the 2nd is flagged as
2014 * following an &&
2015 * - ( as the first character just ups the bracket depth
2016 * - unquoted ) when bracket depth > 0 terminates a bracket and
2017 * adds a CMD_LIST structure with null command
2018 * - Anything else gets put into the command string (including
2019 * redirects)
2021 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
2023 WCHAR *curPos;
2024 BOOL inQuotes = FALSE;
2025 WCHAR curString[MAXSTRING];
2026 int curStringLen = 0;
2027 WCHAR curRedirs[MAXSTRING];
2028 int curRedirsLen = 0;
2029 WCHAR *curCopyTo;
2030 int *curLen;
2031 int curDepth = 0;
2032 CMD_LIST *lastEntry = NULL;
2033 BOOL isAmphersand = FALSE;
2034 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
2035 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
2036 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
2037 const WCHAR ifCmd[] = {'i','f',' ','\0'};
2038 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
2039 BOOL inRem = FALSE;
2040 BOOL inFor = FALSE;
2041 BOOL inIn = FALSE;
2042 BOOL inIf = FALSE;
2043 BOOL inElse= FALSE;
2044 BOOL onlyWhiteSpace = FALSE;
2045 BOOL lastWasWhiteSpace = FALSE;
2046 BOOL lastWasDo = FALSE;
2047 BOOL lastWasIn = FALSE;
2048 BOOL lastWasElse = FALSE;
2049 BOOL lastWasRedirect = TRUE;
2051 /* Allocate working space for a command read from keyboard, file etc */
2052 if (!extraSpace)
2053 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
2055 /* If initial command read in, use that, otherwise get input from handle */
2056 if (optionalcmd != NULL) {
2057 strcpyW(extraSpace, optionalcmd);
2058 } else if (readFrom == INVALID_HANDLE_VALUE) {
2059 WINE_FIXME("No command nor handle supplied\n");
2060 } else {
2061 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
2063 curPos = extraSpace;
2065 /* Handle truncated input - issue warning */
2066 if (strlenW(extraSpace) == MAXSTRING -1) {
2067 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
2068 WCMD_output_asis(extraSpace);
2069 WCMD_output_asis(newline);
2072 /* Replace env vars if in a batch context */
2073 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2075 /* Start with an empty string, copying to the command string */
2076 curStringLen = 0;
2077 curRedirsLen = 0;
2078 curCopyTo = curString;
2079 curLen = &curStringLen;
2080 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
2082 /* Parse every character on the line being processed */
2083 while (*curPos != 0x00) {
2085 WCHAR thisChar;
2087 /* Debugging AID:
2088 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2089 lastWasWhiteSpace, onlyWhiteSpace);
2092 /* Certain commands need special handling */
2093 if (curStringLen == 0 && curCopyTo == curString) {
2094 const WCHAR forDO[] = {'d','o',' ','\0'};
2096 /* If command starts with 'rem', ignore any &&, ( etc */
2097 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2098 curPos, 4, remCmd, -1) == 2) {
2099 inRem = TRUE;
2101 /* If command starts with 'for', handle ('s mid line after IN or DO */
2102 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2103 curPos, 4, forCmd, -1) == 2) {
2104 inFor = TRUE;
2106 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2107 is only true in the command portion of the IF statement, but this
2108 should suffice for now
2109 FIXME: Silly syntax like "if 1(==1( (
2110 echo they equal
2111 )" will be parsed wrong */
2112 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2113 curPos, 3, ifCmd, -1) == 2) {
2114 inIf = TRUE;
2116 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2117 curPos, 5, ifElse, -1) == 2) {
2118 inElse = TRUE;
2119 lastWasElse = TRUE;
2120 onlyWhiteSpace = TRUE;
2121 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
2122 (*curLen)+=5;
2123 curPos+=5;
2124 continue;
2126 /* In a for loop, the DO command will follow a close bracket followed by
2127 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2128 is then 0, and all whitespace is skipped */
2129 } else if (inFor &&
2130 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2131 curPos, 3, forDO, -1) == 2)) {
2132 WINE_TRACE("Found DO\n");
2133 lastWasDo = TRUE;
2134 onlyWhiteSpace = TRUE;
2135 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2136 (*curLen)+=3;
2137 curPos+=3;
2138 continue;
2140 } else if (curCopyTo == curString) {
2142 /* Special handling for the 'FOR' command */
2143 if (inFor && lastWasWhiteSpace) {
2144 const WCHAR forIN[] = {'i','n',' ','\0'};
2146 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2148 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2149 curPos, 3, forIN, -1) == 2) {
2150 WINE_TRACE("Found IN\n");
2151 lastWasIn = TRUE;
2152 onlyWhiteSpace = TRUE;
2153 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2154 (*curLen)+=3;
2155 curPos+=3;
2156 continue;
2161 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2162 so just use the default processing ie skip character specific
2163 matching below */
2164 if (!inRem) thisChar = *curPos;
2165 else thisChar = 'X'; /* Character with no special processing */
2167 lastWasWhiteSpace = FALSE; /* Will be reset below */
2169 switch (thisChar) {
2171 case '=': /* drop through - ignore token delimiters at the start of a command */
2172 case ',': /* drop through - ignore token delimiters at the start of a command */
2173 case '\t':/* drop through - ignore token delimiters at the start of a command */
2174 case ' ':
2175 /* If a redirect in place, it ends here */
2176 if (!inQuotes && !lastWasRedirect) {
2178 /* If finishing off a redirect, add a whitespace delimiter */
2179 if (curCopyTo == curRedirs) {
2180 curCopyTo[(*curLen)++] = ' ';
2182 curCopyTo = curString;
2183 curLen = &curStringLen;
2185 if (*curLen > 0) {
2186 curCopyTo[(*curLen)++] = *curPos;
2189 /* Remember just processed whitespace */
2190 lastWasWhiteSpace = TRUE;
2192 break;
2194 case '>': /* drop through - handle redirect chars the same */
2195 case '<':
2196 /* Make a redirect start here */
2197 if (!inQuotes) {
2198 curCopyTo = curRedirs;
2199 curLen = &curRedirsLen;
2200 lastWasRedirect = TRUE;
2203 /* See if 1>, 2> etc, in which case we have some patching up
2204 to do */
2205 if (curPos != extraSpace &&
2206 *(curPos-1)>='1' && *(curPos-1)<='9') {
2208 curStringLen--;
2209 curString[curStringLen] = 0x00;
2210 curCopyTo[(*curLen)++] = *(curPos-1);
2213 curCopyTo[(*curLen)++] = *curPos;
2214 break;
2216 case '|': /* Pipe character only if not || */
2217 if (!inQuotes && *(curPos++) == '|') {
2219 /* || is an alternative form of && but runs regardless */
2221 /* If finishing off a redirect, add a whitespace delimiter */
2222 if (curCopyTo == curRedirs) {
2223 curCopyTo[(*curLen)++] = ' ';
2226 /* If a redirect in place, it ends here */
2227 curCopyTo = curString;
2228 curLen = &curStringLen;
2229 curCopyTo[(*curLen)++] = *curPos;
2230 lastWasRedirect = FALSE;
2232 } else if (inQuotes) {
2233 curCopyTo[(*curLen)++] = *curPos;
2234 lastWasRedirect = FALSE;
2236 } else {
2237 /* Make a redirect start here */
2238 curCopyTo = curRedirs;
2239 curLen = &curRedirsLen;
2240 curCopyTo[(*curLen)++] = *curPos;
2241 lastWasRedirect = TRUE;
2243 break;
2246 case '"': inQuotes = !inQuotes;
2247 curCopyTo[(*curLen)++] = *curPos;
2248 lastWasRedirect = FALSE;
2249 break;
2251 case '(': /* If a '(' is the first non whitespace in a command portion
2252 ie start of line or just after &&, then we read until an
2253 unquoted ) is found */
2254 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2255 ", for(%d, In:%d, Do:%d)"
2256 ", if(%d, else:%d, lwe:%d)\n",
2257 *curLen, inQuotes,
2258 onlyWhiteSpace,
2259 inFor, lastWasIn, lastWasDo,
2260 inIf, inElse, lastWasElse);
2261 lastWasRedirect = FALSE;
2263 /* Ignore open brackets inside the for set */
2264 if (*curLen == 0 && !inIn) {
2265 curDepth++;
2267 /* If in quotes, ignore brackets */
2268 } else if (inQuotes) {
2269 curCopyTo[(*curLen)++] = *curPos;
2271 /* In a FOR loop, an unquoted '(' may occur straight after
2272 IN or DO
2273 In an IF statement just handle it regardless as we don't
2274 parse the operands
2275 In an ELSE statement, only allow it straight away after
2276 the ELSE and whitespace
2278 } else if (inIf ||
2279 (inElse && lastWasElse && onlyWhiteSpace) ||
2280 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2282 /* If entering into an 'IN', set inIn */
2283 if (inFor && lastWasIn && onlyWhiteSpace) {
2284 WINE_TRACE("Inside an IN\n");
2285 inIn = TRUE;
2288 /* Add the current command */
2289 WCMD_addCommand(curString, &curStringLen,
2290 curRedirs, &curRedirsLen,
2291 &curCopyTo, &curLen,
2292 isAmphersand, curDepth,
2293 &lastEntry, output);
2295 curDepth++;
2296 } else {
2297 curCopyTo[(*curLen)++] = *curPos;
2299 break;
2301 case '&': if (!inQuotes && *(curPos+1) == '&') {
2302 curPos++; /* Skip other & */
2303 lastWasRedirect = FALSE;
2305 /* Add an entry to the command list */
2306 if (curStringLen > 0) {
2308 /* Add the current command */
2309 WCMD_addCommand(curString, &curStringLen,
2310 curRedirs, &curRedirsLen,
2311 &curCopyTo, &curLen,
2312 isAmphersand, curDepth,
2313 &lastEntry, output);
2316 isAmphersand = TRUE;
2317 } else {
2318 curCopyTo[(*curLen)++] = *curPos;
2320 break;
2322 case ')': if (!inQuotes && curDepth > 0) {
2323 lastWasRedirect = FALSE;
2325 /* Add the current command if there is one */
2326 if (curStringLen) {
2328 /* Add the current command */
2329 WCMD_addCommand(curString, &curStringLen,
2330 curRedirs, &curRedirsLen,
2331 &curCopyTo, &curLen,
2332 isAmphersand, curDepth,
2333 &lastEntry, output);
2336 /* Add an empty entry to the command list */
2337 isAmphersand = FALSE;
2338 WCMD_addCommand(NULL, &curStringLen,
2339 curRedirs, &curRedirsLen,
2340 &curCopyTo, &curLen,
2341 isAmphersand, curDepth,
2342 &lastEntry, output);
2343 curDepth--;
2345 /* Leave inIn if necessary */
2346 if (inIn) inIn = FALSE;
2347 } else {
2348 curCopyTo[(*curLen)++] = *curPos;
2350 break;
2351 default:
2352 lastWasRedirect = FALSE;
2353 curCopyTo[(*curLen)++] = *curPos;
2356 curPos++;
2358 /* At various times we need to know if we have only skipped whitespace,
2359 so reset this variable and then it will remain true until a non
2360 whitespace is found */
2361 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2363 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2364 if (!lastWasWhiteSpace) {
2365 lastWasIn = lastWasDo = FALSE;
2368 /* If we have reached the end, add this command into the list */
2369 if (*curPos == 0x00 && *curLen > 0) {
2371 /* Add an entry to the command list */
2372 WCMD_addCommand(curString, &curStringLen,
2373 curRedirs, &curRedirsLen,
2374 &curCopyTo, &curLen,
2375 isAmphersand, curDepth,
2376 &lastEntry, output);
2379 /* If we have reached the end of the string, see if bracketing outstanding */
2380 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2381 inRem = FALSE;
2382 isAmphersand = FALSE;
2383 inQuotes = FALSE;
2384 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2386 /* Read more, skipping any blank lines */
2387 while (*extraSpace == 0x00) {
2388 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2389 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2391 curPos = extraSpace;
2392 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2396 /* Dump out the parsed output */
2397 WCMD_DumpCommands(*output);
2399 return extraSpace;
2402 /***************************************************************************
2403 * WCMD_process_commands
2405 * Process all the commands read in so far
2407 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2408 WCHAR *var, WCHAR *val) {
2410 int bdepth = -1;
2412 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2414 /* Loop through the commands, processing them one by one */
2415 while (thisCmd) {
2417 CMD_LIST *origCmd = thisCmd;
2419 /* If processing one bracket only, and we find the end bracket
2420 entry (or less), return */
2421 if (oneBracket && !thisCmd->command &&
2422 bdepth <= thisCmd->bracketDepth) {
2423 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2424 thisCmd, thisCmd->nextcommand);
2425 return thisCmd->nextcommand;
2428 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2429 about them and it will be handled in there)
2430 Also, skip over any batch labels (eg. :fred) */
2431 if (thisCmd->command && thisCmd->command[0] != ':') {
2433 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2435 if (strchrW(thisCmd->redirects,'|') != NULL) {
2436 WCMD_pipe (&thisCmd, var, val);
2437 } else {
2438 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2442 /* Step on unless the command itself already stepped on */
2443 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2445 return NULL;
2448 /***************************************************************************
2449 * WCMD_free_commands
2451 * Frees the storage held for a parsed command line
2452 * - This is not done in the process_commands, as eventually the current
2453 * pointer will be modified within the commands, and hence a single free
2454 * routine is simpler
2456 void WCMD_free_commands(CMD_LIST *cmds) {
2458 /* Loop through the commands, freeing them one by one */
2459 while (cmds) {
2460 CMD_LIST *thisCmd = cmds;
2461 cmds = cmds->nextcommand;
2462 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2463 HeapFree(GetProcessHeap(), 0, thisCmd);