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
);
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
);
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
);
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
, '%'))) {
500 /* Replace %~ modifications if in batch program */
501 if (context
&& *(p
+1) == '~') {
502 WCMD_HandleTildaModifiers(&p
, NULL
);
505 /* Replace use of %0...%9 if in batch program*/
506 } else if (context
&& (i
>= 0) && (i
<= 9)) {
507 s
= WCMD_strdupW(p
+2);
508 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
513 /* Replace use of %* if in batch program*/
514 } else if (context
&& *(p
+1)=='*') {
515 WCHAR
*startOfParms
= NULL
;
516 s
= WCMD_strdupW(p
+2);
517 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
518 if (startOfParms
!= NULL
) strcpyW (p
, startOfParms
);
524 p
= WCMD_expand_envvar(p
);
529 /* In a batch program, unknown variables are replace by nothing */
530 /* so remove any remaining %var% */
533 while ((p
= strchrW(p
, '%'))) {
534 s
= strchrW(p
+1, '%');
538 t
= WCMD_strdupW(s
+1);
544 /* Show prompt before batch line IF echo is on and in batch program */
545 if (echo_mode
&& (cmd
[0] != '@')) {
547 WCMD_output_asis ( cmd
);
548 WCMD_output_asis ( newline
);
553 * Changing default drive has to be handled as a special case.
556 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
560 /* According to MSDN CreateProcess docs, special env vars record
561 the current directory on each drive, in the form =C:
562 so see if one specified, and if so go back to it */
563 strcpyW(envvar
, equalsW
);
564 strcatW(envvar
, cmd
);
565 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
566 static const WCHAR fmt
[] = {'%','s','\\','\0'};
567 wsprintf(cmd
, fmt
, cmd
);
569 status
= SetCurrentDirectory (cmd
);
570 if (!status
) WCMD_print_error ();
571 HeapFree( GetProcessHeap(), 0, cmd
);
575 sa
.nLength
= sizeof(sa
);
576 sa
.lpSecurityDescriptor
= NULL
;
577 sa
.bInheritHandle
= TRUE
;
580 * Redirect stdin, stdout and/or stderr if required.
583 if ((p
= strchrW(cmd
,'<')) != NULL
) {
584 if (first_redir
== NULL
) first_redir
= p
;
585 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
586 FILE_ATTRIBUTE_NORMAL
, NULL
);
587 if (h
== INVALID_HANDLE_VALUE
) {
589 HeapFree( GetProcessHeap(), 0, cmd
);
592 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
593 SetStdHandle (STD_INPUT_HANDLE
, h
);
596 /* Scan the whole command looking for > and 2> */
598 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
602 if (first_redir
== NULL
) first_redir
= p
;
605 if (first_redir
== NULL
) first_redir
= (p
-1);
611 creationDisposition
= OPEN_ALWAYS
;
615 creationDisposition
= CREATE_ALWAYS
;
618 /* Add support for 2>&1 */
621 int idx
= *(p
+1) - '0';
623 if (DuplicateHandle(GetCurrentProcess(),
624 GetStdHandle(idx_stdhandles
[idx
]),
627 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
628 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
630 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
633 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
634 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
635 FILE_ATTRIBUTE_NORMAL
, NULL
);
636 if (h
== INVALID_HANDLE_VALUE
) {
638 HeapFree( GetProcessHeap(), 0, cmd
);
641 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
642 INVALID_SET_FILE_POINTER
) {
645 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
648 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
649 SetStdHandle (idx_stdhandles
[handle
], h
);
652 /* Terminate the command string at <, or first 2> or > */
653 if (first_redir
!= NULL
) *first_redir
= '\0';
656 * Strip leading whitespaces, and a '@' if supplied
658 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
659 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
660 if (whichcmd
[0] == '@') whichcmd
++;
663 * Check if the command entered is internal. If it is, pass the rest of the
664 * line down to the command. If not try to run a program.
668 while (IsCharAlphaNumeric(whichcmd
[count
])) {
671 for (i
=0; i
<=WCMD_EXIT
; i
++) {
672 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
673 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
675 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
676 WCMD_parse (p
, quals
, param1
, param2
);
680 WCMD_setshow_attrib ();
687 WCMD_setshow_default (p
);
690 WCMD_clear_screen ();
699 WCMD_setshow_date ();
703 WCMD_delete (p
, TRUE
);
709 WCMD_echo(&whichcmd
[count
]);
712 WCMD_for (p
, cmdList
);
721 WCMD_if (p
, cmdList
);
734 WCMD_setshow_path (p
);
740 WCMD_setshow_prompt ();
759 WCMD_setshow_env (p
);
765 WCMD_setshow_time ();
768 if (strlenW(&whichcmd
[count
]) > 0)
769 WCMD_title(&whichcmd
[count
+1]);
796 WCMD_assoc(p
, FALSE
);
805 WCMD_run_program (whichcmd
, 0);
807 HeapFree( GetProcessHeap(), 0, cmd
);
809 /* Restore old handles */
810 for (i
=0; i
<3; i
++) {
811 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
812 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
813 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
818 static void init_msvcrt_io_block(STARTUPINFO
* st
)
821 /* fetch the parent MSVCRT info block if any, so that the child can use the
822 * same handles as its grand-father
824 st_p
.cb
= sizeof(STARTUPINFO
);
825 GetStartupInfo(&st_p
);
826 st
->cbReserved2
= st_p
.cbReserved2
;
827 st
->lpReserved2
= st_p
.lpReserved2
;
828 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
830 /* Override the entries for fd 0,1,2 if we happened
831 * to change those std handles (this depends on the way wcmd sets
832 * it's new input & output handles)
834 size_t sz
= max(sizeof(unsigned) + (sizeof(WCHAR
) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
835 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
838 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
839 WCHAR
* flags
= (WCHAR
*)(ptr
+ sizeof(unsigned));
840 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(WCHAR
));
842 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
843 st
->cbReserved2
= sz
;
844 st
->lpReserved2
= ptr
;
846 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
847 if (num
<= 0 || (flags
[0] & WX_OPEN
))
849 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
852 if (num
<= 1 || (flags
[1] & WX_OPEN
))
854 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
857 if (num
<= 2 || (flags
[2] & WX_OPEN
))
859 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
867 /******************************************************************************
870 * Execute a command line as an external program. Must allow recursion.
873 * Manual testing under windows shows PATHEXT plays a key part in this,
874 * and the search algorithm and precedence appears to be as follows.
877 * If directory supplied on command, just use that directory
878 * If extension supplied on command, look for that explicit name first
879 * Otherwise, search in each directory on the path
881 * If extension supplied on command, look for that explicit name first
882 * Then look for supplied name .* (even if extension supplied, so
883 * 'garbage.exe' will match 'garbage.exe.cmd')
884 * If any found, cycle through PATHEXT looking for name.exe one by one
886 * Once a match has been found, it is launched - Code currently uses
887 * findexecutable to acheive this which is left untouched.
890 void WCMD_run_program (WCHAR
*command
, int called
) {
892 WCHAR temp
[MAX_PATH
];
893 WCHAR pathtosearch
[MAXSTRING
];
895 WCHAR stemofsearch
[MAX_PATH
];
897 WCHAR pathext
[MAXSTRING
];
898 BOOL extensionsupplied
= FALSE
;
899 BOOL launched
= FALSE
;
901 BOOL assumeInternal
= FALSE
;
903 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
904 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
905 static const WCHAR delims
[] = {'/','\\',':','\0'};
907 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
908 if (!(*param1
) && !(*param2
))
911 /* Calculate the search path and stem to search for */
912 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
913 static const WCHAR curDir
[] = {'.',';','\0'};
914 strcpyW(pathtosearch
, curDir
);
915 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
916 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
917 static const WCHAR curDir
[] = {'.','\0'};
918 strcpyW (pathtosearch
, curDir
);
920 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
921 strcpyW(stemofsearch
, param1
);
925 /* Convert eg. ..\fred to include a directory by removing file part */
926 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
927 lastSlash
= strrchrW(pathtosearch
, '\\');
928 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
929 if (lastSlash
) *lastSlash
= 0x00;
930 strcpyW(stemofsearch
, lastSlash
+1);
933 /* Now extract PATHEXT */
934 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
935 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
936 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
939 '.','e','x','e','\0'};
940 strcpyW (pathext
, dfltPathExt
);
943 /* Loop through the search path, dir by dir */
944 pathposn
= pathtosearch
;
945 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
946 wine_dbgstr_w(stemofsearch
));
947 while (!launched
&& pathposn
) {
949 WCHAR thisDir
[MAX_PATH
] = {'\0'};
952 const WCHAR slashW
[] = {'\\','\0'};
954 /* Work on the first directory on the search path */
955 pos
= strchrW(pathposn
, ';');
957 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
958 thisDir
[(pos
-pathposn
)] = 0x00;
962 strcpyW(thisDir
, pathposn
);
966 /* Since you can have eg. ..\.. on the path, need to expand
967 to full information */
968 strcpyW(temp
, thisDir
);
969 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
971 /* 1. If extension supplied, see if that file exists */
972 strcatW(thisDir
, slashW
);
973 strcatW(thisDir
, stemofsearch
);
974 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
976 /* 1. If extension supplied, see if that file exists */
977 if (extensionsupplied
) {
978 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
983 /* 2. Any .* matches? */
986 WIN32_FIND_DATA finddata
;
987 static const WCHAR allFiles
[] = {'.','*','\0'};
989 strcatW(thisDir
,allFiles
);
990 h
= FindFirstFile(thisDir
, &finddata
);
992 if (h
!= INVALID_HANDLE_VALUE
) {
994 WCHAR
*thisExt
= pathext
;
996 /* 3. Yes - Try each path ext */
998 WCHAR
*nextExt
= strchrW(thisExt
, ';');
1001 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
1002 pos
[(nextExt
-thisExt
)] = 0x00;
1003 thisExt
= nextExt
+1;
1005 strcpyW(pos
, thisExt
);
1009 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1017 /* Internal programs won't be picked up by this search, so even
1018 though not found, try one last createprocess and wait for it
1020 Note: Ideally we could tell between a console app (wait) and a
1021 windows app, but the API's for it fail in this case */
1022 if (!found
&& pathposn
== NULL
) {
1023 WINE_TRACE("ASSUMING INTERNAL\n");
1024 assumeInternal
= TRUE
;
1026 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1029 /* Once found, launch it */
1030 if (found
|| assumeInternal
) {
1032 PROCESS_INFORMATION pe
;
1036 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1037 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1038 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1042 /* Special case BAT and CMD */
1043 if (ext
&& !strcmpiW(ext
, batExt
)) {
1044 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1046 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1047 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1051 /* thisDir contains the file to be launched, but with what?
1052 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1053 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1054 if ((INT_PTR
)hinst
< 32)
1057 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1059 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1060 st
.cb
= sizeof(STARTUPINFO
);
1061 init_msvcrt_io_block(&st
);
1063 /* Launch the process and if a CUI wait on it to complete
1064 Note: Launching internal wine processes cannot specify a full path to exe */
1065 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1066 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1067 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1068 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1069 /* strip first and last quote WCHARacters and try again */
1070 WCMD_opt_s_strip_quotes(command
);
1072 WCMD_run_program(command
, called
);
1076 WCMD_print_error ();
1077 /* If a command fails to launch, it sets errorlevel 9009 - which
1078 does not seem to have any associated constant definition */
1082 if (!assumeInternal
&& !console
) errorlevel
= 0;
1085 if (assumeInternal
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1086 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1087 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1089 CloseHandle(pe
.hProcess
);
1090 CloseHandle(pe
.hThread
);
1096 /* Not found anywhere - give up */
1097 SetLastError(ERROR_FILE_NOT_FOUND
);
1098 WCMD_print_error ();
1100 /* If a command fails to launch, it sets errorlevel 9009 - which
1101 does not seem to have any associated constant definition */
1107 /******************************************************************************
1110 * Display the prompt on STDout
1114 void WCMD_show_prompt (void) {
1117 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1120 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1122 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1123 sizeof(prompt_string
)/sizeof(WCHAR
));
1124 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1125 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1126 strcpyW (prompt_string
, dfltPrompt
);
1131 while (*p
!= '\0') {
1138 switch (toupper(*p
)) {
1152 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1171 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1177 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1179 strcatW (q
, curdir
);
1190 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1194 strcatW (q
, version_string
);
1201 if (pushd_directories
) {
1202 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1203 q
= q
+ pushd_directories
->u
.stackdepth
;
1211 WCMD_output_asis (out_string
);
1214 /****************************************************************************
1217 * Print the message for GetLastError
1220 void WCMD_print_error (void) {
1225 error_code
= GetLastError ();
1226 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1227 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1229 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1230 error_code
, GetLastError());
1233 WCMD_output_asis (lpMsgBuf
);
1234 LocalFree ((HLOCAL
)lpMsgBuf
);
1235 WCMD_output_asis (newline
);
1239 /*******************************************************************
1240 * WCMD_parse - parse a command into parameters and qualifiers.
1242 * On exit, all qualifiers are concatenated into q, the first string
1243 * not beginning with "/" is in p1 and the
1244 * second in p2. Any subsequent non-qualifier strings are lost.
1245 * Parameters in quotes are handled.
1248 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1252 *q
= *p1
= *p2
= '\0';
1257 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1258 *q
++ = toupper (*s
++);
1268 while ((*s
!= '\0') && (*s
!= '"')) {
1269 if (p
== 0) *p1
++ = *s
++;
1270 else if (p
== 1) *p2
++ = *s
++;
1273 if (p
== 0) *p1
= '\0';
1274 if (p
== 1) *p2
= '\0';
1281 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')) {
1282 if (p
== 0) *p1
++ = *s
++;
1283 else if (p
== 1) *p2
++ = *s
++;
1286 if (p
== 0) *p1
= '\0';
1287 if (p
== 1) *p2
= '\0';
1293 /*******************************************************************
1294 * WCMD_output_asis_len - send output to current standard output
1296 * Output a formatted unicode string. Ideally this will go to the console
1297 * and hence required WriteConsoleW to output it, however if file i/o is
1298 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1300 static void WCMD_output_asis_len(const WCHAR
*message
, int len
) {
1305 /* If nothing to write, return (MORE does this sometimes) */
1308 /* Try to write as unicode assuming it is to a console */
1309 res
= WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE
),
1310 message
, len
, &nOut
, NULL
);
1312 /* If writing to console fails, assume its file
1313 i/o so convert to OEM codepage and output */
1315 BOOL usedDefaultChar
= FALSE
;
1316 DWORD convertedChars
;
1318 if (!unicodePipes
) {
1320 * Allocate buffer to use when writing to file. (Not freed, as one off)
1322 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1323 MAX_WRITECONSOLE_SIZE
);
1325 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1329 /* Convert to OEM, then output */
1330 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1331 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1332 "?", &usedDefaultChar
);
1333 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), output_bufA
, convertedChars
,
1336 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), message
, len
*sizeof(WCHAR
),
1343 /*******************************************************************
1344 * WCMD_output - send output to current standard output device.
1348 void WCMD_output (const WCHAR
*format
, ...) {
1354 va_start(ap
,format
);
1355 ret
= wvsprintf (string
, format
, ap
);
1356 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1357 WINE_ERR("Output truncated in WCMD_output\n" );
1358 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1362 WCMD_output_asis_len(string
, ret
);
1366 static int line_count
;
1367 static int max_height
;
1368 static int max_width
;
1369 static BOOL paged_mode
;
1370 static int numChars
;
1372 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1374 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1376 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1377 max_height
= consoleInfo
.dwSize
.Y
;
1378 max_width
= consoleInfo
.dwSize
.X
;
1386 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1389 void WCMD_leave_paged_mode(void)
1392 pagedMessage
= NULL
;
1395 /*******************************************************************
1396 * WCMD_output_asis - send output to current standard output device.
1397 * without formatting eg. when message contains '%'
1400 void WCMD_output_asis (const WCHAR
*message
) {
1408 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1412 if (*ptr
== '\n') ptr
++;
1413 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
));
1416 if (++line_count
>= max_height
- 1) {
1418 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
));
1419 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1420 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1423 } while (((message
= ptr
) != NULL
) && (*ptr
));
1425 WCMD_output_asis_len(message
, lstrlen(message
));
1430 /***************************************************************************
1431 * WCMD_strtrim_leading_spaces
1433 * Remove leading spaces from a string. Return a pointer to the first
1434 * non-space character. Does not modify the input string
1437 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1442 while (*ptr
== ' ') ptr
++;
1446 /*************************************************************************
1447 * WCMD_strtrim_trailing_spaces
1449 * Remove trailing spaces from a string. This routine modifies the input
1450 * string by placing a null after the last non-space WCHARacter
1453 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1457 ptr
= string
+ strlenW (string
) - 1;
1458 while ((*ptr
== ' ') && (ptr
>= string
)) {
1464 /*************************************************************************
1465 * WCMD_opt_s_strip_quotes
1467 * Remove first and last quote WCHARacters, preserving all other text
1470 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1471 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1472 while((*dest
=*src
) != '\0') {
1479 while ((*dest
++=*lastq
++) != 0)
1484 /*************************************************************************
1487 * Handle pipes within a command - the DOS way using temporary files.
1490 void WCMD_pipe (CMD_LIST
**cmdEntry
) {
1493 WCHAR
*command
= (*cmdEntry
)->command
;
1494 WCHAR temp_path
[MAX_PATH
], temp_file
[MAX_PATH
], temp_file2
[MAX_PATH
], temp_cmd
[1024];
1495 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1496 static const WCHAR redirIn
[] = {'%','s',' ','<',' ','%','s','\0'};
1497 static const WCHAR redirBoth
[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1498 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1501 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1502 GetTempFileName (temp_path
, cmdW
, 0, temp_file
);
1503 p
= strchrW(command
, '|');
1505 wsprintf (temp_cmd
, redirOut
, command
, temp_file
);
1506 WCMD_process_command (temp_cmd
, cmdEntry
);
1508 while ((p
= strchrW(command
, '|'))) {
1510 GetTempFileName (temp_path
, cmdW
, 0, temp_file2
);
1511 wsprintf (temp_cmd
, redirBoth
, command
, temp_file
, temp_file2
);
1512 WCMD_process_command (temp_cmd
, cmdEntry
);
1513 DeleteFile (temp_file
);
1514 strcpyW (temp_file
, temp_file2
);
1517 wsprintf (temp_cmd
, redirIn
, command
, temp_file
);
1518 WCMD_process_command (temp_cmd
, cmdEntry
);
1519 DeleteFile (temp_file
);
1522 /*************************************************************************
1523 * WCMD_expand_envvar
1525 * Expands environment variables, allowing for WCHARacter substitution
1527 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
) {
1528 WCHAR
*endOfVar
= NULL
, *s
;
1529 WCHAR
*colonpos
= NULL
;
1530 WCHAR thisVar
[MAXSTRING
];
1531 WCHAR thisVarContents
[MAXSTRING
];
1532 WCHAR savedchar
= 0x00;
1535 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1536 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1537 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1538 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1539 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1540 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1541 static const WCHAR Cd
[] = {'C','D','\0'};
1542 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1543 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1544 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1546 /* Find the end of the environment variable, and extract name */
1547 endOfVar
= strchrW(start
+1, '%');
1548 if (endOfVar
== NULL
) {
1549 /* In batch program, missing terminator for % and no following
1550 ':' just removes the '%' */
1551 s
= WCMD_strdupW(start
+ 1);
1555 /* FIXME: Some other special conditions here depending on whether
1556 in batch, complex or not, and whether env var exists or not! */
1559 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1560 thisVar
[(endOfVar
- start
)+1] = 0x00;
1561 colonpos
= strchrW(thisVar
+1, ':');
1563 /* If there's complex substitution, just need %var% for now
1564 to get the expanded data to play with */
1567 savedchar
= *(colonpos
+1);
1568 *(colonpos
+1) = 0x00;
1571 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1573 /* Expand to contents, if unchanged, return */
1574 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1575 /* override if existing env var called that name */
1576 if ((CompareString (LOCALE_USER_DEFAULT
,
1577 NORM_IGNORECASE
| SORT_STRINGSORT
,
1578 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1579 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1580 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1581 static const WCHAR fmt
[] = {'%','d','\0'};
1582 wsprintf(thisVarContents
, fmt
, errorlevel
);
1583 len
= strlenW(thisVarContents
);
1585 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1586 NORM_IGNORECASE
| SORT_STRINGSORT
,
1587 thisVar
, 6, DateP
, -1) == 2) &&
1588 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1589 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1591 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1592 NULL
, thisVarContents
, MAXSTRING
);
1593 len
= strlenW(thisVarContents
);
1595 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1596 NORM_IGNORECASE
| SORT_STRINGSORT
,
1597 thisVar
, 6, TimeP
, -1) == 2) &&
1598 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1599 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1600 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1601 NULL
, thisVarContents
, MAXSTRING
);
1602 len
= strlenW(thisVarContents
);
1604 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1605 NORM_IGNORECASE
| SORT_STRINGSORT
,
1606 thisVar
, 4, CdP
, -1) == 2) &&
1607 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1608 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1609 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1610 len
= strlenW(thisVarContents
);
1612 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1613 NORM_IGNORECASE
| SORT_STRINGSORT
,
1614 thisVar
, 8, RandomP
, -1) == 2) &&
1615 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1616 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1617 static const WCHAR fmt
[] = {'%','d','\0'};
1618 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1619 len
= strlenW(thisVarContents
);
1623 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1624 sizeof(thisVarContents
)/sizeof(WCHAR
));
1630 /* In a batch program, unknown env vars are replaced with nothing,
1631 note syntax %garbage:1,3% results in anything after the ':'
1633 From the command line, you just get back what you entered */
1634 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1636 /* Restore the complex part after the compare */
1639 *(colonpos
+1) = savedchar
;
1642 s
= WCMD_strdupW(endOfVar
+ 1);
1644 /* Command line - just ignore this */
1645 if (context
== NULL
) return endOfVar
+1;
1647 /* Batch - replace unknown env var with nothing */
1648 if (colonpos
== NULL
) {
1652 len
= strlenW(thisVar
);
1653 thisVar
[len
-1] = 0x00;
1654 /* If %:...% supplied, : is retained */
1655 if (colonpos
== thisVar
+1) {
1656 strcpyW (start
, colonpos
);
1658 strcpyW (start
, colonpos
+1);
1667 /* See if we need to do complex substitution (any ':'s), if not
1668 then our work here is done */
1669 if (colonpos
== NULL
) {
1670 s
= WCMD_strdupW(endOfVar
+ 1);
1671 strcpyW (start
, thisVarContents
);
1677 /* Restore complex bit */
1679 *(colonpos
+1) = savedchar
;
1682 Handle complex substitutions:
1683 xxx=yyy (replace xxx with yyy)
1684 *xxx=yyy (replace up to and including xxx with yyy)
1685 ~x (from x WCHARs in)
1686 ~-x (from x WCHARs from the end)
1687 ~x,y (from x WCHARs in for y WCHARacters)
1688 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1691 /* ~ is substring manipulation */
1692 if (savedchar
== '~') {
1694 int substrposition
, substrlength
= 0;
1695 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1698 substrposition
= atolW(colonpos
+2);
1699 if (commapos
) substrlength
= atolW(commapos
+1);
1701 s
= WCMD_strdupW(endOfVar
+ 1);
1704 if (substrposition
>= 0) {
1705 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1707 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1710 if (commapos
== NULL
) {
1711 strcpyW (start
, startCopy
); /* Copy the lot */
1712 } else if (substrlength
< 0) {
1714 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1715 if (copybytes
> len
) copybytes
= len
;
1716 else if (copybytes
< 0) copybytes
= 0;
1717 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1718 start
[copybytes
] = 0x00;
1720 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1721 start
[substrlength
] = 0x00;
1728 /* search and replace manipulation */
1730 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1731 WCHAR
*replacewith
= equalspos
+1;
1732 WCHAR
*found
= NULL
;
1736 s
= WCMD_strdupW(endOfVar
+ 1);
1737 if (equalspos
== NULL
) return start
+1;
1739 /* Null terminate both strings */
1740 thisVar
[strlenW(thisVar
)-1] = 0x00;
1743 /* Since we need to be case insensitive, copy the 2 buffers */
1744 searchIn
= WCMD_strdupW(thisVarContents
);
1745 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1746 searchFor
= WCMD_strdupW(colonpos
+1);
1747 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1750 /* Handle wildcard case */
1751 if (*(colonpos
+1) == '*') {
1752 /* Search for string to replace */
1753 found
= strstrW(searchIn
, searchFor
+1);
1756 /* Do replacement */
1757 strcpyW(start
, replacewith
);
1758 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1763 strcpyW(start
, thisVarContents
);
1768 /* Loop replacing all instances */
1769 WCHAR
*lastFound
= searchIn
;
1770 WCHAR
*outputposn
= start
;
1773 while ((found
= strstrW(lastFound
, searchFor
))) {
1774 lstrcpynW(outputposn
,
1775 thisVarContents
+ (lastFound
-searchIn
),
1776 (found
- lastFound
)+1);
1777 outputposn
= outputposn
+ (found
- lastFound
);
1778 strcatW(outputposn
, replacewith
);
1779 outputposn
= outputposn
+ strlenW(replacewith
);
1780 lastFound
= found
+ strlenW(searchFor
);
1783 thisVarContents
+ (lastFound
-searchIn
));
1784 strcatW(outputposn
, s
);
1793 /*************************************************************************
1795 * Load a string from the resource file, handling any error
1796 * Returns string retrieved from resource file
1798 WCHAR
*WCMD_LoadMessage(UINT id
) {
1799 static WCHAR msg
[2048];
1800 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1802 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1803 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1804 strcpyW(msg
, failedMsg
);
1809 /*************************************************************************
1811 * A wide version of strdup as its missing from unicode.h
1813 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1814 int len
=strlenW(input
)+1;
1815 /* Note: Use malloc not HeapAlloc to emulate strdup */
1816 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1817 memcpy(result
, input
, len
* sizeof(WCHAR
));
1821 /***************************************************************************
1824 * Read characters in from a console/file, returning result in Unicode
1825 * with signature identical to ReadFile
1827 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1828 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1832 /* Try to read from console as Unicode */
1833 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1835 /* If reading from console has failed we assume its file
1836 i/o so read in and convert from OEM codepage */
1841 * Allocate buffer to use when reading from file. Not freed
1843 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1844 MAX_WRITECONSOLE_SIZE
);
1846 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1850 /* Read from file (assume OEM codepage) */
1851 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1853 /* Convert from OEM */
1854 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,
1861 /***************************************************************************
1864 * Domps out the parsed command line to ensure syntax is correct
1866 void WCMD_DumpCommands(CMD_LIST
*commands
) {
1867 WCHAR buffer
[MAXSTRING
];
1868 CMD_LIST
*thisCmd
= commands
;
1869 const WCHAR fmt
[] = {'%','p',' ','%','c',' ','%','2','.','2','d',' ',
1870 '%','p',' ','%','s','\0'};
1872 WINE_TRACE("Parsed line:\n");
1873 while (thisCmd
!= NULL
) {
1874 sprintfW(buffer
, fmt
,
1876 thisCmd
->isAmphersand
?'Y':'N',
1877 thisCmd
->bracketDepth
,
1878 thisCmd
->nextcommand
,
1880 WINE_TRACE("%s\n", wine_dbgstr_w(buffer
));
1881 thisCmd
= thisCmd
->nextcommand
;
1885 /***************************************************************************
1886 * WCMD_ReadAndParseLine
1888 * Either uses supplied input or
1889 * Reads a file from the handle, and then...
1890 * Parse the text buffer, spliting into seperate commands
1891 * - unquoted && strings split 2 commands but the 2nd is flagged as
1893 * - ( as the first character just ups the bracket depth
1894 * - unquoted ) when bracket depth > 0 terminates a bracket and
1895 * adds a CMD_LIST structure with null command
1896 * - Anything else gets put into the command string (including
1899 WCHAR
*WCMD_ReadAndParseLine(WCHAR
*optionalcmd
, CMD_LIST
**output
, HANDLE readFrom
) {
1902 BOOL inQuotes
= FALSE
;
1903 WCHAR curString
[MAXSTRING
];
1906 CMD_LIST
*thisEntry
= NULL
;
1907 CMD_LIST
*lastEntry
= NULL
;
1908 BOOL isAmphersand
= FALSE
;
1909 static WCHAR
*extraSpace
= NULL
; /* Deliberately never freed */
1910 const WCHAR remCmd
[] = {'r','e','m',' ','\0'};
1911 const WCHAR forCmd
[] = {'f','o','r',' ','\0'};
1912 const WCHAR ifCmd
[] = {'i','f',' ','\0'};
1913 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
1918 BOOL onlyWhiteSpace
= FALSE
;
1919 BOOL lastWasWhiteSpace
= FALSE
;
1920 BOOL lastWasDo
= FALSE
;
1921 BOOL lastWasIn
= FALSE
;
1922 BOOL lastWasElse
= FALSE
;
1924 /* Allocate working space for a command read from keyboard, file etc */
1926 extraSpace
= HeapAlloc(GetProcessHeap(), 0, (MAXSTRING
+1) * sizeof(WCHAR
));
1928 /* If initial command read in, use that, otherwise get input from handle */
1929 if (optionalcmd
!= NULL
) {
1930 strcpyW(extraSpace
, optionalcmd
);
1931 } else if (readFrom
== INVALID_HANDLE_VALUE
) {
1932 WINE_FIXME("No command nor handle supplied\n");
1934 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) return NULL
;
1936 curPos
= extraSpace
;
1938 /* Handle truncated input - issue warning */
1939 if (strlenW(extraSpace
) == MAXSTRING
-1) {
1940 WCMD_output_asis(WCMD_LoadMessage(WCMD_TRUNCATEDLINE
));
1941 WCMD_output_asis(extraSpace
);
1942 WCMD_output_asis(newline
);
1945 /* Start with an empty string */
1948 /* Parse every character on the line being processed */
1949 while (*curPos
!= 0x00) {
1954 WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, curLen,
1955 lastWasWhiteSpace, onlyWhiteSpace);
1958 /* Certain commands need special handling */
1960 const WCHAR forDO
[] = {'d','o',' ','\0'};
1962 /* If command starts with 'rem', ignore any &&, ( etc */
1963 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1964 curPos
, 4, remCmd
, -1) == 2) {
1967 /* If command starts with 'for', handle ('s mid line after IN or DO */
1968 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1969 curPos
, 4, forCmd
, -1) == 2) {
1972 /* If command starts with 'if' or 'else', handle ('s mid line. We should ensure this
1973 is only true in the command portion of the IF statement, but this
1974 should suffice for now
1975 FIXME: Silly syntax like "if 1(==1( (
1977 )" will be parsed wrong */
1978 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1979 curPos
, 3, ifCmd
, -1) == 2) {
1982 } else if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1983 curPos
, 5, ifElse
, -1) == 2) {
1986 onlyWhiteSpace
= TRUE
;
1987 memcpy(&curString
[curLen
], curPos
, 5*sizeof(WCHAR
));
1992 /* In a for loop, the DO command will follow a close bracket followed by
1993 whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1994 is then 0, and all whitespace is skipped */
1996 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1997 curPos
, 3, forDO
, -1) == 2)) {
1998 WINE_TRACE("Found DO\n");
2000 onlyWhiteSpace
= TRUE
;
2001 memcpy(&curString
[curLen
], curPos
, 3*sizeof(WCHAR
));
2008 /* Special handling for the 'FOR' command */
2009 if (inFor
&& lastWasWhiteSpace
) {
2010 const WCHAR forIN
[] = {'i','n',' ','\0'};
2012 WINE_TRACE("Found 'FOR', comparing next parm: '%s'\n", wine_dbgstr_w(curPos
));
2014 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2015 curPos
, 3, forIN
, -1) == 2) {
2016 WINE_TRACE("Found IN\n");
2018 onlyWhiteSpace
= TRUE
;
2019 memcpy(&curString
[curLen
], curPos
, 3*sizeof(WCHAR
));
2027 /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
2028 so just use the default processing ie skip character specific
2030 if (!inRem
) thisChar
= *curPos
;
2031 else thisChar
= 'X'; /* Character with no special processing */
2033 lastWasWhiteSpace
= FALSE
; /* Will be reset below */
2037 case '\t':/* drop through - ignore whitespace at the start of a command */
2038 case ' ': if (curLen
> 0)
2039 curString
[curLen
++] = *curPos
;
2041 /* Remember just processed whitespace */
2042 lastWasWhiteSpace
= TRUE
;
2046 case '"': inQuotes
= !inQuotes
;
2049 case '(': /* If a '(' is the first non whitespace in a command portion
2050 ie start of line or just after &&, then we read until an
2051 unquoted ) is found */
2052 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2053 ", for(%d, In:%d, Do:%d)"
2054 ", if(%d, else:%d, lwe:%d)\n",
2057 inFor
, lastWasIn
, lastWasDo
,
2058 inIf
, inElse
, lastWasElse
);
2062 /* If in quotes, ignore brackets */
2063 } else if (inQuotes
) {
2064 curString
[curLen
++] = *curPos
;
2066 /* In a FOR loop, an unquoted '(' may occur straight after
2068 In an IF statement just handle it regardless as we don't
2070 In an ELSE statement, only allow it straight away after
2071 the ELSE and whitespace
2074 (inElse
&& lastWasElse
&& onlyWhiteSpace
) ||
2075 (inFor
&& (lastWasIn
|| lastWasDo
) && onlyWhiteSpace
)) {
2077 /* Add the current command */
2078 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2079 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2080 (curLen
+1) * sizeof(WCHAR
));
2081 memcpy(thisEntry
->command
, curString
, curLen
* sizeof(WCHAR
));
2082 thisEntry
->command
[curLen
] = 0x00;
2084 thisEntry
->nextcommand
= NULL
;
2085 thisEntry
->isAmphersand
= isAmphersand
;
2086 thisEntry
->bracketDepth
= curDepth
;
2088 lastEntry
->nextcommand
= thisEntry
;
2090 *output
= thisEntry
;
2092 lastEntry
= thisEntry
;
2096 curString
[curLen
++] = *curPos
;
2100 case '&': if (!inQuotes
&& *(curPos
+1) == '&') {
2101 curPos
++; /* Skip other & */
2103 /* Add an entry to the command list */
2104 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2105 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2106 (curLen
+1) * sizeof(WCHAR
));
2107 memcpy(thisEntry
->command
, curString
, curLen
* sizeof(WCHAR
));
2108 thisEntry
->command
[curLen
] = 0x00;
2110 thisEntry
->nextcommand
= NULL
;
2111 thisEntry
->isAmphersand
= isAmphersand
;
2112 thisEntry
->bracketDepth
= curDepth
;
2114 lastEntry
->nextcommand
= thisEntry
;
2116 *output
= thisEntry
;
2118 lastEntry
= thisEntry
;
2119 isAmphersand
= TRUE
;
2121 curString
[curLen
++] = *curPos
;
2125 case ')': if (!inQuotes
&& curDepth
> 0) {
2127 /* Add the current command if there is one */
2129 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2130 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2131 (curLen
+1) * sizeof(WCHAR
));
2132 memcpy(thisEntry
->command
, curString
, curLen
* sizeof(WCHAR
));
2133 thisEntry
->command
[curLen
] = 0x00;
2135 thisEntry
->nextcommand
= NULL
;
2136 thisEntry
->isAmphersand
= isAmphersand
;
2137 thisEntry
->bracketDepth
= curDepth
;
2139 lastEntry
->nextcommand
= thisEntry
;
2141 *output
= thisEntry
;
2143 lastEntry
= thisEntry
;
2146 /* Add an empty entry to the command list */
2147 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2148 thisEntry
->command
= NULL
;
2149 thisEntry
->nextcommand
= NULL
;
2150 thisEntry
->isAmphersand
= FALSE
;
2151 thisEntry
->bracketDepth
= curDepth
;
2154 lastEntry
->nextcommand
= thisEntry
;
2156 *output
= thisEntry
;
2158 lastEntry
= thisEntry
;
2160 curString
[curLen
++] = *curPos
;
2164 curString
[curLen
++] = *curPos
;
2169 /* At various times we need to know if we have only skipped whitespace,
2170 so reset this variable and then it will remain true until a non
2171 whitespace is found */
2172 if ((thisChar
!= ' ') && (thisChar
!= '\n')) onlyWhiteSpace
= FALSE
;
2174 /* Flag end of interest in FOR DO and IN parms once something has been processed */
2175 if (!lastWasWhiteSpace
) {
2176 lastWasIn
= lastWasDo
= FALSE
;
2179 /* If we have reached the end, add this command into the list */
2180 if (*curPos
== 0x00 && curLen
> 0) {
2182 /* Add an entry to the command list */
2183 thisEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST
));
2184 thisEntry
->command
= HeapAlloc(GetProcessHeap(), 0,
2185 (curLen
+1) * sizeof(WCHAR
));
2186 memcpy(thisEntry
->command
, curString
, curLen
* sizeof(WCHAR
));
2187 thisEntry
->command
[curLen
] = 0x00;
2189 thisEntry
->nextcommand
= NULL
;
2190 thisEntry
->isAmphersand
= isAmphersand
;
2191 thisEntry
->bracketDepth
= curDepth
;
2193 lastEntry
->nextcommand
= thisEntry
;
2195 *output
= thisEntry
;
2197 lastEntry
= thisEntry
;
2200 /* If we have reached the end of the string, see if bracketing outstanding */
2201 if (*curPos
== 0x00 && curDepth
> 0 && readFrom
!= INVALID_HANDLE_VALUE
) {
2203 isAmphersand
= FALSE
;
2205 memset(extraSpace
, 0x00, (MAXSTRING
+1) * sizeof(WCHAR
));
2207 /* Read more, skipping any blank lines */
2208 while (*extraSpace
== 0x00) {
2209 if (!context
) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT
));
2210 if (WCMD_fgets(extraSpace
, MAXSTRING
, readFrom
) == NULL
) break;
2212 curPos
= extraSpace
;
2216 /* Dump out the parsed output */
2217 WCMD_DumpCommands(*output
);
2222 /***************************************************************************
2223 * WCMD_process_commands
2225 * Process all the commands read in so far
2227 void WCMD_process_commands(CMD_LIST
*thisCmd
) {
2229 /* Loop through the commands, processing them one by one */
2232 /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2233 about them and it will be handled in there)
2234 Also, skip over any batch labels (eg. :fred) */
2235 if (thisCmd
->command
&& thisCmd
->command
[0] != ':') {
2236 WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd
->command
));
2237 if (strchrW(thisCmd
->command
,'|') != NULL
) {
2238 WCMD_pipe (&thisCmd
);
2240 WCMD_process_command (thisCmd
->command
, &thisCmd
);
2243 if (thisCmd
) thisCmd
= thisCmd
->nextcommand
;
2247 /***************************************************************************
2248 * WCMD_free_commands
2250 * Frees the storage held for a parsed command line
2251 * - This is not done in the process_commands, as eventually the current
2252 * pointer will be modified within the commands, and hence a single free
2253 * routine is simpler
2255 void WCMD_free_commands(CMD_LIST
*cmds
) {
2257 /* Loop through the commands, freeing them one by one */
2259 CMD_LIST
*thisCmd
= cmds
;
2260 cmds
= cmds
->nextcommand
;
2261 HeapFree(GetProcessHeap(), 0, thisCmd
->command
);
2262 HeapFree(GetProcessHeap(), 0, thisCmd
);