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
30 const char * const inbuilt
[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
31 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
32 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
33 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
34 "TIME", "TITLE", "TYPE", "VERIFY", "VER", "VOL",
35 "ENDLOCAL", "SETLOCAL", "PUSHD", "POPD", "EXIT" };
39 int echo_mode
= 1, verify_mode
= 0;
40 static int opt_c
, opt_k
, opt_s
;
41 const char nyi
[] = "Not Yet Implemented\n\n";
42 const char newline
[] = "\n";
43 const char version_string
[] = "CMD Version " PACKAGE_VERSION
"\n\n";
44 const char anykey
[] = "Press Return key to continue: ";
45 char quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
46 BATCH_CONTEXT
*context
= NULL
;
47 static HANDLE old_stdin
= INVALID_HANDLE_VALUE
, old_stdout
= INVALID_HANDLE_VALUE
;
49 static char *WCMD_expand_envvar(char *start
);
51 /*****************************************************************************
52 * Main entry point. This is a console application so we have a main() not a
56 int main (int argc
, char *argv
[])
64 opt_c
=opt_k
=opt_q
=opt_s
=0;
68 if ((*argv
)[0]!='/' || (*argv
)[1]=='\0') {
74 if (tolower(c
)=='c') {
76 } else if (tolower(c
)=='q') {
78 } else if (tolower(c
)=='k') {
80 } else if (tolower(c
)=='s') {
82 } else if (tolower(c
)=='t' || tolower(c
)=='x' || tolower(c
)=='y') {
83 /* Ignored for compatibility with Windows */
88 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
91 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
104 /* opt_s left unflagged if the command starts with and contains exactly
105 * one quoted string (exactly two quote characters). The quoted string
106 * must be an executable name that has whitespace and must not have the
107 * following characters: &<>()@^| */
109 /* Build the command to execute */
112 for (arg
= argv
; *arg
; arg
++)
114 int has_space
,bcount
;
120 if( !*a
) has_space
=1;
125 if (*a
==' ' || *a
=='\t') {
127 } else if (*a
=='"') {
128 /* doubling of '\' preceding a '"',
129 * plus escaping of said '"'
138 len
+=(a
-*arg
)+1 /* for the separating space */;
141 len
+=2; /* for the quotes */
149 /* check argv[0] for a space and invalid characters */
154 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
155 || *p
=='@' || *p
=='^' || *p
=='|') {
165 cmd
= HeapAlloc(GetProcessHeap(), 0, len
);
170 for (arg
= argv
; *arg
; arg
++)
172 int has_space
,has_quote
;
175 /* Check for quotes and spaces in this argument */
176 has_space
=has_quote
=0;
178 if( !*a
) has_space
=1;
180 if (*a
==' ' || *a
=='\t') {
184 } else if (*a
=='"') {
192 /* Now transfer it to the command line */
209 /* Double all the '\\' preceding this '"', plus one */
210 for (i
=0;i
<=bcount
;i
++)
229 p
--; /* remove last space */
232 /* strip first and last quote characters if opt_s; check for invalid
233 * executable is done later */
234 if (opt_s
&& *cmd
=='\"')
235 WCMD_opt_s_strip_quotes(cmd
);
239 /* If we do a "wcmd /c command", we don't want to allocate a new
240 * console since the command returns immediately. Rather, we use
241 * the currently allocated input and output handles. This allows
242 * us to pipe to and read from the command interpreter.
244 if (strchr(cmd
,'|') != NULL
)
247 WCMD_process_command(cmd
);
248 HeapFree(GetProcessHeap(), 0, cmd
);
252 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
253 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
254 SetConsoleTitle("Wine Command Prompt");
257 WCMD_process_command(cmd
);
258 HeapFree(GetProcessHeap(), 0, cmd
);
262 * If there is an AUTOEXEC.BAT file, try to execute it.
265 GetFullPathName ("\\autoexec.bat", sizeof(string
), string
, NULL
);
266 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
267 if (h
!= INVALID_HANDLE_VALUE
) {
270 WCMD_batch ((char *)"\\autoexec.bat", (char *)"\\autoexec.bat", 0, NULL
, INVALID_HANDLE_VALUE
);
275 * Loop forever getting commands and executing them.
281 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
283 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
284 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
285 if (lstrlen (string
) != 0) {
286 if (strchr(string
,'|') != NULL
) {
290 WCMD_process_command (string
);
298 /*****************************************************************************
299 * Process one command. If the command is EXIT this routine does not return.
300 * We will recurse through here executing batch files.
304 void WCMD_process_command (char *command
)
306 char *cmd
, *p
, *s
, *t
;
308 DWORD count
, creationDisposition
;
311 SECURITY_ATTRIBUTES sa
;
314 /* Move copy of the command onto the heap so it can be expanded */
315 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
);
316 strcpy(new_cmd
, command
);
318 /* For commands in a context (batch program): */
319 /* Expand environment variables in a batch file %{0-9} first */
320 /* including support for any ~ modifiers */
322 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
323 /* names allowing environment variable overrides */
324 /* NOTE: To support the %PATH:xxx% syntax, also perform */
325 /* manual expansion of environment variables here */
328 while ((p
= strchr(p
, '%'))) {
331 /* Replace %~ modifications if in batch program */
332 if (context
&& *(p
+1) == '~') {
333 WCMD_HandleTildaModifiers(&p
, NULL
);
336 /* Replace use of %0...%9 if in batch program*/
337 } else if (context
&& (i
>= 0) && (i
<= 9)) {
339 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
, NULL
);
344 /* Replace use of %* if in batch program*/
345 } else if (context
&& *(p
+1)=='*') {
346 char *startOfParms
= NULL
;
348 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
349 if (startOfParms
!= NULL
) strcpy (p
, startOfParms
);
355 p
= WCMD_expand_envvar(p
);
360 /* In a batch program, unknown variables are replace by nothing */
361 /* so remove any remaining %var% */
364 while ((p
= strchr(p
, '%'))) {
365 s
= strchr(p
+1, '%');
375 /* Show prompt before batch line IF echo is on and in batch program */
376 if (echo_mode
&& (cmd
[0] != '@')) {
378 WCMD_output_asis ( cmd
);
379 WCMD_output_asis ( "\n");
384 * Changing default drive has to be handled as a special case.
387 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlen(cmd
) == 2)) {
388 status
= SetCurrentDirectory (cmd
);
389 if (!status
) WCMD_print_error ();
390 HeapFree( GetProcessHeap(), 0, cmd
);
394 /* Don't issue newline WCMD_output (newline); @JED*/
396 sa
.nLength
= sizeof(sa
);
397 sa
.lpSecurityDescriptor
= NULL
;
398 sa
.bInheritHandle
= TRUE
;
400 * Redirect stdin and/or stdout if required.
403 if ((p
= strchr(cmd
,'<')) != NULL
) {
404 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
405 FILE_ATTRIBUTE_NORMAL
, NULL
);
406 if (h
== INVALID_HANDLE_VALUE
) {
408 HeapFree( GetProcessHeap(), 0, cmd
);
411 old_stdin
= GetStdHandle (STD_INPUT_HANDLE
);
412 SetStdHandle (STD_INPUT_HANDLE
, h
);
414 if ((p
= strchr(cmd
,'>')) != NULL
) {
417 creationDisposition
= OPEN_ALWAYS
;
421 creationDisposition
= CREATE_ALWAYS
;
423 h
= CreateFile (WCMD_parameter (p
, 0, NULL
), GENERIC_WRITE
, 0, &sa
, creationDisposition
,
424 FILE_ATTRIBUTE_NORMAL
, NULL
);
425 if (h
== INVALID_HANDLE_VALUE
) {
427 HeapFree( GetProcessHeap(), 0, cmd
);
430 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
431 INVALID_SET_FILE_POINTER
) {
434 old_stdout
= GetStdHandle (STD_OUTPUT_HANDLE
);
435 SetStdHandle (STD_OUTPUT_HANDLE
, h
);
437 if ((p
= strchr(cmd
,'<')) != NULL
) *p
= '\0';
440 * Strip leading whitespaces, and a '@' if supplied
442 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
443 if (whichcmd
[0] == '@') whichcmd
++;
446 * Check if the command entered is internal. If it is, pass the rest of the
447 * line down to the command. If not try to run a program.
451 while (IsCharAlphaNumeric(whichcmd
[count
])) {
454 for (i
=0; i
<=WCMD_EXIT
; i
++) {
455 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
456 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
458 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
459 WCMD_parse (p
, quals
, param1
, param2
);
463 WCMD_setshow_attrib ();
470 WCMD_setshow_default ();
473 WCMD_clear_screen ();
482 WCMD_setshow_date ();
492 WCMD_echo(&whichcmd
[count
]);
517 WCMD_setshow_path (p
);
523 WCMD_setshow_prompt ();
542 WCMD_setshow_env (p
);
548 WCMD_setshow_time ();
551 if (strlen(&whichcmd
[count
]) > 0)
552 WCMD_title(&whichcmd
[count
+1]);
576 WCMD_run_program (whichcmd
, 0);
578 HeapFree( GetProcessHeap(), 0, cmd
);
579 if (old_stdin
!= INVALID_HANDLE_VALUE
) {
580 CloseHandle (GetStdHandle (STD_INPUT_HANDLE
));
581 SetStdHandle (STD_INPUT_HANDLE
, old_stdin
);
582 old_stdin
= INVALID_HANDLE_VALUE
;
584 if (old_stdout
!= INVALID_HANDLE_VALUE
) {
585 CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE
));
586 SetStdHandle (STD_OUTPUT_HANDLE
, old_stdout
);
587 old_stdout
= INVALID_HANDLE_VALUE
;
591 static void init_msvcrt_io_block(STARTUPINFO
* st
)
594 /* fetch the parent MSVCRT info block if any, so that the child can use the
595 * same handles as its grand-father
597 st_p
.cb
= sizeof(STARTUPINFO
);
598 GetStartupInfo(&st_p
);
599 st
->cbReserved2
= st_p
.cbReserved2
;
600 st
->lpReserved2
= st_p
.lpReserved2
;
601 if (st_p
.cbReserved2
&& st_p
.lpReserved2
&&
602 (old_stdin
!= INVALID_HANDLE_VALUE
|| old_stdout
!= INVALID_HANDLE_VALUE
))
604 /* Override the entries for fd 0,1,2 if we happened
605 * to change those std handles (this depends on the way wcmd sets
606 * it's new input & output handles)
608 size_t sz
= max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
609 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
612 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
613 char* flags
= (char*)(ptr
+ sizeof(unsigned));
614 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(char));
616 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
617 st
->cbReserved2
= sz
;
618 st
->lpReserved2
= ptr
;
620 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
621 if (num
<= 0 || (flags
[0] & WX_OPEN
))
623 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
626 if (num
<= 1 || (flags
[1] & WX_OPEN
))
628 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
631 if (num
<= 2 || (flags
[2] & WX_OPEN
))
633 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
641 /******************************************************************************
644 * Execute a command line as an external program. Must allow recursion.
647 * Manual testing under windows shows PATHEXT plays a key part in this,
648 * and the search algorithm and precedence appears to be as follows.
651 * If directory supplied on command, just use that directory
652 * If extension supplied on command, look for that explicit name first
653 * Otherwise, search in each directory on the path
655 * If extension supplied on command, look for that explicit name first
656 * Then look for supplied name .* (even if extension supplied, so
657 * 'garbage.exe' will match 'garbage.exe.cmd')
658 * If any found, cycle through PATHEXT looking for name.exe one by one
660 * Once a match has been found, it is launched - Code currently uses
661 * findexecutable to acheive this which is left untouched.
664 void WCMD_run_program (char *command
, int called
) {
667 char pathtosearch
[MAX_PATH
];
669 char stemofsearch
[MAX_PATH
];
671 char pathext
[MAXSTRING
];
672 BOOL extensionsupplied
= FALSE
;
673 BOOL launched
= FALSE
;
678 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
679 if (!(*param1
) && !(*param2
))
682 /* Calculate the search path and stem to search for */
683 if (strpbrk (param1
, "/\\:") == NULL
) { /* No explicit path given, search path */
684 strcpy(pathtosearch
,".;");
685 len
= GetEnvironmentVariable ("PATH", &pathtosearch
[2], sizeof(pathtosearch
)-2);
686 if ((len
== 0) || (len
>= sizeof(pathtosearch
) - 2)) {
687 lstrcpy (pathtosearch
, ".");
689 if (strchr(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
690 strcpy(stemofsearch
, param1
);
694 /* Convert eg. ..\fred to include a directory by removing file part */
695 GetFullPathName(param1
, MAX_PATH
, pathtosearch
, NULL
);
696 lastSlash
= strrchr(pathtosearch
, '\\');
697 if (lastSlash
) *lastSlash
= 0x00;
698 if (strchr(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
699 strcpy(stemofsearch
, lastSlash
+1);
702 /* Now extract PATHEXT */
703 len
= GetEnvironmentVariable ("PATHEXT", pathext
, sizeof(pathext
));
704 if ((len
== 0) || (len
>= sizeof(pathext
))) {
705 lstrcpy (pathext
, ".bat;.com;.cmd;.exe");
708 /* Loop through the search path, dir by dir */
709 pathposn
= pathtosearch
;
710 while (!launched
&& pathposn
) {
712 char thisDir
[MAX_PATH
] = "";
716 /* Work on the first directory on the search path */
717 pos
= strchr(pathposn
, ';');
719 strncpy(thisDir
, pathposn
, (pos
-pathposn
));
720 thisDir
[(pos
-pathposn
)] = 0x00;
724 strcpy(thisDir
, pathposn
);
728 /* Since you can have eg. ..\.. on the path, need to expand
729 to full information */
730 strcpy(temp
, thisDir
);
731 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
733 /* 1. If extension supplied, see if that file exists */
734 strcat(thisDir
, "\\");
735 strcat(thisDir
, stemofsearch
);
736 pos
= &thisDir
[strlen(thisDir
)]; /* Pos = end of name */
738 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
742 /* 2. Any .* matches? */
745 WIN32_FIND_DATA finddata
;
747 strcat(thisDir
,".*");
748 h
= FindFirstFile(thisDir
, &finddata
);
750 if (h
!= INVALID_HANDLE_VALUE
) {
752 char *thisExt
= pathext
;
754 /* 3. Yes - Try each path ext */
756 char *nextExt
= strchr(thisExt
, ';');
759 strncpy(pos
, thisExt
, (nextExt
-thisExt
));
760 pos
[(nextExt
-thisExt
)] = 0x00;
763 strcpy(pos
, thisExt
);
767 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
775 /* Once found, launch it */
778 PROCESS_INFORMATION pe
;
782 char *ext
= strrchr( thisDir
, '.' );
785 /* Special case BAT and CMD */
786 if (ext
&& !strcasecmp(ext
, ".bat")) {
787 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
789 } else if (ext
&& !strcasecmp(ext
, ".cmd")) {
790 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
794 /* thisDir contains the file to be launched, but with what?
795 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
796 hinst
= FindExecutable (param1
, NULL
, temp
);
797 if ((INT_PTR
)hinst
< 32)
800 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
802 ZeroMemory (&st
, sizeof(STARTUPINFO
));
803 st
.cb
= sizeof(STARTUPINFO
);
804 init_msvcrt_io_block(&st
);
806 /* Launch the process and if a CUI wait on it to complete */
807 status
= CreateProcess (thisDir
, command
, NULL
, NULL
, TRUE
,
808 0, NULL
, NULL
, &st
, &pe
);
809 if ((opt_c
|| opt_k
) && !opt_s
&& !status
810 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
811 /* strip first and last quote characters and try again */
812 WCMD_opt_s_strip_quotes(command
);
814 WCMD_run_program(command
, called
);
819 /* If a command fails to launch, it sets errorlevel 9009 - which
820 does not seem to have any associated constant definition */
824 if (!console
) errorlevel
= 0;
827 if (!HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
828 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
829 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
831 CloseHandle(pe
.hProcess
);
832 CloseHandle(pe
.hThread
);
838 /* Not found anywhere - give up */
839 SetLastError(ERROR_FILE_NOT_FOUND
);
842 /* If a command fails to launch, it sets errorlevel 9009 - which
843 does not seem to have any associated constant definition */
849 /******************************************************************************
852 * Display the prompt on STDout
856 void WCMD_show_prompt (void) {
859 char out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
863 len
= GetEnvironmentVariable ("PROMPT", prompt_string
, sizeof(prompt_string
));
864 if ((len
== 0) || (len
>= sizeof(prompt_string
))) {
865 lstrcpy (prompt_string
, "$P$G");
877 switch (toupper(*p
)) {
891 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
910 status
= GetCurrentDirectory (sizeof(curdir
), curdir
);
916 status
= GetCurrentDirectory (sizeof(curdir
), curdir
);
929 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
933 lstrcat (q
, version_string
);
944 WCMD_output_asis (out_string
);
947 /****************************************************************************
950 * Print the message for GetLastError
953 void WCMD_print_error (void) {
958 error_code
= GetLastError ();
959 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
960 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
962 WCMD_output ("FIXME: Cannot display message for error %d, status %d\n",
963 error_code
, GetLastError());
966 WCMD_output_asis (lpMsgBuf
);
967 LocalFree ((HLOCAL
)lpMsgBuf
);
968 WCMD_output_asis (newline
);
972 /*******************************************************************
973 * WCMD_parse - parse a command into parameters and qualifiers.
975 * On exit, all qualifiers are concatenated into q, the first string
976 * not beginning with "/" is in p1 and the
977 * second in p2. Any subsequent non-qualifier strings are lost.
978 * Parameters in quotes are handled.
981 void WCMD_parse (char *s
, char *q
, char *p1
, char *p2
) {
985 *q
= *p1
= *p2
= '\0';
990 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
991 *q
++ = toupper (*s
++);
1001 while ((*s
!= '\0') && (*s
!= '"')) {
1002 if (p
== 0) *p1
++ = *s
++;
1003 else if (p
== 1) *p2
++ = *s
++;
1006 if (p
== 0) *p1
= '\0';
1007 if (p
== 1) *p2
= '\0';
1014 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')) {
1015 if (p
== 0) *p1
++ = *s
++;
1016 else if (p
== 1) *p2
++ = *s
++;
1019 if (p
== 0) *p1
= '\0';
1020 if (p
== 1) *p2
= '\0';
1026 /*******************************************************************
1027 * WCMD_output - send output to current standard output device.
1031 void WCMD_output (const char *format
, ...) {
1037 va_start(ap
,format
);
1038 ret
= vsnprintf (string
, sizeof( string
), format
, ap
);
1040 if( ret
>= sizeof( string
)) {
1041 WCMD_output_asis("ERR: output truncated in WCMD_output\n" );
1042 string
[sizeof( string
) -1] = '\0';
1044 WCMD_output_asis(string
);
1048 static int line_count
;
1049 static int max_height
;
1050 static BOOL paged_mode
;
1052 void WCMD_enter_paged_mode(void)
1054 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1056 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
))
1057 max_height
= consoleInfo
.dwSize
.Y
;
1061 line_count
= 5; /* keep 5 lines from previous output */
1064 void WCMD_leave_paged_mode(void)
1069 /*******************************************************************
1070 * WCMD_output_asis - send output to current standard output device.
1071 * without formatting eg. when message contains '%'
1074 void WCMD_output_asis (const char *message
) {
1081 if ((ptr
= strchr(message
, '\n')) != NULL
) ptr
++;
1082 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE
), message
,
1083 (ptr
) ? ptr
- message
: lstrlen(message
), &count
, NULL
);
1085 if (++line_count
>= max_height
- 1) {
1087 WCMD_output_asis (anykey
);
1088 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
1091 } while ((message
= ptr
) != NULL
);
1093 WriteFile (GetStdHandle(STD_OUTPUT_HANDLE
), message
, lstrlen(message
), &count
, NULL
);
1098 /***************************************************************************
1099 * WCMD_strtrim_leading_spaces
1101 * Remove leading spaces from a string. Return a pointer to the first
1102 * non-space character. Does not modify the input string
1105 char *WCMD_strtrim_leading_spaces (char *string
) {
1110 while (*ptr
== ' ') ptr
++;
1114 /*************************************************************************
1115 * WCMD_strtrim_trailing_spaces
1117 * Remove trailing spaces from a string. This routine modifies the input
1118 * string by placing a null after the last non-space character
1121 void WCMD_strtrim_trailing_spaces (char *string
) {
1125 ptr
= string
+ lstrlen (string
) - 1;
1126 while ((*ptr
== ' ') && (ptr
>= string
)) {
1132 /*************************************************************************
1133 * WCMD_opt_s_strip_quotes
1135 * Remove first and last quote characters, preserving all other text
1138 void WCMD_opt_s_strip_quotes(char *cmd
) {
1139 char *src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1140 while((*dest
=*src
) != '\0') {
1147 while ((*dest
++=*lastq
++) != 0)
1152 /*************************************************************************
1155 * Handle pipes within a command - the DOS way using temporary files.
1158 void WCMD_pipe (char *command
) {
1161 char temp_path
[MAX_PATH
], temp_file
[MAX_PATH
], temp_file2
[MAX_PATH
], temp_cmd
[1024];
1163 GetTempPath (sizeof(temp_path
), temp_path
);
1164 GetTempFileName (temp_path
, "CMD", 0, temp_file
);
1165 p
= strchr(command
, '|');
1167 wsprintf (temp_cmd
, "%s > %s", command
, temp_file
);
1168 WCMD_process_command (temp_cmd
);
1170 while ((p
= strchr(command
, '|'))) {
1172 GetTempFileName (temp_path
, "CMD", 0, temp_file2
);
1173 wsprintf (temp_cmd
, "%s < %s > %s", command
, temp_file
, temp_file2
);
1174 WCMD_process_command (temp_cmd
);
1175 DeleteFile (temp_file
);
1176 lstrcpy (temp_file
, temp_file2
);
1179 wsprintf (temp_cmd
, "%s < %s", command
, temp_file
);
1180 WCMD_process_command (temp_cmd
);
1181 DeleteFile (temp_file
);
1184 /*************************************************************************
1185 * WCMD_expand_envvar
1187 * Expands environment variables, allowing for character substitution
1189 static char *WCMD_expand_envvar(char *start
) {
1190 char *endOfVar
= NULL
, *s
;
1191 char *colonpos
= NULL
;
1192 char thisVar
[MAXSTRING
];
1193 char thisVarContents
[MAXSTRING
];
1194 char savedchar
= 0x00;
1197 /* Find the end of the environment variable, and extract name */
1198 endOfVar
= strchr(start
+1, '%');
1199 if (endOfVar
== NULL
) {
1200 /* FIXME: Some special conditions here depending opn whether
1201 in batch, complex or not, and whether env var exists or not! */
1204 strncpy(thisVar
, start
, (endOfVar
- start
)+1);
1205 thisVar
[(endOfVar
- start
)+1] = 0x00;
1206 colonpos
= strchr(thisVar
+1, ':');
1208 /* If there's complex substitution, just need %var% for now
1209 to get the expanded data to play with */
1212 savedchar
= *(colonpos
+1);
1213 *(colonpos
+1) = 0x00;
1216 /* Expand to contents, if unchanged, return */
1217 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1218 /* override if existing env var called that name */
1219 if ((CompareString (LOCALE_USER_DEFAULT
,
1220 NORM_IGNORECASE
| SORT_STRINGSORT
,
1221 thisVar
, 12, "%ERRORLEVEL%", -1) == 2) &&
1222 (GetEnvironmentVariable("ERRORLEVEL", thisVarContents
, 1) == 0) &&
1223 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1224 sprintf(thisVarContents
, "%d", errorlevel
);
1225 len
= strlen(thisVarContents
);
1227 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1228 NORM_IGNORECASE
| SORT_STRINGSORT
,
1229 thisVar
, 6, "%DATE%", -1) == 2) &&
1230 (GetEnvironmentVariable("DATE", thisVarContents
, 1) == 0) &&
1231 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1233 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1234 NULL
, thisVarContents
, MAXSTRING
);
1235 len
= strlen(thisVarContents
);
1237 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1238 NORM_IGNORECASE
| SORT_STRINGSORT
,
1239 thisVar
, 6, "%TIME%", -1) == 2) &&
1240 (GetEnvironmentVariable("TIME", thisVarContents
, 1) == 0) &&
1241 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1242 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1243 NULL
, thisVarContents
, MAXSTRING
);
1244 len
= strlen(thisVarContents
);
1246 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1247 NORM_IGNORECASE
| SORT_STRINGSORT
,
1248 thisVar
, 4, "%CD%", -1) == 2) &&
1249 (GetEnvironmentVariable("CD", thisVarContents
, 1) == 0) &&
1250 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1251 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1252 len
= strlen(thisVarContents
);
1254 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1255 NORM_IGNORECASE
| SORT_STRINGSORT
,
1256 thisVar
, 8, "%RANDOM%", -1) == 2) &&
1257 (GetEnvironmentVariable("RANDOM", thisVarContents
, 1) == 0) &&
1258 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1259 sprintf(thisVarContents
, "%d", rand() % 32768);
1260 len
= strlen(thisVarContents
);
1264 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1265 sizeof(thisVarContents
));
1271 /* In a batch program, unknown env vars are replaced with nothing,
1272 note syntax %garbage:1,3% results in anything after the ':'
1274 From the command line, you just get back what you entered */
1275 if (lstrcmpi(thisVar
, thisVarContents
) == 0) {
1277 /* Restore the complex part after the compare */
1280 *(colonpos
+1) = savedchar
;
1283 s
= strdup (endOfVar
+ 1);
1285 /* Command line - just ignore this */
1286 if (context
== NULL
) return endOfVar
+1;
1288 /* Batch - replace unknown env var with nothing */
1289 if (colonpos
== NULL
) {
1293 len
= strlen(thisVar
);
1294 thisVar
[len
-1] = 0x00;
1295 /* If %:...% supplied, : is retained */
1296 if (colonpos
== thisVar
+1) {
1297 strcpy (start
, colonpos
);
1299 strcpy (start
, colonpos
+1);
1308 /* See if we need to do complex substitution (any ':'s), if not
1309 then our work here is done */
1310 if (colonpos
== NULL
) {
1311 s
= strdup (endOfVar
+ 1);
1312 strcpy (start
, thisVarContents
);
1318 /* Restore complex bit */
1320 *(colonpos
+1) = savedchar
;
1323 Handle complex substitutions:
1324 xxx=yyy (replace xxx with yyy)
1325 *xxx=yyy (replace up to and including xxx with yyy)
1326 ~x (from x chars in)
1327 ~-x (from x chars from the end)
1328 ~x,y (from x chars in for y characters)
1329 ~x,-y (from x chars in until y characters from the end)
1332 /* ~ is substring manipulation */
1333 if (savedchar
== '~') {
1335 int substrposition
, substrlength
;
1336 char *commapos
= strchr(colonpos
+2, ',');
1339 substrposition
= atol(colonpos
+2);
1340 if (commapos
) substrlength
= atol(commapos
+1);
1342 s
= strdup (endOfVar
+ 1);
1345 if (substrposition
>= 0) {
1346 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1348 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1351 if (commapos
== NULL
) {
1352 strcpy (start
, startCopy
); /* Copy the lot */
1353 } else if (substrlength
< 0) {
1355 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1356 if (copybytes
> len
) copybytes
= len
;
1357 else if (copybytes
< 0) copybytes
= 0;
1358 strncpy (start
, startCopy
, copybytes
); /* Copy the lot */
1359 start
[copybytes
] = 0x00;
1361 strncpy (start
, startCopy
, substrlength
); /* Copy the lot */
1362 start
[substrlength
] = 0x00;
1369 /* search and replace manipulation */
1371 char *equalspos
= strstr(colonpos
, "=");
1372 char *replacewith
= equalspos
+1;
1377 s
= strdup (endOfVar
+ 1);
1378 if (equalspos
== NULL
) return start
+1;
1380 /* Null terminate both strings */
1381 thisVar
[strlen(thisVar
)-1] = 0x00;
1384 /* Since we need to be case insensitive, copy the 2 buffers */
1385 searchIn
= strdup(thisVarContents
);
1386 CharUpperBuff(searchIn
, strlen(thisVarContents
));
1387 searchFor
= strdup(colonpos
+1);
1388 CharUpperBuff(searchFor
, strlen(colonpos
+1));
1391 /* Handle wildcard case */
1392 if (*(colonpos
+1) == '*') {
1393 /* Search for string to replace */
1394 found
= strstr(searchIn
, searchFor
+1);
1397 /* Do replacement */
1398 strcpy(start
, replacewith
);
1399 strcat(start
, thisVarContents
+ (found
-searchIn
) + strlen(searchFor
+1));
1404 strcpy(start
, thisVarContents
);
1409 /* Loop replacing all instances */
1410 char *lastFound
= searchIn
;
1411 char *outputposn
= start
;
1414 while ((found
= strstr(lastFound
, searchFor
))) {
1416 thisVarContents
+ (lastFound
-searchIn
),
1417 (found
- lastFound
));
1418 outputposn
= outputposn
+ (found
- lastFound
);
1420 strcat(outputposn
, replacewith
);
1421 outputposn
= outputposn
+ strlen(replacewith
);
1422 lastFound
= found
+ strlen(searchFor
);
1425 thisVarContents
+ (lastFound
-searchIn
));
1426 strcat(outputposn
, s
);