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 char * const inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
34 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
35 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
36 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
37 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL",
38 "ENDLOCAL", "SETLOCAL", "PUSHD", "POPD", "ASSOC", "COLOR", "EXIT" };
42 int echo_mode = 1, verify_mode = 0, defaultColor = 7;
43 static int opt_c, opt_k, opt_s;
44 const char nyi[] = "Not Yet Implemented\n\n";
45 const char newline[] = "\n";
46 const char version_string[] = "CMD Version " PACKAGE_VERSION "\n\n";
47 const char anykey[] = "Press Return key to continue: ";
48 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
49 BATCH_CONTEXT *context = NULL;
50 extern struct env_stack *pushd_directories;
52 static char *WCMD_expand_envvar(char *start);
54 /*****************************************************************************
55 * Main entry point. This is a console application so we have a main() not a
59 int main (int argc, char *argv[])
68 opt_c=opt_k=opt_q=opt_s=0;
72 if ((*argv)[0]!='/' || (*argv)[1]=='\0') {
78 if (tolower(c)=='c') {
80 } else if (tolower(c)=='q') {
82 } else if (tolower(c)=='k') {
84 } else if (tolower(c)=='s') {
86 } else if (tolower(c)=='t' && (*argv)[2]==':') {
87 opt_t=strtoul(&(*argv)[3], NULL, 16);
88 } else if (tolower(c)=='x' || tolower(c)=='y') {
89 /* Ignored for compatibility with Windows */
94 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
97 if (opt_c || opt_k) /* break out of parsing immediately after c or k */
105 if (opt_c || opt_k) {
110 /* opt_s left unflagged if the command starts with and contains exactly
111 * one quoted string (exactly two quote characters). The quoted string
112 * must be an executable name that has whitespace and must not have the
113 * following characters: &<>()@^| */
115 /* Build the command to execute */
118 for (arg = argv; *arg; arg++)
120 int has_space,bcount;
126 if( !*a ) has_space=1;
131 if (*a==' ' || *a=='\t') {
133 } else if (*a=='"') {
134 /* doubling of '\' preceding a '"',
135 * plus escaping of said '"'
144 len+=(a-*arg)+1 /* for the separating space */;
147 len+=2; /* for the quotes */
155 /* check argv[0] for a space and invalid characters */
160 if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
161 || *p=='@' || *p=='^' || *p=='|') {
171 cmd = HeapAlloc(GetProcessHeap(), 0, len);
176 for (arg = argv; *arg; arg++)
178 int has_space,has_quote;
181 /* Check for quotes and spaces in this argument */
182 has_space=has_quote=0;
184 if( !*a ) has_space=1;
186 if (*a==' ' || *a=='\t') {
190 } else if (*a=='"') {
198 /* Now transfer it to the command line */
215 /* Double all the '\\' preceding this '"', plus one */
216 for (i=0;i<=bcount;i++)
235 p--; /* remove last space */
238 /* strip first and last quote characters if opt_s; check for invalid
239 * executable is done later */
240 if (opt_s && *cmd=='\"')
241 WCMD_opt_s_strip_quotes(cmd);
245 /* If we do a "wcmd /c command", we don't want to allocate a new
246 * console since the command returns immediately. Rather, we use
247 * the currently allocated input and output handles. This allows
248 * us to pipe to and read from the command interpreter.
250 if (strchr(cmd,'|') != NULL)
253 WCMD_process_command(cmd);
254 HeapFree(GetProcessHeap(), 0, cmd);
258 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
259 ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
260 SetConsoleTitle("Wine Command Prompt");
262 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
264 if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
265 defaultColor = opt_t & 0xFF;
270 /* Check HKCU\Software\Microsoft\Command Processor
271 Then HKLM\Software\Microsoft\Command Processor
272 for defaultcolour value
273 Note Can be supplied as DWORD or REG_SZ
274 Note2 When supplied as REG_SZ it's in decimal!!! */
277 DWORD value=0, size=4;
279 if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Command Processor",
280 0, KEY_READ, &key) == ERROR_SUCCESS) {
283 /* See if DWORD or REG_SZ */
284 if (RegQueryValueEx(key, "DefaultColor", NULL, &type,
285 NULL, NULL) == ERROR_SUCCESS) {
286 if (type == REG_DWORD) {
287 size = sizeof(DWORD);
288 RegQueryValueEx(key, "DefaultColor", NULL, NULL,
289 (LPBYTE)&value, &size);
290 } else if (type == REG_SZ) {
291 size = sizeof(strvalue);
292 RegQueryValueEx(key, "DefaultColor", NULL, NULL,
293 (LPBYTE)strvalue, &size);
294 value = strtoul(strvalue, NULL, 10);
299 if (value == 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE,
300 "Software\\Microsoft\\Command Processor",
301 0, KEY_READ, &key) == ERROR_SUCCESS) {
304 /* See if DWORD or REG_SZ */
305 if (RegQueryValueEx(key, "DefaultColor", NULL, &type,
306 NULL, NULL) == ERROR_SUCCESS) {
307 if (type == REG_DWORD) {
308 size = sizeof(DWORD);
309 RegQueryValueEx(key, "DefaultColor", NULL, NULL,
310 (LPBYTE)&value, &size);
311 } else if (type == REG_SZ) {
312 size = sizeof(strvalue);
313 RegQueryValueEx(key, "DefaultColor", NULL, NULL,
314 (LPBYTE)strvalue, &size);
315 value = strtoul(strvalue, NULL, 10);
320 /* If one found, set the screen to that colour */
321 if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
322 defaultColor = value & 0xFF;
330 WCMD_process_command(cmd);
331 HeapFree(GetProcessHeap(), 0, cmd);
335 * If there is an AUTOEXEC.BAT file, try to execute it.
338 GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
339 h = CreateFile (string, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
340 if (h != INVALID_HANDLE_VALUE) {
343 WCMD_batch ((char *)"\\autoexec.bat", (char *)"\\autoexec.bat", 0, NULL, INVALID_HANDLE_VALUE);
348 * Loop forever getting commands and executing them.
354 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
356 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
357 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
358 if (lstrlen (string) != 0) {
359 if (strchr(string,'|') != NULL) {
363 WCMD_process_command (string);
371 /*****************************************************************************
372 * Process one command. If the command is EXIT this routine does not return.
373 * We will recurse through here executing batch files.
377 void WCMD_process_command (char *command)
379 char *cmd, *p, *s, *t;
381 DWORD count, creationDisposition;
384 SECURITY_ATTRIBUTES sa;
386 HANDLE old_stdin = INVALID_HANDLE_VALUE;
387 HANDLE old_stdout = INVALID_HANDLE_VALUE;
389 /* Move copy of the command onto the heap so it can be expanded */
390 new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING );
391 strcpy(new_cmd, command);
393 /* For commands in a context (batch program): */
394 /* Expand environment variables in a batch file %{0-9} first */
395 /* including support for any ~ modifiers */
397 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
398 /* names allowing environment variable overrides */
399 /* NOTE: To support the %PATH:xxx% syntax, also perform */
400 /* manual expansion of environment variables here */
403 while ((p = strchr(p, '%'))) {
406 /* Replace %~ modifications if in batch program */
407 if (context && *(p+1) == '~') {
408 WCMD_HandleTildaModifiers(&p, NULL);
411 /* Replace use of %0...%9 if in batch program*/
412 } else if (context && (i >= 0) && (i <= 9)) {
414 t = WCMD_parameter (context -> command, i + context -> shift_count, NULL);
419 /* Replace use of %* if in batch program*/
420 } else if (context && *(p+1)=='*') {
421 char *startOfParms = NULL;
423 t = WCMD_parameter (context -> command, 1, &startOfParms);
424 if (startOfParms != NULL) strcpy (p, startOfParms);
430 p = WCMD_expand_envvar(p);
435 /* In a batch program, unknown variables are replace by nothing */
436 /* so remove any remaining %var% */
439 while ((p = strchr(p, '%'))) {
440 s = strchr(p+1, '%');
450 /* Show prompt before batch line IF echo is on and in batch program */
451 if (echo_mode && (cmd[0] != '@')) {
453 WCMD_output_asis ( cmd);
454 WCMD_output_asis ( "\n");
459 * Changing default drive has to be handled as a special case.
462 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
463 status = SetCurrentDirectory (cmd);
464 if (!status) WCMD_print_error ();
465 HeapFree( GetProcessHeap(), 0, cmd );
469 /* Don't issue newline WCMD_output (newline); @JED*/
471 sa.nLength = sizeof(sa);
472 sa.lpSecurityDescriptor = NULL;
473 sa.bInheritHandle = TRUE;
475 * Redirect stdin and/or stdout if required.
478 if ((p = strchr(cmd,'<')) != NULL) {
479 h = CreateFile (WCMD_parameter (++p, 0, NULL), GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING,
480 FILE_ATTRIBUTE_NORMAL, NULL);
481 if (h == INVALID_HANDLE_VALUE) {
483 HeapFree( GetProcessHeap(), 0, cmd );
486 old_stdin = GetStdHandle (STD_INPUT_HANDLE);
487 SetStdHandle (STD_INPUT_HANDLE, h);
489 if ((p = strchr(cmd,'>')) != NULL) {
492 creationDisposition = OPEN_ALWAYS;
496 creationDisposition = CREATE_ALWAYS;
498 h = CreateFile (WCMD_parameter (p, 0, NULL), GENERIC_WRITE, 0, &sa, creationDisposition,
499 FILE_ATTRIBUTE_NORMAL, NULL);
500 if (h == INVALID_HANDLE_VALUE) {
502 HeapFree( GetProcessHeap(), 0, cmd );
505 if (SetFilePointer (h, 0, NULL, FILE_END) ==
506 INVALID_SET_FILE_POINTER) {
509 old_stdout = GetStdHandle (STD_OUTPUT_HANDLE);
510 SetStdHandle (STD_OUTPUT_HANDLE, h);
512 if ((p = strchr(cmd,'<')) != NULL) *p = '\0';
515 * Strip leading whitespaces, and a '@' if supplied
517 whichcmd = WCMD_strtrim_leading_spaces(cmd);
518 WINE_TRACE("Command: '%s'\n", cmd);
519 if (whichcmd[0] == '@') whichcmd++;
522 * Check if the command entered is internal. If it is, pass the rest of the
523 * line down to the command. If not try to run a program.
527 while (IsCharAlphaNumeric(whichcmd[count])) {
530 for (i=0; i<=WCMD_EXIT; i++) {
531 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
532 whichcmd, count, inbuilt[i], -1) == 2) break;
534 p = WCMD_strtrim_leading_spaces (&whichcmd[count]);
535 WCMD_parse (p, quals, param1, param2);
539 WCMD_setshow_attrib ();
546 WCMD_setshow_default (p);
549 WCMD_clear_screen ();
558 WCMD_setshow_date ();
568 WCMD_echo(&whichcmd[count]);
593 WCMD_setshow_path (p);
599 WCMD_setshow_prompt ();
618 WCMD_setshow_env (p);
624 WCMD_setshow_time ();
627 if (strlen(&whichcmd[count]) > 0)
628 WCMD_title(&whichcmd[count+1]);
658 WCMD_run_program (whichcmd, 0);
660 HeapFree( GetProcessHeap(), 0, cmd );
661 if (old_stdin != INVALID_HANDLE_VALUE) {
662 CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
663 SetStdHandle (STD_INPUT_HANDLE, old_stdin);
665 if (old_stdout != INVALID_HANDLE_VALUE) {
666 CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
667 SetStdHandle (STD_OUTPUT_HANDLE, old_stdout);
671 static void init_msvcrt_io_block(STARTUPINFO* st)
674 /* fetch the parent MSVCRT info block if any, so that the child can use the
675 * same handles as its grand-father
677 st_p.cb = sizeof(STARTUPINFO);
678 GetStartupInfo(&st_p);
679 st->cbReserved2 = st_p.cbReserved2;
680 st->lpReserved2 = st_p.lpReserved2;
681 if (st_p.cbReserved2 && st_p.lpReserved2)
683 /* Override the entries for fd 0,1,2 if we happened
684 * to change those std handles (this depends on the way wcmd sets
685 * it's new input & output handles)
687 size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
688 BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
691 unsigned num = *(unsigned*)st_p.lpReserved2;
692 char* flags = (char*)(ptr + sizeof(unsigned));
693 HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
695 memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
696 st->cbReserved2 = sz;
697 st->lpReserved2 = ptr;
699 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
700 if (num <= 0 || (flags[0] & WX_OPEN))
702 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
705 if (num <= 1 || (flags[1] & WX_OPEN))
707 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
710 if (num <= 2 || (flags[2] & WX_OPEN))
712 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
720 /******************************************************************************
723 * Execute a command line as an external program. Must allow recursion.
726 * Manual testing under windows shows PATHEXT plays a key part in this,
727 * and the search algorithm and precedence appears to be as follows.
730 * If directory supplied on command, just use that directory
731 * If extension supplied on command, look for that explicit name first
732 * Otherwise, search in each directory on the path
734 * If extension supplied on command, look for that explicit name first
735 * Then look for supplied name .* (even if extension supplied, so
736 * 'garbage.exe' will match 'garbage.exe.cmd')
737 * If any found, cycle through PATHEXT looking for name.exe one by one
739 * Once a match has been found, it is launched - Code currently uses
740 * findexecutable to acheive this which is left untouched.
743 void WCMD_run_program (char *command, int called) {
746 char pathtosearch[MAX_PATH];
748 char stemofsearch[MAX_PATH];
750 char pathext[MAXSTRING];
751 BOOL extensionsupplied = FALSE;
752 BOOL launched = FALSE;
757 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
758 if (!(*param1) && !(*param2))
761 /* Calculate the search path and stem to search for */
762 if (strpbrk (param1, "/\\:") == NULL) { /* No explicit path given, search path */
763 strcpy(pathtosearch,".;");
764 len = GetEnvironmentVariable ("PATH", &pathtosearch[2], sizeof(pathtosearch)-2);
765 if ((len == 0) || (len >= sizeof(pathtosearch) - 2)) {
766 lstrcpy (pathtosearch, ".");
768 if (strchr(param1, '.') != NULL) extensionsupplied = TRUE;
769 strcpy(stemofsearch, param1);
773 /* Convert eg. ..\fred to include a directory by removing file part */
774 GetFullPathName(param1, MAX_PATH, pathtosearch, NULL);
775 lastSlash = strrchr(pathtosearch, '\\');
776 if (lastSlash) *lastSlash = 0x00;
777 if (strchr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
778 strcpy(stemofsearch, lastSlash+1);
781 /* Now extract PATHEXT */
782 len = GetEnvironmentVariable ("PATHEXT", pathext, sizeof(pathext));
783 if ((len == 0) || (len >= sizeof(pathext))) {
784 lstrcpy (pathext, ".bat;.com;.cmd;.exe");
787 /* Loop through the search path, dir by dir */
788 pathposn = pathtosearch;
789 while (!launched && pathposn) {
791 char thisDir[MAX_PATH] = "";
795 /* Work on the first directory on the search path */
796 pos = strchr(pathposn, ';');
798 strncpy(thisDir, pathposn, (pos-pathposn));
799 thisDir[(pos-pathposn)] = 0x00;
803 strcpy(thisDir, pathposn);
807 /* Since you can have eg. ..\.. on the path, need to expand
808 to full information */
809 strcpy(temp, thisDir);
810 GetFullPathName(temp, MAX_PATH, thisDir, NULL);
812 /* 1. If extension supplied, see if that file exists */
813 strcat(thisDir, "\\");
814 strcat(thisDir, stemofsearch);
815 pos = &thisDir[strlen(thisDir)]; /* Pos = end of name */
817 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
821 /* 2. Any .* matches? */
824 WIN32_FIND_DATA finddata;
826 strcat(thisDir,".*");
827 h = FindFirstFile(thisDir, &finddata);
829 if (h != INVALID_HANDLE_VALUE) {
831 char *thisExt = pathext;
833 /* 3. Yes - Try each path ext */
835 char *nextExt = strchr(thisExt, ';');
838 strncpy(pos, thisExt, (nextExt-thisExt));
839 pos[(nextExt-thisExt)] = 0x00;
842 strcpy(pos, thisExt);
846 if (GetFileAttributes(thisDir) != INVALID_FILE_ATTRIBUTES) {
854 /* Once found, launch it */
857 PROCESS_INFORMATION pe;
861 char *ext = strrchr( thisDir, '.' );
864 /* Special case BAT and CMD */
865 if (ext && !strcasecmp(ext, ".bat")) {
866 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
868 } else if (ext && !strcasecmp(ext, ".cmd")) {
869 WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
873 /* thisDir contains the file to be launched, but with what?
874 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
875 hinst = FindExecutable (param1, NULL, temp);
876 if ((INT_PTR)hinst < 32)
879 console = SHGetFileInfo (temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
881 ZeroMemory (&st, sizeof(STARTUPINFO));
882 st.cb = sizeof(STARTUPINFO);
883 init_msvcrt_io_block(&st);
885 /* Launch the process and if a CUI wait on it to complete */
886 status = CreateProcess (thisDir, command, NULL, NULL, TRUE,
887 0, NULL, NULL, &st, &pe);
888 if ((opt_c || opt_k) && !opt_s && !status
889 && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
890 /* strip first and last quote characters and try again */
891 WCMD_opt_s_strip_quotes(command);
893 WCMD_run_program(command, called);
898 /* If a command fails to launch, it sets errorlevel 9009 - which
899 does not seem to have any associated constant definition */
903 if (!console) errorlevel = 0;
906 if (!HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
907 GetExitCodeProcess (pe.hProcess, &errorlevel);
908 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
910 CloseHandle(pe.hProcess);
911 CloseHandle(pe.hThread);
917 /* Not found anywhere - give up */
918 SetLastError(ERROR_FILE_NOT_FOUND);
921 /* If a command fails to launch, it sets errorlevel 9009 - which
922 does not seem to have any associated constant definition */
928 /******************************************************************************
931 * Display the prompt on STDout
935 void WCMD_show_prompt (void) {
938 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
942 len = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
943 if ((len == 0) || (len >= sizeof(prompt_string))) {
944 lstrcpy (prompt_string, "$P$G");
956 switch (toupper(*p)) {
970 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
989 status = GetCurrentDirectory (sizeof(curdir), curdir);
995 status = GetCurrentDirectory (sizeof(curdir), curdir);
1008 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
1012 lstrcat (q, version_string);
1019 if (pushd_directories) {
1020 memset(q, '+', pushd_directories->stackdepth);
1021 q = q + pushd_directories->stackdepth;
1029 WCMD_output_asis (out_string);
1032 /****************************************************************************
1035 * Print the message for GetLastError
1038 void WCMD_print_error (void) {
1043 error_code = GetLastError ();
1044 status = FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
1045 NULL, error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
1047 WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
1048 error_code, GetLastError());
1051 WCMD_output_asis (lpMsgBuf);
1052 LocalFree ((HLOCAL)lpMsgBuf);
1053 WCMD_output_asis (newline);
1057 /*******************************************************************
1058 * WCMD_parse - parse a command into parameters and qualifiers.
1060 * On exit, all qualifiers are concatenated into q, the first string
1061 * not beginning with "/" is in p1 and the
1062 * second in p2. Any subsequent non-qualifier strings are lost.
1063 * Parameters in quotes are handled.
1066 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
1070 *q = *p1 = *p2 = '\0';
1075 while ((*s != '\0') && (*s != ' ') && *s != '/') {
1076 *q++ = toupper (*s++);
1086 while ((*s != '\0') && (*s != '"')) {
1087 if (p == 0) *p1++ = *s++;
1088 else if (p == 1) *p2++ = *s++;
1091 if (p == 0) *p1 = '\0';
1092 if (p == 1) *p2 = '\0';
1099 while ((*s != '\0') && (*s != ' ') && (*s != '\t')) {
1100 if (p == 0) *p1++ = *s++;
1101 else if (p == 1) *p2++ = *s++;
1104 if (p == 0) *p1 = '\0';
1105 if (p == 1) *p2 = '\0';
1111 /*******************************************************************
1112 * WCMD_output - send output to current standard output device.
1116 void WCMD_output (const char *format, ...) {
1122 va_start(ap,format);
1123 ret = vsnprintf (string, sizeof( string), format, ap);
1125 if( ret >= sizeof( string)) {
1126 WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
1127 string[sizeof( string) -1] = '\0';
1129 WCMD_output_asis(string);
1133 static int line_count;
1134 static int max_height;
1135 static BOOL paged_mode;
1137 void WCMD_enter_paged_mode(void)
1139 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
1141 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo))
1142 max_height = consoleInfo.dwSize.Y;
1146 line_count = 5; /* keep 5 lines from previous output */
1149 void WCMD_leave_paged_mode(void)
1154 /*******************************************************************
1155 * WCMD_output_asis - send output to current standard output device.
1156 * without formatting eg. when message contains '%'
1159 void WCMD_output_asis (const char *message) {
1166 if ((ptr = strchr(message, '\n')) != NULL) ptr++;
1167 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message,
1168 (ptr) ? ptr - message : lstrlen(message), &count, NULL);
1170 if (++line_count >= max_height - 1) {
1172 WCMD_output_asis (anykey);
1173 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1176 } while ((message = ptr) != NULL);
1178 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE), message, lstrlen(message), &count, NULL);
1183 /***************************************************************************
1184 * WCMD_strtrim_leading_spaces
1186 * Remove leading spaces from a string. Return a pointer to the first
1187 * non-space character. Does not modify the input string
1190 char *WCMD_strtrim_leading_spaces (char *string) {
1195 while (*ptr == ' ') ptr++;
1199 /*************************************************************************
1200 * WCMD_strtrim_trailing_spaces
1202 * Remove trailing spaces from a string. This routine modifies the input
1203 * string by placing a null after the last non-space character
1206 void WCMD_strtrim_trailing_spaces (char *string) {
1210 ptr = string + lstrlen (string) - 1;
1211 while ((*ptr == ' ') && (ptr >= string)) {
1217 /*************************************************************************
1218 * WCMD_opt_s_strip_quotes
1220 * Remove first and last quote characters, preserving all other text
1223 void WCMD_opt_s_strip_quotes(char *cmd) {
1224 char *src = cmd + 1, *dest = cmd, *lastq = NULL;
1225 while((*dest=*src) != '\0') {
1232 while ((*dest++=*lastq++) != 0)
1237 /*************************************************************************
1240 * Handle pipes within a command - the DOS way using temporary files.
1243 void WCMD_pipe (char *command) {
1246 char temp_path[MAX_PATH], temp_file[MAX_PATH], temp_file2[MAX_PATH], temp_cmd[1024];
1248 GetTempPath (sizeof(temp_path), temp_path);
1249 GetTempFileName (temp_path, "CMD", 0, temp_file);
1250 p = strchr(command, '|');
1252 wsprintf (temp_cmd, "%s > %s", command, temp_file);
1253 WCMD_process_command (temp_cmd);
1255 while ((p = strchr(command, '|'))) {
1257 GetTempFileName (temp_path, "CMD", 0, temp_file2);
1258 wsprintf (temp_cmd, "%s < %s > %s", command, temp_file, temp_file2);
1259 WCMD_process_command (temp_cmd);
1260 DeleteFile (temp_file);
1261 lstrcpy (temp_file, temp_file2);
1264 wsprintf (temp_cmd, "%s < %s", command, temp_file);
1265 WCMD_process_command (temp_cmd);
1266 DeleteFile (temp_file);
1269 /*************************************************************************
1270 * WCMD_expand_envvar
1272 * Expands environment variables, allowing for character substitution
1274 static char *WCMD_expand_envvar(char *start) {
1275 char *endOfVar = NULL, *s;
1276 char *colonpos = NULL;
1277 char thisVar[MAXSTRING];
1278 char thisVarContents[MAXSTRING];
1279 char savedchar = 0x00;
1282 /* Find the end of the environment variable, and extract name */
1283 endOfVar = strchr(start+1, '%');
1284 if (endOfVar == NULL) {
1285 /* FIXME: Some special conditions here depending opn whether
1286 in batch, complex or not, and whether env var exists or not! */
1289 strncpy(thisVar, start, (endOfVar - start)+1);
1290 thisVar[(endOfVar - start)+1] = 0x00;
1291 colonpos = strchr(thisVar+1, ':');
1293 /* If there's complex substitution, just need %var% for now
1294 to get the expanded data to play with */
1297 savedchar = *(colonpos+1);
1298 *(colonpos+1) = 0x00;
1301 /* Expand to contents, if unchanged, return */
1302 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1303 /* override if existing env var called that name */
1304 if ((CompareString (LOCALE_USER_DEFAULT,
1305 NORM_IGNORECASE | SORT_STRINGSORT,
1306 thisVar, 12, "%ERRORLEVEL%", -1) == 2) &&
1307 (GetEnvironmentVariable("ERRORLEVEL", thisVarContents, 1) == 0) &&
1308 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1309 sprintf(thisVarContents, "%d", errorlevel);
1310 len = strlen(thisVarContents);
1312 } else if ((CompareString (LOCALE_USER_DEFAULT,
1313 NORM_IGNORECASE | SORT_STRINGSORT,
1314 thisVar, 6, "%DATE%", -1) == 2) &&
1315 (GetEnvironmentVariable("DATE", thisVarContents, 1) == 0) &&
1316 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1318 GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
1319 NULL, thisVarContents, MAXSTRING);
1320 len = strlen(thisVarContents);
1322 } else if ((CompareString (LOCALE_USER_DEFAULT,
1323 NORM_IGNORECASE | SORT_STRINGSORT,
1324 thisVar, 6, "%TIME%", -1) == 2) &&
1325 (GetEnvironmentVariable("TIME", thisVarContents, 1) == 0) &&
1326 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1327 GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
1328 NULL, thisVarContents, MAXSTRING);
1329 len = strlen(thisVarContents);
1331 } else if ((CompareString (LOCALE_USER_DEFAULT,
1332 NORM_IGNORECASE | SORT_STRINGSORT,
1333 thisVar, 4, "%CD%", -1) == 2) &&
1334 (GetEnvironmentVariable("CD", thisVarContents, 1) == 0) &&
1335 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1336 GetCurrentDirectory (MAXSTRING, thisVarContents);
1337 len = strlen(thisVarContents);
1339 } else if ((CompareString (LOCALE_USER_DEFAULT,
1340 NORM_IGNORECASE | SORT_STRINGSORT,
1341 thisVar, 8, "%RANDOM%", -1) == 2) &&
1342 (GetEnvironmentVariable("RANDOM", thisVarContents, 1) == 0) &&
1343 (GetLastError() == ERROR_ENVVAR_NOT_FOUND)) {
1344 sprintf(thisVarContents, "%d", rand() % 32768);
1345 len = strlen(thisVarContents);
1349 len = ExpandEnvironmentStrings(thisVar, thisVarContents,
1350 sizeof(thisVarContents));
1356 /* In a batch program, unknown env vars are replaced with nothing,
1357 note syntax %garbage:1,3% results in anything after the ':'
1359 From the command line, you just get back what you entered */
1360 if (lstrcmpi(thisVar, thisVarContents) == 0) {
1362 /* Restore the complex part after the compare */
1365 *(colonpos+1) = savedchar;
1368 s = strdup (endOfVar + 1);
1370 /* Command line - just ignore this */
1371 if (context == NULL) return endOfVar+1;
1373 /* Batch - replace unknown env var with nothing */
1374 if (colonpos == NULL) {
1378 len = strlen(thisVar);
1379 thisVar[len-1] = 0x00;
1380 /* If %:...% supplied, : is retained */
1381 if (colonpos == thisVar+1) {
1382 strcpy (start, colonpos);
1384 strcpy (start, colonpos+1);
1393 /* See if we need to do complex substitution (any ':'s), if not
1394 then our work here is done */
1395 if (colonpos == NULL) {
1396 s = strdup (endOfVar + 1);
1397 strcpy (start, thisVarContents);
1403 /* Restore complex bit */
1405 *(colonpos+1) = savedchar;
1408 Handle complex substitutions:
1409 xxx=yyy (replace xxx with yyy)
1410 *xxx=yyy (replace up to and including xxx with yyy)
1411 ~x (from x chars in)
1412 ~-x (from x chars from the end)
1413 ~x,y (from x chars in for y characters)
1414 ~x,-y (from x chars in until y characters from the end)
1417 /* ~ is substring manipulation */
1418 if (savedchar == '~') {
1420 int substrposition, substrlength;
1421 char *commapos = strchr(colonpos+2, ',');
1424 substrposition = atol(colonpos+2);
1425 if (commapos) substrlength = atol(commapos+1);
1427 s = strdup (endOfVar + 1);
1430 if (substrposition >= 0) {
1431 startCopy = &thisVarContents[min(substrposition, len)];
1433 startCopy = &thisVarContents[max(0, len+substrposition-1)];
1436 if (commapos == NULL) {
1437 strcpy (start, startCopy); /* Copy the lot */
1438 } else if (substrlength < 0) {
1440 int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
1441 if (copybytes > len) copybytes = len;
1442 else if (copybytes < 0) copybytes = 0;
1443 strncpy (start, startCopy, copybytes); /* Copy the lot */
1444 start[copybytes] = 0x00;
1446 strncpy (start, startCopy, substrlength); /* Copy the lot */
1447 start[substrlength] = 0x00;
1454 /* search and replace manipulation */
1456 char *equalspos = strstr(colonpos, "=");
1457 char *replacewith = equalspos+1;
1462 s = strdup (endOfVar + 1);
1463 if (equalspos == NULL) return start+1;
1465 /* Null terminate both strings */
1466 thisVar[strlen(thisVar)-1] = 0x00;
1469 /* Since we need to be case insensitive, copy the 2 buffers */
1470 searchIn = strdup(thisVarContents);
1471 CharUpperBuff(searchIn, strlen(thisVarContents));
1472 searchFor = strdup(colonpos+1);
1473 CharUpperBuff(searchFor, strlen(colonpos+1));
1476 /* Handle wildcard case */
1477 if (*(colonpos+1) == '*') {
1478 /* Search for string to replace */
1479 found = strstr(searchIn, searchFor+1);
1482 /* Do replacement */
1483 strcpy(start, replacewith);
1484 strcat(start, thisVarContents + (found-searchIn) + strlen(searchFor+1));
1489 strcpy(start, thisVarContents);
1494 /* Loop replacing all instances */
1495 char *lastFound = searchIn;
1496 char *outputposn = start;
1499 while ((found = strstr(lastFound, searchFor))) {
1501 thisVarContents + (lastFound-searchIn),
1502 (found - lastFound));
1503 outputposn = outputposn + (found - lastFound);
1505 strcat(outputposn, replacewith);
1506 outputposn = outputposn + strlen(replacewith);
1507 lastFound = found + strlen(searchFor);
1510 thisVarContents + (lastFound-searchIn));
1511 strcat(outputposn, s);