inetcomm: Implement IMimeBody_GetProp for the primary context type.
[wine/hacks.git] / programs / cmd / wcmdmain.c
blob2966bcc877660ed7568c235265219434d8bde549
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);
424 if (opt_k) {
425 /* Parse the command string, without reading any more input */
426 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
427 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
428 WCMD_free_commands(toExecute);
429 toExecute = NULL;
430 HeapFree(GetProcessHeap(), 0, cmd);
434 * If there is an AUTOEXEC.BAT file, try to execute it.
437 GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
438 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
439 if (h != INVALID_HANDLE_VALUE) {
440 CloseHandle (h);
441 #if 0
442 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
443 #endif
447 * Loop forever getting commands and executing them.
450 WCMD_version ();
451 while (TRUE) {
453 /* Read until EOF (which for std input is never, but if redirect
454 in place, may occur */
455 WCMD_show_prompt ();
456 if (WCMD_ReadAndParseLine(NULL, &toExecute,
457 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
458 break;
459 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
460 WCMD_free_commands(toExecute);
461 toExecute = NULL;
463 return 0;
466 /*****************************************************************************
467 * Expand the command. Native expands lines from batch programs as they are
468 * read in and not again, except for 'for' variable substitution.
469 * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
471 void handleExpansion(WCHAR *cmd, BOOL justFors, WCHAR *forVariable, WCHAR *forValue) {
473 /* For commands in a context (batch program): */
474 /* Expand environment variables in a batch file %{0-9} first */
475 /* including support for any ~ modifiers */
476 /* Additionally: */
477 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
478 /* names allowing environment variable overrides */
479 /* NOTE: To support the %PATH:xxx% syntax, also perform */
480 /* manual expansion of environment variables here */
482 WCHAR *p = cmd;
483 WCHAR *s, *t;
484 int i;
486 while ((p = strchrW(p, '%'))) {
488 WINE_TRACE("Translate command:%s %d (at: %s)\n",
489 wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
490 i = *(p+1) - '0';
492 /* Don't touch %% unless its in Batch */
493 if (!justFors && *(p+1) == '%') {
494 if (context) {
495 s = WCMD_strdupW(p+1);
496 strcpyW (p, s);
497 free (s);
499 p+=1;
501 /* Replace %~ modifications if in batch program */
502 } else if (*(p+1) == '~') {
503 WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
504 p++;
506 /* Replace use of %0...%9 if in batch program*/
507 } else if (!justFors && context && (i >= 0) && (i <= 9)) {
508 s = WCMD_strdupW(p+2);
509 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
510 strcpyW (p, t);
511 strcatW (p, s);
512 free (s);
514 /* Replace use of %* if in batch program*/
515 } else if (!justFors && context && *(p+1)=='*') {
516 WCHAR *startOfParms = NULL;
517 s = WCMD_strdupW(p+2);
518 t = WCMD_parameter (context -> command, 1, &startOfParms);
519 if (startOfParms != NULL) strcpyW (p, startOfParms);
520 else *p = 0x00;
521 strcatW (p, s);
522 free (s);
524 } else if (forVariable &&
525 (CompareString (LOCALE_USER_DEFAULT,
526 SORT_STRINGSORT,
528 strlenW(forVariable),
529 forVariable, -1) == 2)) {
530 s = WCMD_strdupW(p + strlenW(forVariable));
531 strcpyW(p, forValue);
532 strcatW(p, s);
533 free(s);
535 } else if (!justFors) {
536 p = WCMD_expand_envvar(p, forVariable, forValue);
538 /* In a FOR loop, see if this is the variable to replace */
539 } else { /* Ignore %'s on second pass of batch program */
540 p++;
544 return;
548 /*****************************************************************************
549 * Process one command. If the command is EXIT this routine does not return.
550 * We will recurse through here executing batch files.
554 void WCMD_execute (WCHAR *command, WCHAR *redirects,
555 WCHAR *forVariable, WCHAR *forValue,
556 CMD_LIST **cmdList)
558 WCHAR *cmd, *p, *redir;
559 int status, i;
560 DWORD count, creationDisposition;
561 HANDLE h;
562 WCHAR *whichcmd;
563 SECURITY_ATTRIBUTES sa;
564 WCHAR *new_cmd;
565 HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
566 INVALID_HANDLE_VALUE,
567 INVALID_HANDLE_VALUE};
568 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
569 STD_OUTPUT_HANDLE,
570 STD_ERROR_HANDLE};
572 WINE_TRACE("command on entry:%s (%p), with '%s'='%s'\n",
573 wine_dbgstr_w(command), cmdList,
574 wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
576 /* Move copy of the command onto the heap so it can be expanded */
577 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
578 strcpyW(new_cmd, command);
580 /* Expand variables in command line mode only (batch mode will
581 be expanded as the line is read in, except for 'for' loops) */
582 handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
583 cmd = new_cmd;
585 /* Show prompt before batch line IF echo is on and in batch program */
586 if (context && echo_mode && (cmd[0] != '@')) {
587 WCMD_show_prompt();
588 WCMD_output_asis ( cmd);
589 WCMD_output_asis ( newline);
593 * Changing default drive has to be handled as a special case.
596 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
597 WCHAR envvar[5];
598 WCHAR dir[MAX_PATH];
600 /* According to MSDN CreateProcess docs, special env vars record
601 the current directory on each drive, in the form =C:
602 so see if one specified, and if so go back to it */
603 strcpyW(envvar, equalsW);
604 strcatW(envvar, cmd);
605 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
606 static const WCHAR fmt[] = {'%','s','\\','\0'};
607 wsprintf(cmd, fmt, cmd);
609 status = SetCurrentDirectory (cmd);
610 if (!status) WCMD_print_error ();
611 HeapFree( GetProcessHeap(), 0, cmd );
612 return;
615 sa.nLength = sizeof(sa);
616 sa.lpSecurityDescriptor = NULL;
617 sa.bInheritHandle = TRUE;
620 * Redirect stdin, stdout and/or stderr if required.
623 if ((p = strchrW(redirects,'<')) != NULL) {
624 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
625 FILE_ATTRIBUTE_NORMAL, NULL);
626 if (h == INVALID_HANDLE_VALUE) {
627 WCMD_print_error ();
628 HeapFree( GetProcessHeap(), 0, cmd );
629 return;
631 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
632 SetStdHandle (STD_INPUT_HANDLE, h);
635 /* Scan the whole command looking for > and 2> */
636 redir = redirects;
637 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
638 int handle = 0;
640 if (*(p-1)!='2') {
641 handle = 1;
642 } else {
643 handle = 2;
646 p++;
647 if ('>' == *p) {
648 creationDisposition = OPEN_ALWAYS;
649 p++;
651 else {
652 creationDisposition = CREATE_ALWAYS;
655 /* Add support for 2>&1 */
656 redir = p;
657 if (*p == '&') {
658 int idx = *(p+1) - '0';
660 if (DuplicateHandle(GetCurrentProcess(),
661 GetStdHandle(idx_stdhandles[idx]),
662 GetCurrentProcess(),
664 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
665 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
667 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
669 } else {
670 WCHAR *param = WCMD_parameter (p, 0, NULL);
671 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
672 FILE_ATTRIBUTE_NORMAL, NULL);
673 if (h == INVALID_HANDLE_VALUE) {
674 WCMD_print_error ();
675 HeapFree( GetProcessHeap(), 0, cmd );
676 return;
678 if (SetFilePointer (h, 0, NULL, FILE_END) ==
679 INVALID_SET_FILE_POINTER) {
680 WCMD_print_error ();
682 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
685 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
686 SetStdHandle (idx_stdhandles[handle], h);
690 * Strip leading whitespaces, and a '@' if supplied
692 whichcmd = WCMD_strtrim_leading_spaces(cmd);
693 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
694 if (whichcmd[0] == '@') whichcmd++;
697 * Check if the command entered is internal. If it is, pass the rest of the
698 * line down to the command. If not try to run a program.
701 count = 0;
702 while (IsCharAlphaNumeric(whichcmd[count])) {
703 count++;
705 for (i=0; i<=WCMD_EXIT; i++) {
706 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
707 whichcmd, count, inbuilt[i], -1) == 2) break;
709 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
710 WCMD_parse (p, quals, param1, param2);
711 WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
713 switch (i) {
715 case WCMD_ATTRIB:
716 WCMD_setshow_attrib ();
717 break;
718 case WCMD_CALL:
719 WCMD_call (p);
720 break;
721 case WCMD_CD:
722 case WCMD_CHDIR:
723 WCMD_setshow_default (p);
724 break;
725 case WCMD_CLS:
726 WCMD_clear_screen ();
727 break;
728 case WCMD_COPY:
729 WCMD_copy ();
730 break;
731 case WCMD_CTTY:
732 WCMD_change_tty ();
733 break;
734 case WCMD_DATE:
735 WCMD_setshow_date ();
736 break;
737 case WCMD_DEL:
738 case WCMD_ERASE:
739 WCMD_delete (p, TRUE);
740 break;
741 case WCMD_DIR:
742 WCMD_directory (p);
743 break;
744 case WCMD_ECHO:
745 WCMD_echo(&whichcmd[count]);
746 break;
747 case WCMD_FOR:
748 WCMD_for (p, cmdList);
749 break;
750 case WCMD_GOTO:
751 WCMD_goto (cmdList);
752 break;
753 case WCMD_HELP:
754 WCMD_give_help (p);
755 break;
756 case WCMD_IF:
757 WCMD_if (p, cmdList);
758 break;
759 case WCMD_LABEL:
760 WCMD_volume (1, p);
761 break;
762 case WCMD_MD:
763 case WCMD_MKDIR:
764 WCMD_create_dir ();
765 break;
766 case WCMD_MOVE:
767 WCMD_move ();
768 break;
769 case WCMD_PATH:
770 WCMD_setshow_path (p);
771 break;
772 case WCMD_PAUSE:
773 WCMD_pause ();
774 break;
775 case WCMD_PROMPT:
776 WCMD_setshow_prompt ();
777 break;
778 case WCMD_REM:
779 break;
780 case WCMD_REN:
781 case WCMD_RENAME:
782 WCMD_rename ();
783 break;
784 case WCMD_RD:
785 case WCMD_RMDIR:
786 WCMD_remove_dir (p);
787 break;
788 case WCMD_SETLOCAL:
789 WCMD_setlocal(p);
790 break;
791 case WCMD_ENDLOCAL:
792 WCMD_endlocal();
793 break;
794 case WCMD_SET:
795 WCMD_setshow_env (p);
796 break;
797 case WCMD_SHIFT:
798 WCMD_shift (p);
799 break;
800 case WCMD_TIME:
801 WCMD_setshow_time ();
802 break;
803 case WCMD_TITLE:
804 if (strlenW(&whichcmd[count]) > 0)
805 WCMD_title(&whichcmd[count+1]);
806 break;
807 case WCMD_TYPE:
808 WCMD_type (p);
809 break;
810 case WCMD_VER:
811 WCMD_version ();
812 break;
813 case WCMD_VERIFY:
814 WCMD_verify (p);
815 break;
816 case WCMD_VOL:
817 WCMD_volume (0, p);
818 break;
819 case WCMD_PUSHD:
820 WCMD_pushd(p);
821 break;
822 case WCMD_POPD:
823 WCMD_popd();
824 break;
825 case WCMD_ASSOC:
826 WCMD_assoc(p, TRUE);
827 break;
828 case WCMD_COLOR:
829 WCMD_color();
830 break;
831 case WCMD_FTYPE:
832 WCMD_assoc(p, FALSE);
833 break;
834 case WCMD_MORE:
835 WCMD_more(p);
836 break;
837 case WCMD_EXIT:
838 WCMD_exit (cmdList);
839 break;
840 default:
841 WCMD_run_program (whichcmd, 0);
843 HeapFree( GetProcessHeap(), 0, cmd );
845 /* Restore old handles */
846 for (i=0; i<3; i++) {
847 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
848 CloseHandle (GetStdHandle (idx_stdhandles[i]));
849 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
854 static void init_msvcrt_io_block(STARTUPINFO* st)
856 STARTUPINFO st_p;
857 /* fetch the parent MSVCRT info block if any, so that the child can use the
858 * same handles as its grand-father
860 st_p.cb = sizeof(STARTUPINFO);
861 GetStartupInfo(&st_p);
862 st->cbReserved2 = st_p.cbReserved2;
863 st->lpReserved2 = st_p.lpReserved2;
864 if (st_p.cbReserved2 && st_p.lpReserved2)
866 /* Override the entries for fd 0,1,2 if we happened
867 * to change those std handles (this depends on the way wcmd sets
868 * it's new input & output handles)
870 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
871 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
872 if (ptr)
874 unsigned num = *(unsigned*)st_p.lpReserved2;
875 char* flags = (char*)(ptr + sizeof(unsigned));
876 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
878 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
879 st->cbReserved2 = sz;
880 st->lpReserved2 = ptr;
882 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
883 if (num <= 0 || (flags[0] & WX_OPEN))
885 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
886 flags[0] |= WX_OPEN;
888 if (num <= 1 || (flags[1] & WX_OPEN))
890 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
891 flags[1] |= WX_OPEN;
893 if (num <= 2 || (flags[2] & WX_OPEN))
895 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
896 flags[2] |= WX_OPEN;
898 #undef WX_OPEN
903 /******************************************************************************
904 * WCMD_run_program
906 * Execute a command line as an external program. Must allow recursion.
908 * Precedence:
909 * Manual testing under windows shows PATHEXT plays a key part in this,
910 * and the search algorithm and precedence appears to be as follows.
912 * Search locations:
913 * If directory supplied on command, just use that directory
914 * If extension supplied on command, look for that explicit name first
915 * Otherwise, search in each directory on the path
916 * Precedence:
917 * If extension supplied on command, look for that explicit name first
918 * Then look for supplied name .* (even if extension supplied, so
919 * 'garbage.exe' will match 'garbage.exe.cmd')
920 * If any found, cycle through PATHEXT looking for name.exe one by one
921 * Launching
922 * Once a match has been found, it is launched - Code currently uses
923 * findexecutable to achieve this which is left untouched.
926 void WCMD_run_program (WCHAR *command, int called) {
928 WCHAR temp[MAX_PATH];
929 WCHAR pathtosearch[MAXSTRING];
930 WCHAR *pathposn;
931 WCHAR stemofsearch[MAX_PATH];
932 WCHAR *lastSlash;
933 WCHAR pathext[MAXSTRING];
934 BOOL extensionsupplied = FALSE;
935 BOOL launched = FALSE;
936 BOOL status;
937 BOOL assumeInternal = FALSE;
938 DWORD len;
939 static const WCHAR envPath[] = {'P','A','T','H','\0'};
940 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
941 static const WCHAR delims[] = {'/','\\',':','\0'};
943 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
944 if (!(*param1) && !(*param2))
945 return;
947 /* Calculate the search path and stem to search for */
948 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
949 static const WCHAR curDir[] = {'.',';','\0'};
950 strcpyW(pathtosearch, curDir);
951 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
952 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
953 static const WCHAR curDir[] = {'.','\0'};
954 strcpyW (pathtosearch, curDir);
956 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
957 strcpyW(stemofsearch, param1);
959 } else {
961 /* Convert eg. ..\fred to include a directory by removing file part */
962 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
963 lastSlash = strrchrW(pathtosearch, '\\');
964 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
965 if (lastSlash) *lastSlash = 0x00;
966 strcpyW(stemofsearch, lastSlash+1);
969 /* Now extract PATHEXT */
970 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
971 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
972 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
973 '.','c','o','m',';',
974 '.','c','m','d',';',
975 '.','e','x','e','\0'};
976 strcpyW (pathext, dfltPathExt);
979 /* Loop through the search path, dir by dir */
980 pathposn = pathtosearch;
981 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
982 wine_dbgstr_w(stemofsearch));
983 while (!launched && pathposn) {
985 WCHAR thisDir[MAX_PATH] = {'\0'};
986 WCHAR *pos = NULL;
987 BOOL found = FALSE;
988 const WCHAR slashW[] = {'\\','\0'};
990 /* Work on the first directory on the search path */
991 pos = strchrW(pathposn, ';');
992 if (pos) {
993 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
994 thisDir[(pos-pathposn)] = 0x00;
995 pathposn = pos+1;
997 } else {
998 strcpyW(thisDir, pathposn);
999 pathposn = NULL;
1002 /* Since you can have eg. ..\.. on the path, need to expand
1003 to full information */
1004 strcpyW(temp, thisDir);
1005 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
1007 /* 1. If extension supplied, see if that file exists */
1008 strcatW(thisDir, slashW);
1009 strcatW(thisDir, stemofsearch);
1010 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1012 /* 1. If extension supplied, see if that file exists */
1013 if (extensionsupplied) {
1014 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1015 found = TRUE;
1019 /* 2. Any .* matches? */
1020 if (!found) {
1021 HANDLE h;
1022 WIN32_FIND_DATA finddata;
1023 static const WCHAR allFiles[] = {'.','*','\0'};
1025 strcatW(thisDir,allFiles);
1026 h = FindFirstFile(thisDir, &finddata);
1027 FindClose(h);
1028 if (h != INVALID_HANDLE_VALUE) {
1030 WCHAR *thisExt = pathext;
1032 /* 3. Yes - Try each path ext */
1033 while (thisExt) {
1034 WCHAR *nextExt = strchrW(thisExt, ';');
1036 if (nextExt) {
1037 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1038 pos[(nextExt-thisExt)] = 0x00;
1039 thisExt = nextExt+1;
1040 } else {
1041 strcpyW(pos, thisExt);
1042 thisExt = NULL;
1045 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1046 found = TRUE;
1047 thisExt = NULL;
1053 /* Internal programs won't be picked up by this search, so even
1054 though not found, try one last createprocess and wait for it
1055 to complete.
1056 Note: Ideally we could tell between a console app (wait) and a
1057 windows app, but the API's for it fail in this case */
1058 if (!found && pathposn == NULL) {
1059 WINE_TRACE("ASSUMING INTERNAL\n");
1060 assumeInternal = TRUE;
1061 } else {
1062 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1065 /* Once found, launch it */
1066 if (found || assumeInternal) {
1067 STARTUPINFO st;
1068 PROCESS_INFORMATION pe;
1069 SHFILEINFO psfi;
1070 DWORD console;
1071 HINSTANCE hinst;
1072 WCHAR *ext = strrchrW( thisDir, '.' );
1073 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1074 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1076 launched = TRUE;
1078 /* Special case BAT and CMD */
1079 if (ext && !strcmpiW(ext, batExt)) {
1080 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1081 return;
1082 } else if (ext && !strcmpiW(ext, cmdExt)) {
1083 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1084 return;
1085 } else {
1087 /* thisDir contains the file to be launched, but with what?
1088 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1089 hinst = FindExecutable (thisDir, NULL, temp);
1090 if ((INT_PTR)hinst < 32)
1091 console = 0;
1092 else
1093 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1095 ZeroMemory (&st, sizeof(STARTUPINFO));
1096 st.cb = sizeof(STARTUPINFO);
1097 init_msvcrt_io_block(&st);
1099 /* Launch the process and if a CUI wait on it to complete
1100 Note: Launching internal wine processes cannot specify a full path to exe */
1101 status = CreateProcess (assumeInternal?NULL : thisDir,
1102 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1103 if ((opt_c || opt_k) && !opt_s && !status
1104 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1105 /* strip first and last quote WCHARacters and try again */
1106 WCMD_opt_s_strip_quotes(command);
1107 opt_s=1;
1108 WCMD_run_program(command, called);
1109 return;
1111 if (!status) {
1112 WCMD_print_error ();
1113 /* If a command fails to launch, it sets errorlevel 9009 - which
1114 does not seem to have any associated constant definition */
1115 errorlevel = 9009;
1116 return;
1118 if (!assumeInternal && !console) errorlevel = 0;
1119 else
1121 /* Always wait when called in a batch program context */
1122 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1123 GetExitCodeProcess (pe.hProcess, &errorlevel);
1124 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1126 CloseHandle(pe.hProcess);
1127 CloseHandle(pe.hThread);
1128 return;
1133 /* Not found anywhere - give up */
1134 SetLastError(ERROR_FILE_NOT_FOUND);
1135 WCMD_print_error ();
1137 /* If a command fails to launch, it sets errorlevel 9009 - which
1138 does not seem to have any associated constant definition */
1139 errorlevel = 9009;
1140 return;
1144 /******************************************************************************
1145 * WCMD_show_prompt
1147 * Display the prompt on STDout
1151 void WCMD_show_prompt (void) {
1153 int status;
1154 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1155 WCHAR *p, *q;
1156 DWORD len;
1157 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1159 len = GetEnvironmentVariable (envPrompt, prompt_string,
1160 sizeof(prompt_string)/sizeof(WCHAR));
1161 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1162 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1163 strcpyW (prompt_string, dfltPrompt);
1165 p = prompt_string;
1166 q = out_string;
1167 *q = '\0';
1168 while (*p != '\0') {
1169 if (*p != '$') {
1170 *q++ = *p++;
1171 *q = '\0';
1173 else {
1174 p++;
1175 switch (toupper(*p)) {
1176 case '$':
1177 *q++ = '$';
1178 break;
1179 case 'A':
1180 *q++ = '&';
1181 break;
1182 case 'B':
1183 *q++ = '|';
1184 break;
1185 case 'C':
1186 *q++ = '(';
1187 break;
1188 case 'D':
1189 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1190 while (*q) q++;
1191 break;
1192 case 'E':
1193 *q++ = '\E';
1194 break;
1195 case 'F':
1196 *q++ = ')';
1197 break;
1198 case 'G':
1199 *q++ = '>';
1200 break;
1201 case 'H':
1202 *q++ = '\b';
1203 break;
1204 case 'L':
1205 *q++ = '<';
1206 break;
1207 case 'N':
1208 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1209 if (status) {
1210 *q++ = curdir[0];
1212 break;
1213 case 'P':
1214 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1215 if (status) {
1216 strcatW (q, curdir);
1217 while (*q) q++;
1219 break;
1220 case 'Q':
1221 *q++ = '=';
1222 break;
1223 case 'S':
1224 *q++ = ' ';
1225 break;
1226 case 'T':
1227 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1228 while (*q) q++;
1229 break;
1230 case 'V':
1231 strcatW (q, version_string);
1232 while (*q) q++;
1233 break;
1234 case '_':
1235 *q++ = '\n';
1236 break;
1237 case '+':
1238 if (pushd_directories) {
1239 memset(q, '+', pushd_directories->u.stackdepth);
1240 q = q + pushd_directories->u.stackdepth;
1242 break;
1244 p++;
1245 *q = '\0';
1248 WCMD_output_asis (out_string);
1251 /****************************************************************************
1252 * WCMD_print_error
1254 * Print the message for GetLastError
1257 void WCMD_print_error (void) {
1258 LPVOID lpMsgBuf;
1259 DWORD error_code;
1260 int status;
1262 error_code = GetLastError ();
1263 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1264 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1265 if (!status) {
1266 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1267 error_code, GetLastError());
1268 return;
1271 WCMD_output_asis_len(lpMsgBuf, lstrlen(lpMsgBuf),
1272 GetStdHandle(STD_ERROR_HANDLE));
1273 LocalFree ((HLOCAL)lpMsgBuf);
1274 WCMD_output_asis_len (newline, lstrlen(newline),
1275 GetStdHandle(STD_ERROR_HANDLE));
1276 return;
1279 /*******************************************************************
1280 * WCMD_parse - parse a command into parameters and qualifiers.
1282 * On exit, all qualifiers are concatenated into q, the first string
1283 * not beginning with "/" is in p1 and the
1284 * second in p2. Any subsequent non-qualifier strings are lost.
1285 * Parameters in quotes are handled.
1288 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1290 int p = 0;
1292 *q = *p1 = *p2 = '\0';
1293 while (TRUE) {
1294 switch (*s) {
1295 case '/':
1296 *q++ = *s++;
1297 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1298 *q++ = toupperW (*s++);
1300 *q = '\0';
1301 break;
1302 case ' ':
1303 case '\t':
1304 s++;
1305 break;
1306 case '"':
1307 s++;
1308 while ((*s != '\0') && (*s != '"')) {
1309 if (p == 0) *p1++ = *s++;
1310 else if (p == 1) *p2++ = *s++;
1311 else s++;
1313 if (p == 0) *p1 = '\0';
1314 if (p == 1) *p2 = '\0';
1315 p++;
1316 if (*s == '"') s++;
1317 break;
1318 case '\0':
1319 return;
1320 default:
1321 while ((*s != '\0') && (*s != ' ') && (*s != '\t')
1322 && (*s != '=') && (*s != ',') ) {
1323 if (p == 0) *p1++ = *s++;
1324 else if (p == 1) *p2++ = *s++;
1325 else s++;
1327 /* Skip concurrent parms */
1328 while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
1330 if (p == 0) *p1 = '\0';
1331 if (p == 1) *p2 = '\0';
1332 p++;
1337 /*******************************************************************
1338 * WCMD_output_asis_len - send output to current standard output
1340 * Output a formatted unicode string. Ideally this will go to the console
1341 * and hence required WriteConsoleW to output it, however if file i/o is
1342 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1344 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
1346 DWORD nOut= 0;
1347 DWORD res = 0;
1349 /* If nothing to write, return (MORE does this sometimes) */
1350 if (!len) return;
1352 /* Try to write as unicode assuming it is to a console */
1353 res = WriteConsoleW(device, message, len, &nOut, NULL);
1355 /* If writing to console fails, assume its file
1356 i/o so convert to OEM codepage and output */
1357 if (!res) {
1358 BOOL usedDefaultChar = FALSE;
1359 DWORD convertedChars;
1361 if (!unicodePipes) {
1363 * Allocate buffer to use when writing to file. (Not freed, as one off)
1365 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1366 MAX_WRITECONSOLE_SIZE);
1367 if (!output_bufA) {
1368 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1369 return;
1372 /* Convert to OEM, then output */
1373 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1374 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1375 "?", &usedDefaultChar);
1376 WriteFile(device, output_bufA, convertedChars,
1377 &nOut, FALSE);
1378 } else {
1379 WriteFile(device, message, len*sizeof(WCHAR),
1380 &nOut, FALSE);
1383 return;
1386 /*******************************************************************
1387 * WCMD_output - send output to current standard output device.
1391 void WCMD_output (const WCHAR *format, ...) {
1393 va_list ap;
1394 WCHAR string[1024];
1395 int ret;
1397 va_start(ap,format);
1398 ret = wvsprintf (string, format, ap);
1399 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1400 WINE_ERR("Output truncated in WCMD_output\n" );
1401 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1402 string[ret] = '\0';
1404 va_end(ap);
1405 WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
1409 static int line_count;
1410 static int max_height;
1411 static int max_width;
1412 static BOOL paged_mode;
1413 static int numChars;
1415 void WCMD_enter_paged_mode(const WCHAR *msg)
1417 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1419 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1420 max_height = consoleInfo.dwSize.Y;
1421 max_width = consoleInfo.dwSize.X;
1422 } else {
1423 max_height = 25;
1424 max_width = 80;
1426 paged_mode = TRUE;
1427 line_count = 0;
1428 numChars = 0;
1429 pagedMessage = (msg==NULL)? anykey : msg;
1432 void WCMD_leave_paged_mode(void)
1434 paged_mode = FALSE;
1435 pagedMessage = NULL;
1438 /*******************************************************************
1439 * WCMD_output_asis - send output to current standard output device.
1440 * without formatting eg. when message contains '%'
1443 void WCMD_output_asis (const WCHAR *message) {
1444 DWORD count;
1445 const WCHAR* ptr;
1446 WCHAR string[1024];
1448 if (paged_mode) {
1449 do {
1450 ptr = message;
1451 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1452 numChars++;
1453 ptr++;
1455 if (*ptr == '\n') ptr++;
1456 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message),
1457 GetStdHandle(STD_OUTPUT_HANDLE));
1458 if (ptr) {
1459 numChars = 0;
1460 if (++line_count >= max_height - 1) {
1461 line_count = 0;
1462 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage),
1463 GetStdHandle(STD_OUTPUT_HANDLE));
1464 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1465 sizeof(string)/sizeof(WCHAR), &count, NULL);
1468 } while (((message = ptr) != NULL) && (*ptr));
1469 } else {
1470 WCMD_output_asis_len(message, lstrlen(message),
1471 GetStdHandle(STD_OUTPUT_HANDLE));
1476 /***************************************************************************
1477 * WCMD_strtrim_leading_spaces
1479 * Remove leading spaces from a string. Return a pointer to the first
1480 * non-space character. Does not modify the input string
1483 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1485 WCHAR *ptr;
1487 ptr = string;
1488 while (*ptr == ' ') ptr++;
1489 return ptr;
1492 /*************************************************************************
1493 * WCMD_strtrim_trailing_spaces
1495 * Remove trailing spaces from a string. This routine modifies the input
1496 * string by placing a null after the last non-space WCHARacter
1499 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1501 WCHAR *ptr;
1503 ptr = string + strlenW (string) - 1;
1504 while ((*ptr == ' ') && (ptr >= string)) {
1505 *ptr = '\0';
1506 ptr--;
1510 /*************************************************************************
1511 * WCMD_opt_s_strip_quotes
1513 * Remove first and last quote WCHARacters, preserving all other text
1516 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1517 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1518 while((*dest=*src) != '\0') {
1519 if (*src=='\"')
1520 lastq=dest;
1521 dest++, src++;
1523 if (lastq) {
1524 dest=lastq++;
1525 while ((*dest++=*lastq++) != 0)
1530 /*************************************************************************
1531 * WCMD_pipe
1533 * Handle pipes within a command - the DOS way using temporary files.
1536 void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) {
1538 WCHAR *p;
1539 WCHAR *command = (*cmdEntry)->command;
1540 WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1541 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1542 static const WCHAR redirIn[] = {'%','s',' ','<',' ','%','s','\0'};
1543 static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1544 static const WCHAR cmdW[] = {'C','M','D','\0'};
1547 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1548 GetTempFileName (temp_path, cmdW, 0, temp_file);
1549 p = strchrW(command, '|');
1550 *p++ = '\0';
1551 wsprintf (temp_cmd, redirOut, command, temp_file);
1552 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1553 command = p;
1554 while ((p = strchrW(command, '|'))) {
1555 *p++ = '\0';
1556 GetTempFileName (temp_path, cmdW, 0, temp_file2);
1557 wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1558 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1559 DeleteFile (temp_file);
1560 strcpyW (temp_file, temp_file2);
1561 command = p;
1563 wsprintf (temp_cmd, redirIn, command, temp_file);
1564 WCMD_execute (temp_cmd, (*cmdEntry)->redirects, var, val, cmdEntry);
1565 DeleteFile (temp_file);
1568 /*************************************************************************
1569 * WCMD_expand_envvar
1571 * Expands environment variables, allowing for WCHARacter substitution
1573 static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR *forVar, WCHAR *forVal) {
1574 WCHAR *endOfVar = NULL, *s;
1575 WCHAR *colonpos = NULL;
1576 WCHAR thisVar[MAXSTRING];
1577 WCHAR thisVarContents[MAXSTRING];
1578 WCHAR savedchar = 0x00;
1579 int len;
1581 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1582 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1583 static const WCHAR Date[] = {'D','A','T','E','\0'};
1584 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1585 static const WCHAR Time[] = {'T','I','M','E','\0'};
1586 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1587 static const WCHAR Cd[] = {'C','D','\0'};
1588 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1589 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1590 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1591 static const WCHAR Delims[] = {'%',' ',':','\0'};
1593 WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
1594 wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
1596 /* Find the end of the environment variable, and extract name */
1597 endOfVar = strpbrkW(start+1, Delims);
1599 if (endOfVar == NULL || *endOfVar==' ') {
1601 /* In batch program, missing terminator for % and no following
1602 ':' just removes the '%' */
1603 if (context) {
1604 s = WCMD_strdupW(start + 1);
1605 strcpyW (start, s);
1606 free(s);
1607 return start;
1608 } else {
1610 /* In command processing, just ignore it - allows command line
1611 syntax like: for %i in (a.a) do echo %i */
1612 return start+1;
1616 /* If ':' found, process remaining up until '%' (or stop at ':' if
1617 a missing '%' */
1618 if (*endOfVar==':') {
1619 WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
1620 if (endOfVar2 != NULL) endOfVar = endOfVar2;
1623 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1624 thisVar[(endOfVar - start)+1] = 0x00;
1625 colonpos = strchrW(thisVar+1, ':');
1627 /* If there's complex substitution, just need %var% for now
1628 to get the expanded data to play with */
1629 if (colonpos) {
1630 *colonpos = '%';
1631 savedchar = *(colonpos+1);
1632 *(colonpos+1) = 0x00;
1635 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1637 /* Expand to contents, if unchanged, return */
1638 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1639 /* override if existing env var called that name */
1640 if ((CompareString (LOCALE_USER_DEFAULT,
1641 NORM_IGNORECASE | SORT_STRINGSORT,
1642 thisVar, 12, ErrorLvlP, -1) == 2) &&
1643 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1644 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1645 static const WCHAR fmt[] = {'%','d','\0'};
1646 wsprintf(thisVarContents, fmt, errorlevel);
1647 len = strlenW(thisVarContents);
1649 } else if ((CompareString (LOCALE_USER_DEFAULT,
1650 NORM_IGNORECASE | SORT_STRINGSORT,
1651 thisVar, 6, DateP, -1) == 2) &&
1652 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1653 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1655 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1656 NULL, thisVarContents, MAXSTRING);
1657 len = strlenW(thisVarContents);
1659 } else if ((CompareString (LOCALE_USER_DEFAULT,
1660 NORM_IGNORECASE | SORT_STRINGSORT,
1661 thisVar, 6, TimeP, -1) == 2) &&
1662 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1663 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1664 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1665 NULL, thisVarContents, MAXSTRING);
1666 len = strlenW(thisVarContents);
1668 } else if ((CompareString (LOCALE_USER_DEFAULT,
1669 NORM_IGNORECASE | SORT_STRINGSORT,
1670 thisVar, 4, CdP, -1) == 2) &&
1671 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1672 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1673 GetCurrentDirectory (MAXSTRING, thisVarContents);
1674 len = strlenW(thisVarContents);
1676 } else if ((CompareString (LOCALE_USER_DEFAULT,
1677 NORM_IGNORECASE | SORT_STRINGSORT,
1678 thisVar, 8, RandomP, -1) == 2) &&
1679 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1680 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1681 static const WCHAR fmt[] = {'%','d','\0'};
1682 wsprintf(thisVarContents, fmt, rand() % 32768);
1683 len = strlenW(thisVarContents);
1685 /* Look for a matching 'for' variable */
1686 } else if (forVar &&
1687 (CompareString (LOCALE_USER_DEFAULT,
1688 SORT_STRINGSORT,
1689 thisVar,
1690 (colonpos - thisVar) - 1,
1691 forVar, -1) == 2)) {
1692 strcpyW(thisVarContents, forVal);
1693 len = strlenW(thisVarContents);
1695 } else {
1697 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1698 sizeof(thisVarContents)/sizeof(WCHAR));
1701 if (len == 0)
1702 return endOfVar+1;
1704 /* In a batch program, unknown env vars are replaced with nothing,
1705 note syntax %garbage:1,3% results in anything after the ':'
1706 except the %
1707 From the command line, you just get back what you entered */
1708 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1710 /* Restore the complex part after the compare */
1711 if (colonpos) {
1712 *colonpos = ':';
1713 *(colonpos+1) = savedchar;
1716 /* Command line - just ignore this */
1717 if (context == NULL) return endOfVar+1;
1719 s = WCMD_strdupW(endOfVar + 1);
1721 /* Batch - replace unknown env var with nothing */
1722 if (colonpos == NULL) {
1723 strcpyW (start, s);
1725 } else {
1726 len = strlenW(thisVar);
1727 thisVar[len-1] = 0x00;
1728 /* If %:...% supplied, : is retained */
1729 if (colonpos == thisVar+1) {
1730 strcpyW (start, colonpos);
1731 } else {
1732 strcpyW (start, colonpos+1);
1734 strcatW (start, s);
1736 free (s);
1737 return start;
1741 /* See if we need to do complex substitution (any ':'s), if not
1742 then our work here is done */
1743 if (colonpos == NULL) {
1744 s = WCMD_strdupW(endOfVar + 1);
1745 strcpyW (start, thisVarContents);
1746 strcatW (start, s);
1747 free(s);
1748 return start;
1751 /* Restore complex bit */
1752 *colonpos = ':';
1753 *(colonpos+1) = savedchar;
1756 Handle complex substitutions:
1757 xxx=yyy (replace xxx with yyy)
1758 *xxx=yyy (replace up to and including xxx with yyy)
1759 ~x (from x WCHARs in)
1760 ~-x (from x WCHARs from the end)
1761 ~x,y (from x WCHARs in for y WCHARacters)
1762 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1765 /* ~ is substring manipulation */
1766 if (savedchar == '~') {
1768 int substrposition, substrlength = 0;
1769 WCHAR *commapos = strchrW(colonpos+2, ',');
1770 WCHAR *startCopy;
1772 substrposition = atolW(colonpos+2);
1773 if (commapos) substrlength = atolW(commapos+1);
1775 s = WCMD_strdupW(endOfVar + 1);
1777 /* Check bounds */
1778 if (substrposition >= 0) {
1779 startCopy = &thisVarContents[min(substrposition, len)];
1780 } else {
1781 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1784 if (commapos == NULL) {
1785 strcpyW (start, startCopy); /* Copy the lot */
1786 } else if (substrlength < 0) {
1788 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1789 if (copybytes > len) copybytes = len;
1790 else if (copybytes < 0) copybytes = 0;
1791 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1792 start[copybytes] = 0x00;
1793 } else {
1794 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1795 start[substrlength] = 0x00;
1798 strcatW (start, s);
1799 free(s);
1800 return start;
1802 /* search and replace manipulation */
1803 } else {
1804 WCHAR *equalspos = strstrW(colonpos, equalsW);
1805 WCHAR *replacewith = equalspos+1;
1806 WCHAR *found = NULL;
1807 WCHAR *searchIn;
1808 WCHAR *searchFor;
1810 s = WCMD_strdupW(endOfVar + 1);
1811 if (equalspos == NULL) return start+1;
1813 /* Null terminate both strings */
1814 thisVar[strlenW(thisVar)-1] = 0x00;
1815 *equalspos = 0x00;
1817 /* Since we need to be case insensitive, copy the 2 buffers */
1818 searchIn = WCMD_strdupW(thisVarContents);
1819 CharUpperBuff(searchIn, strlenW(thisVarContents));
1820 searchFor = WCMD_strdupW(colonpos+1);
1821 CharUpperBuff(searchFor, strlenW(colonpos+1));
1824 /* Handle wildcard case */
1825 if (*(colonpos+1) == '*') {
1826 /* Search for string to replace */
1827 found = strstrW(searchIn, searchFor+1);
1829 if (found) {
1830 /* Do replacement */
1831 strcpyW(start, replacewith);
1832 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1833 strcatW(start, s);
1834 free(s);
1835 } else {
1836 /* Copy as it */
1837 strcpyW(start, thisVarContents);
1838 strcatW(start, s);
1841 } else {
1842 /* Loop replacing all instances */
1843 WCHAR *lastFound = searchIn;
1844 WCHAR *outputposn = start;
1846 *start = 0x00;
1847 while ((found = strstrW(lastFound, searchFor))) {
1848 lstrcpynW(outputposn,
1849 thisVarContents + (lastFound-searchIn),
1850 (found - lastFound)+1);
1851 outputposn = outputposn + (found - lastFound);
1852 strcatW(outputposn, replacewith);
1853 outputposn = outputposn + strlenW(replacewith);
1854 lastFound = found + strlenW(searchFor);
1856 strcatW(outputposn,
1857 thisVarContents + (lastFound-searchIn));
1858 strcatW(outputposn, s);
1860 free(searchIn);
1861 free(searchFor);
1862 return start;
1864 return start+1;
1867 /*************************************************************************
1868 * WCMD_LoadMessage
1869 * Load a string from the resource file, handling any error
1870 * Returns string retrieved from resource file
1872 WCHAR *WCMD_LoadMessage(UINT id) {
1873 static WCHAR msg[2048];
1874 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1876 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1877 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1878 strcpyW(msg, failedMsg);
1880 return msg;
1883 /*************************************************************************
1884 * WCMD_strdupW
1885 * A wide version of strdup as its missing from unicode.h
1887 WCHAR *WCMD_strdupW(WCHAR *input) {
1888 int len=strlenW(input)+1;
1889 /* Note: Use malloc not HeapAlloc to emulate strdup */
1890 WCHAR *result = malloc(len * sizeof(WCHAR));
1891 memcpy(result, input, len * sizeof(WCHAR));
1892 return result;
1895 /***************************************************************************
1896 * WCMD_Readfile
1898 * Read characters in from a console/file, returning result in Unicode
1899 * with signature identical to ReadFile
1901 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1902 LPDWORD charsRead, const LPOVERLAPPED unused) {
1904 BOOL res;
1906 /* Try to read from console as Unicode */
1907 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1909 /* If reading from console has failed we assume its file
1910 i/o so read in and convert from OEM codepage */
1911 if (!res) {
1913 DWORD numRead;
1915 * Allocate buffer to use when reading from file. Not freed
1917 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1918 MAX_WRITECONSOLE_SIZE);
1919 if (!output_bufA) {
1920 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1921 return 0;
1924 /* Read from file (assume OEM codepage) */
1925 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1927 /* Convert from OEM */
1928 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1929 intoBuf, maxChars);
1932 return res;
1935 /***************************************************************************
1936 * WCMD_DumpCommands
1938 * Domps out the parsed command line to ensure syntax is correct
1940 void WCMD_DumpCommands(CMD_LIST *commands) {
1941 WCHAR buffer[MAXSTRING];
1942 CMD_LIST *thisCmd = commands;
1943 const WCHAR fmt[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1944 '%','p',' ','%','s',' ','R','e','d','i','r',':',
1945 '%','s','\0'};
1947 WINE_TRACE("Parsed line:\n");
1948 while (thisCmd != NULL) {
1949 sprintfW(buffer, fmt,
1950 thisCmd,
1951 thisCmd->isAmphersand?'Y':'N',
1952 thisCmd->bracketDepth,
1953 thisCmd->nextcommand,
1954 thisCmd->command,
1955 thisCmd->redirects);
1956 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1957 thisCmd = thisCmd->nextcommand;
1961 /***************************************************************************
1962 * WCMD_addCommand
1964 * Adds a command to the current command list
1966 void WCMD_addCommand(WCHAR *command, int *commandLen,
1967 WCHAR *redirs, int *redirLen,
1968 WCHAR **copyTo, int **copyToLen,
1969 BOOL isAmphersand, int curDepth,
1970 CMD_LIST **lastEntry, CMD_LIST **output) {
1972 CMD_LIST *thisEntry = NULL;
1974 /* Allocate storage for command */
1975 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1977 /* Copy in the command */
1978 if (command) {
1979 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1980 (*commandLen+1) * sizeof(WCHAR));
1981 memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1982 thisEntry->command[*commandLen] = 0x00;
1984 /* Copy in the redirects */
1985 thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1986 (*redirLen+1) * sizeof(WCHAR));
1987 memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1988 thisEntry->redirects[*redirLen] = 0x00;
1990 /* Reset the lengths */
1991 *commandLen = 0;
1992 *redirLen = 0;
1993 *copyToLen = commandLen;
1994 *copyTo = command;
1996 } else {
1997 thisEntry->command = NULL;
2000 /* Fill in other fields */
2001 thisEntry->nextcommand = NULL;
2002 thisEntry->isAmphersand = isAmphersand;
2003 thisEntry->bracketDepth = curDepth;
2004 if (*lastEntry) {
2005 (*lastEntry)->nextcommand = thisEntry;
2006 } else {
2007 *output = thisEntry;
2009 *lastEntry = thisEntry;
2012 /***************************************************************************
2013 * WCMD_ReadAndParseLine
2015 * Either uses supplied input or
2016 * Reads a file from the handle, and then...
2017 * Parse the text buffer, spliting into separate commands
2018 * - unquoted && strings split 2 commands but the 2nd is flagged as
2019 * following an &&
2020 * - ( as the first character just ups the bracket depth
2021 * - unquoted ) when bracket depth > 0 terminates a bracket and
2022 * adds a CMD_LIST structure with null command
2023 * - Anything else gets put into the command string (including
2024 * redirects)
2026 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
2028 WCHAR *curPos;
2029 BOOL inQuotes = FALSE;
2030 WCHAR curString[MAXSTRING];
2031 int curStringLen = 0;
2032 WCHAR curRedirs[MAXSTRING];
2033 int curRedirsLen = 0;
2034 WCHAR *curCopyTo;
2035 int *curLen;
2036 int curDepth = 0;
2037 CMD_LIST *lastEntry = NULL;
2038 BOOL isAmphersand = FALSE;
2039 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
2040 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
2041 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
2042 const WCHAR ifCmd[] = {'i','f',' ','\0'};
2043 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
2044 BOOL inRem = FALSE;
2045 BOOL inFor = FALSE;
2046 BOOL inIn = FALSE;
2047 BOOL inIf = FALSE;
2048 BOOL inElse= FALSE;
2049 BOOL onlyWhiteSpace = FALSE;
2050 BOOL lastWasWhiteSpace = FALSE;
2051 BOOL lastWasDo = FALSE;
2052 BOOL lastWasIn = FALSE;
2053 BOOL lastWasElse = FALSE;
2054 BOOL lastWasRedirect = TRUE;
2056 /* Allocate working space for a command read from keyboard, file etc */
2057 if (!extraSpace)
2058 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
2060 /* If initial command read in, use that, otherwise get input from handle */
2061 if (optionalcmd != NULL) {
2062 strcpyW(extraSpace, optionalcmd);
2063 } else if (readFrom == INVALID_HANDLE_VALUE) {
2064 WINE_FIXME("No command nor handle supplied\n");
2065 } else {
2066 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
2068 curPos = extraSpace;
2070 /* Handle truncated input - issue warning */
2071 if (strlenW(extraSpace) == MAXSTRING -1) {
2072 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
2073 WCMD_output_asis(extraSpace);
2074 WCMD_output_asis(newline);
2077 /* Replace env vars if in a batch context */
2078 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2080 /* Start with an empty string, copying to the command string */
2081 curStringLen = 0;
2082 curRedirsLen = 0;
2083 curCopyTo = curString;
2084 curLen = &curStringLen;
2085 lastWasRedirect = FALSE; /* Required for eg spaces between > and filename */
2087 /* Parse every character on the line being processed */
2088 while (*curPos != 0x00) {
2090 WCHAR thisChar;
2092 /* Debugging AID:
2093 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
2094 lastWasWhiteSpace, onlyWhiteSpace);
2097 /* Certain commands need special handling */
2098 if (curStringLen == 0 && curCopyTo == curString) {
2099 const WCHAR forDO[] = {'d','o',' ','\0'};
2101 /* If command starts with 'rem', ignore any &&, ( etc */
2102 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2103 curPos, 4, remCmd, -1) == 2) {
2104 inRem = TRUE;
2106 /* If command starts with 'for', handle ('s mid line after IN or DO */
2107 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2108 curPos, 4, forCmd, -1) == 2) {
2109 inFor = TRUE;
2111 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
2112 is only true in the command portion of the IF statement, but this
2113 should suffice for now
2114 FIXME: Silly syntax like "if 1(==1( (
2115 echo they equal
2116 )" will be parsed wrong */
2117 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2118 curPos, 3, ifCmd, -1) == 2) {
2119 inIf = TRUE;
2121 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2122 curPos, 5, ifElse, -1) == 2) {
2123 inElse = TRUE;
2124 lastWasElse = TRUE;
2125 onlyWhiteSpace = TRUE;
2126 memcpy(&curCopyTo[*curLen], curPos, 5*sizeof(WCHAR));
2127 (*curLen)+=5;
2128 curPos+=5;
2129 continue;
2131 /* In a for loop, the DO command will follow a close bracket followed by
2132 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2133 is then 0, and all whitespace is skipped */
2134 } else if (inFor &&
2135 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2136 curPos, 3, forDO, -1) == 2)) {
2137 WINE_TRACE("Found DO\n");
2138 lastWasDo = TRUE;
2139 onlyWhiteSpace = TRUE;
2140 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2141 (*curLen)+=3;
2142 curPos+=3;
2143 continue;
2145 } else if (curCopyTo == curString) {
2147 /* Special handling for the 'FOR' command */
2148 if (inFor && lastWasWhiteSpace) {
2149 const WCHAR forIN[] = {'i','n',' ','\0'};
2151 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2153 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2154 curPos, 3, forIN, -1) == 2) {
2155 WINE_TRACE("Found IN\n");
2156 lastWasIn = TRUE;
2157 onlyWhiteSpace = TRUE;
2158 memcpy(&curCopyTo[*curLen], curPos, 3*sizeof(WCHAR));
2159 (*curLen)+=3;
2160 curPos+=3;
2161 continue;
2166 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2167 so just use the default processing ie skip character specific
2168 matching below */
2169 if (!inRem) thisChar = *curPos;
2170 else thisChar = 'X'; /* Character with no special processing */
2172 lastWasWhiteSpace = FALSE; /* Will be reset below */
2174 switch (thisChar) {
2176 case '=': /* drop through - ignore token delimiters at the start of a command */
2177 case ',': /* drop through - ignore token delimiters at the start of a command */
2178 case '\t':/* drop through - ignore token delimiters at the start of a command */
2179 case ' ':
2180 /* If a redirect in place, it ends here */
2181 if (!inQuotes && !lastWasRedirect) {
2183 /* If finishing off a redirect, add a whitespace delimiter */
2184 if (curCopyTo == curRedirs) {
2185 curCopyTo[(*curLen)++] = ' ';
2187 curCopyTo = curString;
2188 curLen = &curStringLen;
2190 if (*curLen > 0) {
2191 curCopyTo[(*curLen)++] = *curPos;
2194 /* Remember just processed whitespace */
2195 lastWasWhiteSpace = TRUE;
2197 break;
2199 case '>': /* drop through - handle redirect chars the same */
2200 case '<':
2201 /* Make a redirect start here */
2202 if (!inQuotes) {
2203 curCopyTo = curRedirs;
2204 curLen = &curRedirsLen;
2205 lastWasRedirect = TRUE;
2208 /* See if 1>, 2> etc, in which case we have some patching up
2209 to do */
2210 if (curPos != extraSpace &&
2211 *(curPos-1)>='1' && *(curPos-1)<='9') {
2213 curStringLen--;
2214 curString[curStringLen] = 0x00;
2215 curCopyTo[(*curLen)++] = *(curPos-1);
2218 curCopyTo[(*curLen)++] = *curPos;
2219 break;
2221 case '|': /* Pipe character only if not || */
2222 if (!inQuotes && *(curPos++) == '|') {
2224 /* || is an alternative form of && but runs regardless */
2226 /* If finishing off a redirect, add a whitespace delimiter */
2227 if (curCopyTo == curRedirs) {
2228 curCopyTo[(*curLen)++] = ' ';
2231 /* If a redirect in place, it ends here */
2232 curCopyTo = curString;
2233 curLen = &curStringLen;
2234 curCopyTo[(*curLen)++] = *curPos;
2235 lastWasRedirect = FALSE;
2237 } else if (inQuotes) {
2238 curCopyTo[(*curLen)++] = *curPos;
2239 lastWasRedirect = FALSE;
2241 } else {
2242 /* Make a redirect start here */
2243 curCopyTo = curRedirs;
2244 curLen = &curRedirsLen;
2245 curCopyTo[(*curLen)++] = *curPos;
2246 lastWasRedirect = TRUE;
2248 break;
2251 case '"': inQuotes = !inQuotes;
2252 curCopyTo[(*curLen)++] = *curPos;
2253 lastWasRedirect = FALSE;
2254 break;
2256 case '(': /* If a '(' is the first non whitespace in a command portion
2257 ie start of line or just after &&, then we read until an
2258 unquoted ) is found */
2259 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2260 ", for(%d, In:%d, Do:%d)"
2261 ", if(%d, else:%d, lwe:%d)\n",
2262 *curLen, inQuotes,
2263 onlyWhiteSpace,
2264 inFor, lastWasIn, lastWasDo,
2265 inIf, inElse, lastWasElse);
2266 lastWasRedirect = FALSE;
2268 /* Ignore open brackets inside the for set */
2269 if (*curLen == 0 && !inIn) {
2270 curDepth++;
2272 /* If in quotes, ignore brackets */
2273 } else if (inQuotes) {
2274 curCopyTo[(*curLen)++] = *curPos;
2276 /* In a FOR loop, an unquoted '(' may occur straight after
2277 IN or DO
2278 In an IF statement just handle it regardless as we don't
2279 parse the operands
2280 In an ELSE statement, only allow it straight away after
2281 the ELSE and whitespace
2283 } else if (inIf ||
2284 (inElse && lastWasElse && onlyWhiteSpace) ||
2285 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2287 /* If entering into an 'IN', set inIn */
2288 if (inFor && lastWasIn && onlyWhiteSpace) {
2289 WINE_TRACE("Inside an IN\n");
2290 inIn = TRUE;
2293 /* Add the current command */
2294 WCMD_addCommand(curString, &curStringLen,
2295 curRedirs, &curRedirsLen,
2296 &curCopyTo, &curLen,
2297 isAmphersand, curDepth,
2298 &lastEntry, output);
2300 curDepth++;
2301 } else {
2302 curCopyTo[(*curLen)++] = *curPos;
2304 break;
2306 case '&': if (!inQuotes && *(curPos+1) == '&') {
2307 curPos++; /* Skip other & */
2308 lastWasRedirect = FALSE;
2310 /* Add an entry to the command list */
2311 if (curStringLen > 0) {
2313 /* Add the current command */
2314 WCMD_addCommand(curString, &curStringLen,
2315 curRedirs, &curRedirsLen,
2316 &curCopyTo, &curLen,
2317 isAmphersand, curDepth,
2318 &lastEntry, output);
2321 isAmphersand = TRUE;
2322 } else {
2323 curCopyTo[(*curLen)++] = *curPos;
2325 break;
2327 case ')': if (!inQuotes && curDepth > 0) {
2328 lastWasRedirect = FALSE;
2330 /* Add the current command if there is one */
2331 if (curStringLen) {
2333 /* Add the current command */
2334 WCMD_addCommand(curString, &curStringLen,
2335 curRedirs, &curRedirsLen,
2336 &curCopyTo, &curLen,
2337 isAmphersand, curDepth,
2338 &lastEntry, output);
2341 /* Add an empty entry to the command list */
2342 isAmphersand = FALSE;
2343 WCMD_addCommand(NULL, &curStringLen,
2344 curRedirs, &curRedirsLen,
2345 &curCopyTo, &curLen,
2346 isAmphersand, curDepth,
2347 &lastEntry, output);
2348 curDepth--;
2350 /* Leave inIn if necessary */
2351 if (inIn) inIn = FALSE;
2352 } else {
2353 curCopyTo[(*curLen)++] = *curPos;
2355 break;
2356 default:
2357 lastWasRedirect = FALSE;
2358 curCopyTo[(*curLen)++] = *curPos;
2361 curPos++;
2363 /* At various times we need to know if we have only skipped whitespace,
2364 so reset this variable and then it will remain true until a non
2365 whitespace is found */
2366 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2368 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2369 if (!lastWasWhiteSpace) {
2370 lastWasIn = lastWasDo = FALSE;
2373 /* If we have reached the end, add this command into the list */
2374 if (*curPos == 0x00 && *curLen > 0) {
2376 /* Add an entry to the command list */
2377 WCMD_addCommand(curString, &curStringLen,
2378 curRedirs, &curRedirsLen,
2379 &curCopyTo, &curLen,
2380 isAmphersand, curDepth,
2381 &lastEntry, output);
2384 /* If we have reached the end of the string, see if bracketing outstanding */
2385 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2386 inRem = FALSE;
2387 isAmphersand = FALSE;
2388 inQuotes = FALSE;
2389 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2391 /* Read more, skipping any blank lines */
2392 while (*extraSpace == 0x00) {
2393 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2394 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2396 curPos = extraSpace;
2397 if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2401 /* Dump out the parsed output */
2402 WCMD_DumpCommands(*output);
2404 return extraSpace;
2407 /***************************************************************************
2408 * WCMD_process_commands
2410 * Process all the commands read in so far
2412 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2413 WCHAR *var, WCHAR *val) {
2415 int bdepth = -1;
2417 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2419 /* Loop through the commands, processing them one by one */
2420 while (thisCmd) {
2422 CMD_LIST *origCmd = thisCmd;
2424 /* If processing one bracket only, and we find the end bracket
2425 entry (or less), return */
2426 if (oneBracket && !thisCmd->command &&
2427 bdepth <= thisCmd->bracketDepth) {
2428 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2429 thisCmd, thisCmd->nextcommand);
2430 return thisCmd->nextcommand;
2433 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2434 about them and it will be handled in there)
2435 Also, skip over any batch labels (eg. :fred) */
2436 if (thisCmd->command && thisCmd->command[0] != ':') {
2438 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2440 if (strchrW(thisCmd->redirects,'|') != NULL) {
2441 WCMD_pipe (&thisCmd, var, val);
2442 } else {
2443 WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2447 /* Step on unless the command itself already stepped on */
2448 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2450 return NULL;
2453 /***************************************************************************
2454 * WCMD_free_commands
2456 * Frees the storage held for a parsed command line
2457 * - This is not done in the process_commands, as eventually the current
2458 * pointer will be modified within the commands, and hence a single free
2459 * routine is simpler
2461 void WCMD_free_commands(CMD_LIST *cmds) {
2463 /* Loop through the commands, freeing them one by one */
2464 while (cmds) {
2465 CMD_LIST *thisCmd = cmds;
2466 cmds = cmds->nextcommand;
2467 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2468 HeapFree(GetProcessHeap(), 0, thisCmd);