2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
44 static void WCMD_part_execute(CMD_LIST
**commands
, const WCHAR
*firstcmd
,
45 const WCHAR
*variable
, const WCHAR
*value
,
46 BOOL isIF
, BOOL conditionTRUE
);
48 static struct env_stack
*saved_environment
;
49 struct env_stack
*pushd_directories
;
51 extern HINSTANCE hinst
;
52 extern WCHAR inbuilt
[][10];
53 extern int defaultColor
;
54 extern BOOL echo_mode
;
55 extern WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
56 extern BATCH_CONTEXT
*context
;
57 extern DWORD errorlevel
;
59 static BOOL verify_mode
= FALSE
;
61 static const WCHAR dotW
[] = {'.','\0'};
62 static const WCHAR dotdotW
[] = {'.','.','\0'};
63 static const WCHAR slashW
[] = {'\\','\0'};
64 static const WCHAR starW
[] = {'*','\0'};
65 static const WCHAR equalW
[] = {'=','\0'};
66 static const WCHAR fslashW
[] = {'/','\0'};
67 static const WCHAR onW
[] = {'O','N','\0'};
68 static const WCHAR offW
[] = {'O','F','F','\0'};
69 static const WCHAR parmY
[] = {'/','Y','\0'};
70 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
71 static const WCHAR nullW
[] = {'\0'};
73 /**************************************************************************
76 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
79 * Returns True if Y (or A) answer is selected
80 * If optionAll contains a pointer, ALL is allowed, and if answered
84 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
85 const BOOL
*optionAll
) {
87 WCHAR msgbuffer
[MAXSTRING
];
88 WCHAR Ybuffer
[MAXSTRING
];
89 WCHAR Nbuffer
[MAXSTRING
];
90 WCHAR Abuffer
[MAXSTRING
];
91 WCHAR answer
[MAX_PATH
] = {'\0'};
94 /* Load the translated 'Are you sure', plus valid answers */
95 LoadStringW(hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
96 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
97 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
98 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
100 /* Loop waiting on a Y or N */
101 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
102 static const WCHAR startBkt
[] = {' ','(','\0'};
103 static const WCHAR endBkt
[] = {')','?','\0'};
105 WCMD_output_asis (message
);
107 WCMD_output_asis (msgbuffer
);
109 WCMD_output_asis (startBkt
);
110 WCMD_output_asis (Ybuffer
);
111 WCMD_output_asis (fslashW
);
112 WCMD_output_asis (Nbuffer
);
114 WCMD_output_asis (fslashW
);
115 WCMD_output_asis (Abuffer
);
117 WCMD_output_asis (endBkt
);
118 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
,
119 sizeof(answer
)/sizeof(WCHAR
), &count
, NULL
);
120 answer
[0] = toupperW(answer
[0]);
123 /* Return the answer */
124 return ((answer
[0] == Ybuffer
[0]) ||
125 (optionAll
&& (answer
[0] == Abuffer
[0])));
128 /****************************************************************************
131 * Clear the terminal screen.
134 void WCMD_clear_screen (void) {
136 /* Emulate by filling the screen from the top left to bottom right with
137 spaces, then moving the cursor to the top left afterwards */
138 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
139 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
141 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
146 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
150 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
151 SetConsoleCursorPosition(hStdOut
, topLeft
);
155 /****************************************************************************
158 * Change the default i/o device (ie redirect STDin/STDout).
161 void WCMD_change_tty (void) {
163 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
167 /****************************************************************************
172 void WCMD_choice (const WCHAR
* command
) {
174 static const WCHAR bellW
[] = {7,0};
175 static const WCHAR commaW
[] = {',',0};
176 static const WCHAR bracket_open
[] = {'[',0};
177 static const WCHAR bracket_close
[] = {']','?',0};
182 WCHAR
*my_command
= NULL
;
183 WCHAR opt_default
= 0;
184 DWORD opt_timeout
= 0;
191 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
194 my_command
= WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR
*) command
));
198 ptr
= WCMD_skip_leading_spaces(my_command
);
199 while (*ptr
== '/') {
200 switch (toupperW(ptr
[1])) {
203 /* the colon is optional */
207 if (!*ptr
|| isspaceW(*ptr
)) {
208 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
209 HeapFree(GetProcessHeap(), 0, my_command
);
213 /* remember the allowed keys (overwrite previous /C option) */
215 while (*ptr
&& (!isspaceW(*ptr
)))
219 /* terminate allowed chars */
221 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
223 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
228 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
233 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
238 /* the colon is optional */
242 opt_default
= *ptr
++;
244 if (!opt_default
|| (*ptr
!= ',')) {
245 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
246 HeapFree(GetProcessHeap(), 0, my_command
);
252 while (((answer
[count
] = *ptr
)) && isdigitW(*ptr
) && (count
< 15)) {
258 opt_timeout
= atoiW(answer
);
260 ptr
= WCMD_skip_leading_spaces(ptr
);
264 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
265 HeapFree(GetProcessHeap(), 0, my_command
);
271 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
274 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
276 /* use default keys, when needed: localized versions of "Y"es and "No" */
278 LoadStringW(hinst
, WCMD_YES
, buffer
, sizeof(buffer
)/sizeof(WCHAR
));
279 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, sizeof(buffer
)/sizeof(WCHAR
) - 1);
284 /* print the question, when needed */
286 WCMD_output_asis(ptr
);
290 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
294 /* print a list of all allowed answers inside brackets */
295 WCMD_output_asis(bracket_open
);
298 while ((answer
[0] = *ptr
++)) {
299 WCMD_output_asis(answer
);
301 WCMD_output_asis(commaW
);
303 WCMD_output_asis(bracket_close
);
308 /* FIXME: Add support for option /T */
309 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
, NULL
);
312 answer
[0] = toupperW(answer
[0]);
314 ptr
= strchrW(opt_c
, answer
[0]);
316 WCMD_output_asis(answer
);
317 WCMD_output(newline
);
319 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
321 errorlevel
= (ptr
- opt_c
) + 1;
322 WINE_TRACE("answer: %d\n", errorlevel
);
323 HeapFree(GetProcessHeap(), 0, my_command
);
328 /* key not allowed: play the bell */
329 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
330 WCMD_output_asis(bellW
);
335 /****************************************************************************
338 * Copy a file or wildcarded set.
339 * FIXME: Add support for a+b+c type syntax
342 void WCMD_copy (void) {
347 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[4];
349 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
350 BOOL copyToDir
= FALSE
;
351 WCHAR srcspec
[MAX_PATH
];
355 WCHAR fname
[MAX_PATH
];
358 if (param1
[0] == 0x00) {
359 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
363 /* Convert source into full spec */
364 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1
));
365 GetFullPathNameW(param1
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, NULL
);
366 if (srcpath
[strlenW(srcpath
) - 1] == '\\')
367 srcpath
[strlenW(srcpath
) - 1] = '\0';
369 if ((strchrW(srcpath
,'*') == NULL
) && (strchrW(srcpath
,'?') == NULL
)) {
370 attribs
= GetFileAttributesW(srcpath
);
374 strcpyW(srcspec
, srcpath
);
376 /* If a directory, then add \* on the end when searching */
377 if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
378 strcatW(srcpath
, slashW
);
379 strcatW(srcspec
, slashW
);
380 strcatW(srcspec
, starW
);
382 WCMD_splitpath(srcpath
, drive
, dir
, fname
, ext
);
383 strcpyW(srcpath
, drive
);
384 strcatW(srcpath
, dir
);
387 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath
));
389 /* If no destination supplied, assume current directory */
390 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2
));
391 if (param2
[0] == 0x00) {
392 strcpyW(param2
, dotW
);
395 GetFullPathNameW(param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
396 if (outpath
[strlenW(outpath
) - 1] == '\\')
397 outpath
[strlenW(outpath
) - 1] = '\0';
398 attribs
= GetFileAttributesW(outpath
);
399 if (attribs
!= INVALID_FILE_ATTRIBUTES
&& (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
400 strcatW (outpath
, slashW
);
403 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
404 wine_dbgstr_w(outpath
), copyToDir
);
406 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
407 if (strstrW (quals
, parmNoY
))
409 else if (strstrW (quals
, parmY
))
412 /* By default, we will force the overwrite in batch mode and ask for
413 * confirmation in interactive mode. */
416 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
417 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
418 * default behavior. */
419 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
420 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
421 if (!lstrcmpiW (copycmd
, parmY
))
423 else if (!lstrcmpiW (copycmd
, parmNoY
))
428 /* Loop through all source files */
429 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec
));
430 hff
= FindFirstFileW(srcspec
, &fd
);
431 if (hff
!= INVALID_HANDLE_VALUE
) {
433 WCHAR outname
[MAX_PATH
];
434 WCHAR srcname
[MAX_PATH
];
435 BOOL overwrite
= force
;
437 /* Destination is either supplied filename, or source name in
438 supplied destination directory */
439 strcpyW(outname
, outpath
);
440 if (copyToDir
) strcatW(outname
, fd
.cFileName
);
441 strcpyW(srcname
, srcpath
);
442 strcatW(srcname
, fd
.cFileName
);
444 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname
));
445 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
447 /* Skip . and .., and directories */
448 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
450 WINE_TRACE("Skipping directories\n");
453 /* Prompt before overwriting */
454 else if (!overwrite
) {
455 attribs
= GetFileAttributesW(outname
);
456 if (attribs
!= INVALID_FILE_ATTRIBUTES
) {
457 WCHAR buffer
[MAXSTRING
];
458 wsprintfW(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
459 overwrite
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
461 else overwrite
= TRUE
;
464 /* Do the copy as appropriate */
466 status
= CopyFileW(srcname
, outname
, FALSE
);
467 if (!status
) WCMD_print_error ();
470 } while (FindNextFileW(hff
, &fd
) != 0);
473 status
= ERROR_FILE_NOT_FOUND
;
478 /****************************************************************************
481 * Create a directory (and, if needed, any intermediate directories).
483 * Modifies its argument by replacing slashes temporarily with nulls.
486 static BOOL
create_full_path(WCHAR
* path
)
490 /* don't mess with drive letter portion of path, if any */
495 /* Strip trailing slashes. */
496 for (p
= path
+ strlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
499 /* Step through path, creating intermediate directories as needed. */
500 /* First component includes drive letter, if any. */
504 /* Skip to end of component */
505 while (*p
== '\\') p
++;
506 while (*p
&& *p
!= '\\') p
++;
508 /* path is now the original full path */
509 return CreateDirectoryW(path
, NULL
);
511 /* Truncate path, create intermediate directory, and restore path */
513 rv
= CreateDirectoryW(path
, NULL
);
515 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
522 void WCMD_create_dir (WCHAR
*command
) {
524 WCHAR
*argN
= command
;
526 if (param1
[0] == 0x00) {
527 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
530 /* Loop through all args */
532 WCHAR
*thisArg
= WCMD_parameter(command
, argno
++, &argN
, NULL
);
534 if (!create_full_path(thisArg
)) {
541 /* Parse the /A options given by the user on the commandline
542 * into a bitmask of wanted attributes (*wantSet),
543 * and a bitmask of unwanted attributes (*wantClear).
545 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
546 static const WCHAR parmA
[] = {'/','A','\0'};
549 /* both are strictly 'out' parameters */
553 /* For each /A argument */
554 for (p
=strstrW(quals
, parmA
); p
!= NULL
; p
=strstrW(p
, parmA
)) {
558 /* Skip optional : */
561 /* For each of the attribute specifier chars to this /A option */
562 for (; *p
!= 0 && *p
!= '/'; p
++) {
571 /* Convert the attribute specifier to a bit in one of the masks */
573 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
574 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
575 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
576 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
578 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
588 /* If filename part of parameter is * or *.*,
589 * and neither /Q nor /P options were given,
590 * prompt the user whether to proceed.
591 * Returns FALSE if user says no, TRUE otherwise.
592 * *pPrompted is set to TRUE if the user is prompted.
593 * (If /P supplied, del will prompt for individual files later.)
595 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
596 static const WCHAR parmP
[] = {'/','P','\0'};
597 static const WCHAR parmQ
[] = {'/','Q','\0'};
599 if ((strstrW(quals
, parmQ
) == NULL
) && (strstrW(quals
, parmP
) == NULL
)) {
600 static const WCHAR anyExt
[]= {'.','*','\0'};
603 WCHAR fname
[MAX_PATH
];
605 WCHAR fpath
[MAX_PATH
];
607 /* Convert path into actual directory spec */
608 GetFullPathNameW(filename
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
609 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
611 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
612 if ((strcmpW(fname
, starW
) == 0) &&
613 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
615 WCHAR question
[MAXSTRING
];
616 static const WCHAR fmt
[] = {'%','s',' ','\0'};
618 /* Caller uses this to suppress "file not found" warning later */
621 /* Ask for confirmation */
622 wsprintfW(question
, fmt
, fpath
);
623 return WCMD_ask_confirm(question
, TRUE
, NULL
);
626 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
630 /* Helper function for WCMD_delete().
631 * Deletes a single file, directory, or wildcard.
632 * If /S was given, does it recursively.
633 * Returns TRUE if a file was deleted.
635 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
637 static const WCHAR parmP
[] = {'/','P','\0'};
638 static const WCHAR parmS
[] = {'/','S','\0'};
639 static const WCHAR parmF
[] = {'/','F','\0'};
641 DWORD unwanted_attrs
;
643 WCHAR argCopy
[MAX_PATH
];
646 WCHAR fpath
[MAX_PATH
];
648 BOOL handleParm
= TRUE
;
650 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
652 strcpyW(argCopy
, thisArg
);
653 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
654 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
656 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
657 /* Skip this arg if user declines to delete *.* */
661 /* First, try to delete in the current directory */
662 hff
= FindFirstFileW(argCopy
, &fd
);
663 if (hff
== INVALID_HANDLE_VALUE
) {
669 /* Support del <dirname> by just deleting all files dirname\* */
671 && (strchrW(argCopy
,'*') == NULL
)
672 && (strchrW(argCopy
,'?') == NULL
)
673 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
675 WCHAR modifiedParm
[MAX_PATH
];
676 static const WCHAR slashStar
[] = {'\\','*','\0'};
678 strcpyW(modifiedParm
, argCopy
);
679 strcatW(modifiedParm
, slashStar
);
682 WCMD_delete_one(modifiedParm
);
684 } else if (handleParm
) {
686 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
687 strcpyW (fpath
, argCopy
);
689 p
= strrchrW (fpath
, '\\');
692 strcatW (fpath
, fd
.cFileName
);
694 else strcpyW (fpath
, fd
.cFileName
);
695 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
698 /* Handle attribute matching (/A) */
699 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
700 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
702 /* /P means prompt for each file */
703 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
704 WCHAR question
[MAXSTRING
];
706 /* Ask for confirmation */
707 wsprintfW(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
708 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
711 /* Only proceed if ok to */
714 /* If file is read only, and /A:r or /F supplied, delete it */
715 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
716 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
717 strstrW (quals
, parmF
) != NULL
)) {
718 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
721 /* Now do the delete */
722 if (!DeleteFileW(fpath
)) WCMD_print_error ();
726 } while (FindNextFileW(hff
, &fd
) != 0);
730 /* Now recurse into all subdirectories handling the parameter in the same way */
731 if (strstrW (quals
, parmS
) != NULL
) {
733 WCHAR thisDir
[MAX_PATH
];
738 WCHAR fname
[MAX_PATH
];
741 /* Convert path into actual directory spec */
742 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
743 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
745 strcpyW(thisDir
, drive
);
746 strcatW(thisDir
, dir
);
747 cPos
= strlenW(thisDir
);
749 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
751 /* Append '*' to the directory */
753 thisDir
[cPos
+1] = 0x00;
755 hff
= FindFirstFileW(thisDir
, &fd
);
757 /* Remove residual '*' */
758 thisDir
[cPos
] = 0x00;
760 if (hff
!= INVALID_HANDLE_VALUE
) {
761 DIRECTORY_STACK
*allDirs
= NULL
;
762 DIRECTORY_STACK
*lastEntry
= NULL
;
765 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
766 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
767 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
769 DIRECTORY_STACK
*nextDir
;
770 WCHAR subParm
[MAX_PATH
];
772 /* Work out search parameter in sub dir */
773 strcpyW (subParm
, thisDir
);
774 strcatW (subParm
, fd
.cFileName
);
775 strcatW (subParm
, slashW
);
776 strcatW (subParm
, fname
);
777 strcatW (subParm
, ext
);
778 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
780 /* Allocate memory, add to list */
781 nextDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
782 if (allDirs
== NULL
) allDirs
= nextDir
;
783 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
785 nextDir
->next
= NULL
;
786 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
787 (strlenW(subParm
)+1) * sizeof(WCHAR
));
788 strcpyW(nextDir
->dirName
, subParm
);
790 } while (FindNextFileW(hff
, &fd
) != 0);
793 /* Go through each subdir doing the delete */
794 while (allDirs
!= NULL
) {
795 DIRECTORY_STACK
*tempDir
;
797 tempDir
= allDirs
->next
;
798 found
|= WCMD_delete_one (allDirs
->dirName
);
800 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
801 HeapFree(GetProcessHeap(),0,allDirs
);
810 /****************************************************************************
813 * Delete a file or wildcarded set.
816 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
817 * - Each set is a pattern, eg /ahr /as-r means
818 * readonly+hidden OR nonreadonly system files
819 * - The '-' applies to a single field, ie /a:-hr means read only
823 BOOL
WCMD_delete (WCHAR
*command
) {
826 BOOL argsProcessed
= FALSE
;
827 BOOL foundAny
= FALSE
;
831 for (argno
=0; ; argno
++) {
836 thisArg
= WCMD_parameter (command
, argno
, &argN
, NULL
);
838 break; /* no more parameters */
840 continue; /* skip options */
842 argsProcessed
= TRUE
;
843 found
= WCMD_delete_one(thisArg
);
846 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
851 /* Handle no valid args */
853 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
858 /****************************************************************************
861 * Echo input to the screen (or not). We don't try to emulate the bugs
862 * in DOS (try typing "ECHO ON AGAIN" for an example).
865 void WCMD_echo (const WCHAR
*command
) {
868 const WCHAR
*origcommand
= command
;
870 if ( command
[0]==' ' || command
[0]=='\t' || command
[0]=='.'
871 || command
[0]==':' || command
[0]==';')
874 count
= strlenW(command
);
875 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
876 && origcommand
[0]!=';') {
877 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
878 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
881 if (lstrcmpiW(command
, onW
) == 0) {
885 if (lstrcmpiW(command
, offW
) == 0) {
889 WCMD_output_asis (command
);
890 WCMD_output (newline
);
894 /**************************************************************************
897 * Batch file loop processing.
899 * On entry: cmdList contains the syntax up to the set
900 * next cmdList and all in that bracket contain the set data
901 * next cmdlist contains the DO cmd
902 * following that is either brackets or && entries (as per if)
906 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
911 static const WCHAR inW
[] = {'i','n'};
912 static const WCHAR doW
[] = {'d','o'};
913 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
919 BOOL expandDirs
= FALSE
;
920 BOOL useNumbers
= FALSE
;
921 BOOL doFileset
= FALSE
;
922 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
924 CMD_LIST
*thisCmdStart
;
927 /* Handle optional qualifiers (multiple are allowed) */
928 while (*curPos
&& *curPos
== '/') {
929 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos
));
931 switch (toupperW(*curPos
)) {
932 case 'D': curPos
++; expandDirs
= TRUE
; break;
933 case 'L': curPos
++; useNumbers
= TRUE
; break;
935 /* Recursive is special case - /R can have an optional path following it */
936 /* filenamesets are another special case - /F can have an optional options following it */
940 BOOL isRecursive
= (*curPos
== 'R');
945 /* Skip whitespace */
947 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
949 /* Next parm is either qualifier, path/options or variable -
950 only care about it if it is the path/options */
951 if (*curPos
&& *curPos
!= '/' && *curPos
!= '%') {
952 if (isRecursive
) WINE_FIXME("/R needs to handle supplied root\n");
954 static unsigned int once
;
955 if (!once
++) WINE_FIXME("/F needs to handle options\n");
961 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos
);
965 /* Skip whitespace between qualifiers */
966 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
969 /* Skip whitespace before variable */
970 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
972 /* Ensure line continues with variable */
973 if (!*curPos
|| *curPos
!= '%') {
974 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
978 /* Variable should follow */
980 while (curPos
[i
] && curPos
[i
]!=' ' && curPos
[i
]!='\t') i
++;
981 memcpy(&variable
[0], curPos
, i
*sizeof(WCHAR
));
983 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
986 /* Skip whitespace before IN */
987 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
989 /* Ensure line continues with IN */
991 || !WCMD_keyword_ws_found(inW
, sizeof(inW
)/sizeof(inW
[0]), curPos
)) {
993 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
997 /* Save away where the set of data starts and the variable */
998 thisDepth
= (*cmdList
)->bracketDepth
;
999 *cmdList
= (*cmdList
)->nextcommand
;
1000 setStart
= (*cmdList
);
1002 /* Skip until the close bracket */
1003 WINE_TRACE("Searching %p as the set\n", *cmdList
);
1005 (*cmdList
)->command
!= NULL
&&
1006 (*cmdList
)->bracketDepth
> thisDepth
) {
1007 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
1008 *cmdList
= (*cmdList
)->nextcommand
;
1011 /* Skip the close bracket, if there is one */
1012 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1014 /* Syntax error if missing close bracket, or nothing following it
1015 and once we have the complete set, we expect a DO */
1016 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
1017 if ((*cmdList
== NULL
)
1018 || !WCMD_keyword_ws_found(doW
, sizeof(doW
)/sizeof(doW
[0]), (*cmdList
)->command
)) {
1020 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1024 /* Save away the starting position for the commands (and offset for the
1026 cmdStart
= *cmdList
;
1028 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
1032 /* Loop through all set entries */
1034 thisSet
->command
!= NULL
&&
1035 thisSet
->bracketDepth
>= thisDepth
) {
1037 /* Loop through all entries on the same line */
1041 WINE_TRACE("Processing for set %p\n", thisSet
);
1043 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, NULL
))) {
1046 * If the parameter within the set has a wildcard then search for matching files
1047 * otherwise do a literal substitution.
1049 static const WCHAR wildcards
[] = {'*','?','\0'};
1050 thisCmdStart
= cmdStart
;
1053 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
1055 if (!useNumbers
&& !doFileset
) {
1056 if (strpbrkW (item
, wildcards
)) {
1057 hff
= FindFirstFileW(item
, &fd
);
1058 if (hff
!= INVALID_HANDLE_VALUE
) {
1060 BOOL isDirectory
= FALSE
;
1062 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
1064 /* Handle as files or dirs appropriately, but ignore . and .. */
1065 if (isDirectory
== expandDirs
&&
1066 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1067 (strcmpW(fd
.cFileName
, dotW
) != 0))
1069 thisCmdStart
= cmdStart
;
1070 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
1071 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
1072 fd
.cFileName
, FALSE
, TRUE
);
1075 } while (FindNextFileW(hff
, &fd
) != 0);
1079 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
1082 } else if (useNumbers
) {
1083 /* Convert the first 3 numbers to signed longs and save */
1084 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
1085 /* else ignore them! */
1087 /* Filesets - either a list of files, or a command to run and parse the output */
1088 } else if (doFileset
&& *itemStart
!= '"') {
1091 WCHAR temp_file
[MAX_PATH
];
1093 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
1094 wine_dbgstr_w(item
));
1096 /* If backquote or single quote, we need to launch that command
1097 and parse the results - use a temporary file */
1098 if (*itemStart
== '`' || *itemStart
== '\'') {
1100 WCHAR temp_path
[MAX_PATH
], temp_cmd
[MAXSTRING
];
1101 static const WCHAR redirOut
[] = {'>','%','s','\0'};
1102 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1104 /* Remove trailing character */
1105 itemStart
[strlenW(itemStart
)-1] = 0x00;
1107 /* Get temp filename */
1108 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1109 GetTempFileNameW(temp_path
, cmdW
, 0, temp_file
);
1111 /* Execute program and redirect output */
1112 wsprintfW(temp_cmd
, redirOut
, (itemStart
+1), temp_file
);
1113 WCMD_execute (itemStart
, temp_cmd
, NULL
, NULL
, NULL
);
1115 /* Open the file, read line by line and process */
1116 input
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
1117 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1120 /* Open the file, read line by line and process */
1121 input
= CreateFileW(item
, GENERIC_READ
, FILE_SHARE_READ
,
1122 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1125 /* Process the input file */
1126 if (input
== INVALID_HANDLE_VALUE
) {
1127 WCMD_print_error ();
1128 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), item
);
1130 return; /* FOR loop aborts at first failure here */
1134 WCHAR buffer
[MAXSTRING
] = {'\0'};
1135 WCHAR
*where
, *parm
;
1137 while (WCMD_fgets (buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
1139 /* Skip blank lines*/
1140 parm
= WCMD_parameter (buffer
, 0, &where
, NULL
);
1141 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1142 wine_dbgstr_w(buffer
));
1145 /* FIXME: The following should be moved into its own routine and
1146 reused for the string literal parsing below */
1147 thisCmdStart
= cmdStart
;
1148 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1149 cmdEnd
= thisCmdStart
;
1155 CloseHandle (input
);
1158 /* Delete the temporary file */
1159 if (*itemStart
== '`' || *itemStart
== '\'') {
1160 DeleteFileW(temp_file
);
1163 /* Filesets - A string literal */
1164 } else if (doFileset
&& *itemStart
== '"') {
1165 WCHAR buffer
[MAXSTRING
] = {'\0'};
1166 WCHAR
*where
, *parm
;
1168 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1169 strcpyW(buffer
, item
);
1170 parm
= WCMD_parameter (buffer
, 0, &where
, NULL
);
1171 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1172 wine_dbgstr_w(buffer
));
1175 /* FIXME: The following should be moved into its own routine and
1176 reused for the string literal parsing below */
1177 thisCmdStart
= cmdStart
;
1178 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1179 cmdEnd
= thisCmdStart
;
1183 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
1184 cmdEnd
= thisCmdStart
;
1188 /* Move onto the next set line */
1189 thisSet
= thisSet
->nextcommand
;
1192 /* If /L is provided, now run the for loop */
1195 static const WCHAR fmt
[] = {'%','d','\0'};
1197 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1198 numbers
[0], numbers
[2], numbers
[1]);
1200 (numbers
[1]<0)? i
>numbers
[2] : i
<numbers
[2];
1203 sprintfW(thisNum
, fmt
, i
);
1204 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
1206 thisCmdStart
= cmdStart
;
1207 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, thisNum
, FALSE
, TRUE
);
1208 cmdEnd
= thisCmdStart
;
1212 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1213 all processing, OR it should be pointing to the end of && processing OR
1214 it should be pointing at the NULL end of bracket for the DO. The return
1215 value needs to be the NEXT command to execute, which it either is, or
1216 we need to step over the closing bracket */
1218 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
1222 /*****************************************************************************
1225 * Execute a command, and any && or bracketed follow on to the command. The
1226 * first command to be executed may not be at the front of the
1227 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1229 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
1230 const WCHAR
*variable
, const WCHAR
*value
,
1231 BOOL isIF
, BOOL conditionTRUE
) {
1233 CMD_LIST
*curPosition
= *cmdList
;
1234 int myDepth
= (*cmdList
)->bracketDepth
;
1236 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1237 cmdList
, wine_dbgstr_w(firstcmd
),
1238 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
1241 /* Skip leading whitespace between condition and the command */
1242 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1244 /* Process the first command, if there is one */
1245 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
1246 WCHAR
*command
= WCMD_strdupW(firstcmd
);
1247 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1248 HeapFree(GetProcessHeap(), 0, command
);
1252 /* If it didn't move the position, step to next command */
1253 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1255 /* Process any other parts of the command */
1257 BOOL processThese
= TRUE
;
1259 if (isIF
) processThese
= conditionTRUE
;
1262 static const WCHAR ifElse
[] = {'e','l','s','e'};
1264 /* execute all appropriate commands */
1265 curPosition
= *cmdList
;
1267 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1269 (*cmdList
)->prevDelim
,
1270 (*cmdList
)->bracketDepth
, myDepth
);
1272 /* Execute any statements appended to the line */
1273 /* FIXME: Only if previous call worked for && or failed for || */
1274 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1275 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1276 if (processThese
&& (*cmdList
)->command
) {
1277 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
, variable
,
1280 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1282 /* Execute any appended to the statement with (...) */
1283 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1285 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
1286 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1288 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1290 /* End of the command - does 'ELSE ' follow as the next command? */
1293 && WCMD_keyword_ws_found(ifElse
, sizeof(ifElse
)/sizeof(ifElse
[0]),
1294 (*cmdList
)->command
)) {
1296 /* Swap between if and else processing */
1297 processThese
= !processThese
;
1299 /* Process the ELSE part */
1301 const int keyw_len
= sizeof(ifElse
)/sizeof(ifElse
[0]) + 1;
1302 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1304 /* Skip leading whitespace between condition and the command */
1305 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1307 WCMD_execute (cmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1310 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1312 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1321 /**************************************************************************
1324 * Simple on-line help. Help text is stored in the resource file.
1327 void WCMD_give_help (const WCHAR
*command
) {
1331 command
= WCMD_skip_leading_spaces((WCHAR
*) command
);
1332 if (strlenW(command
) == 0) {
1333 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
1336 /* Display help message for builtin commands */
1337 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1338 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1339 command
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
1340 WCMD_output_asis (WCMD_LoadMessage(i
));
1344 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1345 for (i
= 0; i
<= (sizeof(externals
)/sizeof(externals
[0])); i
++) {
1346 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1347 command
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
1349 static const WCHAR helpW
[] = {' ', '/','?','\0'};
1350 strcpyW(cmd
, command
);
1351 strcatW(cmd
, helpW
);
1352 WCMD_run_program(cmd
, 0);
1356 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), command
);
1361 /****************************************************************************
1364 * Batch file jump instruction. Not the most efficient algorithm ;-)
1365 * Prints error message if the specified label cannot be found - the file pointer is
1366 * then at EOF, effectively stopping the batch file.
1367 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1370 void WCMD_goto (CMD_LIST
**cmdList
) {
1372 WCHAR string
[MAX_PATH
];
1373 WCHAR current
[MAX_PATH
];
1375 /* Do not process any more parts of a processed multipart or multilines command */
1376 if (cmdList
) *cmdList
= NULL
;
1378 if (context
!= NULL
) {
1379 WCHAR
*paramStart
= param1
, *str
;
1380 static const WCHAR eofW
[] = {':','e','o','f','\0'};
1382 if (param1
[0] == 0x00) {
1383 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1387 /* Handle special :EOF label */
1388 if (lstrcmpiW (eofW
, param1
) == 0) {
1389 context
-> skip_rest
= TRUE
;
1393 /* Support goto :label as well as goto label */
1394 if (*paramStart
== ':') paramStart
++;
1396 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
1397 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
1399 while (isspaceW (*str
)) str
++;
1403 while (((current
[index
] = str
[index
])) && (!isspaceW (current
[index
])))
1406 /* ignore space at the end */
1408 if (lstrcmpiW (current
, paramStart
) == 0) return;
1411 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
1416 /*****************************************************************************
1419 * Push a directory onto the stack
1422 void WCMD_pushd (WCHAR
*command
) {
1423 struct env_stack
*curdir
;
1425 static const WCHAR parmD
[] = {'/','D','\0'};
1427 if (strchrW(command
, '/') != NULL
) {
1428 SetLastError(ERROR_INVALID_PARAMETER
);
1433 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1434 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
1435 if( !curdir
|| !thisdir
) {
1438 WINE_ERR ("out of memory\n");
1442 /* Change directory using CD code with /D parameter */
1443 strcpyW(quals
, parmD
);
1444 GetCurrentDirectoryW (1024, thisdir
);
1446 WCMD_setshow_default(command
);
1452 curdir
-> next
= pushd_directories
;
1453 curdir
-> strings
= thisdir
;
1454 if (pushd_directories
== NULL
) {
1455 curdir
-> u
.stackdepth
= 1;
1457 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1459 pushd_directories
= curdir
;
1464 /*****************************************************************************
1467 * Pop a directory from the stack
1470 void WCMD_popd (void) {
1471 struct env_stack
*temp
= pushd_directories
;
1473 if (!pushd_directories
)
1476 /* pop the old environment from the stack, and make it the current dir */
1477 pushd_directories
= temp
->next
;
1478 SetCurrentDirectoryW(temp
->strings
);
1479 LocalFree (temp
->strings
);
1483 /****************************************************************************
1486 * Batch file conditional.
1488 * On entry, cmdlist will point to command containing the IF, and optionally
1489 * the first command to execute (if brackets not found)
1490 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1491 * If ('s were found, execute all within that bracket
1492 * Command may optionally be followed by an ELSE - need to skip instructions
1493 * in the else using the same logic
1495 * FIXME: Much more syntax checking needed!
1498 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
1500 int negate
; /* Negate condition */
1501 int test
; /* Condition evaluation result */
1502 WCHAR condition
[MAX_PATH
], *command
, *s
;
1503 static const WCHAR notW
[] = {'n','o','t','\0'};
1504 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1505 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
1506 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
1507 static const WCHAR eqeqW
[] = {'=','=','\0'};
1508 static const WCHAR parmI
[] = {'/','I','\0'};
1509 int caseInsensitive
= (strstrW(quals
, parmI
) != NULL
);
1511 negate
= !lstrcmpiW(param1
,notW
);
1512 strcpyW(condition
, (negate
? param2
: param1
));
1513 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
1515 if (!lstrcmpiW (condition
, errlvlW
)) {
1516 test
= (errorlevel
>= atoiW(WCMD_parameter(p
, 1+negate
, NULL
, NULL
)));
1517 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1519 else if (!lstrcmpiW (condition
, existW
)) {
1520 test
= (GetFileAttributesW(WCMD_parameter(p
, 1+negate
, NULL
, NULL
)) != INVALID_FILE_ATTRIBUTES
);
1521 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1523 else if (!lstrcmpiW (condition
, defdW
)) {
1524 test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+negate
, NULL
, NULL
), NULL
, 0) > 0);
1525 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1527 else if ((s
= strstrW (p
, eqeqW
))) {
1528 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1529 WCHAR
*leftPart
, *leftPartEnd
, *rightPart
, *rightPartEnd
;
1531 WCMD_parameter(p
, 0+negate
+caseInsensitive
, &leftPart
, &leftPartEnd
);
1532 WCMD_parameter(p
, 1+negate
+caseInsensitive
, &rightPart
, &rightPartEnd
);
1533 test
= caseInsensitive
1534 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1535 leftPart
, leftPartEnd
-leftPart
+1,
1536 rightPart
, rightPartEnd
-rightPart
+1) == CSTR_EQUAL
)
1537 : (CompareStringW(LOCALE_SYSTEM_DEFAULT
, 0,
1538 leftPart
, leftPartEnd
-leftPart
+1,
1539 rightPart
, rightPartEnd
-rightPart
+1) == CSTR_EQUAL
);
1540 WCMD_parameter(s
, 1, &command
, NULL
);
1543 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1547 /* Process rest of IF statement which is on the same line
1548 Note: This may process all or some of the cmdList (eg a GOTO) */
1549 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1552 /****************************************************************************
1555 * Move a file, directory tree or wildcarded set of files.
1558 void WCMD_move (void) {
1561 WIN32_FIND_DATAW fd
;
1563 WCHAR input
[MAX_PATH
];
1564 WCHAR output
[MAX_PATH
];
1566 WCHAR dir
[MAX_PATH
];
1567 WCHAR fname
[MAX_PATH
];
1568 WCHAR ext
[MAX_PATH
];
1570 if (param1
[0] == 0x00) {
1571 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1575 /* If no destination supplied, assume current directory */
1576 if (param2
[0] == 0x00) {
1577 strcpyW(param2
, dotW
);
1580 /* If 2nd parm is directory, then use original filename */
1581 /* Convert partial path to full path */
1582 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1583 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1584 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1585 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1587 /* Split into components */
1588 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1590 hff
= FindFirstFileW(input
, &fd
);
1591 while (hff
!= INVALID_HANDLE_VALUE
) {
1592 WCHAR dest
[MAX_PATH
];
1593 WCHAR src
[MAX_PATH
];
1596 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1598 /* Build src & dest name */
1599 strcpyW(src
, drive
);
1602 /* See if dest is an existing directory */
1603 attribs
= GetFileAttributesW(output
);
1604 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1605 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1606 strcpyW(dest
, output
);
1607 strcatW(dest
, slashW
);
1608 strcatW(dest
, fd
.cFileName
);
1610 strcpyW(dest
, output
);
1613 strcatW(src
, fd
.cFileName
);
1615 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1616 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1618 /* Check if file is read only, otherwise move it */
1619 attribs
= GetFileAttributesW(src
);
1620 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1621 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1622 SetLastError(ERROR_ACCESS_DENIED
);
1627 /* If destination exists, prompt unless /Y supplied */
1628 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
1630 WCHAR copycmd
[MAXSTRING
];
1633 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1634 if (strstrW (quals
, parmNoY
))
1636 else if (strstrW (quals
, parmY
))
1639 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1640 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1641 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1642 && ! lstrcmpiW (copycmd
, parmY
));
1645 /* Prompt if overwriting */
1647 WCHAR question
[MAXSTRING
];
1650 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1652 /* Ask for confirmation */
1653 wsprintfW(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1654 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1656 /* So delete the destination prior to the move */
1658 if (!DeleteFileW(dest
)) {
1659 WCMD_print_error ();
1668 status
= MoveFileW(src
, dest
);
1670 status
= 1; /* Anything other than 0 to prevent error msg below */
1675 WCMD_print_error ();
1679 /* Step on to next match */
1680 if (FindNextFileW(hff
, &fd
) == 0) {
1682 hff
= INVALID_HANDLE_VALUE
;
1688 /****************************************************************************
1691 * Wait for keyboard input.
1694 void WCMD_pause (void) {
1699 WCMD_output (anykey
);
1700 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1701 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1704 /****************************************************************************
1707 * Delete a directory.
1710 void WCMD_remove_dir (WCHAR
*command
) {
1713 int argsProcessed
= 0;
1714 WCHAR
*argN
= command
;
1715 static const WCHAR parmS
[] = {'/','S','\0'};
1716 static const WCHAR parmQ
[] = {'/','Q','\0'};
1718 /* Loop through all args */
1720 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
1721 if (argN
&& argN
[0] != '/') {
1722 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1723 wine_dbgstr_w(quals
));
1726 /* If subdirectory search not supplied, just try to remove
1727 and report error if it fails (eg if it contains a file) */
1728 if (strstrW (quals
, parmS
) == NULL
) {
1729 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
1731 /* Otherwise use ShFileOp to recursively remove a directory */
1734 SHFILEOPSTRUCTW lpDir
;
1737 if (strstrW (quals
, parmQ
) == NULL
) {
1739 WCHAR question
[MAXSTRING
];
1740 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1742 /* Ask for confirmation */
1743 wsprintfW(question
, fmt
, thisArg
);
1744 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1746 /* Abort if answer is 'N' */
1753 lpDir
.pFrom
= thisArg
;
1754 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1755 lpDir
.wFunc
= FO_DELETE
;
1756 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
1761 /* Handle no valid args */
1762 if (argsProcessed
== 0) {
1763 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1769 /****************************************************************************
1775 void WCMD_rename (void) {
1779 WIN32_FIND_DATAW fd
;
1780 WCHAR input
[MAX_PATH
];
1781 WCHAR
*dotDst
= NULL
;
1783 WCHAR dir
[MAX_PATH
];
1784 WCHAR fname
[MAX_PATH
];
1785 WCHAR ext
[MAX_PATH
];
1790 /* Must be at least two args */
1791 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1792 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1797 /* Destination cannot contain a drive letter or directory separator */
1798 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1799 SetLastError(ERROR_INVALID_PARAMETER
);
1805 /* Convert partial path to full path */
1806 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1807 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1808 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1809 dotDst
= strchrW(param2
, '.');
1811 /* Split into components */
1812 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1814 hff
= FindFirstFileW(input
, &fd
);
1815 while (hff
!= INVALID_HANDLE_VALUE
) {
1816 WCHAR dest
[MAX_PATH
];
1817 WCHAR src
[MAX_PATH
];
1818 WCHAR
*dotSrc
= NULL
;
1821 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1823 /* FIXME: If dest name or extension is *, replace with filename/ext
1824 part otherwise use supplied name. This supports:
1826 ren jim.* fred.* etc
1827 However, windows has a more complex algorithm supporting eg
1828 ?'s and *'s mid name */
1829 dotSrc
= strchrW(fd
.cFileName
, '.');
1831 /* Build src & dest name */
1832 strcpyW(src
, drive
);
1835 dirLen
= strlenW(src
);
1836 strcatW(src
, fd
.cFileName
);
1839 if (param2
[0] == '*') {
1840 strcatW(dest
, fd
.cFileName
);
1841 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1843 strcatW(dest
, param2
);
1844 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1847 /* Build Extension */
1848 if (dotDst
&& (*(dotDst
+1)=='*')) {
1849 if (dotSrc
) strcatW(dest
, dotSrc
);
1850 } else if (dotDst
) {
1851 if (dotDst
) strcatW(dest
, dotDst
);
1854 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1855 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1857 /* Check if file is read only, otherwise move it */
1858 attribs
= GetFileAttributesW(src
);
1859 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1860 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1861 SetLastError(ERROR_ACCESS_DENIED
);
1864 status
= MoveFileW(src
, dest
);
1868 WCMD_print_error ();
1872 /* Step on to next match */
1873 if (FindNextFileW(hff
, &fd
) == 0) {
1875 hff
= INVALID_HANDLE_VALUE
;
1881 /*****************************************************************************
1884 * Make a copy of the environment.
1886 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1896 len
+= (strlenW(&env
[len
]) + 1);
1898 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1901 WINE_ERR("out of memory\n");
1904 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1910 /*****************************************************************************
1913 * setlocal pushes the environment onto a stack
1914 * Save the environment as unicode so we don't screw anything up.
1916 void WCMD_setlocal (const WCHAR
*s
) {
1918 struct env_stack
*env_copy
;
1919 WCHAR cwd
[MAX_PATH
];
1921 /* DISABLEEXTENSIONS ignored */
1923 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1926 WINE_ERR ("out of memory\n");
1930 env
= GetEnvironmentStringsW ();
1932 env_copy
->strings
= WCMD_dupenv (env
);
1933 if (env_copy
->strings
)
1935 env_copy
->next
= saved_environment
;
1936 saved_environment
= env_copy
;
1938 /* Save the current drive letter */
1939 GetCurrentDirectoryW(MAX_PATH
, cwd
);
1940 env_copy
->u
.cwd
= cwd
[0];
1943 LocalFree (env_copy
);
1945 FreeEnvironmentStringsW (env
);
1949 /*****************************************************************************
1952 * endlocal pops the environment off a stack
1953 * Note: When searching for '=', search from WCHAR position 1, to handle
1954 * special internal environment variables =C:, =D: etc
1956 void WCMD_endlocal (void) {
1957 WCHAR
*env
, *old
, *p
;
1958 struct env_stack
*temp
;
1961 if (!saved_environment
)
1964 /* pop the old environment from the stack */
1965 temp
= saved_environment
;
1966 saved_environment
= temp
->next
;
1968 /* delete the current environment, totally */
1969 env
= GetEnvironmentStringsW ();
1970 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1973 n
= strlenW(&old
[len
]) + 1;
1974 p
= strchrW(&old
[len
] + 1, '=');
1978 SetEnvironmentVariableW (&old
[len
], NULL
);
1983 FreeEnvironmentStringsW (env
);
1985 /* restore old environment */
1986 env
= temp
->strings
;
1989 n
= strlenW(&env
[len
]) + 1;
1990 p
= strchrW(&env
[len
] + 1, '=');
1994 SetEnvironmentVariableW (&env
[len
], p
);
1999 /* Restore current drive letter */
2000 if (IsCharAlphaW(temp
->u
.cwd
)) {
2002 WCHAR cwd
[MAX_PATH
];
2003 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
2005 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
2006 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
2007 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
2008 SetCurrentDirectoryW(cwd
);
2016 /*****************************************************************************
2017 * WCMD_setshow_default
2019 * Set/Show the current default directory
2022 void WCMD_setshow_default (const WCHAR
*command
) {
2028 WIN32_FIND_DATAW fd
;
2030 static const WCHAR parmD
[] = {'/','D','\0'};
2032 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
2034 /* Skip /D and trailing whitespace if on the front of the command line */
2035 if (CompareStringW(LOCALE_USER_DEFAULT
,
2036 NORM_IGNORECASE
| SORT_STRINGSORT
,
2037 command
, 2, parmD
, -1) == CSTR_EQUAL
) {
2039 while (*command
&& (*command
==' ' || *command
=='\t'))
2043 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
2044 if (strlenW(command
) == 0) {
2045 strcatW (cwd
, newline
);
2049 /* Remove any double quotes, which may be in the
2050 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2053 if (*command
!= '"') *pos
++ = *command
;
2056 while (pos
> command
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
2060 /* Search for appropriate directory */
2061 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
2062 hff
= FindFirstFileW(string
, &fd
);
2063 while (hff
!= INVALID_HANDLE_VALUE
) {
2064 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
2065 WCHAR fpath
[MAX_PATH
];
2067 WCHAR dir
[MAX_PATH
];
2068 WCHAR fname
[MAX_PATH
];
2069 WCHAR ext
[MAX_PATH
];
2070 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
2072 /* Convert path into actual directory spec */
2073 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
2074 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
2077 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
2080 hff
= INVALID_HANDLE_VALUE
;
2084 /* Step on to next match */
2085 if (FindNextFileW(hff
, &fd
) == 0) {
2087 hff
= INVALID_HANDLE_VALUE
;
2092 /* Change to that directory */
2093 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
2095 status
= SetCurrentDirectoryW(string
);
2098 WCMD_print_error ();
2102 /* Save away the actual new directory, to store as current location */
2103 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
2105 /* Restore old directory if drive letter would change, and
2106 CD x:\directory /D (or pushd c:\directory) not supplied */
2107 if ((strstrW(quals
, parmD
) == NULL
) &&
2108 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
2109 SetCurrentDirectoryW(cwd
);
2113 /* Set special =C: type environment variable, for drive letter of
2114 change of directory, even if path was restored due to missing
2115 /D (allows changing drive letter when not resident on that
2117 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
2119 strcpyW(env
, equalW
);
2120 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
2122 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
2123 SetEnvironmentVariableW(env
, string
);
2130 /****************************************************************************
2133 * Set/Show the system date
2134 * FIXME: Can't change date yet
2137 void WCMD_setshow_date (void) {
2139 WCHAR curdate
[64], buffer
[64];
2141 static const WCHAR parmT
[] = {'/','T','\0'};
2143 if (strlenW(param1
) == 0) {
2144 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
2145 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
2146 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
2147 if (strstrW (quals
, parmT
) == NULL
) {
2148 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
2149 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
2150 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2152 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2156 else WCMD_print_error ();
2159 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2163 /****************************************************************************
2166 static int WCMD_compare( const void *a
, const void *b
)
2169 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
2170 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2171 *str_a
, -1, *str_b
, -1 );
2172 if( r
== CSTR_LESS_THAN
) return -1;
2173 if( r
== CSTR_GREATER_THAN
) return 1;
2177 /****************************************************************************
2178 * WCMD_setshow_sortenv
2180 * sort variables into order for display
2181 * Optionally only display those who start with a stub
2182 * returns the count displayed
2184 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
2186 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
2189 if (stub
) stublen
= strlenW(stub
);
2191 /* count the number of strings, and the total length */
2193 len
+= (strlenW(&s
[len
]) + 1);
2197 /* add the strings to an array */
2198 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
2202 for( i
=1; i
<count
; i
++ )
2203 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
2205 /* sort the array */
2206 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
2209 for( i
=0; i
<count
; i
++ ) {
2210 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
2211 NORM_IGNORECASE
| SORT_STRINGSORT
,
2212 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
2213 /* Don't display special internal variables */
2214 if (str
[i
][0] != '=') {
2215 WCMD_output_asis(str
[i
]);
2216 WCMD_output_asis(newline
);
2223 return displayedcount
;
2226 /****************************************************************************
2229 * Set/Show the environment variables
2232 void WCMD_setshow_env (WCHAR
*s
) {
2237 static const WCHAR parmP
[] = {'/','P','\0'};
2239 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
2240 env
= GetEnvironmentStringsW();
2241 WCMD_setshow_sortenv( env
, NULL
);
2245 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2246 if (CompareStringW(LOCALE_USER_DEFAULT
,
2247 NORM_IGNORECASE
| SORT_STRINGSORT
,
2248 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
2249 WCHAR string
[MAXSTRING
];
2253 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
2255 WCMD_opt_s_strip_quotes(s
);
2257 /* If no parameter, or no '=' sign, return an error */
2258 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2259 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2263 /* Output the prompt */
2265 if (strlenW(p
) != 0) WCMD_output(p
);
2267 /* Read the reply */
2268 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2269 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2271 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2272 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2273 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2274 wine_dbgstr_w(string
));
2275 status
= SetEnvironmentVariableW(s
, string
);
2282 WCMD_opt_s_strip_quotes(s
);
2283 p
= strchrW (s
, '=');
2285 env
= GetEnvironmentStringsW();
2286 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2287 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2294 if (strlenW(p
) == 0) p
= NULL
;
2295 status
= SetEnvironmentVariableW(s
, p
);
2296 gle
= GetLastError();
2297 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2299 } else if ((!status
)) WCMD_print_error();
2303 /****************************************************************************
2306 * Set/Show the path environment variable
2309 void WCMD_setshow_path (const WCHAR
*command
) {
2313 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2314 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2316 if (strlenW(param1
) == 0) {
2317 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2319 WCMD_output_asis ( pathEqW
);
2320 WCMD_output_asis ( string
);
2321 WCMD_output_asis ( newline
);
2324 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
2328 if (*command
== '=') command
++; /* Skip leading '=' */
2329 status
= SetEnvironmentVariableW(pathW
, command
);
2330 if (!status
) WCMD_print_error();
2334 /****************************************************************************
2335 * WCMD_setshow_prompt
2337 * Set or show the command prompt.
2340 void WCMD_setshow_prompt (void) {
2343 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2345 if (strlenW(param1
) == 0) {
2346 SetEnvironmentVariableW(promptW
, NULL
);
2350 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
2351 if (strlenW(s
) == 0) {
2352 SetEnvironmentVariableW(promptW
, NULL
);
2354 else SetEnvironmentVariableW(promptW
, s
);
2358 /****************************************************************************
2361 * Set/Show the system time
2362 * FIXME: Can't change time yet
2365 void WCMD_setshow_time (void) {
2367 WCHAR curtime
[64], buffer
[64];
2370 static const WCHAR parmT
[] = {'/','T','\0'};
2372 if (strlenW(param1
) == 0) {
2374 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2375 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2376 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
2377 if (strstrW (quals
, parmT
) == NULL
) {
2378 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2379 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2380 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2382 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2386 else WCMD_print_error ();
2389 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2393 /****************************************************************************
2396 * Shift batch parameters.
2397 * Optional /n says where to start shifting (n=0-8)
2400 void WCMD_shift (const WCHAR
*command
) {
2403 if (context
!= NULL
) {
2404 WCHAR
*pos
= strchrW(command
, '/');
2409 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2410 start
= (*(pos
+1) - '0');
2412 SetLastError(ERROR_INVALID_PARAMETER
);
2417 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2418 for (i
=start
;i
<=8;i
++) {
2419 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2421 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2426 /****************************************************************************
2429 * Set the console title
2431 void WCMD_title (const WCHAR
*command
) {
2432 SetConsoleTitleW(command
);
2435 /****************************************************************************
2438 * Copy a file to standard output.
2441 void WCMD_type (WCHAR
*command
) {
2444 WCHAR
*argN
= command
;
2445 BOOL writeHeaders
= FALSE
;
2447 if (param1
[0] == 0x00) {
2448 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2452 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2454 /* Loop through all args */
2457 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2465 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2466 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2467 FILE_ATTRIBUTE_NORMAL
, NULL
);
2468 if (h
== INVALID_HANDLE_VALUE
) {
2469 WCMD_print_error ();
2470 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
); /* should be _stderr */
2474 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
2475 WCMD_output(fmt
, thisArg
);
2477 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
, NULL
)) {
2478 if (count
== 0) break; /* ReadFile reports success on EOF! */
2480 WCMD_output_asis (buffer
);
2487 /****************************************************************************
2490 * Output either a file or stdin to screen in pages
2493 void WCMD_more (WCHAR
*command
) {
2496 WCHAR
*argN
= command
;
2498 WCHAR moreStrPage
[100];
2501 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2502 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2503 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2504 ')',' ','-','-','\n','\0'};
2505 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2507 /* Prefix the NLS more with '-- ', then load the text */
2509 strcpyW(moreStr
, moreStart
);
2510 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
2511 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2513 if (param1
[0] == 0x00) {
2515 /* Wine implements pipes via temporary files, and hence stdin is
2516 effectively reading from the file. This means the prompts for
2517 more are satisfied by the next line from the input (file). To
2518 avoid this, ensure stdin is to the console */
2519 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2520 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2521 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2522 FILE_ATTRIBUTE_NORMAL
, 0);
2523 WINE_TRACE("No parms - working probably in pipe mode\n");
2524 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2526 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2527 once you get in this bit unless due to a pipe, its going to end badly... */
2528 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
2530 WCMD_enter_paged_mode(moreStrPage
);
2531 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2532 if (count
== 0) break; /* ReadFile reports success on EOF! */
2534 WCMD_output_asis (buffer
);
2536 WCMD_leave_paged_mode();
2538 /* Restore stdin to what it was */
2539 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2540 CloseHandle(hConIn
);
2544 BOOL needsPause
= FALSE
;
2546 /* Loop through all args */
2547 WINE_TRACE("Parms supplied - working through each file\n");
2548 WCMD_enter_paged_mode(moreStrPage
);
2551 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2559 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
2560 WCMD_leave_paged_mode();
2561 WCMD_output_asis(moreStrPage
);
2562 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2563 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2564 WCMD_enter_paged_mode(moreStrPage
);
2568 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2569 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2570 FILE_ATTRIBUTE_NORMAL
, NULL
);
2571 if (h
== INVALID_HANDLE_VALUE
) {
2572 WCMD_print_error ();
2573 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2577 ULONG64 fileLen
= 0;
2578 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2580 /* Get the file size */
2581 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2582 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2585 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2586 if (count
== 0) break; /* ReadFile reports success on EOF! */
2590 /* Update % count (would be used in WCMD_output_asis as prompt) */
2591 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2593 WCMD_output_asis (buffer
);
2599 WCMD_leave_paged_mode();
2603 /****************************************************************************
2606 * Display verify flag.
2607 * FIXME: We don't actually do anything with the verify flag other than toggle
2611 void WCMD_verify (const WCHAR
*command
) {
2615 count
= strlenW(command
);
2617 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2618 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2621 if (lstrcmpiW(command
, onW
) == 0) {
2625 else if (lstrcmpiW(command
, offW
) == 0) {
2626 verify_mode
= FALSE
;
2629 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
2632 /****************************************************************************
2635 * Display version info.
2638 void WCMD_version (void) {
2640 WCMD_output (version_string
);
2644 /****************************************************************************
2647 * Display volume information (set_label = FALSE)
2648 * Additionally set volume label (set_label = TRUE)
2649 * Returns 1 on success, 0 otherwise
2652 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
2654 DWORD count
, serial
;
2655 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2658 if (strlenW(path
) == 0) {
2659 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2661 WCMD_print_error ();
2664 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2665 &serial
, NULL
, NULL
, NULL
, 0);
2668 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2669 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2670 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2673 wsprintfW (curdir
, fmt
, path
);
2674 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2679 WCMD_print_error ();
2682 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2683 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2685 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2686 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2687 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2689 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2690 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2692 if (strlenW(path
) != 0) {
2693 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
2696 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
2702 /**************************************************************************
2705 * Exit either the process, or just this batch program
2709 void WCMD_exit (CMD_LIST
**cmdList
) {
2711 static const WCHAR parmB
[] = {'/','B','\0'};
2712 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2714 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2716 context
-> skip_rest
= TRUE
;
2724 /*****************************************************************************
2727 * Lists or sets file associations (assoc = TRUE)
2728 * Lists or sets file types (assoc = FALSE)
2730 void WCMD_assoc (const WCHAR
*command
, BOOL assoc
) {
2733 DWORD accessOptions
= KEY_READ
;
2735 LONG rc
= ERROR_SUCCESS
;
2736 WCHAR keyValue
[MAXSTRING
];
2737 DWORD valueLen
= MAXSTRING
;
2739 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2740 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2742 /* See if parameter includes '=' */
2744 newValue
= strchrW(command
, '=');
2745 if (newValue
) accessOptions
|= KEY_WRITE
;
2747 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2748 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
2749 accessOptions
, &key
) != ERROR_SUCCESS
) {
2750 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2754 /* If no parameters then list all associations */
2755 if (*command
== 0x00) {
2758 /* Enumerate all the keys */
2759 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2760 WCHAR keyName
[MAXSTRING
];
2763 /* Find the next value */
2764 nameLen
= MAXSTRING
;
2765 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
2767 if (rc
== ERROR_SUCCESS
) {
2769 /* Only interested in extension ones if assoc, or others
2771 if ((keyName
[0] == '.' && assoc
) ||
2772 (!(keyName
[0] == '.') && (!assoc
)))
2774 WCHAR subkey
[MAXSTRING
];
2775 strcpyW(subkey
, keyName
);
2776 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2778 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2780 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2781 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2782 WCMD_output_asis(keyName
);
2783 WCMD_output_asis(equalW
);
2784 /* If no default value found, leave line empty after '=' */
2785 if (rc
== ERROR_SUCCESS
) {
2786 WCMD_output_asis(keyValue
);
2788 WCMD_output_asis(newline
);
2789 RegCloseKey(readKey
);
2797 /* Parameter supplied - if no '=' on command line, its a query */
2798 if (newValue
== NULL
) {
2800 WCHAR subkey
[MAXSTRING
];
2802 /* Query terminates the parameter at the first space */
2803 strcpyW(keyValue
, command
);
2804 space
= strchrW(keyValue
, ' ');
2805 if (space
) *space
=0x00;
2807 /* Set up key name */
2808 strcpyW(subkey
, keyValue
);
2809 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2811 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2813 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2814 WCMD_output_asis(command
);
2815 WCMD_output_asis(equalW
);
2816 /* If no default value found, leave line empty after '=' */
2817 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2818 WCMD_output_asis(newline
);
2819 RegCloseKey(readKey
);
2822 WCHAR msgbuffer
[MAXSTRING
];
2823 WCHAR outbuffer
[MAXSTRING
];
2825 /* Load the translated 'File association not found' */
2827 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2829 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2831 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2832 WCMD_output_asis_stderr(outbuffer
);
2836 /* Not a query - its a set or clear of a value */
2839 WCHAR subkey
[MAXSTRING
];
2841 /* Get pointer to new value */
2845 /* Set up key name */
2846 strcpyW(subkey
, command
);
2847 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2849 /* If nothing after '=' then clear value - only valid for ASSOC */
2850 if (*newValue
== 0x00) {
2852 if (assoc
) rc
= RegDeleteKeyW(key
, command
);
2853 if (assoc
&& rc
== ERROR_SUCCESS
) {
2854 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2856 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2861 WCHAR msgbuffer
[MAXSTRING
];
2862 WCHAR outbuffer
[MAXSTRING
];
2864 /* Load the translated 'File association not found' */
2866 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
2867 sizeof(msgbuffer
)/sizeof(WCHAR
));
2869 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
2870 sizeof(msgbuffer
)/sizeof(WCHAR
));
2872 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2873 WCMD_output_asis_stderr(outbuffer
);
2877 /* It really is a set value = contents */
2879 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2880 accessOptions
, NULL
, &readKey
, NULL
);
2881 if (rc
== ERROR_SUCCESS
) {
2882 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
2884 sizeof(WCHAR
) * (strlenW(newValue
) + 1));
2885 RegCloseKey(readKey
);
2888 if (rc
!= ERROR_SUCCESS
) {
2892 WCMD_output_asis(command
);
2893 WCMD_output_asis(equalW
);
2894 WCMD_output_asis(newValue
);
2895 WCMD_output_asis(newline
);
2905 /****************************************************************************
2908 * Colors the terminal screen.
2911 void WCMD_color (void) {
2913 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2914 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2916 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2917 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
2921 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2927 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2932 /* Convert the color hex digits */
2933 if (param1
[0] == 0x00) {
2934 color
= defaultColor
;
2936 color
= strtoulW(param1
, NULL
, 16);
2939 /* Fail if fg == bg color */
2940 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2945 /* Set the current screen contents and ensure all future writes
2946 remain this color */
2947 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2948 SetConsoleTextAttribute(hStdOut
, color
);