Release 0.9.39.
[wine/gsoc-2012-control.git] / programs / cmd / wcmdmain.c
blob3f4cdd0b11ba8d956a2075e757ecd0f3e70ff86e
1 /*
2 * CMD - Wine-compatible command line interface.
4 * Copyright (C) 1999 - 2001 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * FIXME:
23 * - Cannot handle parameters in quotes
24 * - Lots of functionality missing from builtins
27 #include "config.h"
28 #include "wcmd.h"
29 #include "wine/debug.h"
31 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
33 const WCHAR inbuilt[][10] = {
34 {'A','T','T','R','I','B','\0'},
35 {'C','A','L','L','\0'},
36 {'C','D','\0'},
37 {'C','H','D','I','R','\0'},
38 {'C','L','S','\0'},
39 {'C','O','P','Y','\0'},
40 {'C','T','T','Y','\0'},
41 {'D','A','T','E','\0'},
42 {'D','E','L','\0'},
43 {'D','I','R','\0'},
44 {'E','C','H','O','\0'},
45 {'E','R','A','S','E','\0'},
46 {'F','O','R','\0'},
47 {'G','O','T','O','\0'},
48 {'H','E','L','P','\0'},
49 {'I','F','\0'},
50 {'L','A','B','E','L','\0'},
51 {'M','D','\0'},
52 {'M','K','D','I','R','\0'},
53 {'M','O','V','E','\0'},
54 {'P','A','T','H','\0'},
55 {'P','A','U','S','E','\0'},
56 {'P','R','O','M','P','T','\0'},
57 {'R','E','M','\0'},
58 {'R','E','N','\0'},
59 {'R','E','N','A','M','E','\0'},
60 {'R','D','\0'},
61 {'R','M','D','I','R','\0'},
62 {'S','E','T','\0'},
63 {'S','H','I','F','T','\0'},
64 {'T','I','M','E','\0'},
65 {'T','I','T','L','E','\0'},
66 {'T','Y','P','E','\0'},
67 {'V','E','R','I','F','Y','\0'},
68 {'V','E','R','\0'},
69 {'V','O','L','\0'},
70 {'E','N','D','L','O','C','A','L','\0'},
71 {'S','E','T','L','O','C','A','L','\0'},
72 {'P','U','S','H','D','\0'},
73 {'P','O','P','D','\0'},
74 {'A','S','S','O','C','\0'},
75 {'C','O','L','O','R','\0'},
76 {'F','T','Y','P','E','\0'},
77 {'M','O','R','E','\0'},
78 {'E','X','I','T','\0'}
81 HINSTANCE hinst;
82 DWORD errorlevel;
83 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
84 static int opt_c, opt_k, opt_s;
85 const WCHAR newline[] = {'\n','\0'};
86 static const WCHAR equalsW[] = {'=','\0'};
87 WCHAR anykey[100];
88 WCHAR version_string[100];
89 WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
90 BATCH_CONTEXT *context = NULL;
91 extern struct env_stack *pushd_directories;
92 static const WCHAR *pagedMessage = NULL;
93 static char *output_bufA = NULL;
94 #define MAX_WRITECONSOLE_SIZE 65535
95 BOOL unicodePipes = FALSE;
97 static WCHAR *WCMD_expand_envvar(WCHAR *start);
99 /*****************************************************************************
100 * Main entry point. This is a console application so we have a main() not a
101 * winmain().
104 int wmain (int argc, WCHAR *argvW[])
106 int args;
107 WCHAR *cmd = NULL;
108 WCHAR string[1024];
109 WCHAR envvar[4];
110 DWORD count;
111 HANDLE h;
112 int opt_q;
113 int opt_t = 0;
114 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
115 'b','a','t','\0'};
116 char ansiVersion[100];
118 /* Pre initialize some messages */
119 strcpy(ansiVersion, PACKAGE_VERSION);
120 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
121 wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
122 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
124 args = argc;
125 opt_c=opt_k=opt_q=opt_s=0;
126 while (args > 0)
128 WCHAR c;
129 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
130 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
131 argvW++;
132 args--;
133 continue;
136 c=(*argvW)[1];
137 if (tolowerW(c)=='c') {
138 opt_c=1;
139 } else if (tolowerW(c)=='q') {
140 opt_q=1;
141 } else if (tolowerW(c)=='k') {
142 opt_k=1;
143 } else if (tolowerW(c)=='s') {
144 opt_s=1;
145 } else if (tolowerW(c)=='a') {
146 unicodePipes=FALSE;
147 } else if (tolowerW(c)=='u') {
148 unicodePipes=TRUE;
149 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
150 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
151 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
152 /* Ignored for compatibility with Windows */
155 if ((*argvW)[2]==0) {
156 argvW++;
157 args--;
159 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
161 *argvW+=2;
164 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
165 break;
168 if (opt_q) {
169 const WCHAR eoff[] = {'O','F','F','\0'};
170 WCMD_echo(eoff);
173 if (opt_c || opt_k) {
174 int len,qcount;
175 WCHAR** arg;
176 int argsLeft;
177 WCHAR* p;
179 /* opt_s left unflagged if the command starts with and contains exactly
180 * one quoted string (exactly two quote characters). The quoted string
181 * must be an executable name that has whitespace and must not have the
182 * following characters: &<>()@^| */
184 /* Build the command to execute */
185 len = 0;
186 qcount = 0;
187 argsLeft = args;
188 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
190 int has_space,bcount;
191 WCHAR* a;
193 has_space=0;
194 bcount=0;
195 a=*arg;
196 if( !*a ) has_space=1;
197 while (*a!='\0') {
198 if (*a=='\\') {
199 bcount++;
200 } else {
201 if (*a==' ' || *a=='\t') {
202 has_space=1;
203 } else if (*a=='"') {
204 /* doubling of '\' preceding a '"',
205 * plus escaping of said '"'
207 len+=2*bcount+1;
208 qcount++;
210 bcount=0;
212 a++;
214 len+=(a-*arg) + 1; /* for the separating space */
215 if (has_space)
217 len+=2; /* for the quotes */
218 qcount+=2;
222 if (qcount!=2)
223 opt_s=1;
225 /* check argvW[0] for a space and invalid characters */
226 if (!opt_s) {
227 opt_s=1;
228 p=*argvW;
229 while (*p!='\0') {
230 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
231 || *p=='@' || *p=='^' || *p=='|') {
232 opt_s=1;
233 break;
235 if (*p==' ')
236 opt_s=0;
237 p++;
241 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
242 if (!cmd)
243 exit(1);
245 p = cmd;
246 argsLeft = args;
247 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
249 int has_space,has_quote;
250 WCHAR* a;
252 /* Check for quotes and spaces in this argument */
253 has_space=has_quote=0;
254 a=*arg;
255 if( !*a ) has_space=1;
256 while (*a!='\0') {
257 if (*a==' ' || *a=='\t') {
258 has_space=1;
259 if (has_quote)
260 break;
261 } else if (*a=='"') {
262 has_quote=1;
263 if (has_space)
264 break;
266 a++;
269 /* Now transfer it to the command line */
270 if (has_space)
271 *p++='"';
272 if (has_quote) {
273 int bcount;
274 WCHAR* a;
276 bcount=0;
277 a=*arg;
278 while (*a!='\0') {
279 if (*a=='\\') {
280 *p++=*a;
281 bcount++;
282 } else {
283 if (*a=='"') {
284 int i;
286 /* Double all the '\\' preceding this '"', plus one */
287 for (i=0;i<=bcount;i++)
288 *p++='\\';
289 *p++='"';
290 } else {
291 *p++=*a;
293 bcount=0;
295 a++;
297 } else {
298 strcpyW(p,*arg);
299 p+=strlenW(*arg);
301 if (has_space)
302 *p++='"';
303 *p++=' ';
305 if (p > cmd)
306 p--; /* remove last space */
307 *p = '\0';
309 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
311 /* strip first and last quote characters if opt_s; check for invalid
312 * executable is done later */
313 if (opt_s && *cmd=='\"')
314 WCMD_opt_s_strip_quotes(cmd);
317 if (opt_c) {
318 /* If we do a "wcmd /c command", we don't want to allocate a new
319 * console since the command returns immediately. Rather, we use
320 * the currently allocated input and output handles. This allows
321 * us to pipe to and read from the command interpreter.
323 if (strchrW(cmd,'|') != NULL)
324 WCMD_pipe(cmd);
325 else
326 WCMD_process_command(cmd);
327 HeapFree(GetProcessHeap(), 0, cmd);
328 return errorlevel;
331 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
332 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
333 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
335 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
336 if (opt_t) {
337 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
338 defaultColor = opt_t & 0xFF;
339 param1[0] = 0x00;
340 WCMD_color();
342 } else {
343 /* Check HKCU\Software\Microsoft\Command Processor
344 Then HKLM\Software\Microsoft\Command Processor
345 for defaultcolour value
346 Note Can be supplied as DWORD or REG_SZ
347 Note2 When supplied as REG_SZ it's in decimal!!! */
348 HKEY key;
349 DWORD type;
350 DWORD value=0, size=4;
351 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
352 'M','i','c','r','o','s','o','f','t','\\',
353 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
354 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
356 if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
357 0, KEY_READ, &key) == ERROR_SUCCESS) {
358 WCHAR strvalue[4];
360 /* See if DWORD or REG_SZ */
361 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
362 NULL, NULL) == ERROR_SUCCESS) {
363 if (type == REG_DWORD) {
364 size = sizeof(DWORD);
365 RegQueryValueEx(key, dfltColorW, NULL, NULL,
366 (LPBYTE)&value, &size);
367 } else if (type == REG_SZ) {
368 size = sizeof(strvalue)/sizeof(WCHAR);
369 RegQueryValueEx(key, dfltColorW, NULL, NULL,
370 (LPBYTE)strvalue, &size);
371 value = strtoulW(strvalue, NULL, 10);
376 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
377 0, KEY_READ, &key) == ERROR_SUCCESS) {
378 WCHAR strvalue[4];
380 /* See if DWORD or REG_SZ */
381 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
382 NULL, NULL) == ERROR_SUCCESS) {
383 if (type == REG_DWORD) {
384 size = sizeof(DWORD);
385 RegQueryValueEx(key, dfltColorW, NULL, NULL,
386 (LPBYTE)&value, &size);
387 } else if (type == REG_SZ) {
388 size = sizeof(strvalue)/sizeof(WCHAR);
389 RegQueryValueEx(key, dfltColorW, NULL, NULL,
390 (LPBYTE)strvalue, &size);
391 value = strtoulW(strvalue, NULL, 10);
396 /* If one found, set the screen to that colour */
397 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
398 defaultColor = value & 0xFF;
399 param1[0] = 0x00;
400 WCMD_color();
405 /* Save cwd into appropriate env var */
406 GetCurrentDirectory(1024, string);
407 if (IsCharAlpha(string[0]) && string[1] == ':') {
408 static const WCHAR fmt[] = {'=','%','c',':','\0'};
409 wsprintf(envvar, fmt, string[0]);
410 SetEnvironmentVariable(envvar, string);
413 if (opt_k) {
414 WCMD_process_command(cmd);
415 HeapFree(GetProcessHeap(), 0, cmd);
419 * If there is an AUTOEXEC.BAT file, try to execute it.
422 GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
423 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
424 if (h != INVALID_HANDLE_VALUE) {
425 CloseHandle (h);
426 #if 0
427 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
428 #endif
432 * Loop forever getting commands and executing them.
435 WCMD_version ();
436 while (TRUE) {
437 WCMD_show_prompt ();
438 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
439 sizeof(string)/sizeof(WCHAR), &count, NULL);
440 if (count > 1) {
441 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
442 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
443 if (strlenW (string) != 0) {
444 if (strchrW(string,'|') != NULL) {
445 WCMD_pipe (string);
447 else {
448 WCMD_process_command (string);
456 /*****************************************************************************
457 * Process one command. If the command is EXIT this routine does not return.
458 * We will recurse through here executing batch files.
462 void WCMD_process_command (WCHAR *command)
464 WCHAR *cmd, *p, *s, *t, *redir;
465 int status, i;
466 DWORD count, creationDisposition;
467 HANDLE h;
468 WCHAR *whichcmd;
469 SECURITY_ATTRIBUTES sa;
470 WCHAR *new_cmd;
471 WCHAR *first_redir = NULL;
472 HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
473 INVALID_HANDLE_VALUE,
474 INVALID_HANDLE_VALUE};
475 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
476 STD_OUTPUT_HANDLE,
477 STD_ERROR_HANDLE};
479 /* Move copy of the command onto the heap so it can be expanded */
480 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
481 strcpyW(new_cmd, command);
483 /* For commands in a context (batch program): */
484 /* Expand environment variables in a batch file %{0-9} first */
485 /* including support for any ~ modifiers */
486 /* Additionally: */
487 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
488 /* names allowing environment variable overrides */
489 /* NOTE: To support the %PATH:xxx% syntax, also perform */
490 /* manual expansion of environment variables here */
492 p = new_cmd;
493 while ((p = strchrW(p, '%'))) {
494 i = *(p+1) - '0';
496 /* Replace %~ modifications if in batch program */
497 if (context && *(p+1) == '~') {
498 WCMD_HandleTildaModifiers(&p, NULL);
499 p++;
501 /* Replace use of %0...%9 if in batch program*/
502 } else if (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 (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 {
520 p = WCMD_expand_envvar(p);
523 cmd = new_cmd;
525 /* In a batch program, unknown variables are replace by nothing */
526 /* so remove any remaining %var% */
527 if (context) {
528 p = cmd;
529 while ((p = strchrW(p, '%'))) {
530 s = strchrW(p+1, '%');
531 if (!s) {
532 *p=0x00;
533 } else {
534 t = WCMD_strdupW(s+1);
535 strcpyW(p, t);
536 free(t);
540 /* Show prompt before batch line IF echo is on and in batch program */
541 if (echo_mode && (cmd[0] != '@')) {
542 WCMD_show_prompt();
543 WCMD_output_asis ( cmd);
544 WCMD_output_asis ( newline);
549 * Changing default drive has to be handled as a special case.
552 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
553 WCHAR envvar[5];
554 WCHAR dir[MAX_PATH];
556 /* According to MSDN CreateProcess docs, special env vars record
557 the current directory on each drive, in the form =C:
558 so see if one specified, and if so go back to it */
559 strcpyW(envvar, equalsW);
560 strcatW(envvar, cmd);
561 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
562 static const WCHAR fmt[] = {'%','s','\\','\0'};
563 wsprintf(cmd, fmt, cmd);
565 status = SetCurrentDirectory (cmd);
566 if (!status) WCMD_print_error ();
567 HeapFree( GetProcessHeap(), 0, cmd );
568 return;
571 sa.nLength = sizeof(sa);
572 sa.lpSecurityDescriptor = NULL;
573 sa.bInheritHandle = TRUE;
576 * Redirect stdin, stdout and/or stderr if required.
579 if ((p = strchrW(cmd,'<')) != NULL) {
580 if (first_redir == NULL) first_redir = p;
581 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
582 FILE_ATTRIBUTE_NORMAL, NULL);
583 if (h == INVALID_HANDLE_VALUE) {
584 WCMD_print_error ();
585 HeapFree( GetProcessHeap(), 0, cmd );
586 return;
588 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
589 SetStdHandle (STD_INPUT_HANDLE, h);
592 /* Scan the whole command looking for > and 2> */
593 redir = cmd;
594 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
595 int handle = 0;
597 if (*(p-1)!='2') {
598 if (first_redir == NULL) first_redir = p;
599 handle = 1;
600 } else {
601 if (first_redir == NULL) first_redir = (p-1);
602 handle = 2;
605 p++;
606 if ('>' == *p) {
607 creationDisposition = OPEN_ALWAYS;
608 p++;
610 else {
611 creationDisposition = CREATE_ALWAYS;
614 /* Add support for 2>&1 */
615 redir = p;
616 if (*p == '&') {
617 int idx = *(p+1) - '0';
619 if (DuplicateHandle(GetCurrentProcess(),
620 GetStdHandle(idx_stdhandles[idx]),
621 GetCurrentProcess(),
623 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
624 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
626 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
628 } else {
629 WCHAR *param = WCMD_parameter (p, 0, NULL);
630 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
631 FILE_ATTRIBUTE_NORMAL, NULL);
632 if (h == INVALID_HANDLE_VALUE) {
633 WCMD_print_error ();
634 HeapFree( GetProcessHeap(), 0, cmd );
635 return;
637 if (SetFilePointer (h, 0, NULL, FILE_END) ==
638 INVALID_SET_FILE_POINTER) {
639 WCMD_print_error ();
641 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
644 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
645 SetStdHandle (idx_stdhandles[handle], h);
648 /* Terminate the command string at <, or first 2> or > */
649 if (first_redir != NULL) *first_redir = '\0';
652 * Strip leading whitespaces, and a '@' if supplied
654 whichcmd = WCMD_strtrim_leading_spaces(cmd);
655 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
656 if (whichcmd[0] == '@') whichcmd++;
659 * Check if the command entered is internal. If it is, pass the rest of the
660 * line down to the command. If not try to run a program.
663 count = 0;
664 while (IsCharAlphaNumeric(whichcmd[count])) {
665 count++;
667 for (i=0; i<=WCMD_EXIT; i++) {
668 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
669 whichcmd, count, inbuilt[i], -1) == 2) break;
671 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
672 WCMD_parse (p, quals, param1, param2);
673 switch (i) {
675 case WCMD_ATTRIB:
676 WCMD_setshow_attrib ();
677 break;
678 case WCMD_CALL:
679 WCMD_call (p);
680 break;
681 case WCMD_CD:
682 case WCMD_CHDIR:
683 WCMD_setshow_default (p);
684 break;
685 case WCMD_CLS:
686 WCMD_clear_screen ();
687 break;
688 case WCMD_COPY:
689 WCMD_copy ();
690 break;
691 case WCMD_CTTY:
692 WCMD_change_tty ();
693 break;
694 case WCMD_DATE:
695 WCMD_setshow_date ();
696 break;
697 case WCMD_DEL:
698 case WCMD_ERASE:
699 WCMD_delete (p, TRUE);
700 break;
701 case WCMD_DIR:
702 WCMD_directory (p);
703 break;
704 case WCMD_ECHO:
705 WCMD_echo(&whichcmd[count]);
706 break;
707 case WCMD_FOR:
708 WCMD_for (p);
709 break;
710 case WCMD_GOTO:
711 WCMD_goto ();
712 break;
713 case WCMD_HELP:
714 WCMD_give_help (p);
715 break;
716 case WCMD_IF:
717 WCMD_if (p);
718 break;
719 case WCMD_LABEL:
720 WCMD_volume (1, p);
721 break;
722 case WCMD_MD:
723 case WCMD_MKDIR:
724 WCMD_create_dir ();
725 break;
726 case WCMD_MOVE:
727 WCMD_move ();
728 break;
729 case WCMD_PATH:
730 WCMD_setshow_path (p);
731 break;
732 case WCMD_PAUSE:
733 WCMD_pause ();
734 break;
735 case WCMD_PROMPT:
736 WCMD_setshow_prompt ();
737 break;
738 case WCMD_REM:
739 break;
740 case WCMD_REN:
741 case WCMD_RENAME:
742 WCMD_rename ();
743 break;
744 case WCMD_RD:
745 case WCMD_RMDIR:
746 WCMD_remove_dir (p);
747 break;
748 case WCMD_SETLOCAL:
749 WCMD_setlocal(p);
750 break;
751 case WCMD_ENDLOCAL:
752 WCMD_endlocal();
753 break;
754 case WCMD_SET:
755 WCMD_setshow_env (p);
756 break;
757 case WCMD_SHIFT:
758 WCMD_shift (p);
759 break;
760 case WCMD_TIME:
761 WCMD_setshow_time ();
762 break;
763 case WCMD_TITLE:
764 if (strlenW(&whichcmd[count]) > 0)
765 WCMD_title(&whichcmd[count+1]);
766 break;
767 case WCMD_TYPE:
768 WCMD_type (p);
769 break;
770 case WCMD_VER:
771 WCMD_version ();
772 break;
773 case WCMD_VERIFY:
774 WCMD_verify (p);
775 break;
776 case WCMD_VOL:
777 WCMD_volume (0, p);
778 break;
779 case WCMD_PUSHD:
780 WCMD_pushd(p);
781 break;
782 case WCMD_POPD:
783 WCMD_popd();
784 break;
785 case WCMD_ASSOC:
786 WCMD_assoc(p, TRUE);
787 break;
788 case WCMD_COLOR:
789 WCMD_color();
790 break;
791 case WCMD_FTYPE:
792 WCMD_assoc(p, FALSE);
793 break;
794 case WCMD_MORE:
795 WCMD_more(p);
796 break;
797 case WCMD_EXIT:
798 WCMD_exit ();
799 break;
800 default:
801 WCMD_run_program (whichcmd, 0);
803 HeapFree( GetProcessHeap(), 0, cmd );
805 /* Restore old handles */
806 for (i=0; i<3; i++) {
807 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
808 CloseHandle (GetStdHandle (idx_stdhandles[i]));
809 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
814 static void init_msvcrt_io_block(STARTUPINFO* st)
816 STARTUPINFO st_p;
817 /* fetch the parent MSVCRT info block if any, so that the child can use the
818 * same handles as its grand-father
820 st_p.cb = sizeof(STARTUPINFO);
821 GetStartupInfo(&st_p);
822 st->cbReserved2 = st_p.cbReserved2;
823 st->lpReserved2 = st_p.lpReserved2;
824 if (st_p.cbReserved2 && st_p.lpReserved2)
826 /* Override the entries for fd 0,1,2 if we happened
827 * to change those std handles (this depends on the way wcmd sets
828 * it's new input & output handles)
830 size_t sz = max(sizeof(unsigned) + (sizeof(WCHAR) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
831 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
832 if (ptr)
834 unsigned num = *(unsigned*)st_p.lpReserved2;
835 WCHAR* flags = (WCHAR*)(ptr + sizeof(unsigned));
836 HANDLE* handles = (HANDLE*)(flags + num * sizeof(WCHAR));
838 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
839 st->cbReserved2 = sz;
840 st->lpReserved2 = ptr;
842 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
843 if (num <= 0 || (flags[0] & WX_OPEN))
845 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
846 flags[0] |= WX_OPEN;
848 if (num <= 1 || (flags[1] & WX_OPEN))
850 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
851 flags[1] |= WX_OPEN;
853 if (num <= 2 || (flags[2] & WX_OPEN))
855 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
856 flags[2] |= WX_OPEN;
858 #undef WX_OPEN
863 /******************************************************************************
864 * WCMD_run_program
866 * Execute a command line as an external program. Must allow recursion.
868 * Precedence:
869 * Manual testing under windows shows PATHEXT plays a key part in this,
870 * and the search algorithm and precedence appears to be as follows.
872 * Search locations:
873 * If directory supplied on command, just use that directory
874 * If extension supplied on command, look for that explicit name first
875 * Otherwise, search in each directory on the path
876 * Precedence:
877 * If extension supplied on command, look for that explicit name first
878 * Then look for supplied name .* (even if extension supplied, so
879 * 'garbage.exe' will match 'garbage.exe.cmd')
880 * If any found, cycle through PATHEXT looking for name.exe one by one
881 * Launching
882 * Once a match has been found, it is launched - Code currently uses
883 * findexecutable to acheive this which is left untouched.
886 void WCMD_run_program (WCHAR *command, int called) {
888 WCHAR temp[MAX_PATH];
889 WCHAR pathtosearch[MAXSTRING];
890 WCHAR *pathposn;
891 WCHAR stemofsearch[MAX_PATH];
892 WCHAR *lastSlash;
893 WCHAR pathext[MAXSTRING];
894 BOOL extensionsupplied = FALSE;
895 BOOL launched = FALSE;
896 BOOL status;
897 BOOL assumeInternal = FALSE;
898 DWORD len;
899 static const WCHAR envPath[] = {'P','A','T','H','\0'};
900 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
901 static const WCHAR delims[] = {'/','\\',':','\0'};
903 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
904 if (!(*param1) && !(*param2))
905 return;
907 /* Calculate the search path and stem to search for */
908 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
909 static const WCHAR curDir[] = {'.',';','\0'};
910 strcpyW(pathtosearch, curDir);
911 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
912 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
913 static const WCHAR curDir[] = {'.','\0'};
914 strcpyW (pathtosearch, curDir);
916 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
917 strcpyW(stemofsearch, param1);
919 } else {
921 /* Convert eg. ..\fred to include a directory by removing file part */
922 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
923 lastSlash = strrchrW(pathtosearch, '\\');
924 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
925 if (lastSlash) *lastSlash = 0x00;
926 strcpyW(stemofsearch, lastSlash+1);
929 /* Now extract PATHEXT */
930 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
931 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
932 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
933 '.','c','o','m',';',
934 '.','c','m','d',';',
935 '.','e','x','e','\0'};
936 strcpyW (pathext, dfltPathExt);
939 /* Loop through the search path, dir by dir */
940 pathposn = pathtosearch;
941 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
942 wine_dbgstr_w(stemofsearch));
943 while (!launched && pathposn) {
945 WCHAR thisDir[MAX_PATH] = {'\0'};
946 WCHAR *pos = NULL;
947 BOOL found = FALSE;
948 const WCHAR slashW[] = {'\\','\0'};
950 /* Work on the first directory on the search path */
951 pos = strchrW(pathposn, ';');
952 if (pos) {
953 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
954 thisDir[(pos-pathposn)] = 0x00;
955 pathposn = pos+1;
957 } else {
958 strcpyW(thisDir, pathposn);
959 pathposn = NULL;
962 /* Since you can have eg. ..\.. on the path, need to expand
963 to full information */
964 strcpyW(temp, thisDir);
965 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
967 /* 1. If extension supplied, see if that file exists */
968 strcatW(thisDir, slashW);
969 strcatW(thisDir, stemofsearch);
970 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
972 /* 1. If extension supplied, see if that file exists */
973 if (extensionsupplied) {
974 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
975 found = TRUE;
979 /* 2. Any .* matches? */
980 if (!found) {
981 HANDLE h;
982 WIN32_FIND_DATA finddata;
983 static const WCHAR allFiles[] = {'.','*','\0'};
985 strcatW(thisDir,allFiles);
986 h = FindFirstFile(thisDir, &finddata);
987 FindClose(h);
988 if (h != INVALID_HANDLE_VALUE) {
990 WCHAR *thisExt = pathext;
992 /* 3. Yes - Try each path ext */
993 while (thisExt) {
994 WCHAR *nextExt = strchrW(thisExt, ';');
996 if (nextExt) {
997 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
998 pos[(nextExt-thisExt)] = 0x00;
999 thisExt = nextExt+1;
1000 } else {
1001 strcpyW(pos, thisExt);
1002 thisExt = NULL;
1005 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1006 found = TRUE;
1007 thisExt = NULL;
1013 /* Internal programs won't be picked up by this search, so even
1014 though not found, try one last createprocess and wait for it
1015 to complete.
1016 Note: Ideally we could tell between a console app (wait) and a
1017 windows app, but the API's for it fail in this case */
1018 if (!found && pathposn == NULL) {
1019 WINE_TRACE("ASSUMING INTERNAL\n");
1020 assumeInternal = TRUE;
1021 } else {
1022 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1025 /* Once found, launch it */
1026 if (found || assumeInternal) {
1027 STARTUPINFO st;
1028 PROCESS_INFORMATION pe;
1029 SHFILEINFO psfi;
1030 DWORD console;
1031 HINSTANCE hinst;
1032 WCHAR *ext = strrchrW( thisDir, '.' );
1033 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1034 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1036 launched = TRUE;
1038 /* Special case BAT and CMD */
1039 if (ext && !strcmpiW(ext, batExt)) {
1040 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1041 return;
1042 } else if (ext && !strcmpiW(ext, cmdExt)) {
1043 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1044 return;
1045 } else {
1047 /* thisDir contains the file to be launched, but with what?
1048 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1049 hinst = FindExecutable (thisDir, NULL, temp);
1050 if ((INT_PTR)hinst < 32)
1051 console = 0;
1052 else
1053 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1055 ZeroMemory (&st, sizeof(STARTUPINFO));
1056 st.cb = sizeof(STARTUPINFO);
1057 init_msvcrt_io_block(&st);
1059 /* Launch the process and if a CUI wait on it to complete
1060 Note: Launching internal wine processes cannot specify a full path to exe */
1061 status = CreateProcess (assumeInternal?NULL : thisDir,
1062 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1063 if ((opt_c || opt_k) && !opt_s && !status
1064 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1065 /* strip first and last quote WCHARacters and try again */
1066 WCMD_opt_s_strip_quotes(command);
1067 opt_s=1;
1068 WCMD_run_program(command, called);
1069 return;
1071 if (!status) {
1072 WCMD_print_error ();
1073 /* If a command fails to launch, it sets errorlevel 9009 - which
1074 does not seem to have any associated constant definition */
1075 errorlevel = 9009;
1076 return;
1078 if (!assumeInternal && !console) errorlevel = 0;
1079 else
1081 if (assumeInternal || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1082 GetExitCodeProcess (pe.hProcess, &errorlevel);
1083 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1085 CloseHandle(pe.hProcess);
1086 CloseHandle(pe.hThread);
1087 return;
1092 /* Not found anywhere - give up */
1093 SetLastError(ERROR_FILE_NOT_FOUND);
1094 WCMD_print_error ();
1096 /* If a command fails to launch, it sets errorlevel 9009 - which
1097 does not seem to have any associated constant definition */
1098 errorlevel = 9009;
1099 return;
1103 /******************************************************************************
1104 * WCMD_show_prompt
1106 * Display the prompt on STDout
1110 void WCMD_show_prompt (void) {
1112 int status;
1113 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1114 WCHAR *p, *q;
1115 DWORD len;
1116 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1118 len = GetEnvironmentVariable (envPrompt, prompt_string,
1119 sizeof(prompt_string)/sizeof(WCHAR));
1120 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1121 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1122 strcpyW (prompt_string, dfltPrompt);
1124 p = prompt_string;
1125 q = out_string;
1126 *q = '\0';
1127 while (*p != '\0') {
1128 if (*p != '$') {
1129 *q++ = *p++;
1130 *q = '\0';
1132 else {
1133 p++;
1134 switch (toupper(*p)) {
1135 case '$':
1136 *q++ = '$';
1137 break;
1138 case 'A':
1139 *q++ = '&';
1140 break;
1141 case 'B':
1142 *q++ = '|';
1143 break;
1144 case 'C':
1145 *q++ = '(';
1146 break;
1147 case 'D':
1148 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1149 while (*q) q++;
1150 break;
1151 case 'E':
1152 *q++ = '\E';
1153 break;
1154 case 'F':
1155 *q++ = ')';
1156 break;
1157 case 'G':
1158 *q++ = '>';
1159 break;
1160 case 'H':
1161 *q++ = '\b';
1162 break;
1163 case 'L':
1164 *q++ = '<';
1165 break;
1166 case 'N':
1167 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1168 if (status) {
1169 *q++ = curdir[0];
1171 break;
1172 case 'P':
1173 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1174 if (status) {
1175 strcatW (q, curdir);
1176 while (*q) q++;
1178 break;
1179 case 'Q':
1180 *q++ = '=';
1181 break;
1182 case 'S':
1183 *q++ = ' ';
1184 break;
1185 case 'T':
1186 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1187 while (*q) q++;
1188 break;
1189 case 'V':
1190 strcatW (q, version_string);
1191 while (*q) q++;
1192 break;
1193 case '_':
1194 *q++ = '\n';
1195 break;
1196 case '+':
1197 if (pushd_directories) {
1198 memset(q, '+', pushd_directories->u.stackdepth);
1199 q = q + pushd_directories->u.stackdepth;
1201 break;
1203 p++;
1204 *q = '\0';
1207 WCMD_output_asis (out_string);
1210 /****************************************************************************
1211 * WCMD_print_error
1213 * Print the message for GetLastError
1216 void WCMD_print_error (void) {
1217 LPVOID lpMsgBuf;
1218 DWORD error_code;
1219 int status;
1221 error_code = GetLastError ();
1222 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1223 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1224 if (!status) {
1225 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1226 error_code, GetLastError());
1227 return;
1229 WCMD_output_asis (lpMsgBuf);
1230 LocalFree ((HLOCAL)lpMsgBuf);
1231 WCMD_output_asis (newline);
1232 return;
1235 /*******************************************************************
1236 * WCMD_parse - parse a command into parameters and qualifiers.
1238 * On exit, all qualifiers are concatenated into q, the first string
1239 * not beginning with "/" is in p1 and the
1240 * second in p2. Any subsequent non-qualifier strings are lost.
1241 * Parameters in quotes are handled.
1244 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1246 int p = 0;
1248 *q = *p1 = *p2 = '\0';
1249 while (TRUE) {
1250 switch (*s) {
1251 case '/':
1252 *q++ = *s++;
1253 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1254 *q++ = toupper (*s++);
1256 *q = '\0';
1257 break;
1258 case ' ':
1259 case '\t':
1260 s++;
1261 break;
1262 case '"':
1263 s++;
1264 while ((*s != '\0') && (*s != '"')) {
1265 if (p == 0) *p1++ = *s++;
1266 else if (p == 1) *p2++ = *s++;
1267 else s++;
1269 if (p == 0) *p1 = '\0';
1270 if (p == 1) *p2 = '\0';
1271 p++;
1272 if (*s == '"') s++;
1273 break;
1274 case '\0':
1275 return;
1276 default:
1277 while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1278 if (p == 0) *p1++ = *s++;
1279 else if (p == 1) *p2++ = *s++;
1280 else s++;
1282 if (p == 0) *p1 = '\0';
1283 if (p == 1) *p2 = '\0';
1284 p++;
1289 /*******************************************************************
1290 * WCMD_output_asis_len - send output to current standard output
1292 * Output a formatted unicode string. Ideally this will go to the console
1293 * and hence required WriteConsoleW to output it, however if file i/o is
1294 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1296 static void WCMD_output_asis_len(const WCHAR *message, int len) {
1298 DWORD nOut= 0;
1299 DWORD res = 0;
1301 /* If nothing to write, return (MORE does this sometimes) */
1302 if (!len) return;
1304 /* Try to write as unicode assuming it is to a console */
1305 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1306 message, len, &nOut, NULL);
1308 /* If writing to console fails, assume its file
1309 i/o so convert to OEM codepage and output */
1310 if (!res) {
1311 BOOL usedDefaultChar = FALSE;
1312 DWORD convertedChars;
1314 if (!unicodePipes) {
1316 * Allocate buffer to use when writing to file. (Not freed, as one off)
1318 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1319 MAX_WRITECONSOLE_SIZE);
1320 if (!output_bufA) {
1321 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1322 return;
1325 /* Convert to OEM, then output */
1326 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1327 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1328 "?", &usedDefaultChar);
1329 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1330 &nOut, FALSE);
1331 } else {
1332 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message, len*sizeof(WCHAR),
1333 &nOut, FALSE);
1336 return;
1339 /*******************************************************************
1340 * WCMD_output - send output to current standard output device.
1344 void WCMD_output (const WCHAR *format, ...) {
1346 va_list ap;
1347 WCHAR string[1024];
1348 int ret;
1350 va_start(ap,format);
1351 ret = wvsprintf (string, format, ap);
1352 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1353 WINE_ERR("Output truncated in WCMD_output\n" );
1354 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1355 string[ret] = '\0';
1357 va_end(ap);
1358 WCMD_output_asis_len(string, ret);
1362 static int line_count;
1363 static int max_height;
1364 static int max_width;
1365 static BOOL paged_mode;
1366 static int numChars;
1368 void WCMD_enter_paged_mode(const WCHAR *msg)
1370 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1372 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1373 max_height = consoleInfo.dwSize.Y;
1374 max_width = consoleInfo.dwSize.X;
1375 } else {
1376 max_height = 25;
1377 max_width = 80;
1379 paged_mode = TRUE;
1380 line_count = 0;
1381 numChars = 0;
1382 pagedMessage = (msg==NULL)? anykey : msg;
1385 void WCMD_leave_paged_mode(void)
1387 paged_mode = FALSE;
1388 pagedMessage = NULL;
1391 /*******************************************************************
1392 * WCMD_output_asis - send output to current standard output device.
1393 * without formatting eg. when message contains '%'
1396 void WCMD_output_asis (const WCHAR *message) {
1397 DWORD count;
1398 const WCHAR* ptr;
1399 WCHAR string[1024];
1401 if (paged_mode) {
1402 do {
1403 ptr = message;
1404 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1405 numChars++;
1406 ptr++;
1408 if (*ptr == '\n') ptr++;
1409 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message));
1410 if (ptr) {
1411 numChars = 0;
1412 if (++line_count >= max_height - 1) {
1413 line_count = 0;
1414 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage));
1415 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1416 sizeof(string)/sizeof(WCHAR), &count, NULL);
1419 } while (((message = ptr) != NULL) && (*ptr));
1420 } else {
1421 WCMD_output_asis_len(message, lstrlen(message));
1426 /***************************************************************************
1427 * WCMD_strtrim_leading_spaces
1429 * Remove leading spaces from a string. Return a pointer to the first
1430 * non-space character. Does not modify the input string
1433 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1435 WCHAR *ptr;
1437 ptr = string;
1438 while (*ptr == ' ') ptr++;
1439 return ptr;
1442 /*************************************************************************
1443 * WCMD_strtrim_trailing_spaces
1445 * Remove trailing spaces from a string. This routine modifies the input
1446 * string by placing a null after the last non-space WCHARacter
1449 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1451 WCHAR *ptr;
1453 ptr = string + strlenW (string) - 1;
1454 while ((*ptr == ' ') && (ptr >= string)) {
1455 *ptr = '\0';
1456 ptr--;
1460 /*************************************************************************
1461 * WCMD_opt_s_strip_quotes
1463 * Remove first and last quote WCHARacters, preserving all other text
1466 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1467 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1468 while((*dest=*src) != '\0') {
1469 if (*src=='\"')
1470 lastq=dest;
1471 dest++, src++;
1473 if (lastq) {
1474 dest=lastq++;
1475 while ((*dest++=*lastq++) != 0)
1480 /*************************************************************************
1481 * WCMD_pipe
1483 * Handle pipes within a command - the DOS way using temporary files.
1486 void WCMD_pipe (WCHAR *command) {
1488 WCHAR *p;
1489 WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1490 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1491 static const WCHAR redirIn[] = {'%','s',' ','<',' ','%','s','\0'};
1492 static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1493 static const WCHAR cmdW[] = {'C','M','D','\0'};
1496 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1497 GetTempFileName (temp_path, cmdW, 0, temp_file);
1498 p = strchrW(command, '|');
1499 *p++ = '\0';
1500 wsprintf (temp_cmd, redirOut, command, temp_file);
1501 WCMD_process_command (temp_cmd);
1502 command = p;
1503 while ((p = strchrW(command, '|'))) {
1504 *p++ = '\0';
1505 GetTempFileName (temp_path, cmdW, 0, temp_file2);
1506 wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1507 WCMD_process_command (temp_cmd);
1508 DeleteFile (temp_file);
1509 strcpyW (temp_file, temp_file2);
1510 command = p;
1512 wsprintf (temp_cmd, redirIn, command, temp_file);
1513 WCMD_process_command (temp_cmd);
1514 DeleteFile (temp_file);
1517 /*************************************************************************
1518 * WCMD_expand_envvar
1520 * Expands environment variables, allowing for WCHARacter substitution
1522 static WCHAR *WCMD_expand_envvar(WCHAR *start) {
1523 WCHAR *endOfVar = NULL, *s;
1524 WCHAR *colonpos = NULL;
1525 WCHAR thisVar[MAXSTRING];
1526 WCHAR thisVarContents[MAXSTRING];
1527 WCHAR savedchar = 0x00;
1528 int len;
1530 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1531 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1532 static const WCHAR Date[] = {'D','A','T','E','\0'};
1533 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1534 static const WCHAR Time[] = {'T','I','M','E','\0'};
1535 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1536 static const WCHAR Cd[] = {'C','D','\0'};
1537 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1538 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1539 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1541 /* Find the end of the environment variable, and extract name */
1542 endOfVar = strchrW(start+1, '%');
1543 if (endOfVar == NULL) {
1544 /* In batch program, missing terminator for % and no following
1545 ':' just removes the '%' */
1546 s = WCMD_strdupW(start + 1);
1547 strcpyW (start, s);
1548 free(s);
1550 /* FIXME: Some other special conditions here depending on whether
1551 in batch, complex or not, and whether env var exists or not! */
1552 return start;
1554 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1555 thisVar[(endOfVar - start)+1] = 0x00;
1556 colonpos = strchrW(thisVar+1, ':');
1558 /* If there's complex substitution, just need %var% for now
1559 to get the expanded data to play with */
1560 if (colonpos) {
1561 *colonpos = '%';
1562 savedchar = *(colonpos+1);
1563 *(colonpos+1) = 0x00;
1566 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1568 /* Expand to contents, if unchanged, return */
1569 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1570 /* override if existing env var called that name */
1571 if ((CompareString (LOCALE_USER_DEFAULT,
1572 NORM_IGNORECASE | SORT_STRINGSORT,
1573 thisVar, 12, ErrorLvlP, -1) == 2) &&
1574 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1575 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1576 static const WCHAR fmt[] = {'%','d','\0'};
1577 wsprintf(thisVarContents, fmt, errorlevel);
1578 len = strlenW(thisVarContents);
1580 } else if ((CompareString (LOCALE_USER_DEFAULT,
1581 NORM_IGNORECASE | SORT_STRINGSORT,
1582 thisVar, 6, DateP, -1) == 2) &&
1583 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1584 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1586 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1587 NULL, thisVarContents, MAXSTRING);
1588 len = strlenW(thisVarContents);
1590 } else if ((CompareString (LOCALE_USER_DEFAULT,
1591 NORM_IGNORECASE | SORT_STRINGSORT,
1592 thisVar, 6, TimeP, -1) == 2) &&
1593 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1594 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1595 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1596 NULL, thisVarContents, MAXSTRING);
1597 len = strlenW(thisVarContents);
1599 } else if ((CompareString (LOCALE_USER_DEFAULT,
1600 NORM_IGNORECASE | SORT_STRINGSORT,
1601 thisVar, 4, CdP, -1) == 2) &&
1602 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1603 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1604 GetCurrentDirectory (MAXSTRING, thisVarContents);
1605 len = strlenW(thisVarContents);
1607 } else if ((CompareString (LOCALE_USER_DEFAULT,
1608 NORM_IGNORECASE | SORT_STRINGSORT,
1609 thisVar, 8, RandomP, -1) == 2) &&
1610 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1611 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1612 static const WCHAR fmt[] = {'%','d','\0'};
1613 wsprintf(thisVarContents, fmt, rand() % 32768);
1614 len = strlenW(thisVarContents);
1616 } else {
1618 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1619 sizeof(thisVarContents)/sizeof(WCHAR));
1622 if (len == 0)
1623 return endOfVar+1;
1625 /* In a batch program, unknown env vars are replaced with nothing,
1626 note syntax %garbage:1,3% results in anything after the ':'
1627 except the %
1628 From the command line, you just get back what you entered */
1629 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1631 /* Restore the complex part after the compare */
1632 if (colonpos) {
1633 *colonpos = ':';
1634 *(colonpos+1) = savedchar;
1637 s = WCMD_strdupW(endOfVar + 1);
1639 /* Command line - just ignore this */
1640 if (context == NULL) return endOfVar+1;
1642 /* Batch - replace unknown env var with nothing */
1643 if (colonpos == NULL) {
1644 strcpyW (start, s);
1646 } else {
1647 len = strlenW(thisVar);
1648 thisVar[len-1] = 0x00;
1649 /* If %:...% supplied, : is retained */
1650 if (colonpos == thisVar+1) {
1651 strcpyW (start, colonpos);
1652 } else {
1653 strcpyW (start, colonpos+1);
1655 strcatW (start, s);
1657 free (s);
1658 return start;
1662 /* See if we need to do complex substitution (any ':'s), if not
1663 then our work here is done */
1664 if (colonpos == NULL) {
1665 s = WCMD_strdupW(endOfVar + 1);
1666 strcpyW (start, thisVarContents);
1667 strcatW (start, s);
1668 free(s);
1669 return start;
1672 /* Restore complex bit */
1673 *colonpos = ':';
1674 *(colonpos+1) = savedchar;
1677 Handle complex substitutions:
1678 xxx=yyy (replace xxx with yyy)
1679 *xxx=yyy (replace up to and including xxx with yyy)
1680 ~x (from x WCHARs in)
1681 ~-x (from x WCHARs from the end)
1682 ~x,y (from x WCHARs in for y WCHARacters)
1683 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1686 /* ~ is substring manipulation */
1687 if (savedchar == '~') {
1689 int substrposition, substrlength = 0;
1690 WCHAR *commapos = strchrW(colonpos+2, ',');
1691 WCHAR *startCopy;
1693 substrposition = atolW(colonpos+2);
1694 if (commapos) substrlength = atolW(commapos+1);
1696 s = WCMD_strdupW(endOfVar + 1);
1698 /* Check bounds */
1699 if (substrposition >= 0) {
1700 startCopy = &thisVarContents[min(substrposition, len)];
1701 } else {
1702 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1705 if (commapos == NULL) {
1706 strcpyW (start, startCopy); /* Copy the lot */
1707 } else if (substrlength < 0) {
1709 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1710 if (copybytes > len) copybytes = len;
1711 else if (copybytes < 0) copybytes = 0;
1712 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1713 start[copybytes] = 0x00;
1714 } else {
1715 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1716 start[substrlength] = 0x00;
1719 strcatW (start, s);
1720 free(s);
1721 return start;
1723 /* search and replace manipulation */
1724 } else {
1725 WCHAR *equalspos = strstrW(colonpos, equalsW);
1726 WCHAR *replacewith = equalspos+1;
1727 WCHAR *found = NULL;
1728 WCHAR *searchIn;
1729 WCHAR *searchFor;
1731 s = WCMD_strdupW(endOfVar + 1);
1732 if (equalspos == NULL) return start+1;
1734 /* Null terminate both strings */
1735 thisVar[strlenW(thisVar)-1] = 0x00;
1736 *equalspos = 0x00;
1738 /* Since we need to be case insensitive, copy the 2 buffers */
1739 searchIn = WCMD_strdupW(thisVarContents);
1740 CharUpperBuff(searchIn, strlenW(thisVarContents));
1741 searchFor = WCMD_strdupW(colonpos+1);
1742 CharUpperBuff(searchFor, strlenW(colonpos+1));
1745 /* Handle wildcard case */
1746 if (*(colonpos+1) == '*') {
1747 /* Search for string to replace */
1748 found = strstrW(searchIn, searchFor+1);
1750 if (found) {
1751 /* Do replacement */
1752 strcpyW(start, replacewith);
1753 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1754 strcatW(start, s);
1755 free(s);
1756 } else {
1757 /* Copy as it */
1758 strcpyW(start, thisVarContents);
1759 strcatW(start, s);
1762 } else {
1763 /* Loop replacing all instances */
1764 WCHAR *lastFound = searchIn;
1765 WCHAR *outputposn = start;
1767 *start = 0x00;
1768 while ((found = strstrW(lastFound, searchFor))) {
1769 lstrcpynW(outputposn,
1770 thisVarContents + (lastFound-searchIn),
1771 (found - lastFound)+1);
1772 outputposn = outputposn + (found - lastFound);
1773 strcatW(outputposn, replacewith);
1774 outputposn = outputposn + strlenW(replacewith);
1775 lastFound = found + strlenW(searchFor);
1777 strcatW(outputposn,
1778 thisVarContents + (lastFound-searchIn));
1779 strcatW(outputposn, s);
1781 free(searchIn);
1782 free(searchFor);
1783 return start;
1785 return start+1;
1788 /*************************************************************************
1789 * WCMD_LoadMessage
1790 * Load a string from the resource file, handling any error
1791 * Returns string retrieved from resource file
1793 WCHAR *WCMD_LoadMessage(UINT id) {
1794 static WCHAR msg[2048];
1795 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1797 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1798 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1799 strcpyW(msg, failedMsg);
1801 return msg;
1804 /*************************************************************************
1805 * WCMD_strdupW
1806 * A wide version of strdup as its missing from unicode.h
1808 WCHAR *WCMD_strdupW(WCHAR *input) {
1809 int len=strlenW(input)+1;
1810 /* Note: Use malloc not HeapAlloc to emulate strdup */
1811 WCHAR *result = malloc(len * sizeof(WCHAR));
1812 memcpy(result, input, len * sizeof(WCHAR));
1813 return result;
1816 /***************************************************************************
1817 * WCMD_Readfile
1819 * Read characters in from a console/file, returning result in Unicode
1820 * with signature identical to ReadFile
1822 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1823 LPDWORD charsRead, const LPOVERLAPPED unused) {
1825 BOOL res;
1827 /* Try to read from console as Unicode */
1828 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1830 /* If reading from console has failed we assume its file
1831 i/o so read in and convert from OEM codepage */
1832 if (!res) {
1834 DWORD numRead;
1836 * Allocate buffer to use when reading from file. Not freed
1838 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1839 MAX_WRITECONSOLE_SIZE);
1840 if (!output_bufA) {
1841 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1842 return 0;
1845 /* Read from file (assume OEM codepage) */
1846 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1848 /* Convert from OEM */
1849 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1850 intoBuf, maxChars);
1853 return res;