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
23 * - Cannot handle parameters in quotes
24 * - Lots of functionality missing from builtins
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'},
37 {'C','H','D','I','R','\0'},
39 {'C','O','P','Y','\0'},
40 {'C','T','T','Y','\0'},
41 {'D','A','T','E','\0'},
44 {'E','C','H','O','\0'},
45 {'E','R','A','S','E','\0'},
47 {'G','O','T','O','\0'},
48 {'H','E','L','P','\0'},
50 {'L','A','B','E','L','\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'},
59 {'R','E','N','A','M','E','\0'},
61 {'R','M','D','I','R','\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'},
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'}
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 static const WCHAR closeBW[] = {')','\0'};
89 WCHAR version_string[100];
90 WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
91 BATCH_CONTEXT *context = NULL;
92 extern struct env_stack *pushd_directories;
93 static const WCHAR *pagedMessage = NULL;
94 static char *output_bufA = NULL;
95 #define MAX_WRITECONSOLE_SIZE 65535
96 BOOL unicodePipes = FALSE;
98 static WCHAR *WCMD_expand_envvar(WCHAR *start);
100 /*****************************************************************************
101 * Main entry point. This is a console application so we have a main() not a
105 int wmain (int argc, WCHAR *argvW[])
114 static const WCHAR autoexec[] = {'\\','a','u','t','o','e','x','e','c','.',
116 char ansiVersion[100];
117 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
119 /* Pre initialize some messages */
120 strcpy(ansiVersion, PACKAGE_VERSION);
121 MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
122 wsprintf(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
123 strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
126 opt_c=opt_k=opt_q=opt_s=0;
130 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
131 if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
138 if (tolowerW(c)=='c') {
140 } else if (tolowerW(c)=='q') {
142 } else if (tolowerW(c)=='k') {
144 } else if (tolowerW(c)=='s') {
146 } else if (tolowerW(c)=='a') {
148 } else if (tolowerW(c)=='u') {
150 } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
151 opt_t=strtoulW(&(*argvW)[3], NULL, 16);
152 } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
153 /* Ignored for compatibility with Windows */
156 if ((*argvW)[2]==0) {
160 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
165 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
170 const WCHAR eoff[] = {'O','F','F','\0'};
174 if (opt_c || opt_k) {
180 /* opt_s left unflagged if the command starts with and contains exactly
181 * one quoted string (exactly two quote characters). The quoted string
182 * must be an executable name that has whitespace and must not have the
183 * following characters: &<>()@^| */
185 /* Build the command to execute */
189 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
191 int has_space,bcount;
197 if( !*a ) has_space=1;
202 if (*a==' ' || *a=='\t') {
204 } else if (*a=='"') {
205 /* doubling of '\' preceding a '"',
206 * plus escaping of said '"'
215 len+=(a-*arg) + 1; /* for the separating space */
218 len+=2; /* for the quotes */
226 /* check argvW[0] for a space and invalid characters */
231 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
232 || *p=='@' || *p=='^' || *p=='|') {
242 cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
248 for (arg = argvW; argsLeft>0; arg++,argsLeft--)
250 int has_space,has_quote;
253 /* Check for quotes and spaces in this argument */
254 has_space=has_quote=0;
256 if( !*a ) has_space=1;
258 if (*a==' ' || *a=='\t') {
262 } else if (*a=='"') {
270 /* Now transfer it to the command line */
287 /* Double all the '\\' preceding this '"', plus one */
288 for (i=0;i<=bcount;i++)
307 p--; /* remove last space */
310 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
312 /* strip first and last quote characters if opt_s; check for invalid
313 * executable is done later */
314 if (opt_s && *cmd=='\"')
315 WCMD_opt_s_strip_quotes(cmd);
319 /* If we do a "wcmd /c command", we don't want to allocate a new
320 * console since the command returns immediately. Rather, we use
321 * the currently allocated input and output handles. This allows
322 * us to pipe to and read from the command interpreter.
325 /* Parse the command string, without reading any more input */
326 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
327 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
328 WCMD_free_commands(toExecute);
331 HeapFree(GetProcessHeap(), 0, cmd);
335 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
336 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
337 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE));
339 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
341 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
342 defaultColor = opt_t & 0xFF;
347 /* Check HKCU\Software\Microsoft\Command Processor
348 Then HKLM\Software\Microsoft\Command Processor
349 for defaultcolour value
350 Note Can be supplied as DWORD or REG_SZ
351 Note2 When supplied as REG_SZ it's in decimal!!! */
354 DWORD value=0, size=4;
355 static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
356 'M','i','c','r','o','s','o','f','t','\\',
357 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
358 static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
360 if (RegOpenKeyEx(HKEY_CURRENT_USER, regKeyW,
361 0, KEY_READ, &key) == ERROR_SUCCESS) {
364 /* See if DWORD or REG_SZ */
365 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
366 NULL, NULL) == ERROR_SUCCESS) {
367 if (type == REG_DWORD) {
368 size = sizeof(DWORD);
369 RegQueryValueEx(key, dfltColorW, NULL, NULL,
370 (LPBYTE)&value, &size);
371 } else if (type == REG_SZ) {
372 size = sizeof(strvalue)/sizeof(WCHAR);
373 RegQueryValueEx(key, dfltColorW, NULL, NULL,
374 (LPBYTE)strvalue, &size);
375 value = strtoulW(strvalue, NULL, 10);
380 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKeyW,
381 0, KEY_READ, &key) == ERROR_SUCCESS) {
384 /* See if DWORD or REG_SZ */
385 if (RegQueryValueEx(key, dfltColorW, NULL, &type,
386 NULL, NULL) == ERROR_SUCCESS) {
387 if (type == REG_DWORD) {
388 size = sizeof(DWORD);
389 RegQueryValueEx(key, dfltColorW, NULL, NULL,
390 (LPBYTE)&value, &size);
391 } else if (type == REG_SZ) {
392 size = sizeof(strvalue)/sizeof(WCHAR);
393 RegQueryValueEx(key, dfltColorW, NULL, NULL,
394 (LPBYTE)strvalue, &size);
395 value = strtoulW(strvalue, NULL, 10);
400 /* If one found, set the screen to that colour */
401 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
402 defaultColor = value & 0xFF;
409 /* Save cwd into appropriate env var */
410 GetCurrentDirectory(1024, string);
411 if (IsCharAlpha(string[0]) && string[1] == ':') {
412 static const WCHAR fmt[] = {'=','%','c',':','\0'};
413 wsprintf(envvar, fmt, string[0]);
414 SetEnvironmentVariable(envvar, string);
418 /* Parse the command string, without reading any more input */
419 WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
420 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
421 WCMD_free_commands(toExecute);
423 HeapFree(GetProcessHeap(), 0, cmd);
427 * If there is an AUTOEXEC.BAT file, try to execute it.
430 GetFullPathName (autoexec, sizeof(string)/sizeof(WCHAR), string, NULL);
431 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
432 if (h != INVALID_HANDLE_VALUE) {
435 WCMD_batch (autoexec, autoexec, 0, NULL, INVALID_HANDLE_VALUE);
440 * Loop forever getting commands and executing them.
446 /* Read until EOF (which for std input is never, but if redirect
447 in place, may occur */
449 if (WCMD_ReadAndParseLine(NULL, &toExecute,
450 GetStdHandle(STD_INPUT_HANDLE)) == NULL)
452 WCMD_process_commands(toExecute, FALSE, NULL, NULL);
453 WCMD_free_commands(toExecute);
460 /*****************************************************************************
461 * Process one command. If the command is EXIT this routine does not return.
462 * We will recurse through here executing batch files.
466 void WCMD_process_command (WCHAR *command, CMD_LIST **cmdList)
468 WCHAR *cmd, *p, *s, *t, *redir;
470 DWORD count, creationDisposition;
473 SECURITY_ATTRIBUTES sa;
475 WCHAR *first_redir = NULL;
476 HANDLE old_stdhandles[3] = {INVALID_HANDLE_VALUE,
477 INVALID_HANDLE_VALUE,
478 INVALID_HANDLE_VALUE};
479 DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
483 /* Move copy of the command onto the heap so it can be expanded */
484 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
485 strcpyW(new_cmd, command);
487 /* For commands in a context (batch program): */
488 /* Expand environment variables in a batch file %{0-9} first */
489 /* including support for any ~ modifiers */
491 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
492 /* names allowing environment variable overrides */
493 /* NOTE: To support the %PATH:xxx% syntax, also perform */
494 /* manual expansion of environment variables here */
497 while ((p = strchrW(p, '%'))) {
504 /* Replace %~ modifications if in batch program */
505 } else if (context && *(p+1) == '~') {
506 WCMD_HandleTildaModifiers(&p, NULL);
509 /* Replace use of %0...%9 if in batch program*/
510 } else if (context && (i >= 0) && (i <= 9)) {
511 s = WCMD_strdupW(p+2);
512 t = WCMD_parameter (context -> command, i + context -> shift_count[i], NULL);
517 /* Replace use of %* if in batch program*/
518 } else if (context && *(p+1)=='*') {
519 WCHAR *startOfParms = NULL;
520 s = WCMD_strdupW(p+2);
521 t = WCMD_parameter (context -> command, 1, &startOfParms);
522 if (startOfParms != NULL) strcpyW (p, startOfParms);
528 p = WCMD_expand_envvar(p);
533 /* In a batch program, unknown variables are replace by nothing */
534 /* so remove any remaining %var% */
537 while ((p = strchrW(p, '%'))) {
541 s = strchrW(p+1, '%');
545 t = WCMD_strdupW(s+1);
552 /* Show prompt before batch line IF echo is on and in batch program */
553 if (echo_mode && (cmd[0] != '@')) {
555 WCMD_output_asis ( cmd);
556 WCMD_output_asis ( newline);
561 * Changing default drive has to be handled as a special case.
564 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlenW(cmd) == 2)) {
568 /* According to MSDN CreateProcess docs, special env vars record
569 the current directory on each drive, in the form =C:
570 so see if one specified, and if so go back to it */
571 strcpyW(envvar, equalsW);
572 strcatW(envvar, cmd);
573 if (GetEnvironmentVariable(envvar, dir, MAX_PATH) == 0) {
574 static const WCHAR fmt[] = {'%','s','\\','\0'};
575 wsprintf(cmd, fmt, cmd);
577 status = SetCurrentDirectory (cmd);
578 if (!status) WCMD_print_error ();
579 HeapFree( GetProcessHeap(), 0, cmd );
583 sa.nLength = sizeof(sa);
584 sa.lpSecurityDescriptor = NULL;
585 sa.bInheritHandle = TRUE;
588 * Redirect stdin, stdout and/or stderr if required.
591 if ((p = strchrW(cmd,'<')) != NULL) {
592 if (first_redir == NULL) first_redir = p;
593 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
594 FILE_ATTRIBUTE_NORMAL, NULL);
595 if (h == INVALID_HANDLE_VALUE) {
597 HeapFree( GetProcessHeap(), 0, cmd );
600 old_stdhandles[0] = GetStdHandle (STD_INPUT_HANDLE);
601 SetStdHandle (STD_INPUT_HANDLE, h);
604 /* Scan the whole command looking for > and 2> */
606 while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
610 if (first_redir == NULL) first_redir = p;
613 if (first_redir == NULL) first_redir = (p-1);
619 creationDisposition = OPEN_ALWAYS;
623 creationDisposition = CREATE_ALWAYS;
626 /* Add support for 2>&1 */
629 int idx = *(p+1) - '0';
631 if (DuplicateHandle(GetCurrentProcess(),
632 GetStdHandle(idx_stdhandles[idx]),
635 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
636 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
638 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
641 WCHAR *param = WCMD_parameter (p, 0, NULL);
642 h = CreateFile (param, GENERIC_WRITE, 0, &sa, creationDisposition,
643 FILE_ATTRIBUTE_NORMAL, NULL);
644 if (h == INVALID_HANDLE_VALUE) {
646 HeapFree( GetProcessHeap(), 0, cmd );
649 if (SetFilePointer (h, 0, NULL, FILE_END) ==
650 INVALID_SET_FILE_POINTER) {
653 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
656 old_stdhandles[handle] = GetStdHandle (idx_stdhandles[handle]);
657 SetStdHandle (idx_stdhandles[handle], h);
660 /* Terminate the command string at <, or first 2> or > */
661 if (first_redir != NULL) *first_redir = '\0';
664 * Strip leading whitespaces, and a '@' if supplied
666 whichcmd = WCMD_strtrim_leading_spaces(cmd);
667 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
668 if (whichcmd[0] == '@') whichcmd++;
671 * Check if the command entered is internal. If it is, pass the rest of the
672 * line down to the command. If not try to run a program.
676 while (IsCharAlphaNumeric(whichcmd[count])) {
679 for (i=0; i<=WCMD_EXIT; i++) {
680 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
681 whichcmd, count, inbuilt[i], -1) == 2) break;
683 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
684 WCMD_parse (p, quals, param1, param2);
688 WCMD_setshow_attrib ();
695 WCMD_setshow_default (p);
698 WCMD_clear_screen ();
707 WCMD_setshow_date ();
711 WCMD_delete (p, TRUE);
717 WCMD_echo(&whichcmd[count]);
720 WCMD_for (p, cmdList);
729 WCMD_if (p, cmdList);
742 WCMD_setshow_path (p);
748 WCMD_setshow_prompt ();
767 WCMD_setshow_env (p);
773 WCMD_setshow_time ();
776 if (strlenW(&whichcmd[count]) > 0)
777 WCMD_title(&whichcmd[count+1]);
804 WCMD_assoc(p, FALSE);
813 WCMD_run_program (whichcmd, 0);
815 HeapFree( GetProcessHeap(), 0, cmd );
817 /* Restore old handles */
818 for (i=0; i<3; i++) {
819 if (old_stdhandles[i] != INVALID_HANDLE_VALUE) {
820 CloseHandle (GetStdHandle (idx_stdhandles[i]));
821 SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
826 static void init_msvcrt_io_block(STARTUPINFO* st)
829 /* fetch the parent MSVCRT info block if any, so that the child can use the
830 * same handles as its grand-father
832 st_p.cb = sizeof(STARTUPINFO);
833 GetStartupInfo(&st_p);
834 st->cbReserved2 = st_p.cbReserved2;
835 st->lpReserved2 = st_p.lpReserved2;
836 if (st_p.cbReserved2 && st_p.lpReserved2)
838 /* Override the entries for fd 0,1,2 if we happened
839 * to change those std handles (this depends on the way wcmd sets
840 * it's new input & output handles)
842 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
843 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
846 unsigned num = *(unsigned*)st_p.lpReserved2;
847 char* flags = (char*)(ptr + sizeof(unsigned));
848 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
850 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
851 st->cbReserved2 = sz;
852 st->lpReserved2 = ptr;
854 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
855 if (num <= 0 || (flags[0] & WX_OPEN))
857 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
860 if (num <= 1 || (flags[1] & WX_OPEN))
862 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
865 if (num <= 2 || (flags[2] & WX_OPEN))
867 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
875 /******************************************************************************
878 * Execute a command line as an external program. Must allow recursion.
881 * Manual testing under windows shows PATHEXT plays a key part in this,
882 * and the search algorithm and precedence appears to be as follows.
885 * If directory supplied on command, just use that directory
886 * If extension supplied on command, look for that explicit name first
887 * Otherwise, search in each directory on the path
889 * If extension supplied on command, look for that explicit name first
890 * Then look for supplied name .* (even if extension supplied, so
891 * 'garbage.exe' will match 'garbage.exe.cmd')
892 * If any found, cycle through PATHEXT looking for name.exe one by one
894 * Once a match has been found, it is launched - Code currently uses
895 * findexecutable to acheive this which is left untouched.
898 void WCMD_run_program (WCHAR *command, int called) {
900 WCHAR temp[MAX_PATH];
901 WCHAR pathtosearch[MAXSTRING];
903 WCHAR stemofsearch[MAX_PATH];
905 WCHAR pathext[MAXSTRING];
906 BOOL extensionsupplied = FALSE;
907 BOOL launched = FALSE;
909 BOOL assumeInternal = FALSE;
911 static const WCHAR envPath[] = {'P','A','T','H','\0'};
912 static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
913 static const WCHAR delims[] = {'/','\\',':','\0'};
915 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
916 if (!(*param1) && !(*param2))
919 /* Calculate the search path and stem to search for */
920 if (strpbrkW (param1, delims) == NULL) { /* No explicit path given, search path */
921 static const WCHAR curDir[] = {'.',';','\0'};
922 strcpyW(pathtosearch, curDir);
923 len = GetEnvironmentVariable (envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
924 if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
925 static const WCHAR curDir[] = {'.','\0'};
926 strcpyW (pathtosearch, curDir);
928 if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
929 strcpyW(stemofsearch, param1);
933 /* Convert eg. ..\fred to include a directory by removing file part */
934 GetFullPathName(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
935 lastSlash = strrchrW(pathtosearch, '\\');
936 if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
937 if (lastSlash) *lastSlash = 0x00;
938 strcpyW(stemofsearch, lastSlash+1);
941 /* Now extract PATHEXT */
942 len = GetEnvironmentVariable (envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
943 if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
944 static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
947 '.','e','x','e','\0'};
948 strcpyW (pathext, dfltPathExt);
951 /* Loop through the search path, dir by dir */
952 pathposn = pathtosearch;
953 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
954 wine_dbgstr_w(stemofsearch));
955 while (!launched && pathposn) {
957 WCHAR thisDir[MAX_PATH] = {'\0'};
960 const WCHAR slashW[] = {'\\','\0'};
962 /* Work on the first directory on the search path */
963 pos = strchrW(pathposn, ';');
965 memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
966 thisDir[(pos-pathposn)] = 0x00;
970 strcpyW(thisDir, pathposn);
974 /* Since you can have eg. ..\.. on the path, need to expand
975 to full information */
976 strcpyW(temp, thisDir);
977 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
979 /* 1. If extension supplied, see if that file exists */
980 strcatW(thisDir, slashW);
981 strcatW(thisDir, stemofsearch);
982 pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
984 /* 1. If extension supplied, see if that file exists */
985 if (extensionsupplied) {
986 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
991 /* 2. Any .* matches? */
994 WIN32_FIND_DATA finddata;
995 static const WCHAR allFiles[] = {'.','*','\0'};
997 strcatW(thisDir,allFiles);
998 h = FindFirstFile(thisDir, &finddata);
1000 if (h != INVALID_HANDLE_VALUE) {
1002 WCHAR *thisExt = pathext;
1004 /* 3. Yes - Try each path ext */
1006 WCHAR *nextExt = strchrW(thisExt, ';');
1009 memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1010 pos[(nextExt-thisExt)] = 0x00;
1011 thisExt = nextExt+1;
1013 strcpyW(pos, thisExt);
1017 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
1025 /* Internal programs won't be picked up by this search, so even
1026 though not found, try one last createprocess and wait for it
1028 Note: Ideally we could tell between a console app (wait) and a
1029 windows app, but the API's for it fail in this case */
1030 if (!found && pathposn == NULL) {
1031 WINE_TRACE("ASSUMING INTERNAL\n");
1032 assumeInternal = TRUE;
1034 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1037 /* Once found, launch it */
1038 if (found || assumeInternal) {
1040 PROCESS_INFORMATION pe;
1044 WCHAR *ext = strrchrW( thisDir, '.' );
1045 static const WCHAR batExt[] = {'.','b','a','t','\0'};
1046 static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1050 /* Special case BAT and CMD */
1051 if (ext && !strcmpiW(ext, batExt)) {
1052 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1054 } else if (ext && !strcmpiW(ext, cmdExt)) {
1055 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1059 /* thisDir contains the file to be launched, but with what?
1060 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1061 hinst = FindExecutable (thisDir, NULL, temp);
1062 if ((INT_PTR)hinst < 32)
1065 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1067 ZeroMemory (&st, sizeof(STARTUPINFO));
1068 st.cb = sizeof(STARTUPINFO);
1069 init_msvcrt_io_block(&st);
1071 /* Launch the process and if a CUI wait on it to complete
1072 Note: Launching internal wine processes cannot specify a full path to exe */
1073 status = CreateProcess (assumeInternal?NULL : thisDir,
1074 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1075 if ((opt_c || opt_k) && !opt_s && !status
1076 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1077 /* strip first and last quote WCHARacters and try again */
1078 WCMD_opt_s_strip_quotes(command);
1080 WCMD_run_program(command, called);
1084 WCMD_print_error ();
1085 /* If a command fails to launch, it sets errorlevel 9009 - which
1086 does not seem to have any associated constant definition */
1090 if (!assumeInternal && !console) errorlevel = 0;
1093 /* Always wait when called in a batch program context */
1094 if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1095 GetExitCodeProcess (pe.hProcess, &errorlevel);
1096 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1098 CloseHandle(pe.hProcess);
1099 CloseHandle(pe.hThread);
1105 /* Not found anywhere - give up */
1106 SetLastError(ERROR_FILE_NOT_FOUND);
1107 WCMD_print_error ();
1109 /* If a command fails to launch, it sets errorlevel 9009 - which
1110 does not seem to have any associated constant definition */
1116 /******************************************************************************
1119 * Display the prompt on STDout
1123 void WCMD_show_prompt (void) {
1126 WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
1129 static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
1131 len = GetEnvironmentVariable (envPrompt, prompt_string,
1132 sizeof(prompt_string)/sizeof(WCHAR));
1133 if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
1134 const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
1135 strcpyW (prompt_string, dfltPrompt);
1140 while (*p != '\0') {
1147 switch (toupper(*p)) {
1161 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
1180 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1186 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
1188 strcatW (q, curdir);
1199 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1203 strcatW (q, version_string);
1210 if (pushd_directories) {
1211 memset(q, '+', pushd_directories->u.stackdepth);
1212 q = q + pushd_directories->u.stackdepth;
1220 WCMD_output_asis (out_string);
1223 /****************************************************************************
1226 * Print the message for GetLastError
1229 void WCMD_print_error (void) {
1234 error_code = GetLastError ();
1235 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1236 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1238 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1239 error_code, GetLastError());
1242 WCMD_output_asis (lpMsgBuf);
1243 LocalFree ((HLOCAL)lpMsgBuf);
1244 WCMD_output_asis (newline);
1248 /*******************************************************************
1249 * WCMD_parse - parse a command into parameters and qualifiers.
1251 * On exit, all qualifiers are concatenated into q, the first string
1252 * not beginning with "/" is in p1 and the
1253 * second in p2. Any subsequent non-qualifier strings are lost.
1254 * Parameters in quotes are handled.
1257 void WCMD_parse (WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2) {
1261 *q = *p1 = *p2 = '\0';
1266 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1267 *q++ = toupperW (*s++);
1277 while ((*s != '\0') && (*s != '"')) {
1278 if (p == 0) *p1++ = *s++;
1279 else if (p == 1) *p2++ = *s++;
1282 if (p == 0) *p1 = '\0';
1283 if (p == 1) *p2 = '\0';
1290 while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1291 if (p == 0) *p1++ = *s++;
1292 else if (p == 1) *p2++ = *s++;
1295 if (p == 0) *p1 = '\0';
1296 if (p == 1) *p2 = '\0';
1302 /*******************************************************************
1303 * WCMD_output_asis_len - send output to current standard output
1305 * Output a formatted unicode string. Ideally this will go to the console
1306 * and hence required WriteConsoleW to output it, however if file i/o is
1307 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1309 static void WCMD_output_asis_len(const WCHAR *message, int len) {
1314 /* If nothing to write, return (MORE does this sometimes) */
1317 /* Try to write as unicode assuming it is to a console */
1318 res = WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
1319 message, len, &nOut, NULL);
1321 /* If writing to console fails, assume its file
1322 i/o so convert to OEM codepage and output */
1324 BOOL usedDefaultChar = FALSE;
1325 DWORD convertedChars;
1327 if (!unicodePipes) {
1329 * Allocate buffer to use when writing to file. (Not freed, as one off)
1331 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1332 MAX_WRITECONSOLE_SIZE);
1334 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1338 /* Convert to OEM, then output */
1339 convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
1340 len, output_bufA, MAX_WRITECONSOLE_SIZE,
1341 "?", &usedDefaultChar);
1342 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), output_bufA, convertedChars,
1345 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), message, len*sizeof(WCHAR),
1352 /*******************************************************************
1353 * WCMD_output - send output to current standard output device.
1357 void WCMD_output (const WCHAR *format, ...) {
1363 va_start(ap,format);
1364 ret = wvsprintf (string, format, ap);
1365 if( ret >= (sizeof(string)/sizeof(WCHAR))) {
1366 WINE_ERR("Output truncated in WCMD_output\n" );
1367 ret = (sizeof(string)/sizeof(WCHAR)) - 1;
1371 WCMD_output_asis_len(string, ret);
1375 static int line_count;
1376 static int max_height;
1377 static int max_width;
1378 static BOOL paged_mode;
1379 static int numChars;
1381 void WCMD_enter_paged_mode(const WCHAR *msg)
1383 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1385 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
1386 max_height = consoleInfo.dwSize.Y;
1387 max_width = consoleInfo.dwSize.X;
1395 pagedMessage = (msg==NULL)? anykey : msg;
1398 void WCMD_leave_paged_mode(void)
1401 pagedMessage = NULL;
1404 /*******************************************************************
1405 * WCMD_output_asis - send output to current standard output device.
1406 * without formatting eg. when message contains '%'
1409 void WCMD_output_asis (const WCHAR *message) {
1417 while (*ptr && *ptr!='\n' && (numChars < max_width)) {
1421 if (*ptr == '\n') ptr++;
1422 WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message));
1425 if (++line_count >= max_height - 1) {
1427 WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage));
1428 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1429 sizeof(string)/sizeof(WCHAR), &count, NULL);
1432 } while (((message = ptr) != NULL) && (*ptr));
1434 WCMD_output_asis_len(message, lstrlen(message));
1439 /***************************************************************************
1440 * WCMD_strtrim_leading_spaces
1442 * Remove leading spaces from a string. Return a pointer to the first
1443 * non-space character. Does not modify the input string
1446 WCHAR *WCMD_strtrim_leading_spaces (WCHAR *string) {
1451 while (*ptr == ' ') ptr++;
1455 /*************************************************************************
1456 * WCMD_strtrim_trailing_spaces
1458 * Remove trailing spaces from a string. This routine modifies the input
1459 * string by placing a null after the last non-space WCHARacter
1462 void WCMD_strtrim_trailing_spaces (WCHAR *string) {
1466 ptr = string + strlenW (string) - 1;
1467 while ((*ptr == ' ') && (ptr >= string)) {
1473 /*************************************************************************
1474 * WCMD_opt_s_strip_quotes
1476 * Remove first and last quote WCHARacters, preserving all other text
1479 void WCMD_opt_s_strip_quotes(WCHAR *cmd) {
1480 WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
1481 while((*dest=*src) != '\0') {
1488 while ((*dest++=*lastq++) != 0)
1493 /*************************************************************************
1496 * Handle pipes within a command - the DOS way using temporary files.
1499 void WCMD_pipe (CMD_LIST **cmdEntry, WCHAR *var, WCHAR *val) {
1502 WCHAR *command = (*cmdEntry)->command;
1503 WCHAR temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1504 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1505 static const WCHAR redirIn[] = {'%','s',' ','<',' ','%','s','\0'};
1506 static const WCHAR redirBoth[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1507 static const WCHAR cmdW[] = {'C','M','D','\0'};
1510 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
1511 GetTempFileName (temp_path, cmdW, 0, temp_file);
1512 p = strchrW(command, '|');
1514 wsprintf (temp_cmd, redirOut, command, temp_file);
1515 WCMD_execute (temp_cmd, var, val, cmdEntry);
1517 while ((p = strchrW(command, '|'))) {
1519 GetTempFileName (temp_path, cmdW, 0, temp_file2);
1520 wsprintf (temp_cmd, redirBoth, command, temp_file, temp_file2);
1521 WCMD_execute (temp_cmd, var, val, cmdEntry);
1522 DeleteFile (temp_file);
1523 strcpyW (temp_file, temp_file2);
1526 wsprintf (temp_cmd, redirIn, command, temp_file);
1527 WCMD_execute (temp_cmd, var, val, cmdEntry);
1528 DeleteFile (temp_file);
1531 /*************************************************************************
1532 * WCMD_expand_envvar
1534 * Expands environment variables, allowing for WCHARacter substitution
1536 static WCHAR *WCMD_expand_envvar(WCHAR *start) {
1537 WCHAR *endOfVar = NULL, *s;
1538 WCHAR *colonpos = NULL;
1539 WCHAR thisVar[MAXSTRING];
1540 WCHAR thisVarContents[MAXSTRING];
1541 WCHAR savedchar = 0x00;
1544 static const WCHAR ErrorLvl[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1545 static const WCHAR ErrorLvlP[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1546 static const WCHAR Date[] = {'D','A','T','E','\0'};
1547 static const WCHAR DateP[] = {'%','D','A','T','E','%','\0'};
1548 static const WCHAR Time[] = {'T','I','M','E','\0'};
1549 static const WCHAR TimeP[] = {'%','T','I','M','E','%','\0'};
1550 static const WCHAR Cd[] = {'C','D','\0'};
1551 static const WCHAR CdP[] = {'%','C','D','%','\0'};
1552 static const WCHAR Random[] = {'R','A','N','D','O','M','\0'};
1553 static const WCHAR RandomP[] = {'%','R','A','N','D','O','M','%','\0'};
1555 /* Find the end of the environment variable, and extract name */
1556 endOfVar = strchrW(start+1, '%');
1557 if (endOfVar == NULL) {
1558 /* In batch program, missing terminator for % and no following
1559 ':' just removes the '%' */
1560 s = WCMD_strdupW(start + 1);
1564 /* FIXME: Some other special conditions here depending on whether
1565 in batch, complex or not, and whether env var exists or not! */
1568 memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
1569 thisVar[(endOfVar - start)+1] = 0x00;
1570 colonpos = strchrW(thisVar+1, ':');
1572 /* If there's complex substitution, just need %var% for now
1573 to get the expanded data to play with */
1576 savedchar = *(colonpos+1);
1577 *(colonpos+1) = 0x00;
1580 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
1582 /* Expand to contents, if unchanged, return */
1583 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1584 /* override if existing env var called that name */
1585 if ((CompareString (LOCALE_USER_DEFAULT,
1586 NORM_IGNORECASE | SORT_STRINGSORT,
1587 thisVar, 12, ErrorLvlP, -1) == 2) &&
1588 (GetEnvironmentVariable(ErrorLvl, thisVarContents, 1) == 0) &&
1589 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1590 static const WCHAR fmt[] = {'%','d','\0'};
1591 wsprintf(thisVarContents, fmt, errorlevel);
1592 len = strlenW(thisVarContents);
1594 } else if ((CompareString (LOCALE_USER_DEFAULT,
1595 NORM_IGNORECASE | SORT_STRINGSORT,
1596 thisVar, 6, DateP, -1) == 2) &&
1597 (GetEnvironmentVariable(Date, thisVarContents, 1) == 0) &&
1598 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1600 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1601 NULL, thisVarContents, MAXSTRING);
1602 len = strlenW(thisVarContents);
1604 } else if ((CompareString (LOCALE_USER_DEFAULT,
1605 NORM_IGNORECASE | SORT_STRINGSORT,
1606 thisVar, 6, TimeP, -1) == 2) &&
1607 (GetEnvironmentVariable(Time, thisVarContents, 1) == 0) &&
1608 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1609 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1610 NULL, thisVarContents, MAXSTRING);
1611 len = strlenW(thisVarContents);
1613 } else if ((CompareString (LOCALE_USER_DEFAULT,
1614 NORM_IGNORECASE | SORT_STRINGSORT,
1615 thisVar, 4, CdP, -1) == 2) &&
1616 (GetEnvironmentVariable(Cd, thisVarContents, 1) == 0) &&
1617 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1618 GetCurrentDirectory (MAXSTRING, thisVarContents);
1619 len = strlenW(thisVarContents);
1621 } else if ((CompareString (LOCALE_USER_DEFAULT,
1622 NORM_IGNORECASE | SORT_STRINGSORT,
1623 thisVar, 8, RandomP, -1) == 2) &&
1624 (GetEnvironmentVariable(Random, thisVarContents, 1) == 0) &&
1625 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1626 static const WCHAR fmt[] = {'%','d','\0'};
1627 wsprintf(thisVarContents, fmt, rand() % 32768);
1628 len = strlenW(thisVarContents);
1632 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1633 sizeof(thisVarContents)/sizeof(WCHAR));
1639 /* In a batch program, unknown env vars are replaced with nothing,
1640 note syntax %garbage:1,3% results in anything after the ':'
1642 From the command line, you just get back what you entered */
1643 if (lstrcmpiW(thisVar, thisVarContents) == 0) {
1645 /* Restore the complex part after the compare */
1648 *(colonpos+1) = savedchar;
1651 /* Command line - just ignore this */
1652 if (context == NULL) return endOfVar+1;
1654 s = WCMD_strdupW(endOfVar + 1);
1656 /* Batch - replace unknown env var with nothing */
1657 if (colonpos == NULL) {
1661 len = strlenW(thisVar);
1662 thisVar[len-1] = 0x00;
1663 /* If %:...% supplied, : is retained */
1664 if (colonpos == thisVar+1) {
1665 strcpyW (start, colonpos);
1667 strcpyW (start, colonpos+1);
1676 /* See if we need to do complex substitution (any ':'s), if not
1677 then our work here is done */
1678 if (colonpos == NULL) {
1679 s = WCMD_strdupW(endOfVar + 1);
1680 strcpyW (start, thisVarContents);
1686 /* Restore complex bit */
1688 *(colonpos+1) = savedchar;
1691 Handle complex substitutions:
1692 xxx=yyy (replace xxx with yyy)
1693 *xxx=yyy (replace up to and including xxx with yyy)
1694 ~x (from x WCHARs in)
1695 ~-x (from x WCHARs from the end)
1696 ~x,y (from x WCHARs in for y WCHARacters)
1697 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1700 /* ~ is substring manipulation */
1701 if (savedchar == '~') {
1703 int substrposition, substrlength = 0;
1704 WCHAR *commapos = strchrW(colonpos+2, ',');
1707 substrposition = atolW(colonpos+2);
1708 if (commapos) substrlength = atolW(commapos+1);
1710 s = WCMD_strdupW(endOfVar + 1);
1713 if (substrposition >= 0) {
1714 startCopy = &thisVarContents[min(substrposition, len)];
1716 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1719 if (commapos == NULL) {
1720 strcpyW (start, startCopy); /* Copy the lot */
1721 } else if (substrlength < 0) {
1723 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1724 if (copybytes > len) copybytes = len;
1725 else if (copybytes < 0) copybytes = 0;
1726 memcpy (start, startCopy, copybytes * sizeof(WCHAR)); /* Copy the lot */
1727 start[copybytes] = 0x00;
1729 memcpy (start, startCopy, substrlength * sizeof(WCHAR)); /* Copy the lot */
1730 start[substrlength] = 0x00;
1737 /* search and replace manipulation */
1739 WCHAR *equalspos = strstrW(colonpos, equalsW);
1740 WCHAR *replacewith = equalspos+1;
1741 WCHAR *found = NULL;
1745 s = WCMD_strdupW(endOfVar + 1);
1746 if (equalspos == NULL) return start+1;
1748 /* Null terminate both strings */
1749 thisVar[strlenW(thisVar)-1] = 0x00;
1752 /* Since we need to be case insensitive, copy the 2 buffers */
1753 searchIn = WCMD_strdupW(thisVarContents);
1754 CharUpperBuff(searchIn, strlenW(thisVarContents));
1755 searchFor = WCMD_strdupW(colonpos+1);
1756 CharUpperBuff(searchFor, strlenW(colonpos+1));
1759 /* Handle wildcard case */
1760 if (*(colonpos+1) == '*') {
1761 /* Search for string to replace */
1762 found = strstrW(searchIn, searchFor+1);
1765 /* Do replacement */
1766 strcpyW(start, replacewith);
1767 strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
1772 strcpyW(start, thisVarContents);
1777 /* Loop replacing all instances */
1778 WCHAR *lastFound = searchIn;
1779 WCHAR *outputposn = start;
1782 while ((found = strstrW(lastFound, searchFor))) {
1783 lstrcpynW(outputposn,
1784 thisVarContents + (lastFound-searchIn),
1785 (found - lastFound)+1);
1786 outputposn = outputposn + (found - lastFound);
1787 strcatW(outputposn, replacewith);
1788 outputposn = outputposn + strlenW(replacewith);
1789 lastFound = found + strlenW(searchFor);
1792 thisVarContents + (lastFound-searchIn));
1793 strcatW(outputposn, s);
1802 /*************************************************************************
1804 * Load a string from the resource file, handling any error
1805 * Returns string retrieved from resource file
1807 WCHAR *WCMD_LoadMessage(UINT id) {
1808 static WCHAR msg[2048];
1809 static const WCHAR failedMsg[] = {'F','a','i','l','e','d','!','\0'};
1811 if (!LoadString(GetModuleHandle(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1812 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1813 strcpyW(msg, failedMsg);
1818 /*************************************************************************
1820 * A wide version of strdup as its missing from unicode.h
1822 WCHAR *WCMD_strdupW(WCHAR *input) {
1823 int len=strlenW(input)+1;
1824 /* Note: Use malloc not HeapAlloc to emulate strdup */
1825 WCHAR *result = malloc(len * sizeof(WCHAR));
1826 memcpy(result, input, len * sizeof(WCHAR));
1830 /***************************************************************************
1833 * Read characters in from a console/file, returning result in Unicode
1834 * with signature identical to ReadFile
1836 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars,
1837 LPDWORD charsRead, const LPOVERLAPPED unused) {
1841 /* Try to read from console as Unicode */
1842 res = ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
1844 /* If reading from console has failed we assume its file
1845 i/o so read in and convert from OEM codepage */
1850 * Allocate buffer to use when reading from file. Not freed
1852 if (!output_bufA) output_bufA = HeapAlloc(GetProcessHeap(), 0,
1853 MAX_WRITECONSOLE_SIZE);
1855 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1859 /* Read from file (assume OEM codepage) */
1860 res = ReadFile(hIn, output_bufA, maxChars, &numRead, unused);
1862 /* Convert from OEM */
1863 *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, output_bufA, numRead,
1870 /***************************************************************************
1873 * Domps out the parsed command line to ensure syntax is correct
1875 void WCMD_DumpCommands(CMD_LIST *commands) {
1876 WCHAR buffer[MAXSTRING];
1877 CMD_LIST *thisCmd = commands;
1878 const WCHAR fmt[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1879 '%','p',' ','%','s','\0'};
1881 WINE_TRACE("Parsed line:\n");
1882 while (thisCmd != NULL) {
1883 sprintfW(buffer, fmt,
1885 thisCmd->isAmphersand?'Y':'N',
1886 thisCmd->bracketDepth,
1887 thisCmd->nextcommand,
1889 WINE_TRACE("%s\n", wine_dbgstr_w(buffer));
1890 thisCmd = thisCmd->nextcommand;
1894 /***************************************************************************
1895 * WCMD_ReadAndParseLine
1897 * Either uses supplied input or
1898 * Reads a file from the handle, and then...
1899 * Parse the text buffer, spliting into separate commands
1900 * - unquoted && strings split 2 commands but the 2nd is flagged as
1902 * - ( as the first character just ups the bracket depth
1903 * - unquoted ) when bracket depth > 0 terminates a bracket and
1904 * adds a CMD_LIST structure with null command
1905 * - Anything else gets put into the command string (including
1908 WCHAR *WCMD_ReadAndParseLine(WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom) {
1911 BOOL inQuotes = FALSE;
1912 WCHAR curString[MAXSTRING];
1915 CMD_LIST *thisEntry = NULL;
1916 CMD_LIST *lastEntry = NULL;
1917 BOOL isAmphersand = FALSE;
1918 static WCHAR *extraSpace = NULL; /* Deliberately never freed */
1919 const WCHAR remCmd[] = {'r','e','m',' ','\0'};
1920 const WCHAR forCmd[] = {'f','o','r',' ','\0'};
1921 const WCHAR ifCmd[] = {'i','f',' ','\0'};
1922 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1928 BOOL onlyWhiteSpace = FALSE;
1929 BOOL lastWasWhiteSpace = FALSE;
1930 BOOL lastWasDo = FALSE;
1931 BOOL lastWasIn = FALSE;
1932 BOOL lastWasElse = FALSE;
1934 /* Allocate working space for a command read from keyboard, file etc */
1936 extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1938 /* If initial command read in, use that, otherwise get input from handle */
1939 if (optionalcmd != NULL) {
1940 strcpyW(extraSpace, optionalcmd);
1941 } else if (readFrom == INVALID_HANDLE_VALUE) {
1942 WINE_FIXME("No command nor handle supplied\n");
1944 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) return NULL;
1946 curPos = extraSpace;
1948 /* Handle truncated input - issue warning */
1949 if (strlenW(extraSpace) == MAXSTRING -1) {
1950 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1951 WCMD_output_asis(extraSpace);
1952 WCMD_output_asis(newline);
1955 /* Start with an empty string */
1958 /* Parse every character on the line being processed */
1959 while (*curPos != 0x00) {
1964 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, curLen,
1965 lastWasWhiteSpace, onlyWhiteSpace);
1968 /* Certain commands need special handling */
1970 const WCHAR forDO[] = {'d','o',' ','\0'};
1972 /* If command starts with 'rem', ignore any &&, ( etc */
1973 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1974 curPos, 4, remCmd, -1) == 2) {
1977 /* If command starts with 'for', handle ('s mid line after IN or DO */
1978 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1979 curPos, 4, forCmd, -1) == 2) {
1982 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1983 is only true in the command portion of the IF statement, but this
1984 should suffice for now
1985 FIXME: Silly syntax like "if 1(==1( (
1987 )" will be parsed wrong */
1988 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1989 curPos, 3, ifCmd, -1) == 2) {
1992 } else if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1993 curPos, 5, ifElse, -1) == 2) {
1996 onlyWhiteSpace = TRUE;
1997 memcpy(&curString[curLen], curPos, 5*sizeof(WCHAR));
2002 /* In a for loop, the DO command will follow a close bracket followed by
2003 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
2004 is then 0, and all whitespace is skipped */
2006 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2007 curPos, 3, forDO, -1) == 2)) {
2008 WINE_TRACE("Found DO\n");
2010 onlyWhiteSpace = TRUE;
2011 memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2018 /* Special handling for the 'FOR' command */
2019 if (inFor && lastWasWhiteSpace) {
2020 const WCHAR forIN[] = {'i','n',' ','\0'};
2022 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
2024 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2025 curPos, 3, forIN, -1) == 2) {
2026 WINE_TRACE("Found IN\n");
2028 onlyWhiteSpace = TRUE;
2029 memcpy(&curString[curLen], curPos, 3*sizeof(WCHAR));
2037 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2038 so just use the default processing ie skip character specific
2040 if (!inRem) thisChar = *curPos;
2041 else thisChar = 'X'; /* Character with no special processing */
2043 lastWasWhiteSpace = FALSE; /* Will be reset below */
2047 case '\t':/* drop through - ignore whitespace at the start of a command */
2048 case ' ': if (curLen > 0)
2049 curString[curLen++] = *curPos;
2051 /* Remember just processed whitespace */
2052 lastWasWhiteSpace = TRUE;
2056 case '"': inQuotes = !inQuotes;
2057 curString[curLen++] = *curPos;
2060 case '(': /* If a '(' is the first non whitespace in a command portion
2061 ie start of line or just after &&, then we read until an
2062 unquoted ) is found */
2063 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2064 ", for(%d, In:%d, Do:%d)"
2065 ", if(%d, else:%d, lwe:%d)\n",
2068 inFor, lastWasIn, lastWasDo,
2069 inIf, inElse, lastWasElse);
2071 /* Ignore open brackets inside the for set */
2072 if (curLen == 0 && !inIn) {
2075 /* If in quotes, ignore brackets */
2076 } else if (inQuotes) {
2077 curString[curLen++] = *curPos;
2079 /* In a FOR loop, an unquoted '(' may occur straight after
2081 In an IF statement just handle it regardless as we don't
2083 In an ELSE statement, only allow it straight away after
2084 the ELSE and whitespace
2087 (inElse && lastWasElse && onlyWhiteSpace) ||
2088 (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2090 /* If entering into an 'IN', set inIn */
2091 if (inFor && lastWasIn && onlyWhiteSpace) {
2092 WINE_TRACE("Inside an IN\n");
2096 /* Add the current command */
2097 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2098 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2099 (curLen+1) * sizeof(WCHAR));
2100 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2101 thisEntry->command[curLen] = 0x00;
2103 thisEntry->nextcommand = NULL;
2104 thisEntry->isAmphersand = isAmphersand;
2105 thisEntry->bracketDepth = curDepth;
2107 lastEntry->nextcommand = thisEntry;
2109 *output = thisEntry;
2111 lastEntry = thisEntry;
2115 curString[curLen++] = *curPos;
2119 case '&': if (!inQuotes && *(curPos+1) == '&') {
2120 curPos++; /* Skip other & */
2122 /* Add an entry to the command list */
2124 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2125 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2126 (curLen+1) * sizeof(WCHAR));
2127 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2128 thisEntry->command[curLen] = 0x00;
2130 thisEntry->nextcommand = NULL;
2131 thisEntry->isAmphersand = isAmphersand;
2132 thisEntry->bracketDepth = curDepth;
2134 lastEntry->nextcommand = thisEntry;
2136 *output = thisEntry;
2138 lastEntry = thisEntry;
2140 isAmphersand = TRUE;
2142 curString[curLen++] = *curPos;
2146 case ')': if (!inQuotes && curDepth > 0) {
2148 /* Add the current command if there is one */
2150 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2151 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2152 (curLen+1) * sizeof(WCHAR));
2153 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2154 thisEntry->command[curLen] = 0x00;
2156 thisEntry->nextcommand = NULL;
2157 thisEntry->isAmphersand = isAmphersand;
2158 thisEntry->bracketDepth = curDepth;
2160 lastEntry->nextcommand = thisEntry;
2162 *output = thisEntry;
2164 lastEntry = thisEntry;
2167 /* Add an empty entry to the command list */
2168 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2169 thisEntry->command = NULL;
2170 thisEntry->nextcommand = NULL;
2171 thisEntry->isAmphersand = FALSE;
2172 thisEntry->bracketDepth = curDepth;
2175 lastEntry->nextcommand = thisEntry;
2177 *output = thisEntry;
2179 lastEntry = thisEntry;
2181 /* Leave inIn if necessary */
2182 if (inIn) inIn = FALSE;
2184 curString[curLen++] = *curPos;
2188 curString[curLen++] = *curPos;
2193 /* At various times we need to know if we have only skipped whitespace,
2194 so reset this variable and then it will remain true until a non
2195 whitespace is found */
2196 if ((thisChar != ' ') && (thisChar != '\n')) onlyWhiteSpace = FALSE;
2198 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2199 if (!lastWasWhiteSpace) {
2200 lastWasIn = lastWasDo = FALSE;
2203 /* If we have reached the end, add this command into the list */
2204 if (*curPos == 0x00 && curLen > 0) {
2206 /* Add an entry to the command list */
2207 thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
2208 thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
2209 (curLen+1) * sizeof(WCHAR));
2210 memcpy(thisEntry->command, curString, curLen * sizeof(WCHAR));
2211 thisEntry->command[curLen] = 0x00;
2213 thisEntry->nextcommand = NULL;
2214 thisEntry->isAmphersand = isAmphersand;
2215 thisEntry->bracketDepth = curDepth;
2217 lastEntry->nextcommand = thisEntry;
2219 *output = thisEntry;
2221 lastEntry = thisEntry;
2224 /* If we have reached the end of the string, see if bracketing outstanding */
2225 if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2227 isAmphersand = FALSE;
2229 memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2231 /* Read more, skipping any blank lines */
2232 while (*extraSpace == 0x00) {
2233 if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2234 if (WCMD_fgets(extraSpace, MAXSTRING, readFrom) == NULL) break;
2236 curPos = extraSpace;
2240 /* Dump out the parsed output */
2241 WCMD_DumpCommands(*output);
2246 /***************************************************************************
2247 * WCMD_process_commands
2249 * Process all the commands read in so far
2251 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2252 WCHAR *var, WCHAR *val) {
2256 if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2258 /* Loop through the commands, processing them one by one */
2261 CMD_LIST *origCmd = thisCmd;
2263 /* If processing one bracket only, and we find the end bracket
2264 entry (or less), return */
2265 if (oneBracket && !thisCmd->command &&
2266 bdepth <= thisCmd->bracketDepth) {
2267 WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2268 thisCmd, thisCmd->nextcommand);
2269 return thisCmd->nextcommand;
2272 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2273 about them and it will be handled in there)
2274 Also, skip over any batch labels (eg. :fred) */
2275 if (thisCmd->command && thisCmd->command[0] != ':') {
2277 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2279 if (strchrW(thisCmd->command,'|') != NULL) {
2280 WCMD_pipe (&thisCmd, var, val);
2282 WCMD_execute (thisCmd->command, var, val, &thisCmd);
2286 /* Step on unless the command itself already stepped on */
2287 if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2292 /***************************************************************************
2293 * WCMD_free_commands
2295 * Frees the storage held for a parsed command line
2296 * - This is not done in the process_commands, as eventually the current
2297 * pointer will be modified within the commands, and hence a single free
2298 * routine is simpler
2300 void WCMD_free_commands(CMD_LIST *cmds) {
2302 /* Loop through the commands, freeing them one by one */
2304 CMD_LIST *thisCmd = cmds;
2305 cmds = cmds->nextcommand;
2306 HeapFree(GetProcessHeap(), 0, thisCmd->command);
2307 HeapFree(GetProcessHeap(), 0, thisCmd);