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 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
37 extern int defaultColor
;
38 extern BOOL echo_mode
;
40 struct env_stack
*pushd_directories
;
41 const WCHAR dotW
[] = {'.','\0'};
42 const WCHAR dotdotW
[] = {'.','.','\0'};
43 const WCHAR nullW
[] = {'\0'};
44 const WCHAR starW
[] = {'*','\0'};
45 const WCHAR slashW
[] = {'\\','\0'};
46 const WCHAR equalW
[] = {'=','\0'};
47 const WCHAR inbuilt
[][10] = {
48 {'C','A','L','L','\0'},
50 {'C','H','D','I','R','\0'},
52 {'C','O','P','Y','\0'},
53 {'C','T','T','Y','\0'},
54 {'D','A','T','E','\0'},
57 {'E','C','H','O','\0'},
58 {'E','R','A','S','E','\0'},
60 {'G','O','T','O','\0'},
61 {'H','E','L','P','\0'},
63 {'L','A','B','E','L','\0'},
65 {'M','K','D','I','R','\0'},
66 {'M','O','V','E','\0'},
67 {'P','A','T','H','\0'},
68 {'P','A','U','S','E','\0'},
69 {'P','R','O','M','P','T','\0'},
72 {'R','E','N','A','M','E','\0'},
74 {'R','M','D','I','R','\0'},
76 {'S','H','I','F','T','\0'},
77 {'T','I','M','E','\0'},
78 {'T','I','T','L','E','\0'},
79 {'T','Y','P','E','\0'},
80 {'V','E','R','I','F','Y','\0'},
83 {'E','N','D','L','O','C','A','L','\0'},
84 {'S','E','T','L','O','C','A','L','\0'},
85 {'P','U','S','H','D','\0'},
86 {'P','O','P','D','\0'},
87 {'A','S','S','O','C','\0'},
88 {'C','O','L','O','R','\0'},
89 {'F','T','Y','P','E','\0'},
90 {'M','O','R','E','\0'},
91 {'C','H','O','I','C','E','\0'},
92 {'E','X','I','T','\0'}
94 static const WCHAR externals
[][10] = {
95 {'A','T','T','R','I','B','\0'},
96 {'X','C','O','P','Y','\0'}
98 static const WCHAR fslashW
[] = {'/','\0'};
99 static const WCHAR onW
[] = {'O','N','\0'};
100 static const WCHAR offW
[] = {'O','F','F','\0'};
101 static const WCHAR parmY
[] = {'/','Y','\0'};
102 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
104 static HINSTANCE hinst
;
105 static struct env_stack
*saved_environment
;
106 static BOOL verify_mode
= FALSE
;
108 /**************************************************************************
111 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
114 * Returns True if Y (or A) answer is selected
115 * If optionAll contains a pointer, ALL is allowed, and if answered
119 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
120 const BOOL
*optionAll
) {
122 WCHAR msgbuffer
[MAXSTRING
];
123 WCHAR Ybuffer
[MAXSTRING
];
124 WCHAR Nbuffer
[MAXSTRING
];
125 WCHAR Abuffer
[MAXSTRING
];
126 WCHAR answer
[MAX_PATH
] = {'\0'};
129 /* Load the translated 'Are you sure', plus valid answers */
130 LoadStringW(hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
131 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
132 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
133 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
135 /* Loop waiting on a Y or N */
136 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
137 static const WCHAR startBkt
[] = {' ','(','\0'};
138 static const WCHAR endBkt
[] = {')','?','\0'};
140 WCMD_output_asis (message
);
142 WCMD_output_asis (msgbuffer
);
144 WCMD_output_asis (startBkt
);
145 WCMD_output_asis (Ybuffer
);
146 WCMD_output_asis (fslashW
);
147 WCMD_output_asis (Nbuffer
);
149 WCMD_output_asis (fslashW
);
150 WCMD_output_asis (Abuffer
);
152 WCMD_output_asis (endBkt
);
153 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
)/sizeof(WCHAR
), &count
);
154 answer
[0] = toupperW(answer
[0]);
157 /* Return the answer */
158 return ((answer
[0] == Ybuffer
[0]) ||
159 (optionAll
&& (answer
[0] == Abuffer
[0])));
162 /****************************************************************************
165 * Clear the terminal screen.
168 void WCMD_clear_screen (void) {
170 /* Emulate by filling the screen from the top left to bottom right with
171 spaces, then moving the cursor to the top left afterwards */
172 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
173 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
175 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
180 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
184 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
185 SetConsoleCursorPosition(hStdOut
, topLeft
);
189 /****************************************************************************
192 * Change the default i/o device (ie redirect STDin/STDout).
195 void WCMD_change_tty (void) {
197 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
201 /****************************************************************************
206 void WCMD_choice (const WCHAR
* command
) {
208 static const WCHAR bellW
[] = {7,0};
209 static const WCHAR commaW
[] = {',',0};
210 static const WCHAR bracket_open
[] = {'[',0};
211 static const WCHAR bracket_close
[] = {']','?',0};
216 WCHAR
*my_command
= NULL
;
217 WCHAR opt_default
= 0;
218 DWORD opt_timeout
= 0;
225 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
228 my_command
= WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR
*) command
));
232 ptr
= WCMD_skip_leading_spaces(my_command
);
233 while (*ptr
== '/') {
234 switch (toupperW(ptr
[1])) {
237 /* the colon is optional */
241 if (!*ptr
|| isspaceW(*ptr
)) {
242 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
243 HeapFree(GetProcessHeap(), 0, my_command
);
247 /* remember the allowed keys (overwrite previous /C option) */
249 while (*ptr
&& (!isspaceW(*ptr
)))
253 /* terminate allowed chars */
255 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
257 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
262 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
267 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
272 /* the colon is optional */
276 opt_default
= *ptr
++;
278 if (!opt_default
|| (*ptr
!= ',')) {
279 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
280 HeapFree(GetProcessHeap(), 0, my_command
);
286 while (((answer
[count
] = *ptr
)) && isdigitW(*ptr
) && (count
< 15)) {
292 opt_timeout
= atoiW(answer
);
294 ptr
= WCMD_skip_leading_spaces(ptr
);
298 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
299 HeapFree(GetProcessHeap(), 0, my_command
);
305 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
308 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
310 /* use default keys, when needed: localized versions of "Y"es and "No" */
312 LoadStringW(hinst
, WCMD_YES
, buffer
, sizeof(buffer
)/sizeof(WCHAR
));
313 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, sizeof(buffer
)/sizeof(WCHAR
) - 1);
318 /* print the question, when needed */
320 WCMD_output_asis(ptr
);
324 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
328 /* print a list of all allowed answers inside brackets */
329 WCMD_output_asis(bracket_open
);
332 while ((answer
[0] = *ptr
++)) {
333 WCMD_output_asis(answer
);
335 WCMD_output_asis(commaW
);
337 WCMD_output_asis(bracket_close
);
342 /* FIXME: Add support for option /T */
343 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
);
346 answer
[0] = toupperW(answer
[0]);
348 ptr
= strchrW(opt_c
, answer
[0]);
350 WCMD_output_asis(answer
);
351 WCMD_output_asis(newline
);
353 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
355 errorlevel
= (ptr
- opt_c
) + 1;
356 WINE_TRACE("answer: %d\n", errorlevel
);
357 HeapFree(GetProcessHeap(), 0, my_command
);
362 /* key not allowed: play the bell */
363 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
364 WCMD_output_asis(bellW
);
369 /****************************************************************************
372 * Copy a file or wildcarded set.
373 * FIXME: Add support for a+b+c type syntax
376 void WCMD_copy (void) {
381 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[4];
383 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
384 BOOL copyToDir
= FALSE
;
385 WCHAR srcspec
[MAX_PATH
];
389 WCHAR fname
[MAX_PATH
];
392 if (param1
[0] == 0x00) {
393 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG
));
397 /* Convert source into full spec */
398 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1
));
399 GetFullPathNameW(param1
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, NULL
);
400 if (srcpath
[strlenW(srcpath
) - 1] == '\\')
401 srcpath
[strlenW(srcpath
) - 1] = '\0';
403 if ((strchrW(srcpath
,'*') == NULL
) && (strchrW(srcpath
,'?') == NULL
)) {
404 attribs
= GetFileAttributesW(srcpath
);
408 strcpyW(srcspec
, srcpath
);
410 /* If a directory, then add \* on the end when searching */
411 if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
412 strcatW(srcpath
, slashW
);
413 strcatW(srcspec
, slashW
);
414 strcatW(srcspec
, starW
);
416 WCMD_splitpath(srcpath
, drive
, dir
, fname
, ext
);
417 strcpyW(srcpath
, drive
);
418 strcatW(srcpath
, dir
);
421 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath
));
423 /* If no destination supplied, assume current directory */
424 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2
));
425 if (param2
[0] == 0x00) {
426 strcpyW(param2
, dotW
);
429 GetFullPathNameW(param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
430 if (outpath
[strlenW(outpath
) - 1] == '\\')
431 outpath
[strlenW(outpath
) - 1] = '\0';
432 attribs
= GetFileAttributesW(outpath
);
433 if (attribs
!= INVALID_FILE_ATTRIBUTES
&& (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
434 strcatW (outpath
, slashW
);
437 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
438 wine_dbgstr_w(outpath
), copyToDir
);
440 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
441 if (strstrW (quals
, parmNoY
))
443 else if (strstrW (quals
, parmY
))
446 /* By default, we will force the overwrite in batch mode and ask for
447 * confirmation in interactive mode. */
450 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
451 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
452 * default behavior. */
453 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
454 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
455 if (!lstrcmpiW (copycmd
, parmY
))
457 else if (!lstrcmpiW (copycmd
, parmNoY
))
462 /* Loop through all source files */
463 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec
));
464 hff
= FindFirstFileW(srcspec
, &fd
);
465 if (hff
!= INVALID_HANDLE_VALUE
) {
467 WCHAR outname
[MAX_PATH
];
468 WCHAR srcname
[MAX_PATH
];
469 BOOL overwrite
= force
;
471 /* Destination is either supplied filename, or source name in
472 supplied destination directory */
473 strcpyW(outname
, outpath
);
474 if (copyToDir
) strcatW(outname
, fd
.cFileName
);
475 strcpyW(srcname
, srcpath
);
476 strcatW(srcname
, fd
.cFileName
);
478 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname
));
479 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
481 /* Skip . and .., and directories */
482 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
484 WINE_TRACE("Skipping directories\n");
487 /* Prompt before overwriting */
488 else if (!overwrite
) {
489 attribs
= GetFileAttributesW(outname
);
490 if (attribs
!= INVALID_FILE_ATTRIBUTES
) {
492 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
493 overwrite
= WCMD_ask_confirm(question
, FALSE
, NULL
);
496 else overwrite
= TRUE
;
499 /* Do the copy as appropriate */
501 status
= CopyFileW(srcname
, outname
, FALSE
);
502 if (!status
) WCMD_print_error ();
505 } while (FindNextFileW(hff
, &fd
) != 0);
512 /****************************************************************************
515 * Create a directory (and, if needed, any intermediate directories).
517 * Modifies its argument by replacing slashes temporarily with nulls.
520 static BOOL
create_full_path(WCHAR
* path
)
524 /* don't mess with drive letter portion of path, if any */
529 /* Strip trailing slashes. */
530 for (p
= path
+ strlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
533 /* Step through path, creating intermediate directories as needed. */
534 /* First component includes drive letter, if any. */
538 /* Skip to end of component */
539 while (*p
== '\\') p
++;
540 while (*p
&& *p
!= '\\') p
++;
542 /* path is now the original full path */
543 return CreateDirectoryW(path
, NULL
);
545 /* Truncate path, create intermediate directory, and restore path */
547 rv
= CreateDirectoryW(path
, NULL
);
549 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
556 void WCMD_create_dir (WCHAR
*command
) {
558 WCHAR
*argN
= command
;
560 if (param1
[0] == 0x00) {
561 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
564 /* Loop through all args */
566 WCHAR
*thisArg
= WCMD_parameter(command
, argno
++, &argN
, NULL
);
568 if (!create_full_path(thisArg
)) {
575 /* Parse the /A options given by the user on the commandline
576 * into a bitmask of wanted attributes (*wantSet),
577 * and a bitmask of unwanted attributes (*wantClear).
579 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
580 static const WCHAR parmA
[] = {'/','A','\0'};
583 /* both are strictly 'out' parameters */
587 /* For each /A argument */
588 for (p
=strstrW(quals
, parmA
); p
!= NULL
; p
=strstrW(p
, parmA
)) {
592 /* Skip optional : */
595 /* For each of the attribute specifier chars to this /A option */
596 for (; *p
!= 0 && *p
!= '/'; p
++) {
605 /* Convert the attribute specifier to a bit in one of the masks */
607 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
608 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
609 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
610 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
612 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
622 /* If filename part of parameter is * or *.*,
623 * and neither /Q nor /P options were given,
624 * prompt the user whether to proceed.
625 * Returns FALSE if user says no, TRUE otherwise.
626 * *pPrompted is set to TRUE if the user is prompted.
627 * (If /P supplied, del will prompt for individual files later.)
629 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
630 static const WCHAR parmP
[] = {'/','P','\0'};
631 static const WCHAR parmQ
[] = {'/','Q','\0'};
633 if ((strstrW(quals
, parmQ
) == NULL
) && (strstrW(quals
, parmP
) == NULL
)) {
634 static const WCHAR anyExt
[]= {'.','*','\0'};
637 WCHAR fname
[MAX_PATH
];
639 WCHAR fpath
[MAX_PATH
];
641 /* Convert path into actual directory spec */
642 GetFullPathNameW(filename
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
643 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
645 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
646 if ((strcmpW(fname
, starW
) == 0) &&
647 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
649 WCHAR question
[MAXSTRING
];
650 static const WCHAR fmt
[] = {'%','s',' ','\0'};
652 /* Caller uses this to suppress "file not found" warning later */
655 /* Ask for confirmation */
656 wsprintfW(question
, fmt
, fpath
);
657 return WCMD_ask_confirm(question
, TRUE
, NULL
);
660 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
664 /* Helper function for WCMD_delete().
665 * Deletes a single file, directory, or wildcard.
666 * If /S was given, does it recursively.
667 * Returns TRUE if a file was deleted.
669 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
671 static const WCHAR parmP
[] = {'/','P','\0'};
672 static const WCHAR parmS
[] = {'/','S','\0'};
673 static const WCHAR parmF
[] = {'/','F','\0'};
675 DWORD unwanted_attrs
;
677 WCHAR argCopy
[MAX_PATH
];
680 WCHAR fpath
[MAX_PATH
];
682 BOOL handleParm
= TRUE
;
684 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
686 strcpyW(argCopy
, thisArg
);
687 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
688 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
690 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
691 /* Skip this arg if user declines to delete *.* */
695 /* First, try to delete in the current directory */
696 hff
= FindFirstFileW(argCopy
, &fd
);
697 if (hff
== INVALID_HANDLE_VALUE
) {
703 /* Support del <dirname> by just deleting all files dirname\* */
705 && (strchrW(argCopy
,'*') == NULL
)
706 && (strchrW(argCopy
,'?') == NULL
)
707 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
709 WCHAR modifiedParm
[MAX_PATH
];
710 static const WCHAR slashStar
[] = {'\\','*','\0'};
712 strcpyW(modifiedParm
, argCopy
);
713 strcatW(modifiedParm
, slashStar
);
716 WCMD_delete_one(modifiedParm
);
718 } else if (handleParm
) {
720 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
721 strcpyW (fpath
, argCopy
);
723 p
= strrchrW (fpath
, '\\');
726 strcatW (fpath
, fd
.cFileName
);
728 else strcpyW (fpath
, fd
.cFileName
);
729 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
732 /* Handle attribute matching (/A) */
733 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
734 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
736 /* /P means prompt for each file */
737 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
740 /* Ask for confirmation */
741 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
742 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
746 /* Only proceed if ok to */
749 /* If file is read only, and /A:r or /F supplied, delete it */
750 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
751 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
752 strstrW (quals
, parmF
) != NULL
)) {
753 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
756 /* Now do the delete */
757 if (!DeleteFileW(fpath
)) WCMD_print_error ();
761 } while (FindNextFileW(hff
, &fd
) != 0);
765 /* Now recurse into all subdirectories handling the parameter in the same way */
766 if (strstrW (quals
, parmS
) != NULL
) {
768 WCHAR thisDir
[MAX_PATH
];
773 WCHAR fname
[MAX_PATH
];
776 /* Convert path into actual directory spec */
777 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
778 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
780 strcpyW(thisDir
, drive
);
781 strcatW(thisDir
, dir
);
782 cPos
= strlenW(thisDir
);
784 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
786 /* Append '*' to the directory */
788 thisDir
[cPos
+1] = 0x00;
790 hff
= FindFirstFileW(thisDir
, &fd
);
792 /* Remove residual '*' */
793 thisDir
[cPos
] = 0x00;
795 if (hff
!= INVALID_HANDLE_VALUE
) {
796 DIRECTORY_STACK
*allDirs
= NULL
;
797 DIRECTORY_STACK
*lastEntry
= NULL
;
800 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
801 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
802 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
804 DIRECTORY_STACK
*nextDir
;
805 WCHAR subParm
[MAX_PATH
];
807 /* Work out search parameter in sub dir */
808 strcpyW (subParm
, thisDir
);
809 strcatW (subParm
, fd
.cFileName
);
810 strcatW (subParm
, slashW
);
811 strcatW (subParm
, fname
);
812 strcatW (subParm
, ext
);
813 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
815 /* Allocate memory, add to list */
816 nextDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
817 if (allDirs
== NULL
) allDirs
= nextDir
;
818 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
820 nextDir
->next
= NULL
;
821 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
822 (strlenW(subParm
)+1) * sizeof(WCHAR
));
823 strcpyW(nextDir
->dirName
, subParm
);
825 } while (FindNextFileW(hff
, &fd
) != 0);
828 /* Go through each subdir doing the delete */
829 while (allDirs
!= NULL
) {
830 DIRECTORY_STACK
*tempDir
;
832 tempDir
= allDirs
->next
;
833 found
|= WCMD_delete_one (allDirs
->dirName
);
835 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
836 HeapFree(GetProcessHeap(),0,allDirs
);
845 /****************************************************************************
848 * Delete a file or wildcarded set.
851 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
852 * - Each set is a pattern, eg /ahr /as-r means
853 * readonly+hidden OR nonreadonly system files
854 * - The '-' applies to a single field, ie /a:-hr means read only
858 BOOL
WCMD_delete (WCHAR
*command
) {
861 BOOL argsProcessed
= FALSE
;
862 BOOL foundAny
= FALSE
;
866 for (argno
=0; ; argno
++) {
871 thisArg
= WCMD_parameter (command
, argno
, &argN
, NULL
);
873 break; /* no more parameters */
875 continue; /* skip options */
877 argsProcessed
= TRUE
;
878 found
= WCMD_delete_one(thisArg
);
881 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
886 /* Handle no valid args */
888 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
896 * Returns a trimmed version of s with all leading and trailing whitespace removed
900 static WCHAR
*WCMD_strtrim(const WCHAR
*s
)
902 DWORD len
= strlenW(s
);
903 const WCHAR
*start
= s
;
906 if (!(result
= HeapAlloc(GetProcessHeap(), 0, (len
+ 1) * sizeof(WCHAR
))))
909 while (isspaceW(*start
)) start
++;
911 const WCHAR
*end
= s
+ len
- 1;
912 while (end
> start
&& isspaceW(*end
)) end
--;
913 memcpy(result
, start
, (end
- start
+ 2) * sizeof(WCHAR
));
914 result
[end
- start
+ 1] = '\0';
922 /****************************************************************************
925 * Echo input to the screen (or not). We don't try to emulate the bugs
926 * in DOS (try typing "ECHO ON AGAIN" for an example).
929 void WCMD_echo (const WCHAR
*command
)
932 const WCHAR
*origcommand
= command
;
935 if ( command
[0]==' ' || command
[0]=='\t' || command
[0]=='.'
936 || command
[0]==':' || command
[0]==';')
939 trimmed
= WCMD_strtrim(command
);
940 if (!trimmed
) return;
942 count
= strlenW(trimmed
);
943 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
944 && origcommand
[0]!=';') {
945 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
946 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
950 if (lstrcmpiW(trimmed
, onW
) == 0)
952 else if (lstrcmpiW(trimmed
, offW
) == 0)
955 WCMD_output_asis (command
);
956 WCMD_output_asis (newline
);
958 HeapFree(GetProcessHeap(), 0, trimmed
);
961 /*****************************************************************************
964 * Execute a command, and any && or bracketed follow on to the command. The
965 * first command to be executed may not be at the front of the
966 * commands->thiscommand string (eg. it may point after a DO or ELSE)
968 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
969 const WCHAR
*variable
, const WCHAR
*value
,
970 BOOL isIF
, BOOL conditionTRUE
)
972 CMD_LIST
*curPosition
= *cmdList
;
973 int myDepth
= (*cmdList
)->bracketDepth
;
975 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
976 cmdList
, wine_dbgstr_w(firstcmd
),
977 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
980 /* Skip leading whitespace between condition and the command */
981 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
983 /* Process the first command, if there is one */
984 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
985 WCHAR
*command
= WCMD_strdupW(firstcmd
);
986 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
987 HeapFree(GetProcessHeap(), 0, command
);
991 /* If it didn't move the position, step to next command */
992 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
994 /* Process any other parts of the command */
996 BOOL processThese
= TRUE
;
998 if (isIF
) processThese
= conditionTRUE
;
1001 static const WCHAR ifElse
[] = {'e','l','s','e'};
1003 /* execute all appropriate commands */
1004 curPosition
= *cmdList
;
1006 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1008 (*cmdList
)->prevDelim
,
1009 (*cmdList
)->bracketDepth
, myDepth
);
1011 /* Execute any statements appended to the line */
1012 /* FIXME: Only if previous call worked for && or failed for || */
1013 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1014 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1015 if (processThese
&& (*cmdList
)->command
) {
1016 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
, variable
,
1019 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1021 /* Execute any appended to the statement with (...) */
1022 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1024 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
1025 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1027 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1029 /* End of the command - does 'ELSE ' follow as the next command? */
1032 && WCMD_keyword_ws_found(ifElse
, sizeof(ifElse
)/sizeof(ifElse
[0]),
1033 (*cmdList
)->command
)) {
1035 /* Swap between if and else processing */
1036 processThese
= !processThese
;
1038 /* Process the ELSE part */
1040 const int keyw_len
= sizeof(ifElse
)/sizeof(ifElse
[0]) + 1;
1041 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1043 /* Skip leading whitespace between condition and the command */
1044 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1046 WCMD_execute (cmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1049 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1051 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1060 /**************************************************************************
1063 * Batch file loop processing.
1065 * On entry: cmdList contains the syntax up to the set
1066 * next cmdList and all in that bracket contain the set data
1067 * next cmdlist contains the DO cmd
1068 * following that is either brackets or && entries (as per if)
1072 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
1074 WIN32_FIND_DATAW fd
;
1077 static const WCHAR inW
[] = {'i','n'};
1078 static const WCHAR doW
[] = {'d','o'};
1079 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
1085 BOOL expandDirs
= FALSE
;
1086 BOOL useNumbers
= FALSE
;
1087 BOOL doFileset
= FALSE
;
1088 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
1090 CMD_LIST
*thisCmdStart
;
1093 /* Handle optional qualifiers (multiple are allowed) */
1094 while (*curPos
&& *curPos
== '/') {
1095 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos
));
1097 switch (toupperW(*curPos
)) {
1098 case 'D': curPos
++; expandDirs
= TRUE
; break;
1099 case 'L': curPos
++; useNumbers
= TRUE
; break;
1101 /* Recursive is special case - /R can have an optional path following it */
1102 /* filenamesets are another special case - /F can have an optional options following it */
1106 BOOL isRecursive
= (*curPos
== 'R');
1111 /* Skip whitespace */
1113 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
1115 /* Next parm is either qualifier, path/options or variable -
1116 only care about it if it is the path/options */
1117 if (*curPos
&& *curPos
!= '/' && *curPos
!= '%') {
1118 if (isRecursive
) WINE_FIXME("/R needs to handle supplied root\n");
1120 static unsigned int once
;
1121 if (!once
++) WINE_FIXME("/F needs to handle options\n");
1127 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos
);
1131 /* Skip whitespace between qualifiers */
1132 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
1135 /* Skip whitespace before variable */
1136 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
1138 /* Ensure line continues with variable */
1139 if (!*curPos
|| *curPos
!= '%') {
1140 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1144 /* Variable should follow */
1146 while (curPos
[i
] && curPos
[i
]!=' ' && curPos
[i
]!='\t') i
++;
1147 memcpy(&variable
[0], curPos
, i
*sizeof(WCHAR
));
1149 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
1150 curPos
= &curPos
[i
];
1152 /* Skip whitespace before IN */
1153 while (*curPos
&& (*curPos
==' ' || *curPos
=='\t')) curPos
++;
1155 /* Ensure line continues with IN */
1157 || !WCMD_keyword_ws_found(inW
, sizeof(inW
)/sizeof(inW
[0]), curPos
)) {
1159 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1163 /* Save away where the set of data starts and the variable */
1164 thisDepth
= (*cmdList
)->bracketDepth
;
1165 *cmdList
= (*cmdList
)->nextcommand
;
1166 setStart
= (*cmdList
);
1168 /* Skip until the close bracket */
1169 WINE_TRACE("Searching %p as the set\n", *cmdList
);
1171 (*cmdList
)->command
!= NULL
&&
1172 (*cmdList
)->bracketDepth
> thisDepth
) {
1173 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
1174 *cmdList
= (*cmdList
)->nextcommand
;
1177 /* Skip the close bracket, if there is one */
1178 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1180 /* Syntax error if missing close bracket, or nothing following it
1181 and once we have the complete set, we expect a DO */
1182 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
1183 if ((*cmdList
== NULL
)
1184 || !WCMD_keyword_ws_found(doW
, sizeof(doW
)/sizeof(doW
[0]), (*cmdList
)->command
)) {
1186 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1190 /* Save away the starting position for the commands (and offset for the
1192 cmdStart
= *cmdList
;
1194 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
1198 /* Loop through all set entries */
1200 thisSet
->command
!= NULL
&&
1201 thisSet
->bracketDepth
>= thisDepth
) {
1203 /* Loop through all entries on the same line */
1207 WINE_TRACE("Processing for set %p\n", thisSet
);
1209 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, NULL
))) {
1212 * If the parameter within the set has a wildcard then search for matching files
1213 * otherwise do a literal substitution.
1215 static const WCHAR wildcards
[] = {'*','?','\0'};
1216 thisCmdStart
= cmdStart
;
1219 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
1221 if (!useNumbers
&& !doFileset
) {
1222 if (strpbrkW (item
, wildcards
)) {
1223 hff
= FindFirstFileW(item
, &fd
);
1224 if (hff
!= INVALID_HANDLE_VALUE
) {
1226 BOOL isDirectory
= FALSE
;
1228 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
1230 /* Handle as files or dirs appropriately, but ignore . and .. */
1231 if (isDirectory
== expandDirs
&&
1232 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1233 (strcmpW(fd
.cFileName
, dotW
) != 0))
1235 thisCmdStart
= cmdStart
;
1236 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
1237 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
1238 fd
.cFileName
, FALSE
, TRUE
);
1241 } while (FindNextFileW(hff
, &fd
) != 0);
1245 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
1248 } else if (useNumbers
) {
1249 /* Convert the first 3 numbers to signed longs and save */
1250 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
1251 /* else ignore them! */
1253 /* Filesets - either a list of files, or a command to run and parse the output */
1254 } else if (doFileset
&& *itemStart
!= '"') {
1257 WCHAR temp_file
[MAX_PATH
];
1259 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
1260 wine_dbgstr_w(item
));
1262 /* If backquote or single quote, we need to launch that command
1263 and parse the results - use a temporary file */
1264 if (*itemStart
== '`' || *itemStart
== '\'') {
1266 WCHAR temp_path
[MAX_PATH
], temp_cmd
[MAXSTRING
];
1267 static const WCHAR redirOut
[] = {'>','%','s','\0'};
1268 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1270 /* Remove trailing character */
1271 itemStart
[strlenW(itemStart
)-1] = 0x00;
1273 /* Get temp filename */
1274 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1275 GetTempFileNameW(temp_path
, cmdW
, 0, temp_file
);
1277 /* Execute program and redirect output */
1278 wsprintfW(temp_cmd
, redirOut
, (itemStart
+1), temp_file
);
1279 WCMD_execute (itemStart
, temp_cmd
, NULL
, NULL
, NULL
);
1281 /* Open the file, read line by line and process */
1282 input
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
1283 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1286 /* Open the file, read line by line and process */
1287 input
= CreateFileW(item
, GENERIC_READ
, FILE_SHARE_READ
,
1288 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1291 /* Process the input file */
1292 if (input
== INVALID_HANDLE_VALUE
) {
1293 WCMD_print_error ();
1294 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), item
);
1296 return; /* FOR loop aborts at first failure here */
1300 WCHAR buffer
[MAXSTRING
] = {'\0'};
1301 WCHAR
*where
, *parm
;
1303 while (WCMD_fgets(buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
1305 /* Skip blank lines*/
1306 parm
= WCMD_parameter (buffer
, 0, &where
, NULL
);
1307 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1308 wine_dbgstr_w(buffer
));
1311 /* FIXME: The following should be moved into its own routine and
1312 reused for the string literal parsing below */
1313 thisCmdStart
= cmdStart
;
1314 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1315 cmdEnd
= thisCmdStart
;
1321 CloseHandle (input
);
1324 /* Delete the temporary file */
1325 if (*itemStart
== '`' || *itemStart
== '\'') {
1326 DeleteFileW(temp_file
);
1329 /* Filesets - A string literal */
1330 } else if (doFileset
&& *itemStart
== '"') {
1331 WCHAR buffer
[MAXSTRING
] = {'\0'};
1332 WCHAR
*where
, *parm
;
1334 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1335 strcpyW(buffer
, item
);
1336 parm
= WCMD_parameter (buffer
, 0, &where
, NULL
);
1337 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1338 wine_dbgstr_w(buffer
));
1341 /* FIXME: The following should be moved into its own routine and
1342 reused for the string literal parsing below */
1343 thisCmdStart
= cmdStart
;
1344 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1345 cmdEnd
= thisCmdStart
;
1349 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
1350 cmdEnd
= thisCmdStart
;
1354 /* Move onto the next set line */
1355 thisSet
= thisSet
->nextcommand
;
1358 /* If /L is provided, now run the for loop */
1361 static const WCHAR fmt
[] = {'%','d','\0'};
1363 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1364 numbers
[0], numbers
[2], numbers
[1]);
1366 (numbers
[1]<0)? i
>numbers
[2] : i
<numbers
[2];
1369 sprintfW(thisNum
, fmt
, i
);
1370 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
1372 thisCmdStart
= cmdStart
;
1373 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, thisNum
, FALSE
, TRUE
);
1374 cmdEnd
= thisCmdStart
;
1378 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1379 all processing, OR it should be pointing to the end of && processing OR
1380 it should be pointing at the NULL end of bracket for the DO. The return
1381 value needs to be the NEXT command to execute, which it either is, or
1382 we need to step over the closing bracket */
1384 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
1387 /**************************************************************************
1390 * Simple on-line help. Help text is stored in the resource file.
1393 void WCMD_give_help (const WCHAR
*command
)
1397 command
= WCMD_skip_leading_spaces((WCHAR
*) command
);
1398 if (strlenW(command
) == 0) {
1399 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
1402 /* Display help message for builtin commands */
1403 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1404 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1405 command
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
1406 WCMD_output_asis (WCMD_LoadMessage(i
));
1410 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1411 for (i
= 0; i
<= (sizeof(externals
)/sizeof(externals
[0])); i
++) {
1412 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1413 command
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
1415 static const WCHAR helpW
[] = {' ', '/','?','\0'};
1416 strcpyW(cmd
, command
);
1417 strcatW(cmd
, helpW
);
1418 WCMD_run_program(cmd
, 0);
1422 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), command
);
1427 /****************************************************************************
1430 * Batch file jump instruction. Not the most efficient algorithm ;-)
1431 * Prints error message if the specified label cannot be found - the file pointer is
1432 * then at EOF, effectively stopping the batch file.
1433 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1436 void WCMD_goto (CMD_LIST
**cmdList
) {
1438 WCHAR string
[MAX_PATH
];
1439 WCHAR current
[MAX_PATH
];
1441 /* Do not process any more parts of a processed multipart or multilines command */
1442 if (cmdList
) *cmdList
= NULL
;
1444 if (context
!= NULL
) {
1445 WCHAR
*paramStart
= param1
, *str
;
1446 static const WCHAR eofW
[] = {':','e','o','f','\0'};
1448 if (param1
[0] == 0x00) {
1449 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1453 /* Handle special :EOF label */
1454 if (lstrcmpiW (eofW
, param1
) == 0) {
1455 context
-> skip_rest
= TRUE
;
1459 /* Support goto :label as well as goto label */
1460 if (*paramStart
== ':') paramStart
++;
1462 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
1463 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
1465 while (isspaceW (*str
)) str
++;
1469 while (((current
[index
] = str
[index
])) && (!isspaceW (current
[index
])))
1472 /* ignore space at the end */
1474 if (lstrcmpiW (current
, paramStart
) == 0) return;
1477 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
1482 /*****************************************************************************
1485 * Push a directory onto the stack
1488 void WCMD_pushd (const WCHAR
*command
)
1490 struct env_stack
*curdir
;
1492 static const WCHAR parmD
[] = {'/','D','\0'};
1494 if (strchrW(command
, '/') != NULL
) {
1495 SetLastError(ERROR_INVALID_PARAMETER
);
1500 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1501 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
1502 if( !curdir
|| !thisdir
) {
1505 WINE_ERR ("out of memory\n");
1509 /* Change directory using CD code with /D parameter */
1510 strcpyW(quals
, parmD
);
1511 GetCurrentDirectoryW (1024, thisdir
);
1513 WCMD_setshow_default(command
);
1519 curdir
-> next
= pushd_directories
;
1520 curdir
-> strings
= thisdir
;
1521 if (pushd_directories
== NULL
) {
1522 curdir
-> u
.stackdepth
= 1;
1524 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1526 pushd_directories
= curdir
;
1531 /*****************************************************************************
1534 * Pop a directory from the stack
1537 void WCMD_popd (void) {
1538 struct env_stack
*temp
= pushd_directories
;
1540 if (!pushd_directories
)
1543 /* pop the old environment from the stack, and make it the current dir */
1544 pushd_directories
= temp
->next
;
1545 SetCurrentDirectoryW(temp
->strings
);
1546 LocalFree (temp
->strings
);
1550 /****************************************************************************
1553 * Batch file conditional.
1555 * On entry, cmdlist will point to command containing the IF, and optionally
1556 * the first command to execute (if brackets not found)
1557 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1558 * If ('s were found, execute all within that bracket
1559 * Command may optionally be followed by an ELSE - need to skip instructions
1560 * in the else using the same logic
1562 * FIXME: Much more syntax checking needed!
1565 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
1567 int negate
; /* Negate condition */
1568 int test
; /* Condition evaluation result */
1569 WCHAR condition
[MAX_PATH
], *command
, *s
;
1570 static const WCHAR notW
[] = {'n','o','t','\0'};
1571 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1572 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
1573 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
1574 static const WCHAR eqeqW
[] = {'=','=','\0'};
1575 static const WCHAR parmI
[] = {'/','I','\0'};
1576 int caseInsensitive
= (strstrW(quals
, parmI
) != NULL
);
1578 negate
= !lstrcmpiW(param1
,notW
);
1579 strcpyW(condition
, (negate
? param2
: param1
));
1580 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
1582 if (!lstrcmpiW (condition
, errlvlW
)) {
1583 WCHAR
*param
= WCMD_parameter(p
, 1+negate
, NULL
, NULL
);
1585 long int param_int
= strtolW(param
, &endptr
, 10);
1587 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1590 test
= ((long int)errorlevel
>= param_int
);
1591 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1593 else if (!lstrcmpiW (condition
, existW
)) {
1594 test
= (GetFileAttributesW(WCMD_parameter(p
, 1+negate
, NULL
, NULL
)) != INVALID_FILE_ATTRIBUTES
);
1595 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1597 else if (!lstrcmpiW (condition
, defdW
)) {
1598 test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+negate
, NULL
, NULL
), NULL
, 0) > 0);
1599 WCMD_parameter(p
, 2+negate
, &command
, NULL
);
1601 else if ((s
= strstrW (p
, eqeqW
))) {
1602 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1603 WCHAR
*leftPart
, *leftPartEnd
, *rightPart
, *rightPartEnd
;
1605 WCMD_parameter(p
, 0+negate
+caseInsensitive
, &leftPart
, &leftPartEnd
);
1606 WCMD_parameter(p
, 1+negate
+caseInsensitive
, &rightPart
, &rightPartEnd
);
1607 test
= caseInsensitive
1608 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT
, NORM_IGNORECASE
,
1609 leftPart
, leftPartEnd
-leftPart
+1,
1610 rightPart
, rightPartEnd
-rightPart
+1) == CSTR_EQUAL
)
1611 : (CompareStringW(LOCALE_SYSTEM_DEFAULT
, 0,
1612 leftPart
, leftPartEnd
-leftPart
+1,
1613 rightPart
, rightPartEnd
-rightPart
+1) == CSTR_EQUAL
);
1614 WCMD_parameter(s
, 1, &command
, NULL
);
1617 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1621 /* Process rest of IF statement which is on the same line
1622 Note: This may process all or some of the cmdList (eg a GOTO) */
1623 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1626 /****************************************************************************
1629 * Move a file, directory tree or wildcarded set of files.
1632 void WCMD_move (void)
1635 WIN32_FIND_DATAW fd
;
1637 WCHAR input
[MAX_PATH
];
1638 WCHAR output
[MAX_PATH
];
1640 WCHAR dir
[MAX_PATH
];
1641 WCHAR fname
[MAX_PATH
];
1642 WCHAR ext
[MAX_PATH
];
1644 if (param1
[0] == 0x00) {
1645 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1649 /* If no destination supplied, assume current directory */
1650 if (param2
[0] == 0x00) {
1651 strcpyW(param2
, dotW
);
1654 /* If 2nd parm is directory, then use original filename */
1655 /* Convert partial path to full path */
1656 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1657 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1658 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1659 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1661 /* Split into components */
1662 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1664 hff
= FindFirstFileW(input
, &fd
);
1665 if (hff
== INVALID_HANDLE_VALUE
)
1669 WCHAR dest
[MAX_PATH
];
1670 WCHAR src
[MAX_PATH
];
1674 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1676 /* Build src & dest name */
1677 strcpyW(src
, drive
);
1680 /* See if dest is an existing directory */
1681 attribs
= GetFileAttributesW(output
);
1682 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1683 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1684 strcpyW(dest
, output
);
1685 strcatW(dest
, slashW
);
1686 strcatW(dest
, fd
.cFileName
);
1688 strcpyW(dest
, output
);
1691 strcatW(src
, fd
.cFileName
);
1693 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1694 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1696 /* If destination exists, prompt unless /Y supplied */
1697 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
1699 WCHAR copycmd
[MAXSTRING
];
1702 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1703 if (strstrW (quals
, parmNoY
))
1705 else if (strstrW (quals
, parmY
))
1708 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1709 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1710 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1711 && ! lstrcmpiW (copycmd
, parmY
));
1714 /* Prompt if overwriting */
1718 /* Ask for confirmation */
1719 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1720 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1721 LocalFree(question
);
1723 /* So delete the destination prior to the move */
1725 if (!DeleteFileW(dest
)) {
1726 WCMD_print_error ();
1735 status
= MoveFileW(src
, dest
);
1737 status
= 1; /* Anything other than 0 to prevent error msg below */
1741 WCMD_print_error ();
1744 } while (FindNextFileW(hff
, &fd
) != 0);
1749 /****************************************************************************
1752 * Suspend execution of a batch script until a key is typed
1755 void WCMD_pause (void)
1761 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
1763 have_console
= GetConsoleMode(hIn
, &oldmode
);
1765 SetConsoleMode(hIn
, 0);
1767 WCMD_output_asis(anykey
);
1768 WCMD_ReadFile(hIn
, &key
, 1, &count
);
1770 SetConsoleMode(hIn
, oldmode
);
1773 /****************************************************************************
1776 * Delete a directory.
1779 void WCMD_remove_dir (WCHAR
*command
) {
1782 int argsProcessed
= 0;
1783 WCHAR
*argN
= command
;
1784 static const WCHAR parmS
[] = {'/','S','\0'};
1785 static const WCHAR parmQ
[] = {'/','Q','\0'};
1787 /* Loop through all args */
1789 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
1790 if (argN
&& argN
[0] != '/') {
1791 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1792 wine_dbgstr_w(quals
));
1795 /* If subdirectory search not supplied, just try to remove
1796 and report error if it fails (eg if it contains a file) */
1797 if (strstrW (quals
, parmS
) == NULL
) {
1798 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
1800 /* Otherwise use ShFileOp to recursively remove a directory */
1803 SHFILEOPSTRUCTW lpDir
;
1806 if (strstrW (quals
, parmQ
) == NULL
) {
1808 WCHAR question
[MAXSTRING
];
1809 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1811 /* Ask for confirmation */
1812 wsprintfW(question
, fmt
, thisArg
);
1813 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1815 /* Abort if answer is 'N' */
1822 lpDir
.pFrom
= thisArg
;
1823 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1824 lpDir
.wFunc
= FO_DELETE
;
1825 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
1830 /* Handle no valid args */
1831 if (argsProcessed
== 0) {
1832 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1838 /****************************************************************************
1844 void WCMD_rename (void)
1848 WIN32_FIND_DATAW fd
;
1849 WCHAR input
[MAX_PATH
];
1850 WCHAR
*dotDst
= NULL
;
1852 WCHAR dir
[MAX_PATH
];
1853 WCHAR fname
[MAX_PATH
];
1854 WCHAR ext
[MAX_PATH
];
1858 /* Must be at least two args */
1859 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1860 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1865 /* Destination cannot contain a drive letter or directory separator */
1866 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1867 SetLastError(ERROR_INVALID_PARAMETER
);
1873 /* Convert partial path to full path */
1874 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1875 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1876 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1877 dotDst
= strchrW(param2
, '.');
1879 /* Split into components */
1880 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1882 hff
= FindFirstFileW(input
, &fd
);
1883 if (hff
== INVALID_HANDLE_VALUE
)
1887 WCHAR dest
[MAX_PATH
];
1888 WCHAR src
[MAX_PATH
];
1889 WCHAR
*dotSrc
= NULL
;
1892 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1894 /* FIXME: If dest name or extension is *, replace with filename/ext
1895 part otherwise use supplied name. This supports:
1897 ren jim.* fred.* etc
1898 However, windows has a more complex algorithm supporting eg
1899 ?'s and *'s mid name */
1900 dotSrc
= strchrW(fd
.cFileName
, '.');
1902 /* Build src & dest name */
1903 strcpyW(src
, drive
);
1906 dirLen
= strlenW(src
);
1907 strcatW(src
, fd
.cFileName
);
1910 if (param2
[0] == '*') {
1911 strcatW(dest
, fd
.cFileName
);
1912 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1914 strcatW(dest
, param2
);
1915 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1918 /* Build Extension */
1919 if (dotDst
&& (*(dotDst
+1)=='*')) {
1920 if (dotSrc
) strcatW(dest
, dotSrc
);
1921 } else if (dotDst
) {
1922 if (dotDst
) strcatW(dest
, dotDst
);
1925 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1926 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1928 status
= MoveFileW(src
, dest
);
1931 WCMD_print_error ();
1934 } while (FindNextFileW(hff
, &fd
) != 0);
1939 /*****************************************************************************
1942 * Make a copy of the environment.
1944 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1954 len
+= (strlenW(&env
[len
]) + 1);
1956 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1959 WINE_ERR("out of memory\n");
1962 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1968 /*****************************************************************************
1971 * setlocal pushes the environment onto a stack
1972 * Save the environment as unicode so we don't screw anything up.
1974 void WCMD_setlocal (const WCHAR
*s
) {
1976 struct env_stack
*env_copy
;
1977 WCHAR cwd
[MAX_PATH
];
1979 /* DISABLEEXTENSIONS ignored */
1981 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1984 WINE_ERR ("out of memory\n");
1988 env
= GetEnvironmentStringsW ();
1990 env_copy
->strings
= WCMD_dupenv (env
);
1991 if (env_copy
->strings
)
1993 env_copy
->next
= saved_environment
;
1994 saved_environment
= env_copy
;
1996 /* Save the current drive letter */
1997 GetCurrentDirectoryW(MAX_PATH
, cwd
);
1998 env_copy
->u
.cwd
= cwd
[0];
2001 LocalFree (env_copy
);
2003 FreeEnvironmentStringsW (env
);
2007 /*****************************************************************************
2010 * endlocal pops the environment off a stack
2011 * Note: When searching for '=', search from WCHAR position 1, to handle
2012 * special internal environment variables =C:, =D: etc
2014 void WCMD_endlocal (void) {
2015 WCHAR
*env
, *old
, *p
;
2016 struct env_stack
*temp
;
2019 if (!saved_environment
)
2022 /* pop the old environment from the stack */
2023 temp
= saved_environment
;
2024 saved_environment
= temp
->next
;
2026 /* delete the current environment, totally */
2027 env
= GetEnvironmentStringsW ();
2028 old
= WCMD_dupenv (GetEnvironmentStringsW ());
2031 n
= strlenW(&old
[len
]) + 1;
2032 p
= strchrW(&old
[len
] + 1, '=');
2036 SetEnvironmentVariableW (&old
[len
], NULL
);
2041 FreeEnvironmentStringsW (env
);
2043 /* restore old environment */
2044 env
= temp
->strings
;
2047 n
= strlenW(&env
[len
]) + 1;
2048 p
= strchrW(&env
[len
] + 1, '=');
2052 SetEnvironmentVariableW (&env
[len
], p
);
2057 /* Restore current drive letter */
2058 if (IsCharAlphaW(temp
->u
.cwd
)) {
2060 WCHAR cwd
[MAX_PATH
];
2061 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
2063 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
2064 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
2065 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
2066 SetCurrentDirectoryW(cwd
);
2074 /*****************************************************************************
2075 * WCMD_setshow_default
2077 * Set/Show the current default directory
2080 void WCMD_setshow_default (const WCHAR
*command
) {
2086 WIN32_FIND_DATAW fd
;
2088 static const WCHAR parmD
[] = {'/','D','\0'};
2090 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
2092 /* Skip /D and trailing whitespace if on the front of the command line */
2093 if (CompareStringW(LOCALE_USER_DEFAULT
,
2094 NORM_IGNORECASE
| SORT_STRINGSORT
,
2095 command
, 2, parmD
, -1) == CSTR_EQUAL
) {
2097 while (*command
&& (*command
==' ' || *command
=='\t'))
2101 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
2102 if (strlenW(command
) == 0) {
2103 strcatW (cwd
, newline
);
2104 WCMD_output_asis (cwd
);
2107 /* Remove any double quotes, which may be in the
2108 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2111 if (*command
!= '"') *pos
++ = *command
;
2114 while (pos
> command
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
2118 /* Search for appropriate directory */
2119 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
2120 hff
= FindFirstFileW(string
, &fd
);
2121 if (hff
!= INVALID_HANDLE_VALUE
) {
2123 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
2124 WCHAR fpath
[MAX_PATH
];
2126 WCHAR dir
[MAX_PATH
];
2127 WCHAR fname
[MAX_PATH
];
2128 WCHAR ext
[MAX_PATH
];
2129 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
2131 /* Convert path into actual directory spec */
2132 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
2133 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
2136 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
2139 } while (FindNextFileW(hff
, &fd
) != 0);
2143 /* Change to that directory */
2144 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
2146 status
= SetCurrentDirectoryW(string
);
2149 WCMD_print_error ();
2153 /* Save away the actual new directory, to store as current location */
2154 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
2156 /* Restore old directory if drive letter would change, and
2157 CD x:\directory /D (or pushd c:\directory) not supplied */
2158 if ((strstrW(quals
, parmD
) == NULL
) &&
2159 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
2160 SetCurrentDirectoryW(cwd
);
2164 /* Set special =C: type environment variable, for drive letter of
2165 change of directory, even if path was restored due to missing
2166 /D (allows changing drive letter when not resident on that
2168 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
2170 strcpyW(env
, equalW
);
2171 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
2173 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
2174 SetEnvironmentVariableW(env
, string
);
2181 /****************************************************************************
2184 * Set/Show the system date
2185 * FIXME: Can't change date yet
2188 void WCMD_setshow_date (void) {
2190 WCHAR curdate
[64], buffer
[64];
2192 static const WCHAR parmT
[] = {'/','T','\0'};
2194 if (strlenW(param1
) == 0) {
2195 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
2196 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
2197 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
2198 if (strstrW (quals
, parmT
) == NULL
) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
2200 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2202 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2206 else WCMD_print_error ();
2209 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2213 /****************************************************************************
2216 static int WCMD_compare( const void *a
, const void *b
)
2219 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
2220 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2221 *str_a
, -1, *str_b
, -1 );
2222 if( r
== CSTR_LESS_THAN
) return -1;
2223 if( r
== CSTR_GREATER_THAN
) return 1;
2227 /****************************************************************************
2228 * WCMD_setshow_sortenv
2230 * sort variables into order for display
2231 * Optionally only display those who start with a stub
2232 * returns the count displayed
2234 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
2236 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
2239 if (stub
) stublen
= strlenW(stub
);
2241 /* count the number of strings, and the total length */
2243 len
+= (strlenW(&s
[len
]) + 1);
2247 /* add the strings to an array */
2248 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
2252 for( i
=1; i
<count
; i
++ )
2253 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
2255 /* sort the array */
2256 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
2259 for( i
=0; i
<count
; i
++ ) {
2260 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
2261 NORM_IGNORECASE
| SORT_STRINGSORT
,
2262 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
2263 /* Don't display special internal variables */
2264 if (str
[i
][0] != '=') {
2265 WCMD_output_asis(str
[i
]);
2266 WCMD_output_asis(newline
);
2273 return displayedcount
;
2276 /****************************************************************************
2279 * Set/Show the environment variables
2282 void WCMD_setshow_env (WCHAR
*s
) {
2287 static const WCHAR parmP
[] = {'/','P','\0'};
2289 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
2290 env
= GetEnvironmentStringsW();
2291 WCMD_setshow_sortenv( env
, NULL
);
2295 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2296 if (CompareStringW(LOCALE_USER_DEFAULT
,
2297 NORM_IGNORECASE
| SORT_STRINGSORT
,
2298 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
2299 WCHAR string
[MAXSTRING
];
2303 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
2305 WCMD_strip_quotes(s
);
2307 /* If no parameter, or no '=' sign, return an error */
2308 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2309 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2313 /* Output the prompt */
2315 if (strlenW(p
) != 0) WCMD_output_asis(p
);
2317 /* Read the reply */
2318 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
2320 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2321 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2322 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2323 wine_dbgstr_w(string
));
2324 status
= SetEnvironmentVariableW(s
, string
);
2331 WCMD_strip_quotes(s
);
2332 p
= strchrW (s
, '=');
2334 env
= GetEnvironmentStringsW();
2335 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2336 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2343 if (strlenW(p
) == 0) p
= NULL
;
2344 status
= SetEnvironmentVariableW(s
, p
);
2345 gle
= GetLastError();
2346 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2348 } else if ((!status
)) WCMD_print_error();
2352 /****************************************************************************
2355 * Set/Show the path environment variable
2358 void WCMD_setshow_path (const WCHAR
*command
) {
2362 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2363 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2365 if (strlenW(param1
) == 0) {
2366 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2368 WCMD_output_asis ( pathEqW
);
2369 WCMD_output_asis ( string
);
2370 WCMD_output_asis ( newline
);
2373 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
2377 if (*command
== '=') command
++; /* Skip leading '=' */
2378 status
= SetEnvironmentVariableW(pathW
, command
);
2379 if (!status
) WCMD_print_error();
2383 /****************************************************************************
2384 * WCMD_setshow_prompt
2386 * Set or show the command prompt.
2389 void WCMD_setshow_prompt (void) {
2392 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2394 if (strlenW(param1
) == 0) {
2395 SetEnvironmentVariableW(promptW
, NULL
);
2399 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
2400 if (strlenW(s
) == 0) {
2401 SetEnvironmentVariableW(promptW
, NULL
);
2403 else SetEnvironmentVariableW(promptW
, s
);
2407 /****************************************************************************
2410 * Set/Show the system time
2411 * FIXME: Can't change time yet
2414 void WCMD_setshow_time (void) {
2416 WCHAR curtime
[64], buffer
[64];
2419 static const WCHAR parmT
[] = {'/','T','\0'};
2421 if (strlenW(param1
) == 0) {
2423 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2424 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2425 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
2426 if (strstrW (quals
, parmT
) == NULL
) {
2427 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2428 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2430 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2434 else WCMD_print_error ();
2437 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2441 /****************************************************************************
2444 * Shift batch parameters.
2445 * Optional /n says where to start shifting (n=0-8)
2448 void WCMD_shift (const WCHAR
*command
) {
2451 if (context
!= NULL
) {
2452 WCHAR
*pos
= strchrW(command
, '/');
2457 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2458 start
= (*(pos
+1) - '0');
2460 SetLastError(ERROR_INVALID_PARAMETER
);
2465 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2466 for (i
=start
;i
<=8;i
++) {
2467 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2469 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2474 /****************************************************************************
2477 * Set the console title
2479 void WCMD_title (const WCHAR
*command
) {
2480 SetConsoleTitleW(command
);
2483 /****************************************************************************
2486 * Copy a file to standard output.
2489 void WCMD_type (WCHAR
*command
) {
2492 WCHAR
*argN
= command
;
2493 BOOL writeHeaders
= FALSE
;
2495 if (param1
[0] == 0x00) {
2496 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2500 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2502 /* Loop through all args */
2505 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2513 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2514 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2515 FILE_ATTRIBUTE_NORMAL
, NULL
);
2516 if (h
== INVALID_HANDLE_VALUE
) {
2517 WCMD_print_error ();
2518 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2522 static const WCHAR fmt
[] = {'\n','%','1','\n','\n','\0'};
2523 WCMD_output(fmt
, thisArg
);
2525 while (WCMD_ReadFile(h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
)) {
2526 if (count
== 0) break; /* ReadFile reports success on EOF! */
2528 WCMD_output_asis (buffer
);
2535 /****************************************************************************
2538 * Output either a file or stdin to screen in pages
2541 void WCMD_more (WCHAR
*command
) {
2544 WCHAR
*argN
= command
;
2546 WCHAR moreStrPage
[100];
2549 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2550 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2551 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2552 ')',' ','-','-','\n','\0'};
2553 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2555 /* Prefix the NLS more with '-- ', then load the text */
2557 strcpyW(moreStr
, moreStart
);
2558 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
2559 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2561 if (param1
[0] == 0x00) {
2563 /* Wine implements pipes via temporary files, and hence stdin is
2564 effectively reading from the file. This means the prompts for
2565 more are satisfied by the next line from the input (file). To
2566 avoid this, ensure stdin is to the console */
2567 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2568 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2569 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2570 FILE_ATTRIBUTE_NORMAL
, 0);
2571 WINE_TRACE("No parms - working probably in pipe mode\n");
2572 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2574 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2575 once you get in this bit unless due to a pipe, its going to end badly... */
2576 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
2578 WCMD_enter_paged_mode(moreStrPage
);
2579 while (WCMD_ReadFile(hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
2580 if (count
== 0) break; /* ReadFile reports success on EOF! */
2582 WCMD_output_asis (buffer
);
2584 WCMD_leave_paged_mode();
2586 /* Restore stdin to what it was */
2587 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2588 CloseHandle(hConIn
);
2592 BOOL needsPause
= FALSE
;
2594 /* Loop through all args */
2595 WINE_TRACE("Parms supplied - working through each file\n");
2596 WCMD_enter_paged_mode(moreStrPage
);
2599 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2607 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
2608 WCMD_leave_paged_mode();
2609 WCMD_output_asis(moreStrPage
);
2610 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2611 WCMD_enter_paged_mode(moreStrPage
);
2615 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2616 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2617 FILE_ATTRIBUTE_NORMAL
, NULL
);
2618 if (h
== INVALID_HANDLE_VALUE
) {
2619 WCMD_print_error ();
2620 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2624 ULONG64 fileLen
= 0;
2625 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2627 /* Get the file size */
2628 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2629 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2632 while (WCMD_ReadFile(h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
2633 if (count
== 0) break; /* ReadFile reports success on EOF! */
2637 /* Update % count (would be used in WCMD_output_asis as prompt) */
2638 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2640 WCMD_output_asis (buffer
);
2646 WCMD_leave_paged_mode();
2650 /****************************************************************************
2653 * Display verify flag.
2654 * FIXME: We don't actually do anything with the verify flag other than toggle
2658 void WCMD_verify (const WCHAR
*command
) {
2662 count
= strlenW(command
);
2664 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2665 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2668 if (lstrcmpiW(command
, onW
) == 0) {
2672 else if (lstrcmpiW(command
, offW
) == 0) {
2673 verify_mode
= FALSE
;
2676 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
2679 /****************************************************************************
2682 * Display version info.
2685 void WCMD_version (void) {
2687 WCMD_output_asis (version_string
);
2691 /****************************************************************************
2694 * Display volume information (set_label = FALSE)
2695 * Additionally set volume label (set_label = TRUE)
2696 * Returns 1 on success, 0 otherwise
2699 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
2701 DWORD count
, serial
;
2702 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2705 if (strlenW(path
) == 0) {
2706 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2708 WCMD_print_error ();
2711 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2712 &serial
, NULL
, NULL
, NULL
, 0);
2715 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2716 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2717 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2720 wsprintfW (curdir
, fmt
, path
);
2721 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2726 WCMD_print_error ();
2729 if (label
[0] != '\0') {
2730 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL
),
2734 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL
),
2737 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO
),
2738 HIWORD(serial
), LOWORD(serial
));
2740 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2741 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
2743 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2744 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2746 if (strlenW(path
) != 0) {
2747 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
2750 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
2756 /**************************************************************************
2759 * Exit either the process, or just this batch program
2763 void WCMD_exit (CMD_LIST
**cmdList
) {
2765 static const WCHAR parmB
[] = {'/','B','\0'};
2766 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2768 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2770 context
-> skip_rest
= TRUE
;
2778 /*****************************************************************************
2781 * Lists or sets file associations (assoc = TRUE)
2782 * Lists or sets file types (assoc = FALSE)
2784 void WCMD_assoc (const WCHAR
*command
, BOOL assoc
) {
2787 DWORD accessOptions
= KEY_READ
;
2789 LONG rc
= ERROR_SUCCESS
;
2790 WCHAR keyValue
[MAXSTRING
];
2791 DWORD valueLen
= MAXSTRING
;
2793 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2794 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2796 /* See if parameter includes '=' */
2798 newValue
= strchrW(command
, '=');
2799 if (newValue
) accessOptions
|= KEY_WRITE
;
2801 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2802 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
2803 accessOptions
, &key
) != ERROR_SUCCESS
) {
2804 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2808 /* If no parameters then list all associations */
2809 if (*command
== 0x00) {
2812 /* Enumerate all the keys */
2813 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2814 WCHAR keyName
[MAXSTRING
];
2817 /* Find the next value */
2818 nameLen
= MAXSTRING
;
2819 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
2821 if (rc
== ERROR_SUCCESS
) {
2823 /* Only interested in extension ones if assoc, or others
2825 if ((keyName
[0] == '.' && assoc
) ||
2826 (!(keyName
[0] == '.') && (!assoc
)))
2828 WCHAR subkey
[MAXSTRING
];
2829 strcpyW(subkey
, keyName
);
2830 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2832 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2834 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2835 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2836 WCMD_output_asis(keyName
);
2837 WCMD_output_asis(equalW
);
2838 /* If no default value found, leave line empty after '=' */
2839 if (rc
== ERROR_SUCCESS
) {
2840 WCMD_output_asis(keyValue
);
2842 WCMD_output_asis(newline
);
2843 RegCloseKey(readKey
);
2851 /* Parameter supplied - if no '=' on command line, its a query */
2852 if (newValue
== NULL
) {
2854 WCHAR subkey
[MAXSTRING
];
2856 /* Query terminates the parameter at the first space */
2857 strcpyW(keyValue
, command
);
2858 space
= strchrW(keyValue
, ' ');
2859 if (space
) *space
=0x00;
2861 /* Set up key name */
2862 strcpyW(subkey
, keyValue
);
2863 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2865 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2867 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2868 WCMD_output_asis(command
);
2869 WCMD_output_asis(equalW
);
2870 /* If no default value found, leave line empty after '=' */
2871 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2872 WCMD_output_asis(newline
);
2873 RegCloseKey(readKey
);
2876 WCHAR msgbuffer
[MAXSTRING
];
2878 /* Load the translated 'File association not found' */
2880 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2882 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2884 WCMD_output_stderr(msgbuffer
, keyValue
);
2888 /* Not a query - its a set or clear of a value */
2891 WCHAR subkey
[MAXSTRING
];
2893 /* Get pointer to new value */
2897 /* Set up key name */
2898 strcpyW(subkey
, command
);
2899 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2901 /* If nothing after '=' then clear value - only valid for ASSOC */
2902 if (*newValue
== 0x00) {
2904 if (assoc
) rc
= RegDeleteKeyW(key
, command
);
2905 if (assoc
&& rc
== ERROR_SUCCESS
) {
2906 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2908 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2913 WCHAR msgbuffer
[MAXSTRING
];
2915 /* Load the translated 'File association not found' */
2917 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
2918 sizeof(msgbuffer
)/sizeof(WCHAR
));
2920 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
2921 sizeof(msgbuffer
)/sizeof(WCHAR
));
2923 WCMD_output_stderr(msgbuffer
, keyValue
);
2927 /* It really is a set value = contents */
2929 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2930 accessOptions
, NULL
, &readKey
, NULL
);
2931 if (rc
== ERROR_SUCCESS
) {
2932 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
2934 sizeof(WCHAR
) * (strlenW(newValue
) + 1));
2935 RegCloseKey(readKey
);
2938 if (rc
!= ERROR_SUCCESS
) {
2942 WCMD_output_asis(command
);
2943 WCMD_output_asis(equalW
);
2944 WCMD_output_asis(newValue
);
2945 WCMD_output_asis(newline
);
2955 /****************************************************************************
2958 * Colors the terminal screen.
2961 void WCMD_color (void) {
2963 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2964 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2966 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2967 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
2971 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2977 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2982 /* Convert the color hex digits */
2983 if (param1
[0] == 0x00) {
2984 color
= defaultColor
;
2986 color
= strtoulW(param1
, NULL
, 16);
2989 /* Fail if fg == bg color */
2990 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2995 /* Set the current screen contents and ensure all future writes
2996 remain this color */
2997 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2998 SetConsoleTextAttribute(hStdOut
, color
);