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 */
1719 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1721 /* Ask for confirmation */
1722 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1723 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1724 LocalFree(question
);
1726 /* So delete the destination prior to the move */
1728 if (!DeleteFileW(dest
)) {
1729 WCMD_print_error ();
1738 status
= MoveFileW(src
, dest
);
1740 status
= 1; /* Anything other than 0 to prevent error msg below */
1744 WCMD_print_error ();
1747 } while (FindNextFileW(hff
, &fd
) != 0);
1752 /****************************************************************************
1755 * Suspend execution of a batch script until a key is typed
1758 void WCMD_pause (void)
1764 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
1766 have_console
= GetConsoleMode(hIn
, &oldmode
);
1768 SetConsoleMode(hIn
, 0);
1770 WCMD_output_asis(anykey
);
1771 WCMD_ReadFile(hIn
, &key
, 1, &count
);
1773 SetConsoleMode(hIn
, oldmode
);
1776 /****************************************************************************
1779 * Delete a directory.
1782 void WCMD_remove_dir (WCHAR
*command
) {
1785 int argsProcessed
= 0;
1786 WCHAR
*argN
= command
;
1787 static const WCHAR parmS
[] = {'/','S','\0'};
1788 static const WCHAR parmQ
[] = {'/','Q','\0'};
1790 /* Loop through all args */
1792 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
1793 if (argN
&& argN
[0] != '/') {
1794 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1795 wine_dbgstr_w(quals
));
1798 /* If subdirectory search not supplied, just try to remove
1799 and report error if it fails (eg if it contains a file) */
1800 if (strstrW (quals
, parmS
) == NULL
) {
1801 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
1803 /* Otherwise use ShFileOp to recursively remove a directory */
1806 SHFILEOPSTRUCTW lpDir
;
1809 if (strstrW (quals
, parmQ
) == NULL
) {
1811 WCHAR question
[MAXSTRING
];
1812 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1814 /* Ask for confirmation */
1815 wsprintfW(question
, fmt
, thisArg
);
1816 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1818 /* Abort if answer is 'N' */
1825 lpDir
.pFrom
= thisArg
;
1826 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1827 lpDir
.wFunc
= FO_DELETE
;
1828 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
1833 /* Handle no valid args */
1834 if (argsProcessed
== 0) {
1835 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1841 /****************************************************************************
1847 void WCMD_rename (void)
1851 WIN32_FIND_DATAW fd
;
1852 WCHAR input
[MAX_PATH
];
1853 WCHAR
*dotDst
= NULL
;
1855 WCHAR dir
[MAX_PATH
];
1856 WCHAR fname
[MAX_PATH
];
1857 WCHAR ext
[MAX_PATH
];
1861 /* Must be at least two args */
1862 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1863 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1868 /* Destination cannot contain a drive letter or directory separator */
1869 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1870 SetLastError(ERROR_INVALID_PARAMETER
);
1876 /* Convert partial path to full path */
1877 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1878 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1879 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1880 dotDst
= strchrW(param2
, '.');
1882 /* Split into components */
1883 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1885 hff
= FindFirstFileW(input
, &fd
);
1886 if (hff
== INVALID_HANDLE_VALUE
)
1890 WCHAR dest
[MAX_PATH
];
1891 WCHAR src
[MAX_PATH
];
1892 WCHAR
*dotSrc
= NULL
;
1895 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1897 /* FIXME: If dest name or extension is *, replace with filename/ext
1898 part otherwise use supplied name. This supports:
1900 ren jim.* fred.* etc
1901 However, windows has a more complex algorithm supporting eg
1902 ?'s and *'s mid name */
1903 dotSrc
= strchrW(fd
.cFileName
, '.');
1905 /* Build src & dest name */
1906 strcpyW(src
, drive
);
1909 dirLen
= strlenW(src
);
1910 strcatW(src
, fd
.cFileName
);
1913 if (param2
[0] == '*') {
1914 strcatW(dest
, fd
.cFileName
);
1915 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1917 strcatW(dest
, param2
);
1918 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1921 /* Build Extension */
1922 if (dotDst
&& (*(dotDst
+1)=='*')) {
1923 if (dotSrc
) strcatW(dest
, dotSrc
);
1924 } else if (dotDst
) {
1925 if (dotDst
) strcatW(dest
, dotDst
);
1928 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1929 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1931 status
= MoveFileW(src
, dest
);
1934 WCMD_print_error ();
1937 } while (FindNextFileW(hff
, &fd
) != 0);
1942 /*****************************************************************************
1945 * Make a copy of the environment.
1947 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1957 len
+= (strlenW(&env
[len
]) + 1);
1959 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1962 WINE_ERR("out of memory\n");
1965 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1971 /*****************************************************************************
1974 * setlocal pushes the environment onto a stack
1975 * Save the environment as unicode so we don't screw anything up.
1977 void WCMD_setlocal (const WCHAR
*s
) {
1979 struct env_stack
*env_copy
;
1980 WCHAR cwd
[MAX_PATH
];
1982 /* DISABLEEXTENSIONS ignored */
1984 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1987 WINE_ERR ("out of memory\n");
1991 env
= GetEnvironmentStringsW ();
1993 env_copy
->strings
= WCMD_dupenv (env
);
1994 if (env_copy
->strings
)
1996 env_copy
->next
= saved_environment
;
1997 saved_environment
= env_copy
;
1999 /* Save the current drive letter */
2000 GetCurrentDirectoryW(MAX_PATH
, cwd
);
2001 env_copy
->u
.cwd
= cwd
[0];
2004 LocalFree (env_copy
);
2006 FreeEnvironmentStringsW (env
);
2010 /*****************************************************************************
2013 * endlocal pops the environment off a stack
2014 * Note: When searching for '=', search from WCHAR position 1, to handle
2015 * special internal environment variables =C:, =D: etc
2017 void WCMD_endlocal (void) {
2018 WCHAR
*env
, *old
, *p
;
2019 struct env_stack
*temp
;
2022 if (!saved_environment
)
2025 /* pop the old environment from the stack */
2026 temp
= saved_environment
;
2027 saved_environment
= temp
->next
;
2029 /* delete the current environment, totally */
2030 env
= GetEnvironmentStringsW ();
2031 old
= WCMD_dupenv (GetEnvironmentStringsW ());
2034 n
= strlenW(&old
[len
]) + 1;
2035 p
= strchrW(&old
[len
] + 1, '=');
2039 SetEnvironmentVariableW (&old
[len
], NULL
);
2044 FreeEnvironmentStringsW (env
);
2046 /* restore old environment */
2047 env
= temp
->strings
;
2050 n
= strlenW(&env
[len
]) + 1;
2051 p
= strchrW(&env
[len
] + 1, '=');
2055 SetEnvironmentVariableW (&env
[len
], p
);
2060 /* Restore current drive letter */
2061 if (IsCharAlphaW(temp
->u
.cwd
)) {
2063 WCHAR cwd
[MAX_PATH
];
2064 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
2066 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
2067 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
2068 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
2069 SetCurrentDirectoryW(cwd
);
2077 /*****************************************************************************
2078 * WCMD_setshow_default
2080 * Set/Show the current default directory
2083 void WCMD_setshow_default (const WCHAR
*command
) {
2089 WIN32_FIND_DATAW fd
;
2091 static const WCHAR parmD
[] = {'/','D','\0'};
2093 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
2095 /* Skip /D and trailing whitespace if on the front of the command line */
2096 if (CompareStringW(LOCALE_USER_DEFAULT
,
2097 NORM_IGNORECASE
| SORT_STRINGSORT
,
2098 command
, 2, parmD
, -1) == CSTR_EQUAL
) {
2100 while (*command
&& (*command
==' ' || *command
=='\t'))
2104 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
2105 if (strlenW(command
) == 0) {
2106 strcatW (cwd
, newline
);
2107 WCMD_output_asis (cwd
);
2110 /* Remove any double quotes, which may be in the
2111 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2114 if (*command
!= '"') *pos
++ = *command
;
2117 while (pos
> command
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
2121 /* Search for appropriate directory */
2122 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
2123 hff
= FindFirstFileW(string
, &fd
);
2124 if (hff
!= INVALID_HANDLE_VALUE
) {
2126 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
2127 WCHAR fpath
[MAX_PATH
];
2129 WCHAR dir
[MAX_PATH
];
2130 WCHAR fname
[MAX_PATH
];
2131 WCHAR ext
[MAX_PATH
];
2132 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
2134 /* Convert path into actual directory spec */
2135 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
2136 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
2139 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
2142 } while (FindNextFileW(hff
, &fd
) != 0);
2146 /* Change to that directory */
2147 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
2149 status
= SetCurrentDirectoryW(string
);
2152 WCMD_print_error ();
2156 /* Save away the actual new directory, to store as current location */
2157 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
2159 /* Restore old directory if drive letter would change, and
2160 CD x:\directory /D (or pushd c:\directory) not supplied */
2161 if ((strstrW(quals
, parmD
) == NULL
) &&
2162 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
2163 SetCurrentDirectoryW(cwd
);
2167 /* Set special =C: type environment variable, for drive letter of
2168 change of directory, even if path was restored due to missing
2169 /D (allows changing drive letter when not resident on that
2171 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
2173 strcpyW(env
, equalW
);
2174 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
2176 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
2177 SetEnvironmentVariableW(env
, string
);
2184 /****************************************************************************
2187 * Set/Show the system date
2188 * FIXME: Can't change date yet
2191 void WCMD_setshow_date (void) {
2193 WCHAR curdate
[64], buffer
[64];
2195 static const WCHAR parmT
[] = {'/','T','\0'};
2197 if (strlenW(param1
) == 0) {
2198 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
2199 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
2200 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
2201 if (strstrW (quals
, parmT
) == NULL
) {
2202 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
2203 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2205 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2209 else WCMD_print_error ();
2212 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2216 /****************************************************************************
2219 static int WCMD_compare( const void *a
, const void *b
)
2222 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
2223 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2224 *str_a
, -1, *str_b
, -1 );
2225 if( r
== CSTR_LESS_THAN
) return -1;
2226 if( r
== CSTR_GREATER_THAN
) return 1;
2230 /****************************************************************************
2231 * WCMD_setshow_sortenv
2233 * sort variables into order for display
2234 * Optionally only display those who start with a stub
2235 * returns the count displayed
2237 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
2239 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
2242 if (stub
) stublen
= strlenW(stub
);
2244 /* count the number of strings, and the total length */
2246 len
+= (strlenW(&s
[len
]) + 1);
2250 /* add the strings to an array */
2251 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
2255 for( i
=1; i
<count
; i
++ )
2256 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
2258 /* sort the array */
2259 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
2262 for( i
=0; i
<count
; i
++ ) {
2263 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
2264 NORM_IGNORECASE
| SORT_STRINGSORT
,
2265 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
2266 /* Don't display special internal variables */
2267 if (str
[i
][0] != '=') {
2268 WCMD_output_asis(str
[i
]);
2269 WCMD_output_asis(newline
);
2276 return displayedcount
;
2279 /****************************************************************************
2282 * Set/Show the environment variables
2285 void WCMD_setshow_env (WCHAR
*s
) {
2290 static const WCHAR parmP
[] = {'/','P','\0'};
2292 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
2293 env
= GetEnvironmentStringsW();
2294 WCMD_setshow_sortenv( env
, NULL
);
2298 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2299 if (CompareStringW(LOCALE_USER_DEFAULT
,
2300 NORM_IGNORECASE
| SORT_STRINGSORT
,
2301 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
2302 WCHAR string
[MAXSTRING
];
2306 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
2308 WCMD_strip_quotes(s
);
2310 /* If no parameter, or no '=' sign, return an error */
2311 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2312 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2316 /* Output the prompt */
2318 if (strlenW(p
) != 0) WCMD_output_asis(p
);
2320 /* Read the reply */
2321 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
2323 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2324 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2325 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2326 wine_dbgstr_w(string
));
2327 status
= SetEnvironmentVariableW(s
, string
);
2334 WCMD_strip_quotes(s
);
2335 p
= strchrW (s
, '=');
2337 env
= GetEnvironmentStringsW();
2338 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2339 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2346 if (strlenW(p
) == 0) p
= NULL
;
2347 status
= SetEnvironmentVariableW(s
, p
);
2348 gle
= GetLastError();
2349 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2351 } else if ((!status
)) WCMD_print_error();
2355 /****************************************************************************
2358 * Set/Show the path environment variable
2361 void WCMD_setshow_path (const WCHAR
*command
) {
2365 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2366 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2368 if (strlenW(param1
) == 0) {
2369 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2371 WCMD_output_asis ( pathEqW
);
2372 WCMD_output_asis ( string
);
2373 WCMD_output_asis ( newline
);
2376 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
2380 if (*command
== '=') command
++; /* Skip leading '=' */
2381 status
= SetEnvironmentVariableW(pathW
, command
);
2382 if (!status
) WCMD_print_error();
2386 /****************************************************************************
2387 * WCMD_setshow_prompt
2389 * Set or show the command prompt.
2392 void WCMD_setshow_prompt (void) {
2395 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2397 if (strlenW(param1
) == 0) {
2398 SetEnvironmentVariableW(promptW
, NULL
);
2402 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
2403 if (strlenW(s
) == 0) {
2404 SetEnvironmentVariableW(promptW
, NULL
);
2406 else SetEnvironmentVariableW(promptW
, s
);
2410 /****************************************************************************
2413 * Set/Show the system time
2414 * FIXME: Can't change time yet
2417 void WCMD_setshow_time (void) {
2419 WCHAR curtime
[64], buffer
[64];
2422 static const WCHAR parmT
[] = {'/','T','\0'};
2424 if (strlenW(param1
) == 0) {
2426 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2427 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2428 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
2429 if (strstrW (quals
, parmT
) == NULL
) {
2430 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2431 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2433 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2437 else WCMD_print_error ();
2440 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
2444 /****************************************************************************
2447 * Shift batch parameters.
2448 * Optional /n says where to start shifting (n=0-8)
2451 void WCMD_shift (const WCHAR
*command
) {
2454 if (context
!= NULL
) {
2455 WCHAR
*pos
= strchrW(command
, '/');
2460 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2461 start
= (*(pos
+1) - '0');
2463 SetLastError(ERROR_INVALID_PARAMETER
);
2468 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2469 for (i
=start
;i
<=8;i
++) {
2470 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2472 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2477 /****************************************************************************
2480 * Set the console title
2482 void WCMD_title (const WCHAR
*command
) {
2483 SetConsoleTitleW(command
);
2486 /****************************************************************************
2489 * Copy a file to standard output.
2492 void WCMD_type (WCHAR
*command
) {
2495 WCHAR
*argN
= command
;
2496 BOOL writeHeaders
= FALSE
;
2498 if (param1
[0] == 0x00) {
2499 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2503 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2505 /* Loop through all args */
2508 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2516 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2517 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2518 FILE_ATTRIBUTE_NORMAL
, NULL
);
2519 if (h
== INVALID_HANDLE_VALUE
) {
2520 WCMD_print_error ();
2521 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2525 static const WCHAR fmt
[] = {'\n','%','1','\n','\n','\0'};
2526 WCMD_output(fmt
, thisArg
);
2528 while (WCMD_ReadFile(h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
)) {
2529 if (count
== 0) break; /* ReadFile reports success on EOF! */
2531 WCMD_output_asis (buffer
);
2538 /****************************************************************************
2541 * Output either a file or stdin to screen in pages
2544 void WCMD_more (WCHAR
*command
) {
2547 WCHAR
*argN
= command
;
2549 WCHAR moreStrPage
[100];
2552 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2553 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2554 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2555 ')',' ','-','-','\n','\0'};
2556 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2558 /* Prefix the NLS more with '-- ', then load the text */
2560 strcpyW(moreStr
, moreStart
);
2561 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
2562 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2564 if (param1
[0] == 0x00) {
2566 /* Wine implements pipes via temporary files, and hence stdin is
2567 effectively reading from the file. This means the prompts for
2568 more are satisfied by the next line from the input (file). To
2569 avoid this, ensure stdin is to the console */
2570 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2571 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2572 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2573 FILE_ATTRIBUTE_NORMAL
, 0);
2574 WINE_TRACE("No parms - working probably in pipe mode\n");
2575 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2577 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2578 once you get in this bit unless due to a pipe, its going to end badly... */
2579 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
2581 WCMD_enter_paged_mode(moreStrPage
);
2582 while (WCMD_ReadFile(hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
2583 if (count
== 0) break; /* ReadFile reports success on EOF! */
2585 WCMD_output_asis (buffer
);
2587 WCMD_leave_paged_mode();
2589 /* Restore stdin to what it was */
2590 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2591 CloseHandle(hConIn
);
2595 BOOL needsPause
= FALSE
;
2597 /* Loop through all args */
2598 WINE_TRACE("Parms supplied - working through each file\n");
2599 WCMD_enter_paged_mode(moreStrPage
);
2602 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
, NULL
);
2610 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
2611 WCMD_leave_paged_mode();
2612 WCMD_output_asis(moreStrPage
);
2613 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
2614 WCMD_enter_paged_mode(moreStrPage
);
2618 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2619 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2620 FILE_ATTRIBUTE_NORMAL
, NULL
);
2621 if (h
== INVALID_HANDLE_VALUE
) {
2622 WCMD_print_error ();
2623 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2627 ULONG64 fileLen
= 0;
2628 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2630 /* Get the file size */
2631 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2632 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2635 while (WCMD_ReadFile(h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
2636 if (count
== 0) break; /* ReadFile reports success on EOF! */
2640 /* Update % count (would be used in WCMD_output_asis as prompt) */
2641 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2643 WCMD_output_asis (buffer
);
2649 WCMD_leave_paged_mode();
2653 /****************************************************************************
2656 * Display verify flag.
2657 * FIXME: We don't actually do anything with the verify flag other than toggle
2661 void WCMD_verify (const WCHAR
*command
) {
2665 count
= strlenW(command
);
2667 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2668 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2671 if (lstrcmpiW(command
, onW
) == 0) {
2675 else if (lstrcmpiW(command
, offW
) == 0) {
2676 verify_mode
= FALSE
;
2679 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
2682 /****************************************************************************
2685 * Display version info.
2688 void WCMD_version (void) {
2690 WCMD_output_asis (version_string
);
2694 /****************************************************************************
2697 * Display volume information (set_label = FALSE)
2698 * Additionally set volume label (set_label = TRUE)
2699 * Returns 1 on success, 0 otherwise
2702 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
2704 DWORD count
, serial
;
2705 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2708 if (strlenW(path
) == 0) {
2709 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2711 WCMD_print_error ();
2714 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2715 &serial
, NULL
, NULL
, NULL
, 0);
2718 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2719 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2720 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2723 wsprintfW (curdir
, fmt
, path
);
2724 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2729 WCMD_print_error ();
2732 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2733 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2735 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2736 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
2738 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2739 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2741 if (strlenW(path
) != 0) {
2742 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
2745 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
2751 /**************************************************************************
2754 * Exit either the process, or just this batch program
2758 void WCMD_exit (CMD_LIST
**cmdList
) {
2760 static const WCHAR parmB
[] = {'/','B','\0'};
2761 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2763 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2765 context
-> skip_rest
= TRUE
;
2773 /*****************************************************************************
2776 * Lists or sets file associations (assoc = TRUE)
2777 * Lists or sets file types (assoc = FALSE)
2779 void WCMD_assoc (const WCHAR
*command
, BOOL assoc
) {
2782 DWORD accessOptions
= KEY_READ
;
2784 LONG rc
= ERROR_SUCCESS
;
2785 WCHAR keyValue
[MAXSTRING
];
2786 DWORD valueLen
= MAXSTRING
;
2788 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2789 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2791 /* See if parameter includes '=' */
2793 newValue
= strchrW(command
, '=');
2794 if (newValue
) accessOptions
|= KEY_WRITE
;
2796 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2797 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
2798 accessOptions
, &key
) != ERROR_SUCCESS
) {
2799 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2803 /* If no parameters then list all associations */
2804 if (*command
== 0x00) {
2807 /* Enumerate all the keys */
2808 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2809 WCHAR keyName
[MAXSTRING
];
2812 /* Find the next value */
2813 nameLen
= MAXSTRING
;
2814 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
2816 if (rc
== ERROR_SUCCESS
) {
2818 /* Only interested in extension ones if assoc, or others
2820 if ((keyName
[0] == '.' && assoc
) ||
2821 (!(keyName
[0] == '.') && (!assoc
)))
2823 WCHAR subkey
[MAXSTRING
];
2824 strcpyW(subkey
, keyName
);
2825 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2827 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2829 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2830 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2831 WCMD_output_asis(keyName
);
2832 WCMD_output_asis(equalW
);
2833 /* If no default value found, leave line empty after '=' */
2834 if (rc
== ERROR_SUCCESS
) {
2835 WCMD_output_asis(keyValue
);
2837 WCMD_output_asis(newline
);
2838 RegCloseKey(readKey
);
2846 /* Parameter supplied - if no '=' on command line, its a query */
2847 if (newValue
== NULL
) {
2849 WCHAR subkey
[MAXSTRING
];
2851 /* Query terminates the parameter at the first space */
2852 strcpyW(keyValue
, command
);
2853 space
= strchrW(keyValue
, ' ');
2854 if (space
) *space
=0x00;
2856 /* Set up key name */
2857 strcpyW(subkey
, keyValue
);
2858 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2860 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2862 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2863 WCMD_output_asis(command
);
2864 WCMD_output_asis(equalW
);
2865 /* If no default value found, leave line empty after '=' */
2866 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2867 WCMD_output_asis(newline
);
2868 RegCloseKey(readKey
);
2871 WCHAR msgbuffer
[MAXSTRING
];
2873 /* Load the translated 'File association not found' */
2875 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2877 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2879 WCMD_output_stderr(msgbuffer
, keyValue
);
2883 /* Not a query - its a set or clear of a value */
2886 WCHAR subkey
[MAXSTRING
];
2888 /* Get pointer to new value */
2892 /* Set up key name */
2893 strcpyW(subkey
, command
);
2894 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2896 /* If nothing after '=' then clear value - only valid for ASSOC */
2897 if (*newValue
== 0x00) {
2899 if (assoc
) rc
= RegDeleteKeyW(key
, command
);
2900 if (assoc
&& rc
== ERROR_SUCCESS
) {
2901 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2903 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2908 WCHAR msgbuffer
[MAXSTRING
];
2910 /* Load the translated 'File association not found' */
2912 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
2913 sizeof(msgbuffer
)/sizeof(WCHAR
));
2915 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
2916 sizeof(msgbuffer
)/sizeof(WCHAR
));
2918 WCMD_output_stderr(msgbuffer
, keyValue
);
2922 /* It really is a set value = contents */
2924 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2925 accessOptions
, NULL
, &readKey
, NULL
);
2926 if (rc
== ERROR_SUCCESS
) {
2927 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
2929 sizeof(WCHAR
) * (strlenW(newValue
) + 1));
2930 RegCloseKey(readKey
);
2933 if (rc
!= ERROR_SUCCESS
) {
2937 WCMD_output_asis(command
);
2938 WCMD_output_asis(equalW
);
2939 WCMD_output_asis(newValue
);
2940 WCMD_output_asis(newline
);
2950 /****************************************************************************
2953 * Colors the terminal screen.
2956 void WCMD_color (void) {
2958 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2959 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2961 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2962 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
2966 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2972 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2977 /* Convert the color hex digits */
2978 if (param1
[0] == 0x00) {
2979 color
= defaultColor
;
2981 color
= strtoulW(param1
, NULL
, 16);
2984 /* Fail if fg == bg color */
2985 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2990 /* Set the current screen contents and ensure all future writes
2991 remain this color */
2992 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2993 SetConsoleTextAttribute(hStdOut
, color
);