2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
44 static void WCMD_part_execute(CMD_LIST
**commands
, WCHAR
*firstcmd
, WCHAR
*variable
,
45 WCHAR
*value
, BOOL isIF
, BOOL conditionTRUE
);
47 struct env_stack
*saved_environment
;
48 struct env_stack
*pushd_directories
;
50 extern HINSTANCE hinst
;
51 extern WCHAR inbuilt
[][10];
52 extern int echo_mode
, verify_mode
, defaultColor
;
53 extern WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
54 extern BATCH_CONTEXT
*context
;
55 extern DWORD errorlevel
;
57 static const WCHAR dotW
[] = {'.','\0'};
58 static const WCHAR dotdotW
[] = {'.','.','\0'};
59 static const WCHAR slashW
[] = {'\\','\0'};
60 static const WCHAR starW
[] = {'*','\0'};
61 static const WCHAR equalW
[] = {'=','\0'};
62 static const WCHAR fslashW
[] = {'/','\0'};
63 static const WCHAR onW
[] = {'O','N','\0'};
64 static const WCHAR offW
[] = {'O','F','F','\0'};
65 static const WCHAR parmY
[] = {'/','Y','\0'};
66 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
67 static const WCHAR nullW
[] = {'\0'};
69 /**************************************************************************
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
80 static BOOL
WCMD_ask_confirm (WCHAR
*message
, BOOL showSureText
, BOOL
*optionAll
) {
82 WCHAR msgbuffer
[MAXSTRING
];
83 WCHAR Ybuffer
[MAXSTRING
];
84 WCHAR Nbuffer
[MAXSTRING
];
85 WCHAR Abuffer
[MAXSTRING
];
86 WCHAR answer
[MAX_PATH
] = {'\0'};
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadStringW(hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
91 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
92 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
93 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
95 /* Loop waiting on a Y or N */
96 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
97 static const WCHAR startBkt
[] = {' ','(','\0'};
98 static const WCHAR endBkt
[] = {')','?','\0'};
100 WCMD_output_asis (message
);
102 WCMD_output_asis (msgbuffer
);
104 WCMD_output_asis (startBkt
);
105 WCMD_output_asis (Ybuffer
);
106 WCMD_output_asis (fslashW
);
107 WCMD_output_asis (Nbuffer
);
109 WCMD_output_asis (fslashW
);
110 WCMD_output_asis (Abuffer
);
112 WCMD_output_asis (endBkt
);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
,
114 sizeof(answer
)/sizeof(WCHAR
), &count
, NULL
);
115 answer
[0] = toupperW(answer
[0]);
118 /* Return the answer */
119 return ((answer
[0] == Ybuffer
[0]) ||
120 (optionAll
&& (answer
[0] == Abuffer
[0])));
123 /****************************************************************************
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
134 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
136 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
141 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
145 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
146 SetConsoleCursorPosition(hStdOut
, topLeft
);
150 /****************************************************************************
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
162 /****************************************************************************
167 void WCMD_choice (WCHAR
* command
) {
169 static const WCHAR bellW
[] = {7,0};
170 static const WCHAR commaW
[] = {',',0};
171 static const WCHAR bracket_open
[] = {'[',0};
172 static const WCHAR bracket_close
[] = {']','?',0};
177 WCHAR
*my_command
= NULL
;
178 WCHAR opt_default
= 0;
179 DWORD opt_timeout
= 0;
186 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
189 my_command
= WCMD_strdupW(WCMD_strtrim_leading_spaces(command
));
193 ptr
= WCMD_strtrim_leading_spaces(my_command
);
194 while (*ptr
== '/') {
195 switch (toupperW(ptr
[1])) {
198 /* the colon is optional */
202 if (!*ptr
|| isspaceW(*ptr
)) {
203 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
204 HeapFree(GetProcessHeap(), 0, my_command
);
208 /* remember the allowed keys (overwrite previous /C option) */
210 while (*ptr
&& (!isspaceW(*ptr
)))
214 /* terminate allowed chars */
216 ptr
= WCMD_strtrim_leading_spaces(&ptr
[1]);
218 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
223 ptr
= WCMD_strtrim_leading_spaces(&ptr
[2]);
228 ptr
= WCMD_strtrim_leading_spaces(&ptr
[2]);
233 /* the colon is optional */
237 opt_default
= *ptr
++;
239 if (!opt_default
|| (*ptr
!= ',')) {
240 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
241 HeapFree(GetProcessHeap(), 0, my_command
);
247 while (((answer
[count
] = *ptr
)) && isdigitW(*ptr
) && (count
< 15)) {
253 opt_timeout
= atoiW(answer
);
255 ptr
= WCMD_strtrim_leading_spaces(ptr
);
259 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
260 HeapFree(GetProcessHeap(), 0, my_command
);
266 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
269 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
271 /* use default keys, when needed: localized versions of "Y"es and "No" */
273 LoadStringW(hinst
, WCMD_YES
, buffer
, sizeof(buffer
)/sizeof(WCHAR
));
274 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, sizeof(buffer
)/sizeof(WCHAR
) - 1);
279 /* print the question, when needed */
281 WCMD_output_asis(ptr
);
285 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
289 /* print a list of all allowed answers inside brackets */
290 WCMD_output_asis(bracket_open
);
293 while ((answer
[0] = *ptr
++)) {
294 WCMD_output_asis(answer
);
296 WCMD_output_asis(commaW
);
298 WCMD_output_asis(bracket_close
);
303 /* FIXME: Add support for option /T */
304 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
, NULL
);
307 answer
[0] = toupperW(answer
[0]);
309 ptr
= strchrW(opt_c
, answer
[0]);
311 WCMD_output_asis(answer
);
312 WCMD_output(newline
);
314 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
316 errorlevel
= (ptr
- opt_c
) + 1;
317 WINE_TRACE("answer: %d\n", errorlevel
);
318 HeapFree(GetProcessHeap(), 0, my_command
);
323 /* key not allowed: play the bell */
324 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
325 WCMD_output_asis(bellW
);
330 /****************************************************************************
333 * Copy a file or wildcarded set.
334 * FIXME: Add support for a+b+c type syntax
337 void WCMD_copy (void) {
342 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[4];
344 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
345 BOOL copyToDir
= FALSE
;
346 WCHAR srcspec
[MAX_PATH
];
350 WCHAR fname
[MAX_PATH
];
353 if (param1
[0] == 0x00) {
354 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
358 /* Convert source into full spec */
359 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1
));
360 GetFullPathNameW(param1
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, NULL
);
361 if (srcpath
[strlenW(srcpath
) - 1] == '\\')
362 srcpath
[strlenW(srcpath
) - 1] = '\0';
364 if ((strchrW(srcpath
,'*') == NULL
) && (strchrW(srcpath
,'?') == NULL
)) {
365 attribs
= GetFileAttributesW(srcpath
);
369 strcpyW(srcspec
, srcpath
);
371 /* If a directory, then add \* on the end when searching */
372 if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
373 strcatW(srcpath
, slashW
);
374 strcatW(srcspec
, slashW
);
375 strcatW(srcspec
, starW
);
377 WCMD_splitpath(srcpath
, drive
, dir
, fname
, ext
);
378 strcpyW(srcpath
, drive
);
379 strcatW(srcpath
, dir
);
382 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath
));
384 /* If no destination supplied, assume current directory */
385 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2
));
386 if (param2
[0] == 0x00) {
387 strcpyW(param2
, dotW
);
390 GetFullPathNameW(param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
391 if (outpath
[strlenW(outpath
) - 1] == '\\')
392 outpath
[strlenW(outpath
) - 1] = '\0';
393 attribs
= GetFileAttributesW(outpath
);
394 if (attribs
!= INVALID_FILE_ATTRIBUTES
&& (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
395 strcatW (outpath
, slashW
);
398 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
399 wine_dbgstr_w(outpath
), copyToDir
);
401 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
402 if (strstrW (quals
, parmNoY
))
404 else if (strstrW (quals
, parmY
))
407 /* By default, we will force the overwrite in batch mode and ask for
408 * confirmation in interactive mode. */
411 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
412 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
413 * default behavior. */
414 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
415 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
416 if (!lstrcmpiW (copycmd
, parmY
))
418 else if (!lstrcmpiW (copycmd
, parmNoY
))
423 /* Loop through all source files */
424 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec
));
425 hff
= FindFirstFileW(srcspec
, &fd
);
426 if (hff
!= INVALID_HANDLE_VALUE
) {
428 WCHAR outname
[MAX_PATH
];
429 WCHAR srcname
[MAX_PATH
];
430 BOOL overwrite
= force
;
432 /* Destination is either supplied filename, or source name in
433 supplied destination directory */
434 strcpyW(outname
, outpath
);
435 if (copyToDir
) strcatW(outname
, fd
.cFileName
);
436 strcpyW(srcname
, srcpath
);
437 strcatW(srcname
, fd
.cFileName
);
439 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname
));
440 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
442 /* Skip . and .., and directories */
443 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
445 WINE_TRACE("Skipping directories\n");
448 /* Prompt before overwriting */
449 else if (!overwrite
) {
450 attribs
= GetFileAttributesW(outname
);
451 if (attribs
!= INVALID_FILE_ATTRIBUTES
) {
452 WCHAR buffer
[MAXSTRING
];
453 wsprintfW(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
454 overwrite
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
456 else overwrite
= TRUE
;
459 /* Do the copy as appropriate */
461 status
= CopyFileW(srcname
, outname
, FALSE
);
462 if (!status
) WCMD_print_error ();
465 } while (FindNextFileW(hff
, &fd
) != 0);
468 status
= ERROR_FILE_NOT_FOUND
;
473 /****************************************************************************
476 * Create a directory (and, if needed, any intermediate directories).
478 * Modifies its argument by replacing slashes temporarily with nulls.
481 static BOOL
create_full_path(WCHAR
* path
)
485 /* don't mess with drive letter portion of path, if any */
490 /* Strip trailing slashes. */
491 for (p
= path
+ strlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
494 /* Step through path, creating intermediate directories as needed. */
495 /* First component includes drive letter, if any. */
499 /* Skip to end of component */
500 while (*p
== '\\') p
++;
501 while (*p
&& *p
!= '\\') p
++;
503 /* path is now the original full path */
504 return CreateDirectoryW(path
, NULL
);
506 /* Truncate path, create intermediate directory, and restore path */
508 rv
= CreateDirectoryW(path
, NULL
);
510 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
517 void WCMD_create_dir (WCHAR
*command
) {
519 WCHAR
*argN
= command
;
521 if (param1
[0] == 0x00) {
522 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
525 /* Loop through all args */
527 WCHAR
*thisArg
= WCMD_parameter(command
, argno
++, &argN
);
529 if (!create_full_path(thisArg
)) {
536 /* Parse the /A options given by the user on the commandline
537 * into a bitmask of wanted attributes (*wantSet),
538 * and a bitmask of unwanted attributes (*wantClear).
540 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
541 static const WCHAR parmA
[] = {'/','A','\0'};
544 /* both are strictly 'out' parameters */
548 /* For each /A argument */
549 for (p
=strstrW(quals
, parmA
); p
!= NULL
; p
=strstrW(p
, parmA
)) {
553 /* Skip optional : */
556 /* For each of the attribute specifier chars to this /A option */
557 for (; *p
!= 0 && *p
!= '/'; p
++) {
566 /* Convert the attribute specifier to a bit in one of the masks */
568 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
569 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
570 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
571 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
573 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
583 /* If filename part of parameter is * or *.*,
584 * and neither /Q nor /P options were given,
585 * prompt the user whether to proceed.
586 * Returns FALSE if user says no, TRUE otherwise.
587 * *pPrompted is set to TRUE if the user is prompted.
588 * (If /P supplied, del will prompt for individual files later.)
590 static BOOL
WCMD_delete_confirm_wildcard(WCHAR
*filename
, BOOL
*pPrompted
) {
591 static const WCHAR parmP
[] = {'/','P','\0'};
592 static const WCHAR parmQ
[] = {'/','Q','\0'};
594 if ((strstrW(quals
, parmQ
) == NULL
) && (strstrW(quals
, parmP
) == NULL
)) {
595 static const WCHAR anyExt
[]= {'.','*','\0'};
598 WCHAR fname
[MAX_PATH
];
600 WCHAR fpath
[MAX_PATH
];
602 /* Convert path into actual directory spec */
603 GetFullPathNameW(filename
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
604 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
606 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
607 if ((strcmpW(fname
, starW
) == 0) &&
608 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
610 WCHAR question
[MAXSTRING
];
611 static const WCHAR fmt
[] = {'%','s',' ','\0'};
613 /* Caller uses this to suppress "file not found" warning later */
616 /* Ask for confirmation */
617 wsprintfW(question
, fmt
, fpath
);
618 return WCMD_ask_confirm(question
, TRUE
, NULL
);
621 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
625 /* Helper function for WCMD_delete().
626 * Deletes a single file, directory, or wildcard.
627 * If /S was given, does it recursively.
628 * Returns TRUE if a file was deleted.
630 static BOOL
WCMD_delete_one (WCHAR
*thisArg
) {
632 static const WCHAR parmP
[] = {'/','P','\0'};
633 static const WCHAR parmS
[] = {'/','S','\0'};
634 static const WCHAR parmF
[] = {'/','F','\0'};
636 DWORD unwanted_attrs
;
638 WCHAR argCopy
[MAX_PATH
];
641 WCHAR fpath
[MAX_PATH
];
643 BOOL handleParm
= TRUE
;
645 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
647 strcpyW(argCopy
, thisArg
);
648 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
649 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
651 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
652 /* Skip this arg if user declines to delete *.* */
656 /* First, try to delete in the current directory */
657 hff
= FindFirstFileW(argCopy
, &fd
);
658 if (hff
== INVALID_HANDLE_VALUE
) {
664 /* Support del <dirname> by just deleting all files dirname\* */
666 && (strchrW(argCopy
,'*') == NULL
)
667 && (strchrW(argCopy
,'?') == NULL
)
668 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
670 WCHAR modifiedParm
[MAX_PATH
];
671 static const WCHAR slashStar
[] = {'\\','*','\0'};
673 strcpyW(modifiedParm
, argCopy
);
674 strcatW(modifiedParm
, slashStar
);
677 WCMD_delete_one(modifiedParm
);
679 } else if (handleParm
) {
681 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
682 strcpyW (fpath
, argCopy
);
684 p
= strrchrW (fpath
, '\\');
687 strcatW (fpath
, fd
.cFileName
);
689 else strcpyW (fpath
, fd
.cFileName
);
690 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
693 /* Handle attribute matching (/A) */
694 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
695 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
697 /* /P means prompt for each file */
698 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
699 WCHAR question
[MAXSTRING
];
701 /* Ask for confirmation */
702 wsprintfW(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
703 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
706 /* Only proceed if ok to */
709 /* If file is read only, and /A:r or /F supplied, delete it */
710 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
711 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
712 strstrW (quals
, parmF
) != NULL
)) {
713 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
716 /* Now do the delete */
717 if (!DeleteFileW(fpath
)) WCMD_print_error ();
721 } while (FindNextFileW(hff
, &fd
) != 0);
725 /* Now recurse into all subdirectories handling the parameter in the same way */
726 if (strstrW (quals
, parmS
) != NULL
) {
728 WCHAR thisDir
[MAX_PATH
];
733 WCHAR fname
[MAX_PATH
];
736 /* Convert path into actual directory spec */
737 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
738 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
740 strcpyW(thisDir
, drive
);
741 strcatW(thisDir
, dir
);
742 cPos
= strlenW(thisDir
);
744 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
746 /* Append '*' to the directory */
748 thisDir
[cPos
+1] = 0x00;
750 hff
= FindFirstFileW(thisDir
, &fd
);
752 /* Remove residual '*' */
753 thisDir
[cPos
] = 0x00;
755 if (hff
!= INVALID_HANDLE_VALUE
) {
756 DIRECTORY_STACK
*allDirs
= NULL
;
757 DIRECTORY_STACK
*lastEntry
= NULL
;
760 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
761 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
762 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
764 DIRECTORY_STACK
*nextDir
;
765 WCHAR subParm
[MAX_PATH
];
767 /* Work out search parameter in sub dir */
768 strcpyW (subParm
, thisDir
);
769 strcatW (subParm
, fd
.cFileName
);
770 strcatW (subParm
, slashW
);
771 strcatW (subParm
, fname
);
772 strcatW (subParm
, ext
);
773 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
775 /* Allocate memory, add to list */
776 nextDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
777 if (allDirs
== NULL
) allDirs
= nextDir
;
778 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
780 nextDir
->next
= NULL
;
781 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
782 (strlenW(subParm
)+1) * sizeof(WCHAR
));
783 strcpyW(nextDir
->dirName
, subParm
);
785 } while (FindNextFileW(hff
, &fd
) != 0);
788 /* Go through each subdir doing the delete */
789 while (allDirs
!= NULL
) {
790 DIRECTORY_STACK
*tempDir
;
792 tempDir
= allDirs
->next
;
793 found
|= WCMD_delete_one (allDirs
->dirName
);
795 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
796 HeapFree(GetProcessHeap(),0,allDirs
);
805 /****************************************************************************
808 * Delete a file or wildcarded set.
811 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
812 * - Each set is a pattern, eg /ahr /as-r means
813 * readonly+hidden OR nonreadonly system files
814 * - The '-' applies to a single field, ie /a:-hr means read only
818 BOOL
WCMD_delete (WCHAR
*command
) {
821 BOOL argsProcessed
= FALSE
;
822 BOOL foundAny
= FALSE
;
826 for (argno
=0; ; argno
++) {
831 thisArg
= WCMD_parameter (command
, argno
, &argN
);
833 break; /* no more parameters */
835 continue; /* skip options */
837 argsProcessed
= TRUE
;
838 found
= WCMD_delete_one(thisArg
);
841 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
846 /* Handle no valid args */
848 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
853 /****************************************************************************
856 * Echo input to the screen (or not). We don't try to emulate the bugs
857 * in DOS (try typing "ECHO ON AGAIN" for an example).
860 void WCMD_echo (const WCHAR
*command
) {
863 const WCHAR
*origcommand
= command
;
865 if (command
[0]==' ' || command
[0]=='.' || command
[0]==':')
867 count
= strlenW(command
);
868 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':') {
869 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
870 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
873 if (lstrcmpiW(command
, onW
) == 0) {
877 if (lstrcmpiW(command
, offW
) == 0) {
881 WCMD_output_asis (command
);
882 WCMD_output (newline
);
886 /**************************************************************************
889 * Batch file loop processing.
891 * On entry: cmdList contains the syntax up to the set
892 * next cmdList and all in that bracket contain the set data
893 * next cmdlist contains the DO cmd
894 * following that is either brackets or && entries (as per if)
898 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
903 const WCHAR inW
[] = {'i', 'n', ' ', '\0'};
904 const WCHAR doW
[] = {'d', 'o', ' ', '\0'};
905 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
911 BOOL expandDirs
= FALSE
;
912 BOOL useNumbers
= FALSE
;
913 BOOL doFileset
= FALSE
;
914 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
916 CMD_LIST
*thisCmdStart
;
919 /* Handle optional qualifiers (multiple are allowed) */
920 while (*curPos
&& *curPos
== '/') {
921 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos
));
923 switch (toupperW(*curPos
)) {
924 case 'D': curPos
++; expandDirs
= TRUE
; break;
925 case 'L': curPos
++; useNumbers
= TRUE
; break;
927 /* Recursive is special case - /R can have an optional path following it */
928 /* filenamesets are another special case - /F can have an optional options following it */
932 BOOL isRecursive
= (*curPos
== 'R');
937 /* Skip whitespace */
939 while (*curPos
&& *curPos
==' ') curPos
++;
941 /* Next parm is either qualifier, path/options or variable -
942 only care about it if it is the path/options */
943 if (*curPos
&& *curPos
!= '/' && *curPos
!= '%') {
944 if (isRecursive
) WINE_FIXME("/R needs to handle supplied root\n");
945 else WINE_FIXME("/F needs to handle options\n");
950 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos
);
954 /* Skip whitespace between qualifiers */
955 while (*curPos
&& *curPos
==' ') curPos
++;
958 /* Skip whitespace before variable */
959 while (*curPos
&& *curPos
==' ') curPos
++;
961 /* Ensure line continues with variable */
962 if (!*curPos
|| *curPos
!= '%') {
963 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
967 /* Variable should follow */
969 while (curPos
[i
] && curPos
[i
]!=' ') i
++;
970 memcpy(&variable
[0], curPos
, i
*sizeof(WCHAR
));
972 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
975 /* Skip whitespace before IN */
976 while (*curPos
&& *curPos
==' ') curPos
++;
978 /* Ensure line continues with IN */
979 if (!*curPos
|| lstrcmpiW (curPos
, inW
)) {
980 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
984 /* Save away where the set of data starts and the variable */
985 thisDepth
= (*cmdList
)->bracketDepth
;
986 *cmdList
= (*cmdList
)->nextcommand
;
987 setStart
= (*cmdList
);
989 /* Skip until the close bracket */
990 WINE_TRACE("Searching %p as the set\n", *cmdList
);
992 (*cmdList
)->command
!= NULL
&&
993 (*cmdList
)->bracketDepth
> thisDepth
) {
994 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
995 *cmdList
= (*cmdList
)->nextcommand
;
998 /* Skip the close bracket, if there is one */
999 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1001 /* Syntax error if missing close bracket, or nothing following it
1002 and once we have the complete set, we expect a DO */
1003 WINE_TRACE("Looking for 'do' in %p\n", *cmdList
);
1004 if ((*cmdList
== NULL
) ||
1005 (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1006 (*cmdList
)->command
, 3, doW
, -1) != 2)) {
1007 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1011 /* Save away the starting position for the commands (and offset for the
1013 cmdStart
= *cmdList
;
1015 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
1019 /* Loop through all set entries */
1021 thisSet
->command
!= NULL
&&
1022 thisSet
->bracketDepth
>= thisDepth
) {
1024 /* Loop through all entries on the same line */
1028 WINE_TRACE("Processing for set %p\n", thisSet
);
1030 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
))) {
1033 * If the parameter within the set has a wildcard then search for matching files
1034 * otherwise do a literal substitution.
1036 static const WCHAR wildcards
[] = {'*','?','\0'};
1037 thisCmdStart
= cmdStart
;
1040 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
1042 if (!useNumbers
&& !doFileset
) {
1043 if (strpbrkW (item
, wildcards
)) {
1044 hff
= FindFirstFileW(item
, &fd
);
1045 if (hff
!= INVALID_HANDLE_VALUE
) {
1047 BOOL isDirectory
= FALSE
;
1049 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
1051 /* Handle as files or dirs appropriately, but ignore . and .. */
1052 if (isDirectory
== expandDirs
&&
1053 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1054 (strcmpW(fd
.cFileName
, dotW
) != 0))
1056 thisCmdStart
= cmdStart
;
1057 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
1058 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
1059 fd
.cFileName
, FALSE
, TRUE
);
1062 } while (FindNextFileW(hff
, &fd
) != 0);
1066 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
1069 } else if (useNumbers
) {
1070 /* Convert the first 3 numbers to signed longs and save */
1071 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
1072 /* else ignore them! */
1074 /* Filesets - either a list of files, or a command to run and parse the output */
1075 } else if (doFileset
&& *itemStart
!= '"') {
1078 WCHAR temp_file
[MAX_PATH
];
1080 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
1081 wine_dbgstr_w(item
));
1083 /* If backquote or single quote, we need to launch that command
1084 and parse the results - use a temporary file */
1085 if (*itemStart
== '`' || *itemStart
== '\'') {
1087 WCHAR temp_path
[MAX_PATH
], temp_cmd
[MAXSTRING
];
1088 static const WCHAR redirOut
[] = {'>','%','s','\0'};
1089 static const WCHAR cmdW
[] = {'C','M','D','\0'};
1091 /* Remove trailing character */
1092 itemStart
[strlenW(itemStart
)-1] = 0x00;
1094 /* Get temp filename */
1095 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
1096 GetTempFileNameW(temp_path
, cmdW
, 0, temp_file
);
1098 /* Execute program and redirect output */
1099 wsprintfW(temp_cmd
, redirOut
, (itemStart
+1), temp_file
);
1100 WCMD_execute (itemStart
, temp_cmd
, NULL
, NULL
, NULL
);
1102 /* Open the file, read line by line and process */
1103 input
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
1104 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1107 /* Open the file, read line by line and process */
1108 input
= CreateFileW(item
, GENERIC_READ
, FILE_SHARE_READ
,
1109 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1112 /* Process the input file */
1113 if (input
== INVALID_HANDLE_VALUE
) {
1114 WCMD_print_error ();
1115 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), item
);
1117 return; /* FOR loop aborts at first failure here */
1121 WCHAR buffer
[MAXSTRING
] = {'\0'};
1122 WCHAR
*where
, *parm
;
1124 while (WCMD_fgets (buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
1126 /* Skip blank lines*/
1127 parm
= WCMD_parameter (buffer
, 0, &where
);
1128 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1129 wine_dbgstr_w(buffer
));
1132 /* FIXME: The following should be moved into its own routine and
1133 reused for the string literal parsing below */
1134 thisCmdStart
= cmdStart
;
1135 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1136 cmdEnd
= thisCmdStart
;
1142 CloseHandle (input
);
1145 /* Delete the temporary file */
1146 if (*itemStart
== '`' || *itemStart
== '\'') {
1147 DeleteFileW(temp_file
);
1150 /* Filesets - A string literal */
1151 } else if (doFileset
&& *itemStart
== '"') {
1152 WCHAR buffer
[MAXSTRING
] = {'\0'};
1153 WCHAR
*where
, *parm
;
1155 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1156 strcpyW(buffer
, item
);
1157 parm
= WCMD_parameter (buffer
, 0, &where
);
1158 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
1159 wine_dbgstr_w(buffer
));
1162 /* FIXME: The following should be moved into its own routine and
1163 reused for the string literal parsing below */
1164 thisCmdStart
= cmdStart
;
1165 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
1166 cmdEnd
= thisCmdStart
;
1170 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
1171 cmdEnd
= thisCmdStart
;
1175 /* Move onto the next set line */
1176 thisSet
= thisSet
->nextcommand
;
1179 /* If /L is provided, now run the for loop */
1182 static const WCHAR fmt
[] = {'%','d','\0'};
1184 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1185 numbers
[0], numbers
[2], numbers
[1]);
1187 (numbers
[1]<0)? i
>numbers
[2] : i
<numbers
[2];
1190 sprintfW(thisNum
, fmt
, i
);
1191 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
1193 thisCmdStart
= cmdStart
;
1194 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, thisNum
, FALSE
, TRUE
);
1195 cmdEnd
= thisCmdStart
;
1199 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1200 all processing, OR it should be pointing to the end of && processing OR
1201 it should be pointing at the NULL end of bracket for the DO. The return
1202 value needs to be the NEXT command to execute, which it either is, or
1203 we need to step over the closing bracket */
1205 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
1209 /*****************************************************************************
1212 * Execute a command, and any && or bracketed follow on to the command. The
1213 * first command to be executed may not be at the front of the
1214 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1216 void WCMD_part_execute(CMD_LIST
**cmdList
, WCHAR
*firstcmd
, WCHAR
*variable
,
1217 WCHAR
*value
, BOOL isIF
, BOOL conditionTRUE
) {
1219 CMD_LIST
*curPosition
= *cmdList
;
1220 int myDepth
= (*cmdList
)->bracketDepth
;
1222 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1223 cmdList
, wine_dbgstr_w(firstcmd
),
1224 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
1227 /* Skip leading whitespace between condition and the command */
1228 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1230 /* Process the first command, if there is one */
1231 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
1232 WCHAR
*command
= WCMD_strdupW(firstcmd
);
1233 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1234 HeapFree(GetProcessHeap(), 0, command
);
1238 /* If it didn't move the position, step to next command */
1239 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1241 /* Process any other parts of the command */
1243 BOOL processThese
= TRUE
;
1245 if (isIF
) processThese
= conditionTRUE
;
1248 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
1250 /* execute all appropriate commands */
1251 curPosition
= *cmdList
;
1253 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1255 (*cmdList
)->prevDelim
,
1256 (*cmdList
)->bracketDepth
, myDepth
);
1258 /* Execute any statements appended to the line */
1259 /* FIXME: Only if previous call worked for && or failed for || */
1260 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1261 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1262 if (processThese
&& (*cmdList
)->command
) {
1263 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
, variable
,
1266 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1268 /* Execute any appended to the statement with (...) */
1269 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1271 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
1272 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1274 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1276 /* End of the command - does 'ELSE ' follow as the next command? */
1278 if (isIF
&& CompareStringW(LOCALE_USER_DEFAULT
,
1279 NORM_IGNORECASE
| SORT_STRINGSORT
,
1280 (*cmdList
)->command
, 5, ifElse
, -1) == 2) {
1282 /* Swap between if and else processing */
1283 processThese
= !processThese
;
1285 /* Process the ELSE part */
1287 WCHAR
*cmd
= ((*cmdList
)->command
) + strlenW(ifElse
);
1289 /* Skip leading whitespace between condition and the command */
1290 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1292 WCMD_execute (cmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1295 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1297 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1306 /**************************************************************************
1309 * Simple on-line help. Help text is stored in the resource file.
1312 void WCMD_give_help (WCHAR
*command
) {
1316 command
= WCMD_strtrim_leading_spaces(command
);
1317 if (strlenW(command
) == 0) {
1318 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
1321 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1322 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1323 command
, -1, inbuilt
[i
], -1) == 2) {
1324 WCMD_output_asis (WCMD_LoadMessage(i
));
1328 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), command
);
1333 /****************************************************************************
1336 * Batch file jump instruction. Not the most efficient algorithm ;-)
1337 * Prints error message if the specified label cannot be found - the file pointer is
1338 * then at EOF, effectively stopping the batch file.
1339 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1342 void WCMD_goto (CMD_LIST
**cmdList
) {
1344 WCHAR string
[MAX_PATH
];
1345 WCHAR current
[MAX_PATH
];
1347 /* Do not process any more parts of a processed multipart or multilines command */
1348 if (cmdList
) *cmdList
= NULL
;
1350 if (param1
[0] == 0x00) {
1351 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1354 if (context
!= NULL
) {
1355 WCHAR
*paramStart
= param1
, *str
;
1356 static const WCHAR eofW
[] = {':','e','o','f','\0'};
1358 /* Handle special :EOF label */
1359 if (lstrcmpiW (eofW
, param1
) == 0) {
1360 context
-> skip_rest
= TRUE
;
1364 /* Support goto :label as well as goto label */
1365 if (*paramStart
== ':') paramStart
++;
1367 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
1368 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
1370 while (isspaceW (*str
)) str
++;
1374 while (((current
[index
] = str
[index
])) && (!isspaceW (current
[index
])))
1377 /* ignore space at the end */
1379 if (lstrcmpiW (current
, paramStart
) == 0) return;
1382 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET
));
1387 /*****************************************************************************
1390 * Push a directory onto the stack
1393 void WCMD_pushd (WCHAR
*command
) {
1394 struct env_stack
*curdir
;
1396 static const WCHAR parmD
[] = {'/','D','\0'};
1398 if (strchrW(command
, '/') != NULL
) {
1399 SetLastError(ERROR_INVALID_PARAMETER
);
1404 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1405 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
1406 if( !curdir
|| !thisdir
) {
1409 WINE_ERR ("out of memory\n");
1413 /* Change directory using CD code with /D parameter */
1414 strcpyW(quals
, parmD
);
1415 GetCurrentDirectoryW (1024, thisdir
);
1417 WCMD_setshow_default(command
);
1423 curdir
-> next
= pushd_directories
;
1424 curdir
-> strings
= thisdir
;
1425 if (pushd_directories
== NULL
) {
1426 curdir
-> u
.stackdepth
= 1;
1428 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1430 pushd_directories
= curdir
;
1435 /*****************************************************************************
1438 * Pop a directory from the stack
1441 void WCMD_popd (void) {
1442 struct env_stack
*temp
= pushd_directories
;
1444 if (!pushd_directories
)
1447 /* pop the old environment from the stack, and make it the current dir */
1448 pushd_directories
= temp
->next
;
1449 SetCurrentDirectoryW(temp
->strings
);
1450 LocalFree (temp
->strings
);
1454 /****************************************************************************
1457 * Batch file conditional.
1459 * On entry, cmdlist will point to command containing the IF, and optionally
1460 * the first command to execute (if brackets not found)
1461 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1462 * If ('s were found, execute all within that bracket
1463 * Command may optionally be followed by an ELSE - need to skip instructions
1464 * in the else using the same logic
1466 * FIXME: Much more syntax checking needed!
1469 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
1471 int negate
= 0, test
= 0;
1472 WCHAR condition
[MAX_PATH
], *command
, *s
;
1473 static const WCHAR notW
[] = {'n','o','t','\0'};
1474 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1475 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
1476 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
1477 static const WCHAR eqeqW
[] = {'=','=','\0'};
1478 static const WCHAR parmI
[] = {'/','I','\0'};
1480 if (!lstrcmpiW (param1
, notW
)) {
1482 strcpyW (condition
, param2
);
1485 strcpyW (condition
, param1
);
1487 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
1489 if (!lstrcmpiW (condition
, errlvlW
)) {
1490 if (errorlevel
>= atoiW(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
1491 WCMD_parameter (p
, 2+negate
, &command
);
1493 else if (!lstrcmpiW (condition
, existW
)) {
1494 if (GetFileAttributesW(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
1497 WCMD_parameter (p
, 2+negate
, &command
);
1499 else if (!lstrcmpiW (condition
, defdW
)) {
1500 if (GetEnvironmentVariableW(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
1503 WCMD_parameter (p
, 2+negate
, &command
);
1505 else if ((s
= strstrW (p
, eqeqW
))) {
1507 if (strstrW (quals
, parmI
) == NULL
) {
1508 if (!lstrcmpW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1511 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1513 WCMD_parameter (s
, 1, &command
);
1516 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1520 /* Process rest of IF statement which is on the same line
1521 Note: This may process all or some of the cmdList (eg a GOTO) */
1522 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1525 /****************************************************************************
1528 * Move a file, directory tree or wildcarded set of files.
1531 void WCMD_move (void) {
1534 WIN32_FIND_DATAW fd
;
1536 WCHAR input
[MAX_PATH
];
1537 WCHAR output
[MAX_PATH
];
1539 WCHAR dir
[MAX_PATH
];
1540 WCHAR fname
[MAX_PATH
];
1541 WCHAR ext
[MAX_PATH
];
1543 if (param1
[0] == 0x00) {
1544 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1548 /* If no destination supplied, assume current directory */
1549 if (param2
[0] == 0x00) {
1550 strcpyW(param2
, dotW
);
1553 /* If 2nd parm is directory, then use original filename */
1554 /* Convert partial path to full path */
1555 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1556 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1557 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1558 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1560 /* Split into components */
1561 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1563 hff
= FindFirstFileW(input
, &fd
);
1564 while (hff
!= INVALID_HANDLE_VALUE
) {
1565 WCHAR dest
[MAX_PATH
];
1566 WCHAR src
[MAX_PATH
];
1569 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1571 /* Build src & dest name */
1572 strcpyW(src
, drive
);
1575 /* See if dest is an existing directory */
1576 attribs
= GetFileAttributesW(output
);
1577 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1578 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1579 strcpyW(dest
, output
);
1580 strcatW(dest
, slashW
);
1581 strcatW(dest
, fd
.cFileName
);
1583 strcpyW(dest
, output
);
1586 strcatW(src
, fd
.cFileName
);
1588 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1589 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1591 /* Check if file is read only, otherwise move it */
1592 attribs
= GetFileAttributesW(src
);
1593 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1594 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1595 SetLastError(ERROR_ACCESS_DENIED
);
1600 /* If destination exists, prompt unless /Y supplied */
1601 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
1603 WCHAR copycmd
[MAXSTRING
];
1606 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1607 if (strstrW (quals
, parmNoY
))
1609 else if (strstrW (quals
, parmY
))
1612 const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1613 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1614 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1615 && ! lstrcmpiW (copycmd
, parmY
));
1618 /* Prompt if overwriting */
1620 WCHAR question
[MAXSTRING
];
1623 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1625 /* Ask for confirmation */
1626 wsprintfW(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1627 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1629 /* So delete the destination prior to the move */
1631 if (!DeleteFileW(dest
)) {
1632 WCMD_print_error ();
1641 status
= MoveFileW(src
, dest
);
1643 status
= 1; /* Anything other than 0 to prevent error msg below */
1648 WCMD_print_error ();
1652 /* Step on to next match */
1653 if (FindNextFileW(hff
, &fd
) == 0) {
1655 hff
= INVALID_HANDLE_VALUE
;
1661 /****************************************************************************
1664 * Wait for keyboard input.
1667 void WCMD_pause (void) {
1672 WCMD_output (anykey
);
1673 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1674 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1677 /****************************************************************************
1680 * Delete a directory.
1683 void WCMD_remove_dir (WCHAR
*command
) {
1686 int argsProcessed
= 0;
1687 WCHAR
*argN
= command
;
1688 static const WCHAR parmS
[] = {'/','S','\0'};
1689 static const WCHAR parmQ
[] = {'/','Q','\0'};
1691 /* Loop through all args */
1693 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1694 if (argN
&& argN
[0] != '/') {
1695 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1696 wine_dbgstr_w(quals
));
1699 /* If subdirectory search not supplied, just try to remove
1700 and report error if it fails (eg if it contains a file) */
1701 if (strstrW (quals
, parmS
) == NULL
) {
1702 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
1704 /* Otherwise use ShFileOp to recursively remove a directory */
1707 SHFILEOPSTRUCTW lpDir
;
1710 if (strstrW (quals
, parmQ
) == NULL
) {
1712 WCHAR question
[MAXSTRING
];
1713 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1715 /* Ask for confirmation */
1716 wsprintfW(question
, fmt
, thisArg
);
1717 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1719 /* Abort if answer is 'N' */
1726 lpDir
.pFrom
= thisArg
;
1727 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1728 lpDir
.wFunc
= FO_DELETE
;
1729 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
1734 /* Handle no valid args */
1735 if (argsProcessed
== 0) {
1736 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1742 /****************************************************************************
1748 void WCMD_rename (void) {
1752 WIN32_FIND_DATAW fd
;
1753 WCHAR input
[MAX_PATH
];
1754 WCHAR
*dotDst
= NULL
;
1756 WCHAR dir
[MAX_PATH
];
1757 WCHAR fname
[MAX_PATH
];
1758 WCHAR ext
[MAX_PATH
];
1763 /* Must be at least two args */
1764 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1765 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1770 /* Destination cannot contain a drive letter or directory separator */
1771 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1772 SetLastError(ERROR_INVALID_PARAMETER
);
1778 /* Convert partial path to full path */
1779 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1780 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1781 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1782 dotDst
= strchrW(param2
, '.');
1784 /* Split into components */
1785 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1787 hff
= FindFirstFileW(input
, &fd
);
1788 while (hff
!= INVALID_HANDLE_VALUE
) {
1789 WCHAR dest
[MAX_PATH
];
1790 WCHAR src
[MAX_PATH
];
1791 WCHAR
*dotSrc
= NULL
;
1794 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1796 /* FIXME: If dest name or extension is *, replace with filename/ext
1797 part otherwise use supplied name. This supports:
1799 ren jim.* fred.* etc
1800 However, windows has a more complex algorithm supporting eg
1801 ?'s and *'s mid name */
1802 dotSrc
= strchrW(fd
.cFileName
, '.');
1804 /* Build src & dest name */
1805 strcpyW(src
, drive
);
1808 dirLen
= strlenW(src
);
1809 strcatW(src
, fd
.cFileName
);
1812 if (param2
[0] == '*') {
1813 strcatW(dest
, fd
.cFileName
);
1814 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1816 strcatW(dest
, param2
);
1817 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1820 /* Build Extension */
1821 if (dotDst
&& (*(dotDst
+1)=='*')) {
1822 if (dotSrc
) strcatW(dest
, dotSrc
);
1823 } else if (dotDst
) {
1824 if (dotDst
) strcatW(dest
, dotDst
);
1827 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1828 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1830 /* Check if file is read only, otherwise move it */
1831 attribs
= GetFileAttributesW(src
);
1832 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1833 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1834 SetLastError(ERROR_ACCESS_DENIED
);
1837 status
= MoveFileW(src
, dest
);
1841 WCMD_print_error ();
1845 /* Step on to next match */
1846 if (FindNextFileW(hff
, &fd
) == 0) {
1848 hff
= INVALID_HANDLE_VALUE
;
1854 /*****************************************************************************
1857 * Make a copy of the environment.
1859 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1869 len
+= (strlenW(&env
[len
]) + 1);
1871 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1874 WINE_ERR("out of memory\n");
1877 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1883 /*****************************************************************************
1886 * setlocal pushes the environment onto a stack
1887 * Save the environment as unicode so we don't screw anything up.
1889 void WCMD_setlocal (const WCHAR
*s
) {
1891 struct env_stack
*env_copy
;
1892 WCHAR cwd
[MAX_PATH
];
1894 /* DISABLEEXTENSIONS ignored */
1896 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1899 WINE_ERR ("out of memory\n");
1903 env
= GetEnvironmentStringsW ();
1905 env_copy
->strings
= WCMD_dupenv (env
);
1906 if (env_copy
->strings
)
1908 env_copy
->next
= saved_environment
;
1909 saved_environment
= env_copy
;
1911 /* Save the current drive letter */
1912 GetCurrentDirectoryW(MAX_PATH
, cwd
);
1913 env_copy
->u
.cwd
= cwd
[0];
1916 LocalFree (env_copy
);
1918 FreeEnvironmentStringsW (env
);
1922 /*****************************************************************************
1925 * endlocal pops the environment off a stack
1926 * Note: When searching for '=', search from WCHAR position 1, to handle
1927 * special internal environment variables =C:, =D: etc
1929 void WCMD_endlocal (void) {
1930 WCHAR
*env
, *old
, *p
;
1931 struct env_stack
*temp
;
1934 if (!saved_environment
)
1937 /* pop the old environment from the stack */
1938 temp
= saved_environment
;
1939 saved_environment
= temp
->next
;
1941 /* delete the current environment, totally */
1942 env
= GetEnvironmentStringsW ();
1943 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1946 n
= strlenW(&old
[len
]) + 1;
1947 p
= strchrW(&old
[len
] + 1, '=');
1951 SetEnvironmentVariableW (&old
[len
], NULL
);
1956 FreeEnvironmentStringsW (env
);
1958 /* restore old environment */
1959 env
= temp
->strings
;
1962 n
= strlenW(&env
[len
]) + 1;
1963 p
= strchrW(&env
[len
] + 1, '=');
1967 SetEnvironmentVariableW (&env
[len
], p
);
1972 /* Restore current drive letter */
1973 if (IsCharAlphaW(temp
->u
.cwd
)) {
1975 WCHAR cwd
[MAX_PATH
];
1976 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
1978 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
1979 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
1980 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
1981 SetCurrentDirectoryW(cwd
);
1989 /*****************************************************************************
1990 * WCMD_setshow_attrib
1992 * Display and optionally sets DOS attributes on a file or directory
1996 void WCMD_setshow_attrib (void) {
2000 WIN32_FIND_DATAW fd
;
2001 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2002 WCHAR
*name
= param1
;
2004 DWORD attrib_clear
=0;
2006 if (param1
[0] == '+' || param1
[0] == '-') {
2008 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2009 switch (param1
[1]) {
2010 case 'H': case 'h': attrib
|= FILE_ATTRIBUTE_HIDDEN
; break;
2011 case 'S': case 's': attrib
|= FILE_ATTRIBUTE_SYSTEM
; break;
2012 case 'R': case 'r': attrib
|= FILE_ATTRIBUTE_READONLY
; break;
2013 case 'A': case 'a': attrib
|= FILE_ATTRIBUTE_ARCHIVE
; break;
2015 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2018 switch (param1
[0]) {
2019 case '+': attrib_set
= attrib
; break;
2020 case '-': attrib_clear
= attrib
; break;
2025 if (strlenW(name
) == 0) {
2026 static const WCHAR slashStarW
[] = {'\\','*','\0'};
2028 GetCurrentDirectoryW(sizeof(param2
)/sizeof(WCHAR
), name
);
2029 strcatW (name
, slashStarW
);
2032 hff
= FindFirstFileW(name
, &fd
);
2033 if (hff
== INVALID_HANDLE_VALUE
) {
2034 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), name
);
2038 if (attrib_set
|| attrib_clear
) {
2039 fd
.dwFileAttributes
&= ~attrib_clear
;
2040 fd
.dwFileAttributes
|= attrib_set
;
2041 if (!fd
.dwFileAttributes
)
2042 fd
.dwFileAttributes
|= FILE_ATTRIBUTE_NORMAL
;
2043 SetFileAttributesW(name
, fd
.dwFileAttributes
);
2045 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2046 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
2049 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
2052 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
2055 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
2058 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
2061 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
2064 WCMD_output (fmt
, flags
, fd
.cFileName
);
2065 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
2067 } while (FindNextFileW(hff
, &fd
) != 0);
2072 /*****************************************************************************
2073 * WCMD_setshow_default
2075 * Set/Show the current default directory
2078 void WCMD_setshow_default (WCHAR
*command
) {
2084 WIN32_FIND_DATAW fd
;
2086 static const WCHAR parmD
[] = {'/','D','\0'};
2088 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
2090 /* Skip /D and trailing whitespace if on the front of the command line */
2091 if (CompareStringW(LOCALE_USER_DEFAULT
,
2092 NORM_IGNORECASE
| SORT_STRINGSORT
,
2093 command
, 2, parmD
, -1) == 2) {
2095 while (*command
&& *command
==' ') command
++;
2098 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
2099 if (strlenW(command
) == 0) {
2100 strcatW (cwd
, newline
);
2104 /* Remove any double quotes, which may be in the
2105 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2108 if (*command
!= '"') *pos
++ = *command
;
2113 /* Search for appropriate directory */
2114 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
2115 hff
= FindFirstFileW(string
, &fd
);
2116 while (hff
!= INVALID_HANDLE_VALUE
) {
2117 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
2118 WCHAR fpath
[MAX_PATH
];
2120 WCHAR dir
[MAX_PATH
];
2121 WCHAR fname
[MAX_PATH
];
2122 WCHAR ext
[MAX_PATH
];
2123 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
2125 /* Convert path into actual directory spec */
2126 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
2127 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
2130 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
2133 hff
= INVALID_HANDLE_VALUE
;
2137 /* Step on to next match */
2138 if (FindNextFileW(hff
, &fd
) == 0) {
2140 hff
= INVALID_HANDLE_VALUE
;
2145 /* Change to that directory */
2146 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
2148 status
= SetCurrentDirectoryW(string
);
2151 WCMD_print_error ();
2155 /* Save away the actual new directory, to store as current location */
2156 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
2158 /* Restore old directory if drive letter would change, and
2159 CD x:\directory /D (or pushd c:\directory) not supplied */
2160 if ((strstrW(quals
, parmD
) == NULL
) &&
2161 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
2162 SetCurrentDirectoryW(cwd
);
2166 /* Set special =C: type environment variable, for drive letter of
2167 change of directory, even if path was restored due to missing
2168 /D (allows changing drive letter when not resident on that
2170 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
2172 strcpyW(env
, equalW
);
2173 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
2175 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
2176 SetEnvironmentVariableW(env
, string
);
2183 /****************************************************************************
2186 * Set/Show the system date
2187 * FIXME: Can't change date yet
2190 void WCMD_setshow_date (void) {
2192 WCHAR curdate
[64], buffer
[64];
2194 static const WCHAR parmT
[] = {'/','T','\0'};
2196 if (strlenW(param1
) == 0) {
2197 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
2198 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
2200 if (strstrW (quals
, parmT
) == NULL
) {
2201 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
2202 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
2203 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2205 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2209 else WCMD_print_error ();
2212 WCMD_output (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) == 2) {
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) == 2) {
2302 WCHAR string
[MAXSTRING
];
2306 while (*s
&& *s
==' ') s
++;
2308 WCMD_opt_s_strip_quotes(s
);
2310 /* If no parameter, or no '=' sign, return an error */
2311 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2312 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2316 /* Output the prompt */
2318 if (strlenW(p
) != 0) WCMD_output(p
);
2320 /* Read the reply */
2321 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2322 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2324 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2325 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2326 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2327 wine_dbgstr_w(string
));
2328 status
= SetEnvironmentVariableW(s
, string
);
2335 WCMD_opt_s_strip_quotes(s
);
2336 p
= strchrW (s
, '=');
2338 env
= GetEnvironmentStringsW();
2339 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2340 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2347 if (strlenW(p
) == 0) p
= NULL
;
2348 status
= SetEnvironmentVariableW(s
, p
);
2349 gle
= GetLastError();
2350 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2352 } else if ((!status
)) WCMD_print_error();
2356 /****************************************************************************
2359 * Set/Show the path environment variable
2362 void WCMD_setshow_path (WCHAR
*command
) {
2366 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2367 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2369 if (strlenW(param1
) == 0) {
2370 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2372 WCMD_output_asis ( pathEqW
);
2373 WCMD_output_asis ( string
);
2374 WCMD_output_asis ( newline
);
2377 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
2381 if (*command
== '=') command
++; /* Skip leading '=' */
2382 status
= SetEnvironmentVariableW(pathW
, command
);
2383 if (!status
) WCMD_print_error();
2387 /****************************************************************************
2388 * WCMD_setshow_prompt
2390 * Set or show the command prompt.
2393 void WCMD_setshow_prompt (void) {
2396 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2398 if (strlenW(param1
) == 0) {
2399 SetEnvironmentVariableW(promptW
, NULL
);
2403 while ((*s
== '=') || (*s
== ' ')) s
++;
2404 if (strlenW(s
) == 0) {
2405 SetEnvironmentVariableW(promptW
, NULL
);
2407 else SetEnvironmentVariableW(promptW
, s
);
2411 /****************************************************************************
2414 * Set/Show the system time
2415 * FIXME: Can't change time yet
2418 void WCMD_setshow_time (void) {
2420 WCHAR curtime
[64], buffer
[64];
2423 static const WCHAR parmT
[] = {'/','T','\0'};
2425 if (strlenW(param1
) == 0) {
2427 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2428 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2429 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
2430 if (strstrW (quals
, parmT
) == NULL
) {
2431 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2432 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2433 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2435 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2439 else WCMD_print_error ();
2442 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2446 /****************************************************************************
2449 * Shift batch parameters.
2450 * Optional /n says where to start shifting (n=0-8)
2453 void WCMD_shift (WCHAR
*command
) {
2456 if (context
!= NULL
) {
2457 WCHAR
*pos
= strchrW(command
, '/');
2462 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2463 start
= (*(pos
+1) - '0');
2465 SetLastError(ERROR_INVALID_PARAMETER
);
2470 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2471 for (i
=start
;i
<=8;i
++) {
2472 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2474 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2479 /****************************************************************************
2482 * Set the console title
2484 void WCMD_title (WCHAR
*command
) {
2485 SetConsoleTitleW(command
);
2488 /****************************************************************************
2491 * Copy a file to standard output.
2494 void WCMD_type (WCHAR
*command
) {
2497 WCHAR
*argN
= command
;
2498 BOOL writeHeaders
= FALSE
;
2500 if (param1
[0] == 0x00) {
2501 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2505 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2507 /* Loop through all args */
2510 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2518 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2519 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2520 FILE_ATTRIBUTE_NORMAL
, NULL
);
2521 if (h
== INVALID_HANDLE_VALUE
) {
2522 WCMD_print_error ();
2523 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2527 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
2528 WCMD_output(fmt
, thisArg
);
2530 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
, NULL
)) {
2531 if (count
== 0) break; /* ReadFile reports success on EOF! */
2533 WCMD_output_asis (buffer
);
2540 /****************************************************************************
2543 * Output either a file or stdin to screen in pages
2546 void WCMD_more (WCHAR
*command
) {
2549 WCHAR
*argN
= command
;
2551 WCHAR moreStrPage
[100];
2554 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2555 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2556 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2557 ')',' ','-','-','\n','\0'};
2558 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2560 /* Prefix the NLS more with '-- ', then load the text */
2562 strcpyW(moreStr
, moreStart
);
2563 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
2564 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2566 if (param1
[0] == 0x00) {
2568 /* Wine implements pipes via temporary files, and hence stdin is
2569 effectively reading from the file. This means the prompts for
2570 more are satisfied by the next line from the input (file). To
2571 avoid this, ensure stdin is to the console */
2572 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2573 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2574 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2575 FILE_ATTRIBUTE_NORMAL
, 0);
2576 WINE_TRACE("No parms - working probably in pipe mode\n");
2577 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2579 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2580 once you get in this bit unless due to a pipe, its going to end badly... */
2581 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
2583 WCMD_enter_paged_mode(moreStrPage
);
2584 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2585 if (count
== 0) break; /* ReadFile reports success on EOF! */
2587 WCMD_output_asis (buffer
);
2589 WCMD_leave_paged_mode();
2591 /* Restore stdin to what it was */
2592 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2593 CloseHandle(hConIn
);
2597 BOOL needsPause
= FALSE
;
2599 /* Loop through all args */
2600 WINE_TRACE("Parms supplied - working through each file\n");
2601 WCMD_enter_paged_mode(moreStrPage
);
2604 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2612 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
2613 WCMD_leave_paged_mode();
2614 WCMD_output_asis(moreStrPage
);
2615 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2616 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2617 WCMD_enter_paged_mode(moreStrPage
);
2621 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2622 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2623 FILE_ATTRIBUTE_NORMAL
, NULL
);
2624 if (h
== INVALID_HANDLE_VALUE
) {
2625 WCMD_print_error ();
2626 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2630 ULONG64 fileLen
= 0;
2631 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2633 /* Get the file size */
2634 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2635 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2638 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2639 if (count
== 0) break; /* ReadFile reports success on EOF! */
2643 /* Update % count (would be used in WCMD_output_asis as prompt) */
2644 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2646 WCMD_output_asis (buffer
);
2652 WCMD_leave_paged_mode();
2656 /****************************************************************************
2659 * Display verify flag.
2660 * FIXME: We don't actually do anything with the verify flag other than toggle
2664 void WCMD_verify (WCHAR
*command
) {
2668 count
= strlenW(command
);
2670 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2671 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2674 if (lstrcmpiW(command
, onW
) == 0) {
2678 else if (lstrcmpiW(command
, offW
) == 0) {
2682 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
2685 /****************************************************************************
2688 * Display version info.
2691 void WCMD_version (void) {
2693 WCMD_output (version_string
);
2697 /****************************************************************************
2700 * Display volume info and/or set volume label. Returns 0 if error.
2703 int WCMD_volume (int mode
, WCHAR
*path
) {
2705 DWORD count
, serial
;
2706 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2709 if (strlenW(path
) == 0) {
2710 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2712 WCMD_print_error ();
2715 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2716 &serial
, NULL
, NULL
, NULL
, 0);
2719 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2720 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2721 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2724 wsprintfW (curdir
, fmt
, path
);
2725 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2730 WCMD_print_error ();
2733 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2734 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2736 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2737 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2738 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2740 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2741 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2743 if (strlenW(path
) != 0) {
2744 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
2747 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
2753 /**************************************************************************
2756 * Exit either the process, or just this batch program
2760 void WCMD_exit (CMD_LIST
**cmdList
) {
2762 static const WCHAR parmB
[] = {'/','B','\0'};
2763 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2765 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2767 context
-> skip_rest
= TRUE
;
2775 /*****************************************************************************
2778 * Lists or sets file associations (assoc = TRUE)
2779 * Lists or sets file types (assoc = FALSE)
2781 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2784 DWORD accessOptions
= KEY_READ
;
2786 LONG rc
= ERROR_SUCCESS
;
2787 WCHAR keyValue
[MAXSTRING
];
2788 DWORD valueLen
= MAXSTRING
;
2790 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2791 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2793 /* See if parameter includes '=' */
2795 newValue
= strchrW(command
, '=');
2796 if (newValue
) accessOptions
|= KEY_WRITE
;
2798 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2799 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
2800 accessOptions
, &key
) != ERROR_SUCCESS
) {
2801 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2805 /* If no parameters then list all associations */
2806 if (*command
== 0x00) {
2809 /* Enumerate all the keys */
2810 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2811 WCHAR keyName
[MAXSTRING
];
2814 /* Find the next value */
2815 nameLen
= MAXSTRING
;
2816 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
2818 if (rc
== ERROR_SUCCESS
) {
2820 /* Only interested in extension ones if assoc, or others
2822 if ((keyName
[0] == '.' && assoc
) ||
2823 (!(keyName
[0] == '.') && (!assoc
)))
2825 WCHAR subkey
[MAXSTRING
];
2826 strcpyW(subkey
, keyName
);
2827 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2829 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2831 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2832 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2833 WCMD_output_asis(keyName
);
2834 WCMD_output_asis(equalW
);
2835 /* If no default value found, leave line empty after '=' */
2836 if (rc
== ERROR_SUCCESS
) {
2837 WCMD_output_asis(keyValue
);
2839 WCMD_output_asis(newline
);
2840 RegCloseKey(readKey
);
2848 /* Parameter supplied - if no '=' on command line, its a query */
2849 if (newValue
== NULL
) {
2851 WCHAR subkey
[MAXSTRING
];
2853 /* Query terminates the parameter at the first space */
2854 strcpyW(keyValue
, command
);
2855 space
= strchrW(keyValue
, ' ');
2856 if (space
) *space
=0x00;
2858 /* Set up key name */
2859 strcpyW(subkey
, keyValue
);
2860 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2862 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2864 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2865 WCMD_output_asis(command
);
2866 WCMD_output_asis(equalW
);
2867 /* If no default value found, leave line empty after '=' */
2868 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2869 WCMD_output_asis(newline
);
2870 RegCloseKey(readKey
);
2873 WCHAR msgbuffer
[MAXSTRING
];
2874 WCHAR outbuffer
[MAXSTRING
];
2876 /* Load the translated 'File association not found' */
2878 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2880 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2882 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2883 WCMD_output_asis(outbuffer
);
2887 /* Not a query - its a set or clear of a value */
2890 WCHAR subkey
[MAXSTRING
];
2892 /* Get pointer to new value */
2896 /* Set up key name */
2897 strcpyW(subkey
, command
);
2898 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2900 /* If nothing after '=' then clear value - only valid for ASSOC */
2901 if (*newValue
== 0x00) {
2903 if (assoc
) rc
= RegDeleteKeyW(key
, command
);
2904 if (assoc
&& rc
== ERROR_SUCCESS
) {
2905 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2907 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2912 WCHAR msgbuffer
[MAXSTRING
];
2913 WCHAR outbuffer
[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 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2924 WCMD_output_asis(outbuffer
);
2928 /* It really is a set value = contents */
2930 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2931 accessOptions
, NULL
, &readKey
, NULL
);
2932 if (rc
== ERROR_SUCCESS
) {
2933 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
2934 (LPBYTE
)newValue
, strlenW(newValue
));
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 * Clear the terminal screen.
2961 void WCMD_color (void) {
2963 /* Emulate by filling the screen from the top left to bottom right with
2964 spaces, then moving the cursor to the top left afterwards */
2965 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2966 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2968 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2969 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR
));
2973 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2979 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2984 /* Convert the color hex digits */
2985 if (param1
[0] == 0x00) {
2986 color
= defaultColor
;
2988 color
= strtoulW(param1
, NULL
, 16);
2991 /* Fail if fg == bg color */
2992 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2997 /* Set the current screen contents and ensure all future writes
2998 remain this color */
2999 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
3000 SetConsoleTextAttribute(hStdOut
, color
);