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'};
88 WCHAR version_string
[100];
89 WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
90 BATCH_CONTEXT
*context
= NULL
;
91 extern struct env_stack
*pushd_directories
;
92 static const WCHAR
*pagedMessage
= NULL
;
93 static char *output_bufA
= NULL
;
94 #define MAX_WRITECONSOLE_SIZE 65535
95 BOOL unicodePipes
= FALSE
;
97 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
);
99 /*****************************************************************************
100 * Main entry point. This is a console application so we have a main() not a
104 int wmain (int argc
, WCHAR
*argvW
[])
114 static const WCHAR autoexec
[] = {'\\','a','u','t','o','e','x','e','c','.',
116 char ansiVersion
[100];
118 /* Pre initialize some messages */
119 strcpy(ansiVersion
, PACKAGE_VERSION
);
120 MultiByteToWideChar(CP_ACP
, 0, ansiVersion
, -1, string
, 1024);
121 wsprintf(version_string
, WCMD_LoadMessage(WCMD_VERSION
), string
);
122 strcpyW(anykey
, WCMD_LoadMessage(WCMD_ANYKEY
));
125 opt_c
=opt_k
=opt_q
=opt_s
=0;
129 WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW
));
130 if ((*argvW
)[0]!='/' || (*argvW
)[1]=='\0') {
137 if (tolowerW(c
)=='c') {
139 } else if (tolowerW(c
)=='q') {
141 } else if (tolowerW(c
)=='k') {
143 } else if (tolowerW(c
)=='s') {
145 } else if (tolowerW(c
)=='a') {
147 } else if (tolowerW(c
)=='u') {
149 } else if (tolowerW(c
)=='t' && (*argvW
)[2]==':') {
150 opt_t
=strtoulW(&(*argvW
)[3], NULL
, 16);
151 } else if (tolowerW(c
)=='x' || tolowerW(c
)=='y') {
152 /* Ignored for compatibility with Windows */
155 if ((*argvW
)[2]==0) {
159 else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
164 if (opt_c
|| opt_k
) /* break out of parsing immediately after c or k */
169 const WCHAR eoff
[] = {'O','F','F','\0'};
173 if (opt_c
|| opt_k
) {
179 /* opt_s left unflagged if the command starts with and contains exactly
180 * one quoted string (exactly two quote characters). The quoted string
181 * must be an executable name that has whitespace and must not have the
182 * following characters: &<>()@^| */
184 /* Build the command to execute */
188 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
190 int has_space
,bcount
;
196 if( !*a
) has_space
=1;
201 if (*a
==' ' || *a
=='\t') {
203 } else if (*a
=='"') {
204 /* doubling of '\' preceding a '"',
205 * plus escaping of said '"'
214 len
+=(a
-*arg
) + 1; /* for the separating space */
217 len
+=2; /* for the quotes */
225 /* check argvW[0] for a space and invalid characters */
230 if (*p
=='&' || *p
=='<' || *p
=='>' || *p
=='(' || *p
==')'
231 || *p
=='@' || *p
=='^' || *p
=='|') {
241 cmd
= HeapAlloc(GetProcessHeap(), 0, len
* sizeof(WCHAR
));
247 for (arg
= argvW
; argsLeft
>0; arg
++,argsLeft
--)
249 int has_space
,has_quote
;
252 /* Check for quotes and spaces in this argument */
253 has_space
=has_quote
=0;
255 if( !*a
) has_space
=1;
257 if (*a
==' ' || *a
=='\t') {
261 } else if (*a
=='"') {
269 /* Now transfer it to the command line */
286 /* Double all the '\\' preceding this '"', plus one */
287 for (i
=0;i
<=bcount
;i
++)
306 p
--; /* remove last space */
309 WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd
));
311 /* strip first and last quote characters if opt_s; check for invalid
312 * executable is done later */
313 if (opt_s
&& *cmd
=='\"')
314 WCMD_opt_s_strip_quotes(cmd
);
318 /* If we do a "wcmd /c command", we don't want to allocate a new
319 * console since the command returns immediately. Rather, we use
320 * the currently allocated input and output handles. This allows
321 * us to pipe to and read from the command interpreter.
323 if (strchrW(cmd
,'|') != NULL
)
326 WCMD_process_command(cmd
);
327 HeapFree(GetProcessHeap(), 0, cmd
);
331 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), ENABLE_LINE_INPUT
|
332 ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
);
333 SetConsoleTitle(WCMD_LoadMessage(WCMD_CONSTITLE
));
335 /* Note: cmd.exe /c dir does not get a new color, /k dir does */
337 if (!(((opt_t
& 0xF0) >> 4) == (opt_t
& 0x0F))) {
338 defaultColor
= opt_t
& 0xFF;
343 /* Check HKCU\Software\Microsoft\Command Processor
344 Then HKLM\Software\Microsoft\Command Processor
345 for defaultcolour value
346 Note Can be supplied as DWORD or REG_SZ
347 Note2 When supplied as REG_SZ it's in decimal!!! */
350 DWORD value
=0, size
=4;
351 static const WCHAR regKeyW
[] = {'S','o','f','t','w','a','r','e','\\',
352 'M','i','c','r','o','s','o','f','t','\\',
353 'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
354 static const WCHAR dfltColorW
[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
356 if (RegOpenKeyEx(HKEY_CURRENT_USER
, regKeyW
,
357 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
360 /* See if DWORD or REG_SZ */
361 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
362 NULL
, NULL
) == ERROR_SUCCESS
) {
363 if (type
== REG_DWORD
) {
364 size
= sizeof(DWORD
);
365 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
366 (LPBYTE
)&value
, &size
);
367 } else if (type
== REG_SZ
) {
368 size
= sizeof(strvalue
)/sizeof(WCHAR
);
369 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
370 (LPBYTE
)strvalue
, &size
);
371 value
= strtoulW(strvalue
, NULL
, 10);
376 if (value
== 0 && RegOpenKeyEx(HKEY_LOCAL_MACHINE
, regKeyW
,
377 0, KEY_READ
, &key
) == ERROR_SUCCESS
) {
380 /* See if DWORD or REG_SZ */
381 if (RegQueryValueEx(key
, dfltColorW
, NULL
, &type
,
382 NULL
, NULL
) == ERROR_SUCCESS
) {
383 if (type
== REG_DWORD
) {
384 size
= sizeof(DWORD
);
385 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
386 (LPBYTE
)&value
, &size
);
387 } else if (type
== REG_SZ
) {
388 size
= sizeof(strvalue
)/sizeof(WCHAR
);
389 RegQueryValueEx(key
, dfltColorW
, NULL
, NULL
,
390 (LPBYTE
)strvalue
, &size
);
391 value
= strtoulW(strvalue
, NULL
, 10);
396 /* If one found, set the screen to that colour */
397 if (!(((value
& 0xF0) >> 4) == (value
& 0x0F))) {
398 defaultColor
= value
& 0xFF;
405 /* Save cwd into appropriate env var */
406 GetCurrentDirectory(1024, string
);
407 if (IsCharAlpha(string
[0]) && string
[1] == ':') {
408 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
409 wsprintf(envvar
, fmt
, string
[0]);
410 SetEnvironmentVariable(envvar
, string
);
414 WCMD_process_command(cmd
);
415 HeapFree(GetProcessHeap(), 0, cmd
);
419 * If there is an AUTOEXEC.BAT file, try to execute it.
422 GetFullPathName (autoexec
, sizeof(string
)/sizeof(WCHAR
), string
, NULL
);
423 h
= CreateFile (string
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
424 if (h
!= INVALID_HANDLE_VALUE
) {
427 WCMD_batch (autoexec
, autoexec
, 0, NULL
, INVALID_HANDLE_VALUE
);
432 * Loop forever getting commands and executing them.
438 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
439 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
441 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
442 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
443 if (strlenW (string
) != 0) {
444 if (strchrW(string
,'|') != NULL
) {
448 WCMD_process_command (string
);
456 /*****************************************************************************
457 * Process one command. If the command is EXIT this routine does not return.
458 * We will recurse through here executing batch files.
462 void WCMD_process_command (WCHAR
*command
)
464 WCHAR
*cmd
, *p
, *s
, *t
, *redir
;
466 DWORD count
, creationDisposition
;
469 SECURITY_ATTRIBUTES sa
;
471 WCHAR
*first_redir
= NULL
;
472 HANDLE old_stdhandles
[3] = {INVALID_HANDLE_VALUE
,
473 INVALID_HANDLE_VALUE
,
474 INVALID_HANDLE_VALUE
};
475 DWORD idx_stdhandles
[3] = {STD_INPUT_HANDLE
,
479 /* Move copy of the command onto the heap so it can be expanded */
480 new_cmd
= HeapAlloc( GetProcessHeap(), 0, MAXSTRING
* sizeof(WCHAR
));
481 strcpyW(new_cmd
, command
);
483 /* For commands in a context (batch program): */
484 /* Expand environment variables in a batch file %{0-9} first */
485 /* including support for any ~ modifiers */
487 /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
488 /* names allowing environment variable overrides */
489 /* NOTE: To support the %PATH:xxx% syntax, also perform */
490 /* manual expansion of environment variables here */
493 while ((p
= strchrW(p
, '%'))) {
496 /* Replace %~ modifications if in batch program */
497 if (context
&& *(p
+1) == '~') {
498 WCMD_HandleTildaModifiers(&p
, NULL
);
501 /* Replace use of %0...%9 if in batch program*/
502 } else if (context
&& (i
>= 0) && (i
<= 9)) {
503 s
= WCMD_strdupW(p
+2);
504 t
= WCMD_parameter (context
-> command
, i
+ context
-> shift_count
[i
], NULL
);
509 /* Replace use of %* if in batch program*/
510 } else if (context
&& *(p
+1)=='*') {
511 WCHAR
*startOfParms
= NULL
;
512 s
= WCMD_strdupW(p
+2);
513 t
= WCMD_parameter (context
-> command
, 1, &startOfParms
);
514 if (startOfParms
!= NULL
) strcpyW (p
, startOfParms
);
520 p
= WCMD_expand_envvar(p
);
525 /* In a batch program, unknown variables are replace by nothing */
526 /* so remove any remaining %var% */
529 while ((p
= strchrW(p
, '%'))) {
530 s
= strchrW(p
+1, '%');
534 t
= WCMD_strdupW(s
+1);
540 /* Show prompt before batch line IF echo is on and in batch program */
541 if (echo_mode
&& (cmd
[0] != '@')) {
543 WCMD_output_asis ( cmd
);
544 WCMD_output_asis ( newline
);
549 * Changing default drive has to be handled as a special case.
552 if ((cmd
[1] == ':') && IsCharAlpha (cmd
[0]) && (strlenW(cmd
) == 2)) {
556 /* According to MSDN CreateProcess docs, special env vars record
557 the current directory on each drive, in the form =C:
558 so see if one specified, and if so go back to it */
559 strcpyW(envvar
, equalsW
);
560 strcatW(envvar
, cmd
);
561 if (GetEnvironmentVariable(envvar
, dir
, MAX_PATH
) == 0) {
562 static const WCHAR fmt
[] = {'%','s','\\','\0'};
563 wsprintf(cmd
, fmt
, cmd
);
565 status
= SetCurrentDirectory (cmd
);
566 if (!status
) WCMD_print_error ();
567 HeapFree( GetProcessHeap(), 0, cmd
);
571 sa
.nLength
= sizeof(sa
);
572 sa
.lpSecurityDescriptor
= NULL
;
573 sa
.bInheritHandle
= TRUE
;
576 * Redirect stdin, stdout and/or stderr if required.
579 if ((p
= strchrW(cmd
,'<')) != NULL
) {
580 if (first_redir
== NULL
) first_redir
= p
;
581 h
= CreateFile (WCMD_parameter (++p
, 0, NULL
), GENERIC_READ
, FILE_SHARE_READ
, &sa
, OPEN_EXISTING
,
582 FILE_ATTRIBUTE_NORMAL
, NULL
);
583 if (h
== INVALID_HANDLE_VALUE
) {
585 HeapFree( GetProcessHeap(), 0, cmd
);
588 old_stdhandles
[0] = GetStdHandle (STD_INPUT_HANDLE
);
589 SetStdHandle (STD_INPUT_HANDLE
, h
);
592 /* Scan the whole command looking for > and 2> */
594 while (redir
!= NULL
&& ((p
= strchrW(redir
,'>')) != NULL
)) {
598 if (first_redir
== NULL
) first_redir
= p
;
601 if (first_redir
== NULL
) first_redir
= (p
-1);
607 creationDisposition
= OPEN_ALWAYS
;
611 creationDisposition
= CREATE_ALWAYS
;
614 /* Add support for 2>&1 */
617 int idx
= *(p
+1) - '0';
619 if (DuplicateHandle(GetCurrentProcess(),
620 GetStdHandle(idx_stdhandles
[idx
]),
623 0, TRUE
, DUPLICATE_SAME_ACCESS
) == 0) {
624 WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
626 WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle
, GetStdHandle(idx_stdhandles
[idx
]), idx
, h
);
629 WCHAR
*param
= WCMD_parameter (p
, 0, NULL
);
630 h
= CreateFile (param
, GENERIC_WRITE
, 0, &sa
, creationDisposition
,
631 FILE_ATTRIBUTE_NORMAL
, NULL
);
632 if (h
== INVALID_HANDLE_VALUE
) {
634 HeapFree( GetProcessHeap(), 0, cmd
);
637 if (SetFilePointer (h
, 0, NULL
, FILE_END
) ==
638 INVALID_SET_FILE_POINTER
) {
641 WINE_TRACE("Redirect %d to '%s' (%p)\n", handle
, wine_dbgstr_w(param
), h
);
644 old_stdhandles
[handle
] = GetStdHandle (idx_stdhandles
[handle
]);
645 SetStdHandle (idx_stdhandles
[handle
], h
);
648 /* Terminate the command string at <, or first 2> or > */
649 if (first_redir
!= NULL
) *first_redir
= '\0';
652 * Strip leading whitespaces, and a '@' if supplied
654 whichcmd
= WCMD_strtrim_leading_spaces(cmd
);
655 WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd
));
656 if (whichcmd
[0] == '@') whichcmd
++;
659 * Check if the command entered is internal. If it is, pass the rest of the
660 * line down to the command. If not try to run a program.
664 while (IsCharAlphaNumeric(whichcmd
[count
])) {
667 for (i
=0; i
<=WCMD_EXIT
; i
++) {
668 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
669 whichcmd
, count
, inbuilt
[i
], -1) == 2) break;
671 p
= WCMD_strtrim_leading_spaces (&whichcmd
[count
]);
672 WCMD_parse (p
, quals
, param1
, param2
);
676 WCMD_setshow_attrib ();
683 WCMD_setshow_default (p
);
686 WCMD_clear_screen ();
695 WCMD_setshow_date ();
699 WCMD_delete (p
, TRUE
);
705 WCMD_echo(&whichcmd
[count
]);
730 WCMD_setshow_path (p
);
736 WCMD_setshow_prompt ();
755 WCMD_setshow_env (p
);
761 WCMD_setshow_time ();
764 if (strlenW(&whichcmd
[count
]) > 0)
765 WCMD_title(&whichcmd
[count
+1]);
792 WCMD_assoc(p
, FALSE
);
801 WCMD_run_program (whichcmd
, 0);
803 HeapFree( GetProcessHeap(), 0, cmd
);
805 /* Restore old handles */
806 for (i
=0; i
<3; i
++) {
807 if (old_stdhandles
[i
] != INVALID_HANDLE_VALUE
) {
808 CloseHandle (GetStdHandle (idx_stdhandles
[i
]));
809 SetStdHandle (idx_stdhandles
[i
], old_stdhandles
[i
]);
814 static void init_msvcrt_io_block(STARTUPINFO
* st
)
817 /* fetch the parent MSVCRT info block if any, so that the child can use the
818 * same handles as its grand-father
820 st_p
.cb
= sizeof(STARTUPINFO
);
821 GetStartupInfo(&st_p
);
822 st
->cbReserved2
= st_p
.cbReserved2
;
823 st
->lpReserved2
= st_p
.lpReserved2
;
824 if (st_p
.cbReserved2
&& st_p
.lpReserved2
)
826 /* Override the entries for fd 0,1,2 if we happened
827 * to change those std handles (this depends on the way wcmd sets
828 * it's new input & output handles)
830 size_t sz
= max(sizeof(unsigned) + (sizeof(WCHAR
) + sizeof(HANDLE
)) * 3, st_p
.cbReserved2
);
831 BYTE
* ptr
= HeapAlloc(GetProcessHeap(), 0, sz
);
834 unsigned num
= *(unsigned*)st_p
.lpReserved2
;
835 WCHAR
* flags
= (WCHAR
*)(ptr
+ sizeof(unsigned));
836 HANDLE
* handles
= (HANDLE
*)(flags
+ num
* sizeof(WCHAR
));
838 memcpy(ptr
, st_p
.lpReserved2
, st_p
.cbReserved2
);
839 st
->cbReserved2
= sz
;
840 st
->lpReserved2
= ptr
;
842 #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
843 if (num
<= 0 || (flags
[0] & WX_OPEN
))
845 handles
[0] = GetStdHandle(STD_INPUT_HANDLE
);
848 if (num
<= 1 || (flags
[1] & WX_OPEN
))
850 handles
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
853 if (num
<= 2 || (flags
[2] & WX_OPEN
))
855 handles
[2] = GetStdHandle(STD_ERROR_HANDLE
);
863 /******************************************************************************
866 * Execute a command line as an external program. Must allow recursion.
869 * Manual testing under windows shows PATHEXT plays a key part in this,
870 * and the search algorithm and precedence appears to be as follows.
873 * If directory supplied on command, just use that directory
874 * If extension supplied on command, look for that explicit name first
875 * Otherwise, search in each directory on the path
877 * If extension supplied on command, look for that explicit name first
878 * Then look for supplied name .* (even if extension supplied, so
879 * 'garbage.exe' will match 'garbage.exe.cmd')
880 * If any found, cycle through PATHEXT looking for name.exe one by one
882 * Once a match has been found, it is launched - Code currently uses
883 * findexecutable to acheive this which is left untouched.
886 void WCMD_run_program (WCHAR
*command
, int called
) {
888 WCHAR temp
[MAX_PATH
];
889 WCHAR pathtosearch
[MAXSTRING
];
891 WCHAR stemofsearch
[MAX_PATH
];
893 WCHAR pathext
[MAXSTRING
];
894 BOOL extensionsupplied
= FALSE
;
895 BOOL launched
= FALSE
;
897 BOOL assumeInternal
= FALSE
;
899 static const WCHAR envPath
[] = {'P','A','T','H','\0'};
900 static const WCHAR envPathExt
[] = {'P','A','T','H','E','X','T','\0'};
901 static const WCHAR delims
[] = {'/','\\',':','\0'};
903 WCMD_parse (command
, quals
, param1
, param2
); /* Quick way to get the filename */
904 if (!(*param1
) && !(*param2
))
907 /* Calculate the search path and stem to search for */
908 if (strpbrkW (param1
, delims
) == NULL
) { /* No explicit path given, search path */
909 static const WCHAR curDir
[] = {'.',';','\0'};
910 strcpyW(pathtosearch
, curDir
);
911 len
= GetEnvironmentVariable (envPath
, &pathtosearch
[2], (sizeof(pathtosearch
)/sizeof(WCHAR
))-2);
912 if ((len
== 0) || (len
>= (sizeof(pathtosearch
)/sizeof(WCHAR
)) - 2)) {
913 static const WCHAR curDir
[] = {'.','\0'};
914 strcpyW (pathtosearch
, curDir
);
916 if (strchrW(param1
, '.') != NULL
) extensionsupplied
= TRUE
;
917 strcpyW(stemofsearch
, param1
);
921 /* Convert eg. ..\fred to include a directory by removing file part */
922 GetFullPathName(param1
, sizeof(pathtosearch
)/sizeof(WCHAR
), pathtosearch
, NULL
);
923 lastSlash
= strrchrW(pathtosearch
, '\\');
924 if (lastSlash
&& strchrW(lastSlash
, '.') != NULL
) extensionsupplied
= TRUE
;
925 if (lastSlash
) *lastSlash
= 0x00;
926 strcpyW(stemofsearch
, lastSlash
+1);
929 /* Now extract PATHEXT */
930 len
= GetEnvironmentVariable (envPathExt
, pathext
, sizeof(pathext
)/sizeof(WCHAR
));
931 if ((len
== 0) || (len
>= (sizeof(pathext
)/sizeof(WCHAR
)))) {
932 static const WCHAR dfltPathExt
[] = {'.','b','a','t',';',
935 '.','e','x','e','\0'};
936 strcpyW (pathext
, dfltPathExt
);
939 /* Loop through the search path, dir by dir */
940 pathposn
= pathtosearch
;
941 WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch
),
942 wine_dbgstr_w(stemofsearch
));
943 while (!launched
&& pathposn
) {
945 WCHAR thisDir
[MAX_PATH
] = {'\0'};
948 const WCHAR slashW
[] = {'\\','\0'};
950 /* Work on the first directory on the search path */
951 pos
= strchrW(pathposn
, ';');
953 memcpy(thisDir
, pathposn
, (pos
-pathposn
) * sizeof(WCHAR
));
954 thisDir
[(pos
-pathposn
)] = 0x00;
958 strcpyW(thisDir
, pathposn
);
962 /* Since you can have eg. ..\.. on the path, need to expand
963 to full information */
964 strcpyW(temp
, thisDir
);
965 GetFullPathName(temp
, MAX_PATH
, thisDir
, NULL
);
967 /* 1. If extension supplied, see if that file exists */
968 strcatW(thisDir
, slashW
);
969 strcatW(thisDir
, stemofsearch
);
970 pos
= &thisDir
[strlenW(thisDir
)]; /* Pos = end of name */
972 /* 1. If extension supplied, see if that file exists */
973 if (extensionsupplied
) {
974 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
979 /* 2. Any .* matches? */
982 WIN32_FIND_DATA finddata
;
983 static const WCHAR allFiles
[] = {'.','*','\0'};
985 strcatW(thisDir
,allFiles
);
986 h
= FindFirstFile(thisDir
, &finddata
);
988 if (h
!= INVALID_HANDLE_VALUE
) {
990 WCHAR
*thisExt
= pathext
;
992 /* 3. Yes - Try each path ext */
994 WCHAR
*nextExt
= strchrW(thisExt
, ';');
997 memcpy(pos
, thisExt
, (nextExt
-thisExt
) * sizeof(WCHAR
));
998 pos
[(nextExt
-thisExt
)] = 0x00;
1001 strcpyW(pos
, thisExt
);
1005 if (GetFileAttributes(thisDir
) != INVALID_FILE_ATTRIBUTES
) {
1013 /* Internal programs won't be picked up by this search, so even
1014 though not found, try one last createprocess and wait for it
1016 Note: Ideally we could tell between a console app (wait) and a
1017 windows app, but the API's for it fail in this case */
1018 if (!found
&& pathposn
== NULL
) {
1019 WINE_TRACE("ASSUMING INTERNAL\n");
1020 assumeInternal
= TRUE
;
1022 WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir
));
1025 /* Once found, launch it */
1026 if (found
|| assumeInternal
) {
1028 PROCESS_INFORMATION pe
;
1032 WCHAR
*ext
= strrchrW( thisDir
, '.' );
1033 static const WCHAR batExt
[] = {'.','b','a','t','\0'};
1034 static const WCHAR cmdExt
[] = {'.','c','m','d','\0'};
1038 /* Special case BAT and CMD */
1039 if (ext
&& !strcmpiW(ext
, batExt
)) {
1040 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1042 } else if (ext
&& !strcmpiW(ext
, cmdExt
)) {
1043 WCMD_batch (thisDir
, command
, called
, NULL
, INVALID_HANDLE_VALUE
);
1047 /* thisDir contains the file to be launched, but with what?
1048 eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1049 hinst
= FindExecutable (thisDir
, NULL
, temp
);
1050 if ((INT_PTR
)hinst
< 32)
1053 console
= SHGetFileInfo (temp
, 0, &psfi
, sizeof(psfi
), SHGFI_EXETYPE
);
1055 ZeroMemory (&st
, sizeof(STARTUPINFO
));
1056 st
.cb
= sizeof(STARTUPINFO
);
1057 init_msvcrt_io_block(&st
);
1059 /* Launch the process and if a CUI wait on it to complete
1060 Note: Launching internal wine processes cannot specify a full path to exe */
1061 status
= CreateProcess (assumeInternal
?NULL
: thisDir
,
1062 command
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pe
);
1063 if ((opt_c
|| opt_k
) && !opt_s
&& !status
1064 && GetLastError()==ERROR_FILE_NOT_FOUND
&& command
[0]=='\"') {
1065 /* strip first and last quote WCHARacters and try again */
1066 WCMD_opt_s_strip_quotes(command
);
1068 WCMD_run_program(command
, called
);
1072 WCMD_print_error ();
1073 /* If a command fails to launch, it sets errorlevel 9009 - which
1074 does not seem to have any associated constant definition */
1078 if (!assumeInternal
&& !console
) errorlevel
= 0;
1081 if (assumeInternal
|| !HIWORD(console
)) WaitForSingleObject (pe
.hProcess
, INFINITE
);
1082 GetExitCodeProcess (pe
.hProcess
, &errorlevel
);
1083 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
1085 CloseHandle(pe
.hProcess
);
1086 CloseHandle(pe
.hThread
);
1092 /* Not found anywhere - give up */
1093 SetLastError(ERROR_FILE_NOT_FOUND
);
1094 WCMD_print_error ();
1096 /* If a command fails to launch, it sets errorlevel 9009 - which
1097 does not seem to have any associated constant definition */
1103 /******************************************************************************
1106 * Display the prompt on STDout
1110 void WCMD_show_prompt (void) {
1113 WCHAR out_string
[MAX_PATH
], curdir
[MAX_PATH
], prompt_string
[MAX_PATH
];
1116 static const WCHAR envPrompt
[] = {'P','R','O','M','P','T','\0'};
1118 len
= GetEnvironmentVariable (envPrompt
, prompt_string
,
1119 sizeof(prompt_string
)/sizeof(WCHAR
));
1120 if ((len
== 0) || (len
>= (sizeof(prompt_string
)/sizeof(WCHAR
)))) {
1121 const WCHAR dfltPrompt
[] = {'$','P','$','G','\0'};
1122 strcpyW (prompt_string
, dfltPrompt
);
1127 while (*p
!= '\0') {
1134 switch (toupper(*p
)) {
1148 GetDateFormat (LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
, NULL
, q
, MAX_PATH
);
1167 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1173 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
1175 strcatW (q
, curdir
);
1186 GetTimeFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, q
, MAX_PATH
);
1190 strcatW (q
, version_string
);
1197 if (pushd_directories
) {
1198 memset(q
, '+', pushd_directories
->u
.stackdepth
);
1199 q
= q
+ pushd_directories
->u
.stackdepth
;
1207 WCMD_output_asis (out_string
);
1210 /****************************************************************************
1213 * Print the message for GetLastError
1216 void WCMD_print_error (void) {
1221 error_code
= GetLastError ();
1222 status
= FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
,
1223 NULL
, error_code
, 0, (LPTSTR
) &lpMsgBuf
, 0, NULL
);
1225 WINE_FIXME ("Cannot display message for error %d, status %d\n",
1226 error_code
, GetLastError());
1229 WCMD_output_asis (lpMsgBuf
);
1230 LocalFree ((HLOCAL
)lpMsgBuf
);
1231 WCMD_output_asis (newline
);
1235 /*******************************************************************
1236 * WCMD_parse - parse a command into parameters and qualifiers.
1238 * On exit, all qualifiers are concatenated into q, the first string
1239 * not beginning with "/" is in p1 and the
1240 * second in p2. Any subsequent non-qualifier strings are lost.
1241 * Parameters in quotes are handled.
1244 void WCMD_parse (WCHAR
*s
, WCHAR
*q
, WCHAR
*p1
, WCHAR
*p2
) {
1248 *q
= *p1
= *p2
= '\0';
1253 while ((*s
!= '\0') && (*s
!= ' ') && *s
!= '/') {
1254 *q
++ = toupper (*s
++);
1264 while ((*s
!= '\0') && (*s
!= '"')) {
1265 if (p
== 0) *p1
++ = *s
++;
1266 else if (p
== 1) *p2
++ = *s
++;
1269 if (p
== 0) *p1
= '\0';
1270 if (p
== 1) *p2
= '\0';
1277 while ((*s
!= '\0') && (*s
!= ' ') && (*s
!= '\t')) {
1278 if (p
== 0) *p1
++ = *s
++;
1279 else if (p
== 1) *p2
++ = *s
++;
1282 if (p
== 0) *p1
= '\0';
1283 if (p
== 1) *p2
= '\0';
1289 /*******************************************************************
1290 * WCMD_output_asis_len - send output to current standard output
1292 * Output a formatted unicode string. Ideally this will go to the console
1293 * and hence required WriteConsoleW to output it, however if file i/o is
1294 * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
1296 static void WCMD_output_asis_len(const WCHAR
*message
, int len
) {
1301 /* If nothing to write, return (MORE does this sometimes) */
1304 /* Try to write as unicode assuming it is to a console */
1305 res
= WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE
),
1306 message
, len
, &nOut
, NULL
);
1308 /* If writing to console fails, assume its file
1309 i/o so convert to OEM codepage and output */
1311 BOOL usedDefaultChar
= FALSE
;
1312 DWORD convertedChars
;
1314 if (!unicodePipes
) {
1316 * Allocate buffer to use when writing to file. (Not freed, as one off)
1318 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1319 MAX_WRITECONSOLE_SIZE
);
1321 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1325 /* Convert to OEM, then output */
1326 convertedChars
= WideCharToMultiByte(GetConsoleOutputCP(), 0, message
,
1327 len
, output_bufA
, MAX_WRITECONSOLE_SIZE
,
1328 "?", &usedDefaultChar
);
1329 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), output_bufA
, convertedChars
,
1332 WriteFile(GetStdHandle(STD_OUTPUT_HANDLE
), message
, len
*sizeof(WCHAR
),
1339 /*******************************************************************
1340 * WCMD_output - send output to current standard output device.
1344 void WCMD_output (const WCHAR
*format
, ...) {
1350 va_start(ap
,format
);
1351 ret
= wvsprintf (string
, format
, ap
);
1352 if( ret
>= (sizeof(string
)/sizeof(WCHAR
))) {
1353 WINE_ERR("Output truncated in WCMD_output\n" );
1354 ret
= (sizeof(string
)/sizeof(WCHAR
)) - 1;
1358 WCMD_output_asis_len(string
, ret
);
1362 static int line_count
;
1363 static int max_height
;
1364 static int max_width
;
1365 static BOOL paged_mode
;
1366 static int numChars
;
1368 void WCMD_enter_paged_mode(const WCHAR
*msg
)
1370 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
1372 if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE
), &consoleInfo
)) {
1373 max_height
= consoleInfo
.dwSize
.Y
;
1374 max_width
= consoleInfo
.dwSize
.X
;
1382 pagedMessage
= (msg
==NULL
)? anykey
: msg
;
1385 void WCMD_leave_paged_mode(void)
1388 pagedMessage
= NULL
;
1391 /*******************************************************************
1392 * WCMD_output_asis - send output to current standard output device.
1393 * without formatting eg. when message contains '%'
1396 void WCMD_output_asis (const WCHAR
*message
) {
1404 while (*ptr
&& *ptr
!='\n' && (numChars
< max_width
)) {
1408 if (*ptr
== '\n') ptr
++;
1409 WCMD_output_asis_len(message
, (ptr
) ? ptr
- message
: strlenW(message
));
1412 if (++line_count
>= max_height
- 1) {
1414 WCMD_output_asis_len(pagedMessage
, strlenW(pagedMessage
));
1415 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1416 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1419 } while (((message
= ptr
) != NULL
) && (*ptr
));
1421 WCMD_output_asis_len(message
, lstrlen(message
));
1426 /***************************************************************************
1427 * WCMD_strtrim_leading_spaces
1429 * Remove leading spaces from a string. Return a pointer to the first
1430 * non-space character. Does not modify the input string
1433 WCHAR
*WCMD_strtrim_leading_spaces (WCHAR
*string
) {
1438 while (*ptr
== ' ') ptr
++;
1442 /*************************************************************************
1443 * WCMD_strtrim_trailing_spaces
1445 * Remove trailing spaces from a string. This routine modifies the input
1446 * string by placing a null after the last non-space WCHARacter
1449 void WCMD_strtrim_trailing_spaces (WCHAR
*string
) {
1453 ptr
= string
+ strlenW (string
) - 1;
1454 while ((*ptr
== ' ') && (ptr
>= string
)) {
1460 /*************************************************************************
1461 * WCMD_opt_s_strip_quotes
1463 * Remove first and last quote WCHARacters, preserving all other text
1466 void WCMD_opt_s_strip_quotes(WCHAR
*cmd
) {
1467 WCHAR
*src
= cmd
+ 1, *dest
= cmd
, *lastq
= NULL
;
1468 while((*dest
=*src
) != '\0') {
1475 while ((*dest
++=*lastq
++) != 0)
1480 /*************************************************************************
1483 * Handle pipes within a command - the DOS way using temporary files.
1486 void WCMD_pipe (WCHAR
*command
) {
1489 WCHAR temp_path
[MAX_PATH
], temp_file
[MAX_PATH
], temp_file2
[MAX_PATH
], temp_cmd
[1024];
1490 static const WCHAR redirOut
[] = {'%','s',' ','>',' ','%','s','\0'};
1491 static const WCHAR redirIn
[] = {'%','s',' ','<',' ','%','s','\0'};
1492 static const WCHAR redirBoth
[]= {'%','s',' ','<',' ','%','s',' ','>','%','s','\0'};
1493 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1496 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1497 GetTempFileName (temp_path
, cmdW
, 0, temp_file
);
1498 p
= strchrW(command
, '|');
1500 wsprintf (temp_cmd
, redirOut
, command
, temp_file
);
1501 WCMD_process_command (temp_cmd
);
1503 while ((p
= strchrW(command
, '|'))) {
1505 GetTempFileName (temp_path
, cmdW
, 0, temp_file2
);
1506 wsprintf (temp_cmd
, redirBoth
, command
, temp_file
, temp_file2
);
1507 WCMD_process_command (temp_cmd
);
1508 DeleteFile (temp_file
);
1509 strcpyW (temp_file
, temp_file2
);
1512 wsprintf (temp_cmd
, redirIn
, command
, temp_file
);
1513 WCMD_process_command (temp_cmd
);
1514 DeleteFile (temp_file
);
1517 /*************************************************************************
1518 * WCMD_expand_envvar
1520 * Expands environment variables, allowing for WCHARacter substitution
1522 static WCHAR
*WCMD_expand_envvar(WCHAR
*start
) {
1523 WCHAR
*endOfVar
= NULL
, *s
;
1524 WCHAR
*colonpos
= NULL
;
1525 WCHAR thisVar
[MAXSTRING
];
1526 WCHAR thisVarContents
[MAXSTRING
];
1527 WCHAR savedchar
= 0x00;
1530 static const WCHAR ErrorLvl
[] = {'E','R','R','O','R','L','E','V','E','L','\0'};
1531 static const WCHAR ErrorLvlP
[] = {'%','E','R','R','O','R','L','E','V','E','L','%','\0'};
1532 static const WCHAR Date
[] = {'D','A','T','E','\0'};
1533 static const WCHAR DateP
[] = {'%','D','A','T','E','%','\0'};
1534 static const WCHAR Time
[] = {'T','I','M','E','\0'};
1535 static const WCHAR TimeP
[] = {'%','T','I','M','E','%','\0'};
1536 static const WCHAR Cd
[] = {'C','D','\0'};
1537 static const WCHAR CdP
[] = {'%','C','D','%','\0'};
1538 static const WCHAR Random
[] = {'R','A','N','D','O','M','\0'};
1539 static const WCHAR RandomP
[] = {'%','R','A','N','D','O','M','%','\0'};
1541 /* Find the end of the environment variable, and extract name */
1542 endOfVar
= strchrW(start
+1, '%');
1543 if (endOfVar
== NULL
) {
1544 /* In batch program, missing terminator for % and no following
1545 ':' just removes the '%' */
1546 s
= WCMD_strdupW(start
+ 1);
1550 /* FIXME: Some other special conditions here depending on whether
1551 in batch, complex or not, and whether env var exists or not! */
1554 memcpy(thisVar
, start
, ((endOfVar
- start
) + 1) * sizeof(WCHAR
));
1555 thisVar
[(endOfVar
- start
)+1] = 0x00;
1556 colonpos
= strchrW(thisVar
+1, ':');
1558 /* If there's complex substitution, just need %var% for now
1559 to get the expanded data to play with */
1562 savedchar
= *(colonpos
+1);
1563 *(colonpos
+1) = 0x00;
1566 WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar
));
1568 /* Expand to contents, if unchanged, return */
1569 /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
1570 /* override if existing env var called that name */
1571 if ((CompareString (LOCALE_USER_DEFAULT
,
1572 NORM_IGNORECASE
| SORT_STRINGSORT
,
1573 thisVar
, 12, ErrorLvlP
, -1) == 2) &&
1574 (GetEnvironmentVariable(ErrorLvl
, thisVarContents
, 1) == 0) &&
1575 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1576 static const WCHAR fmt
[] = {'%','d','\0'};
1577 wsprintf(thisVarContents
, fmt
, errorlevel
);
1578 len
= strlenW(thisVarContents
);
1580 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1581 NORM_IGNORECASE
| SORT_STRINGSORT
,
1582 thisVar
, 6, DateP
, -1) == 2) &&
1583 (GetEnvironmentVariable(Date
, thisVarContents
, 1) == 0) &&
1584 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1586 GetDateFormat(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, NULL
,
1587 NULL
, thisVarContents
, MAXSTRING
);
1588 len
= strlenW(thisVarContents
);
1590 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1591 NORM_IGNORECASE
| SORT_STRINGSORT
,
1592 thisVar
, 6, TimeP
, -1) == 2) &&
1593 (GetEnvironmentVariable(Time
, thisVarContents
, 1) == 0) &&
1594 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1595 GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
,
1596 NULL
, thisVarContents
, MAXSTRING
);
1597 len
= strlenW(thisVarContents
);
1599 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1600 NORM_IGNORECASE
| SORT_STRINGSORT
,
1601 thisVar
, 4, CdP
, -1) == 2) &&
1602 (GetEnvironmentVariable(Cd
, thisVarContents
, 1) == 0) &&
1603 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1604 GetCurrentDirectory (MAXSTRING
, thisVarContents
);
1605 len
= strlenW(thisVarContents
);
1607 } else if ((CompareString (LOCALE_USER_DEFAULT
,
1608 NORM_IGNORECASE
| SORT_STRINGSORT
,
1609 thisVar
, 8, RandomP
, -1) == 2) &&
1610 (GetEnvironmentVariable(Random
, thisVarContents
, 1) == 0) &&
1611 (GetLastError() == ERROR_ENVVAR_NOT_FOUND
)) {
1612 static const WCHAR fmt
[] = {'%','d','\0'};
1613 wsprintf(thisVarContents
, fmt
, rand() % 32768);
1614 len
= strlenW(thisVarContents
);
1618 len
= ExpandEnvironmentStrings(thisVar
, thisVarContents
,
1619 sizeof(thisVarContents
)/sizeof(WCHAR
));
1625 /* In a batch program, unknown env vars are replaced with nothing,
1626 note syntax %garbage:1,3% results in anything after the ':'
1628 From the command line, you just get back what you entered */
1629 if (lstrcmpiW(thisVar
, thisVarContents
) == 0) {
1631 /* Restore the complex part after the compare */
1634 *(colonpos
+1) = savedchar
;
1637 s
= WCMD_strdupW(endOfVar
+ 1);
1639 /* Command line - just ignore this */
1640 if (context
== NULL
) return endOfVar
+1;
1642 /* Batch - replace unknown env var with nothing */
1643 if (colonpos
== NULL
) {
1647 len
= strlenW(thisVar
);
1648 thisVar
[len
-1] = 0x00;
1649 /* If %:...% supplied, : is retained */
1650 if (colonpos
== thisVar
+1) {
1651 strcpyW (start
, colonpos
);
1653 strcpyW (start
, colonpos
+1);
1662 /* See if we need to do complex substitution (any ':'s), if not
1663 then our work here is done */
1664 if (colonpos
== NULL
) {
1665 s
= WCMD_strdupW(endOfVar
+ 1);
1666 strcpyW (start
, thisVarContents
);
1672 /* Restore complex bit */
1674 *(colonpos
+1) = savedchar
;
1677 Handle complex substitutions:
1678 xxx=yyy (replace xxx with yyy)
1679 *xxx=yyy (replace up to and including xxx with yyy)
1680 ~x (from x WCHARs in)
1681 ~-x (from x WCHARs from the end)
1682 ~x,y (from x WCHARs in for y WCHARacters)
1683 ~x,-y (from x WCHARs in until y WCHARacters from the end)
1686 /* ~ is substring manipulation */
1687 if (savedchar
== '~') {
1689 int substrposition
, substrlength
= 0;
1690 WCHAR
*commapos
= strchrW(colonpos
+2, ',');
1693 substrposition
= atolW(colonpos
+2);
1694 if (commapos
) substrlength
= atolW(commapos
+1);
1696 s
= WCMD_strdupW(endOfVar
+ 1);
1699 if (substrposition
>= 0) {
1700 startCopy
= &thisVarContents
[min(substrposition
, len
)];
1702 startCopy
= &thisVarContents
[max(0, len
+substrposition
-1)];
1705 if (commapos
== NULL
) {
1706 strcpyW (start
, startCopy
); /* Copy the lot */
1707 } else if (substrlength
< 0) {
1709 int copybytes
= (len
+substrlength
-1)-(startCopy
-thisVarContents
);
1710 if (copybytes
> len
) copybytes
= len
;
1711 else if (copybytes
< 0) copybytes
= 0;
1712 memcpy (start
, startCopy
, copybytes
* sizeof(WCHAR
)); /* Copy the lot */
1713 start
[copybytes
] = 0x00;
1715 memcpy (start
, startCopy
, substrlength
* sizeof(WCHAR
)); /* Copy the lot */
1716 start
[substrlength
] = 0x00;
1723 /* search and replace manipulation */
1725 WCHAR
*equalspos
= strstrW(colonpos
, equalsW
);
1726 WCHAR
*replacewith
= equalspos
+1;
1727 WCHAR
*found
= NULL
;
1731 s
= WCMD_strdupW(endOfVar
+ 1);
1732 if (equalspos
== NULL
) return start
+1;
1734 /* Null terminate both strings */
1735 thisVar
[strlenW(thisVar
)-1] = 0x00;
1738 /* Since we need to be case insensitive, copy the 2 buffers */
1739 searchIn
= WCMD_strdupW(thisVarContents
);
1740 CharUpperBuff(searchIn
, strlenW(thisVarContents
));
1741 searchFor
= WCMD_strdupW(colonpos
+1);
1742 CharUpperBuff(searchFor
, strlenW(colonpos
+1));
1745 /* Handle wildcard case */
1746 if (*(colonpos
+1) == '*') {
1747 /* Search for string to replace */
1748 found
= strstrW(searchIn
, searchFor
+1);
1751 /* Do replacement */
1752 strcpyW(start
, replacewith
);
1753 strcatW(start
, thisVarContents
+ (found
-searchIn
) + strlenW(searchFor
+1));
1758 strcpyW(start
, thisVarContents
);
1763 /* Loop replacing all instances */
1764 WCHAR
*lastFound
= searchIn
;
1765 WCHAR
*outputposn
= start
;
1768 while ((found
= strstrW(lastFound
, searchFor
))) {
1769 lstrcpynW(outputposn
,
1770 thisVarContents
+ (lastFound
-searchIn
),
1771 (found
- lastFound
)+1);
1772 outputposn
= outputposn
+ (found
- lastFound
);
1773 strcatW(outputposn
, replacewith
);
1774 outputposn
= outputposn
+ strlenW(replacewith
);
1775 lastFound
= found
+ strlenW(searchFor
);
1778 thisVarContents
+ (lastFound
-searchIn
));
1779 strcatW(outputposn
, s
);
1788 /*************************************************************************
1790 * Load a string from the resource file, handling any error
1791 * Returns string retrieved from resource file
1793 WCHAR
*WCMD_LoadMessage(UINT id
) {
1794 static WCHAR msg
[2048];
1795 static const WCHAR failedMsg
[] = {'F','a','i','l','e','d','!','\0'};
1797 if (!LoadString(GetModuleHandle(NULL
), id
, msg
, sizeof(msg
)/sizeof(WCHAR
))) {
1798 WINE_FIXME("LoadString failed with %d\n", GetLastError());
1799 strcpyW(msg
, failedMsg
);
1804 /*************************************************************************
1806 * A wide version of strdup as its missing from unicode.h
1808 WCHAR
*WCMD_strdupW(WCHAR
*input
) {
1809 int len
=strlenW(input
)+1;
1810 /* Note: Use malloc not HeapAlloc to emulate strdup */
1811 WCHAR
*result
= malloc(len
* sizeof(WCHAR
));
1812 memcpy(result
, input
, len
* sizeof(WCHAR
));
1816 /***************************************************************************
1819 * Read characters in from a console/file, returning result in Unicode
1820 * with signature identical to ReadFile
1822 BOOL
WCMD_ReadFile(const HANDLE hIn
, WCHAR
*intoBuf
, const DWORD maxChars
,
1823 LPDWORD charsRead
, const LPOVERLAPPED unused
) {
1827 /* Try to read from console as Unicode */
1828 res
= ReadConsoleW(hIn
, intoBuf
, maxChars
, charsRead
, NULL
);
1830 /* If reading from console has failed we assume its file
1831 i/o so read in and convert from OEM codepage */
1836 * Allocate buffer to use when reading from file. Not freed
1838 if (!output_bufA
) output_bufA
= HeapAlloc(GetProcessHeap(), 0,
1839 MAX_WRITECONSOLE_SIZE
);
1841 WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
1845 /* Read from file (assume OEM codepage) */
1846 res
= ReadFile(hIn
, output_bufA
, maxChars
, &numRead
, unused
);
1848 /* Convert from OEM */
1849 *charsRead
= MultiByteToWideChar(GetConsoleCP(), 0, output_bufA
, numRead
,