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 * Clear the terminal screen.
75 void WCMD_clear_screen (void) {
77 /* Emulate by filling the screen from the top left to bottom right with
78 spaces, then moving the cursor to the top left afterwards */
79 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
80 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
82 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
87 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
91 FillConsoleOutputCharacter(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
92 SetConsoleCursorPosition(hStdOut
, topLeft
);
96 /****************************************************************************
99 * Change the default i/o device (ie redirect STDin/STDout).
102 void WCMD_change_tty (void) {
104 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
108 /****************************************************************************
111 * Copy a file or wildcarded set.
112 * FIXME: Add support for a+b+c type syntax
115 void WCMD_copy (void) {
120 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[3];
122 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
123 BOOL copyToDir
= FALSE
;
124 BOOL copyFromDir
= FALSE
;
125 WCHAR srcspec
[MAX_PATH
];
129 WCHAR fname
[MAX_PATH
];
132 if (param1
[0] == 0x00) {
133 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
137 /* Convert source into full spec */
138 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1
));
139 GetFullPathName (param1
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, NULL
);
140 if (srcpath
[strlenW(srcpath
) - 1] == '\\')
141 srcpath
[strlenW(srcpath
) - 1] = '\0';
143 if ((strchrW(srcpath
,'*') == NULL
) && (strchrW(srcpath
,'?') == NULL
)) {
144 attribs
= GetFileAttributes(srcpath
);
148 strcpyW(srcspec
, srcpath
);
150 /* If a directory, then add \* on the end when searching */
151 if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
152 strcatW(srcpath
, slashW
);
154 strcatW(srcspec
, slashW
);
155 strcatW(srcspec
, starW
);
157 WCMD_splitpath(srcpath
, drive
, dir
, fname
, ext
);
158 strcpyW(srcpath
, drive
);
159 strcatW(srcpath
, dir
);
162 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath
));
164 /* If no destination supplied, assume current directory */
165 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2
));
166 if (param2
[0] == 0x00) {
167 strcpyW(param2
, dotW
);
170 GetFullPathName (param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
171 if (outpath
[strlenW(outpath
) - 1] == '\\')
172 outpath
[strlenW(outpath
) - 1] = '\0';
173 attribs
= GetFileAttributes(outpath
);
174 if (attribs
!= INVALID_FILE_ATTRIBUTES
&& (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
175 strcatW (outpath
, slashW
);
178 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
179 wine_dbgstr_w(outpath
), copyToDir
);
181 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
182 if (strstrW (quals
, parmNoY
))
184 else if (strstrW (quals
, parmY
))
187 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
188 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
)) && ! lstrcmpiW (copycmd
, parmY
));
191 /* Loop through all source files */
192 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec
));
193 hff
= FindFirstFile (srcspec
, &fd
);
194 if (hff
!= INVALID_HANDLE_VALUE
) {
196 WCHAR outname
[MAX_PATH
];
197 WCHAR srcname
[MAX_PATH
];
198 BOOL overwrite
= force
;
200 /* Destination is either supplied filename, or source name in
201 supplied destination directory */
202 strcpyW(outname
, outpath
);
203 if (copyToDir
) strcatW(outname
, fd
.cFileName
);
204 strcpyW(srcname
, srcpath
);
205 strcatW(srcname
, fd
.cFileName
);
207 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname
));
208 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
210 /* Skip . and .., and directories */
211 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
213 WINE_TRACE("Skipping directories\n");
216 /* Prompt before overwriting */
217 else if (!overwrite
) {
218 attribs
= GetFileAttributes(outname
);
219 if (attribs
!= INVALID_FILE_ATTRIBUTES
) {
220 WCHAR buffer
[MAXSTRING
];
221 wsprintf(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
222 overwrite
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
224 else overwrite
= TRUE
;
227 /* Do the copy as appropriate */
229 status
= CopyFile (srcname
, outname
, FALSE
);
230 if (!status
) WCMD_print_error ();
233 } while (FindNextFile(hff
, &fd
) != 0);
236 status
= ERROR_FILE_NOT_FOUND
;
241 /****************************************************************************
244 * Create a directory.
246 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
247 * they do not already exist.
250 static BOOL
create_full_path(WCHAR
* path
)
256 new_path
= HeapAlloc(GetProcessHeap(),0,(strlenW(path
) * sizeof(WCHAR
))+1);
257 strcpyW(new_path
,path
);
259 while ((len
= strlenW(new_path
)) && new_path
[len
- 1] == '\\')
260 new_path
[len
- 1] = 0;
262 while (!CreateDirectory(new_path
,NULL
))
265 DWORD last_error
= GetLastError();
266 if (last_error
== ERROR_ALREADY_EXISTS
)
269 if (last_error
!= ERROR_PATH_NOT_FOUND
)
275 if (!(slash
= strrchrW(new_path
,'\\')) && ! (slash
= strrchrW(new_path
,'/')))
281 len
= slash
- new_path
;
283 if (!create_full_path(new_path
))
288 new_path
[len
] = '\\';
290 HeapFree(GetProcessHeap(),0,new_path
);
294 void WCMD_create_dir (void) {
296 if (param1
[0] == 0x00) {
297 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
300 if (!create_full_path(param1
)) WCMD_print_error ();
303 /****************************************************************************
306 * Delete a file or wildcarded set.
309 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
310 * - Each set is a pattern, eg /ahr /as-r means
311 * readonly+hidden OR nonreadonly system files
312 * - The '-' applies to a single field, ie /a:-hr means read only
316 BOOL
WCMD_delete (WCHAR
*command
, BOOL expectDir
) {
319 int argsProcessed
= 0;
320 WCHAR
*argN
= command
;
321 BOOL foundAny
= FALSE
;
322 static const WCHAR parmA
[] = {'/','A','\0'};
323 static const WCHAR parmQ
[] = {'/','Q','\0'};
324 static const WCHAR parmP
[] = {'/','P','\0'};
325 static const WCHAR parmS
[] = {'/','S','\0'};
326 static const WCHAR parmF
[] = {'/','F','\0'};
328 /* If not recursing, clear error flag */
329 if (expectDir
) errorlevel
= 0;
331 /* Loop through all args */
333 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
334 WCHAR argCopy
[MAX_PATH
];
336 if (argN
&& argN
[0] != '/') {
340 WCHAR fpath
[MAX_PATH
];
342 BOOL handleParm
= TRUE
;
344 static const WCHAR anyExt
[]= {'.','*','\0'};
346 strcpyW(argCopy
, thisArg
);
347 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
348 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
351 /* If filename part of parameter is * or *.*, prompt unless
353 if ((strstrW (quals
, parmQ
) == NULL
) && (strstrW (quals
, parmP
) == NULL
)) {
357 WCHAR fname
[MAX_PATH
];
360 /* Convert path into actual directory spec */
361 GetFullPathName (argCopy
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
362 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
364 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
365 if ((strcmpW(fname
, starW
) == 0) &&
366 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
368 WCHAR question
[MAXSTRING
];
369 static const WCHAR fmt
[] = {'%','s',' ','\0'};
371 /* Note: Flag as found, to avoid file not found message */
374 /* Ask for confirmation */
375 wsprintf(question
, fmt
, fpath
);
376 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
378 /* Abort if answer is 'N' */
383 /* First, try to delete in the current directory */
384 hff
= FindFirstFile (argCopy
, &fd
);
385 if (hff
== INVALID_HANDLE_VALUE
) {
391 /* Support del <dirname> by just deleting all files dirname\* */
392 if (handleParm
&& (strchrW(argCopy
,'*') == NULL
) && (strchrW(argCopy
,'?') == NULL
)
393 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
394 WCHAR modifiedParm
[MAX_PATH
];
395 static const WCHAR slashStar
[] = {'\\','*','\0'};
397 strcpyW(modifiedParm
, argCopy
);
398 strcatW(modifiedParm
, slashStar
);
401 WCMD_delete(modifiedParm
, FALSE
);
403 } else if (handleParm
) {
405 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
406 strcpyW (fpath
, argCopy
);
408 p
= strrchrW (fpath
, '\\');
411 strcatW (fpath
, fd
.cFileName
);
413 else strcpyW (fpath
, fd
.cFileName
);
414 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
416 WCHAR
*nextA
= strstrW (quals
, parmA
);
418 /* Handle attribute matching (/A) */
421 while (nextA
!= NULL
&& !ok
) {
423 WCHAR
*thisA
= (nextA
+2);
426 /* Skip optional : */
427 if (*thisA
== ':') thisA
++;
429 /* Parse each of the /A[:]xxx in turn */
430 while (*thisA
&& *thisA
!= '/') {
432 BOOL attribute
= FALSE
;
434 /* Match negation of attribute first */
440 /* Match attribute */
442 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
444 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
446 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
448 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
451 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
454 /* Now check result, keeping a running boolean about whether it
455 matches all parsed attribues so far */
456 if (attribute
&& !negate
) {
458 } else if (!attribute
&& negate
) {
466 /* Save the running total as the final result */
469 /* Step on to next /A set */
470 nextA
= strstrW (nextA
+1, parmA
);
474 /* /P means prompt for each file */
475 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
476 WCHAR question
[MAXSTRING
];
478 /* Ask for confirmation */
479 wsprintf(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
480 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
483 /* Only proceed if ok to */
486 /* If file is read only, and /F supplied, delete it */
487 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
488 strstrW (quals
, parmF
) != NULL
) {
489 SetFileAttributes(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
492 /* Now do the delete */
493 if (!DeleteFile (fpath
)) WCMD_print_error ();
497 } while (FindNextFile(hff
, &fd
) != 0);
501 /* Now recurse into all subdirectories handling the parameter in the same way */
502 if (strstrW (quals
, parmS
) != NULL
) {
504 WCHAR thisDir
[MAX_PATH
];
509 WCHAR fname
[MAX_PATH
];
512 /* Convert path into actual directory spec */
513 GetFullPathName (argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
514 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
516 strcpyW(thisDir
, drive
);
517 strcatW(thisDir
, dir
);
518 cPos
= strlenW(thisDir
);
520 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
522 /* Append '*' to the directory */
524 thisDir
[cPos
+1] = 0x00;
526 hff
= FindFirstFile (thisDir
, &fd
);
528 /* Remove residual '*' */
529 thisDir
[cPos
] = 0x00;
531 if (hff
!= INVALID_HANDLE_VALUE
) {
532 DIRECTORY_STACK
*allDirs
= NULL
;
533 DIRECTORY_STACK
*lastEntry
= NULL
;
536 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
537 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
538 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
540 DIRECTORY_STACK
*nextDir
;
541 WCHAR subParm
[MAX_PATH
];
543 /* Work out search parameter in sub dir */
544 strcpyW (subParm
, thisDir
);
545 strcatW (subParm
, fd
.cFileName
);
546 strcatW (subParm
, slashW
);
547 strcatW (subParm
, fname
);
548 strcatW (subParm
, ext
);
549 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
551 /* Allocate memory, add to list */
552 nextDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
553 if (allDirs
== NULL
) allDirs
= nextDir
;
554 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
556 nextDir
->next
= NULL
;
557 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
558 (strlenW(subParm
)+1) * sizeof(WCHAR
));
559 strcpyW(nextDir
->dirName
, subParm
);
561 } while (FindNextFile(hff
, &fd
) != 0);
564 /* Go through each subdir doing the delete */
565 while (allDirs
!= NULL
) {
566 DIRECTORY_STACK
*tempDir
;
568 tempDir
= allDirs
->next
;
569 found
|= WCMD_delete (allDirs
->dirName
, FALSE
);
571 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
572 HeapFree(GetProcessHeap(),0,allDirs
);
577 /* Keep running total to see if any found, and if not recursing
578 issue error message */
582 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), argCopy
);
589 /* Handle no valid args */
590 if (argsProcessed
== 0) {
591 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
597 /****************************************************************************
600 * Echo input to the screen (or not). We don't try to emulate the bugs
601 * in DOS (try typing "ECHO ON AGAIN" for an example).
604 void WCMD_echo (const WCHAR
*command
) {
608 if ((command
[0] == '.') && (command
[1] == 0)) {
609 WCMD_output (newline
);
614 count
= strlenW(command
);
616 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
617 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
620 if (lstrcmpiW(command
, onW
) == 0) {
624 if (lstrcmpiW(command
, offW
) == 0) {
628 WCMD_output_asis (command
);
629 WCMD_output (newline
);
633 /**************************************************************************
636 * Batch file loop processing.
638 * On entry: cmdList contains the syntax up to the set
639 * next cmdList and all in that bracket contain the set data
640 * next cmdlist contains the DO cmd
641 * following that is either brackets or && entries (as per if)
645 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
650 const WCHAR inW
[] = {'i', 'n', ' ', '\0'};
651 const WCHAR doW
[] = {'d', 'o', ' ', '\0'};
652 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
658 BOOL expandDirs
= FALSE
;
659 BOOL useNumbers
= FALSE
;
660 BOOL doRecursive
= FALSE
;
661 BOOL doFileset
= FALSE
;
662 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
664 CMD_LIST
*thisCmdStart
;
667 /* Handle optional qualifiers (multiple are allowed) */
668 while (*curPos
&& *curPos
== '/') {
669 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos
));
671 switch (toupperW(*curPos
)) {
672 case 'D': curPos
++; expandDirs
= TRUE
; break;
673 case 'L': curPos
++; useNumbers
= TRUE
; break;
675 /* Recursive is special case - /R can have an optional path following it */
676 /* filenamesets are another special case - /F can have an optional options following it */
680 BOOL isRecursive
= (*curPos
== 'R');
682 if (isRecursive
) doRecursive
= TRUE
;
683 else doFileset
= TRUE
;
685 /* Skip whitespace */
687 while (*curPos
&& *curPos
==' ') curPos
++;
689 /* Next parm is either qualifier, path/options or variable -
690 only care about it if it is the path/options */
691 if (*curPos
&& *curPos
!= '/' && *curPos
!= '%') {
692 if (isRecursive
) WINE_FIXME("/R needs to handle supplied root\n");
693 else WINE_FIXME("/F needs to handle options\n");
698 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos
);
702 /* Skip whitespace between qualifiers */
703 while (*curPos
&& *curPos
==' ') curPos
++;
706 /* Skip whitespace before variable */
707 while (*curPos
&& *curPos
==' ') curPos
++;
709 /* Ensure line continues with variable */
710 if (!*curPos
|| *curPos
!= '%') {
711 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
715 /* Variable should follow */
717 while (curPos
[i
] && curPos
[i
]!=' ') i
++;
718 memcpy(&variable
[0], curPos
, i
*sizeof(WCHAR
));
720 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
723 /* Skip whitespace before IN */
724 while (*curPos
&& *curPos
==' ') curPos
++;
726 /* Ensure line continues with IN */
727 if (!*curPos
|| lstrcmpiW (curPos
, inW
)) {
728 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
732 /* Save away where the set of data starts and the variable */
733 thisDepth
= (*cmdList
)->bracketDepth
;
734 *cmdList
= (*cmdList
)->nextcommand
;
735 setStart
= (*cmdList
);
737 /* Skip until the close bracket */
738 WINE_TRACE("Searching %p as the set\n", *cmdList
);
740 (*cmdList
)->command
!= NULL
&&
741 (*cmdList
)->bracketDepth
> thisDepth
) {
742 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
743 *cmdList
= (*cmdList
)->nextcommand
;
746 /* Skip the close bracket, if there is one */
747 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
749 /* Syntax error if missing close bracket, or nothing following it
750 and once we have the complete set, we expect a DO */
751 WINE_TRACE("Looking for 'do' in %p\n", *cmdList
);
752 if ((*cmdList
== NULL
) ||
753 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
754 (*cmdList
)->command
, 3, doW
, -1) != 2)) {
755 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
759 /* Save away the starting position for the commands (and offset for the
763 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
767 /* Loop through all set entries */
769 thisSet
->command
!= NULL
&&
770 thisSet
->bracketDepth
>= thisDepth
) {
772 /* Loop through all entries on the same line */
776 WINE_TRACE("Processing for set %p\n", thisSet
);
778 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
))) {
781 * If the parameter within the set has a wildcard then search for matching files
782 * otherwise do a literal substitution.
784 static const WCHAR wildcards
[] = {'*','?','\0'};
785 thisCmdStart
= cmdStart
;
788 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
790 if (!useNumbers
&& !doFileset
) {
791 if (strpbrkW (item
, wildcards
)) {
792 hff
= FindFirstFile (item
, &fd
);
793 if (hff
!= INVALID_HANDLE_VALUE
) {
795 BOOL isDirectory
= FALSE
;
797 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
799 /* Handle as files or dirs appropriately, but ignore . and .. */
800 if (isDirectory
== expandDirs
&&
801 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
802 (strcmpW(fd
.cFileName
, dotW
) != 0))
804 thisCmdStart
= cmdStart
;
805 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
806 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
807 fd
.cFileName
, FALSE
, TRUE
);
810 } while (FindNextFile(hff
, &fd
) != 0);
814 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
817 } else if (useNumbers
) {
818 /* Convert the first 3 numbers to signed longs and save */
819 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
820 /* else ignore them! */
822 /* Filesets - either a list of files, or a command to run and parse the output */
823 } else if (doFileset
&& *itemStart
!= '"') {
826 WCHAR temp_file
[MAX_PATH
];
828 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
829 wine_dbgstr_w(item
));
831 /* If backquote or single quote, we need to launch that command
832 and parse the results - use a temporary file */
833 if (*itemStart
== '`' || *itemStart
== '\'') {
835 WCHAR temp_path
[MAX_PATH
], temp_cmd
[MAXSTRING
];
836 static const WCHAR redirOut
[] = {'>','%','s','\0'};
837 static const WCHAR cmdW
[] = {'C','M','D','\0'};
839 /* Remove trailing character */
840 itemStart
[strlenW(itemStart
)-1] = 0x00;
842 /* Get temp filename */
843 GetTempPath (sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
844 GetTempFileName (temp_path
, cmdW
, 0, temp_file
);
846 /* Execute program and redirect output */
847 wsprintf (temp_cmd
, redirOut
, (itemStart
+1), temp_file
);
848 WCMD_execute (itemStart
, temp_cmd
, NULL
, NULL
, NULL
);
850 /* Open the file, read line by line and process */
851 input
= CreateFile (temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
852 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
855 /* Open the file, read line by line and process */
856 input
= CreateFile (item
, GENERIC_READ
, FILE_SHARE_READ
,
857 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
860 /* Process the input file */
861 if (input
== INVALID_HANDLE_VALUE
) {
863 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), item
);
865 return; /* FOR loop aborts at first failure here */
869 WCHAR buffer
[MAXSTRING
] = {'\0'};
872 while (WCMD_fgets (buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
874 /* Skip blank lines*/
875 parm
= WCMD_parameter (buffer
, 0, &where
);
876 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
877 wine_dbgstr_w(buffer
));
880 /* FIXME: The following should be moved into its own routine and
881 reused for the string literal parsing below */
882 thisCmdStart
= cmdStart
;
883 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
884 cmdEnd
= thisCmdStart
;
893 /* Delete the temporary file */
894 if (*itemStart
== '`' || *itemStart
== '\'') {
895 DeleteFile (temp_file
);
898 /* Filesets - A string literal */
899 } else if (doFileset
&& *itemStart
== '"') {
900 WCHAR buffer
[MAXSTRING
] = {'\0'};
903 /* Skip blank lines, and re-extract parameter now string has quotes removed */
904 strcpyW(buffer
, item
);
905 parm
= WCMD_parameter (buffer
, 0, &where
);
906 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
907 wine_dbgstr_w(buffer
));
910 /* FIXME: The following should be moved into its own routine and
911 reused for the string literal parsing below */
912 thisCmdStart
= cmdStart
;
913 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
914 cmdEnd
= thisCmdStart
;
918 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
919 cmdEnd
= thisCmdStart
;
923 /* Move onto the next set line */
924 thisSet
= thisSet
->nextcommand
;
927 /* If /L is provided, now run the for loop */
930 static const WCHAR fmt
[] = {'%','d','\0'};
932 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
933 numbers
[0], numbers
[2], numbers
[1]);
935 (numbers
[1]<0)? i
>numbers
[2] : i
<numbers
[2];
938 sprintfW(thisNum
, fmt
, i
);
939 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
941 thisCmdStart
= cmdStart
;
942 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, thisNum
, FALSE
, TRUE
);
943 cmdEnd
= thisCmdStart
;
947 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
948 all processing, OR it should be pointing to the end of && processing OR
949 it should be pointing at the NULL end of bracket for the DO. The return
950 value needs to be the NEXT command to execute, which it either is, or
951 we need to step over the closing bracket */
953 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
957 /*****************************************************************************
960 * Execute a command, and any && or bracketed follow on to the command. The
961 * first command to be executed may not be at the front of the
962 * commands->thiscommand string (eg. it may point after a DO or ELSE)
964 void WCMD_part_execute(CMD_LIST
**cmdList
, WCHAR
*firstcmd
, WCHAR
*variable
,
965 WCHAR
*value
, BOOL isIF
, BOOL conditionTRUE
) {
967 CMD_LIST
*curPosition
= *cmdList
;
968 int myDepth
= (*cmdList
)->bracketDepth
;
970 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
971 cmdList
, wine_dbgstr_w(firstcmd
),
972 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
975 /* Skip leading whitespace between condition and the command */
976 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
978 /* Process the first command, if there is one */
979 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
980 WCHAR
*command
= WCMD_strdupW(firstcmd
);
981 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
986 /* If it didn't move the position, step to next command */
987 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
989 /* Process any other parts of the command */
991 BOOL processThese
= TRUE
;
993 if (isIF
) processThese
= conditionTRUE
;
996 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
998 /* execute all appropriate commands */
999 curPosition
= *cmdList
;
1001 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
1003 (*cmdList
)->isAmphersand
,
1004 (*cmdList
)->bracketDepth
, myDepth
);
1006 /* Execute any appended to the statement with &&'s */
1007 if ((*cmdList
)->isAmphersand
) {
1009 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
, variable
,
1012 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1014 /* Execute any appended to the statement with (...) */
1015 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1017 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
1018 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1020 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1022 /* End of the command - does 'ELSE ' follow as the next command? */
1024 if (isIF
&& CompareString (LOCALE_USER_DEFAULT
,
1025 NORM_IGNORECASE
| SORT_STRINGSORT
,
1026 (*cmdList
)->command
, 5, ifElse
, -1) == 2) {
1028 /* Swap between if and else processing */
1029 processThese
= !processThese
;
1031 /* Process the ELSE part */
1033 WCHAR
*cmd
= ((*cmdList
)->command
) + strlenW(ifElse
);
1035 /* Skip leading whitespace between condition and the command */
1036 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1038 WCMD_execute (cmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1041 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1043 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1052 /**************************************************************************
1055 * Simple on-line help. Help text is stored in the resource file.
1058 void WCMD_give_help (WCHAR
*command
) {
1062 command
= WCMD_strtrim_leading_spaces(command
);
1063 if (strlenW(command
) == 0) {
1064 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
1067 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1068 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1069 param1
, -1, inbuilt
[i
], -1) == 2) {
1070 WCMD_output_asis (WCMD_LoadMessage(i
));
1074 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), param1
);
1079 /****************************************************************************
1082 * Batch file jump instruction. Not the most efficient algorithm ;-)
1083 * Prints error message if the specified label cannot be found - the file pointer is
1084 * then at EOF, effectively stopping the batch file.
1085 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1088 void WCMD_goto (CMD_LIST
**cmdList
) {
1090 WCHAR string
[MAX_PATH
];
1092 /* Do not process any more parts of a processed multipart or multilines command */
1093 if (cmdList
) *cmdList
= NULL
;
1095 if (param1
[0] == 0x00) {
1096 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1099 if (context
!= NULL
) {
1100 WCHAR
*paramStart
= param1
;
1101 static const WCHAR eofW
[] = {':','e','o','f','\0'};
1103 /* Handle special :EOF label */
1104 if (lstrcmpiW (eofW
, param1
) == 0) {
1105 context
-> skip_rest
= TRUE
;
1109 /* Support goto :label as well as goto label */
1110 if (*paramStart
== ':') paramStart
++;
1112 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
1113 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
1114 if ((string
[0] == ':') && (lstrcmpiW (&string
[1], paramStart
) == 0)) return;
1116 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET
));
1121 /*****************************************************************************
1124 * Push a directory onto the stack
1127 void WCMD_pushd (WCHAR
*command
) {
1128 struct env_stack
*curdir
;
1130 static const WCHAR parmD
[] = {'/','D','\0'};
1132 if (strchrW(command
, '/') != NULL
) {
1133 SetLastError(ERROR_INVALID_PARAMETER
);
1138 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1139 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
1140 if( !curdir
|| !thisdir
) {
1143 WINE_ERR ("out of memory\n");
1147 /* Change directory using CD code with /D parameter */
1148 strcpyW(quals
, parmD
);
1149 GetCurrentDirectoryW (1024, thisdir
);
1151 WCMD_setshow_default(command
);
1157 curdir
-> next
= pushd_directories
;
1158 curdir
-> strings
= thisdir
;
1159 if (pushd_directories
== NULL
) {
1160 curdir
-> u
.stackdepth
= 1;
1162 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1164 pushd_directories
= curdir
;
1169 /*****************************************************************************
1172 * Pop a directory from the stack
1175 void WCMD_popd (void) {
1176 struct env_stack
*temp
= pushd_directories
;
1178 if (!pushd_directories
)
1181 /* pop the old environment from the stack, and make it the current dir */
1182 pushd_directories
= temp
->next
;
1183 SetCurrentDirectoryW(temp
->strings
);
1184 LocalFree (temp
->strings
);
1188 /****************************************************************************
1191 * Batch file conditional.
1193 * On entry, cmdlist will point to command containing the IF, and optionally
1194 * the first command to execute (if brackets not found)
1195 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1196 * If ('s were found, execute all within that bracket
1197 * Command may optionally be followed by an ELSE - need to skip instructions
1198 * in the else using the same logic
1200 * FIXME: Much more syntax checking needed!
1203 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
1205 int negate
= 0, test
= 0;
1206 WCHAR condition
[MAX_PATH
], *command
, *s
;
1207 static const WCHAR notW
[] = {'n','o','t','\0'};
1208 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1209 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
1210 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
1211 static const WCHAR eqeqW
[] = {'=','=','\0'};
1213 if (!lstrcmpiW (param1
, notW
)) {
1215 strcpyW (condition
, param2
);
1218 strcpyW (condition
, param1
);
1220 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
1222 if (!lstrcmpiW (condition
, errlvlW
)) {
1223 if (errorlevel
>= atoiW(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
1224 WCMD_parameter (p
, 2+negate
, &command
);
1226 else if (!lstrcmpiW (condition
, existW
)) {
1227 if (GetFileAttributes(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
1230 WCMD_parameter (p
, 2+negate
, &command
);
1232 else if (!lstrcmpiW (condition
, defdW
)) {
1233 if (GetEnvironmentVariable(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
1236 WCMD_parameter (p
, 2+negate
, &command
);
1238 else if ((s
= strstrW (p
, eqeqW
))) {
1240 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1241 WCMD_parameter (s
, 1, &command
);
1244 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1248 /* Process rest of IF statement which is on the same line
1249 Note: This may process all or some of the cmdList (eg a GOTO) */
1250 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1253 /****************************************************************************
1256 * Move a file, directory tree or wildcarded set of files.
1259 void WCMD_move (void) {
1264 WCHAR input
[MAX_PATH
];
1265 WCHAR output
[MAX_PATH
];
1267 WCHAR dir
[MAX_PATH
];
1268 WCHAR fname
[MAX_PATH
];
1269 WCHAR ext
[MAX_PATH
];
1271 if (param1
[0] == 0x00) {
1272 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1276 /* If no destination supplied, assume current directory */
1277 if (param2
[0] == 0x00) {
1278 strcpyW(param2
, dotW
);
1281 /* If 2nd parm is directory, then use original filename */
1282 /* Convert partial path to full path */
1283 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1284 GetFullPathName (param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1285 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1286 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1288 /* Split into components */
1289 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1291 hff
= FindFirstFile (input
, &fd
);
1292 while (hff
!= INVALID_HANDLE_VALUE
) {
1293 WCHAR dest
[MAX_PATH
];
1294 WCHAR src
[MAX_PATH
];
1297 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1299 /* Build src & dest name */
1300 strcpyW(src
, drive
);
1303 /* See if dest is an existing directory */
1304 attribs
= GetFileAttributes(output
);
1305 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1306 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1307 strcpyW(dest
, output
);
1308 strcatW(dest
, slashW
);
1309 strcatW(dest
, fd
.cFileName
);
1311 strcpyW(dest
, output
);
1314 strcatW(src
, fd
.cFileName
);
1316 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1317 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1319 /* Check if file is read only, otherwise move it */
1320 attribs
= GetFileAttributes(src
);
1321 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1322 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1323 SetLastError(ERROR_ACCESS_DENIED
);
1328 /* If destination exists, prompt unless /Y supplied */
1329 if (GetFileAttributes(dest
) != INVALID_FILE_ATTRIBUTES
) {
1331 WCHAR copycmd
[MAXSTRING
];
1334 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1335 if (strstrW (quals
, parmNoY
))
1337 else if (strstrW (quals
, parmY
))
1340 const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1341 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1342 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1343 && ! lstrcmpiW (copycmd
, parmY
));
1346 /* Prompt if overwriting */
1348 WCHAR question
[MAXSTRING
];
1351 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1353 /* Ask for confirmation */
1354 wsprintf(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1355 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1357 /* So delete the destination prior to the move */
1359 if (!DeleteFile (dest
)) {
1360 WCMD_print_error ();
1369 status
= MoveFile (src
, dest
);
1371 status
= 1; /* Anything other than 0 to prevent error msg below */
1376 WCMD_print_error ();
1380 /* Step on to next match */
1381 if (FindNextFile(hff
, &fd
) == 0) {
1383 hff
= INVALID_HANDLE_VALUE
;
1389 /****************************************************************************
1392 * Wait for keyboard input.
1395 void WCMD_pause (void) {
1400 WCMD_output (anykey
);
1401 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1402 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1405 /****************************************************************************
1408 * Delete a directory.
1411 void WCMD_remove_dir (WCHAR
*command
) {
1414 int argsProcessed
= 0;
1415 WCHAR
*argN
= command
;
1416 static const WCHAR parmS
[] = {'/','S','\0'};
1417 static const WCHAR parmQ
[] = {'/','Q','\0'};
1419 /* Loop through all args */
1421 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1422 if (argN
&& argN
[0] != '/') {
1423 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1424 wine_dbgstr_w(quals
));
1427 /* If subdirectory search not supplied, just try to remove
1428 and report error if it fails (eg if it contains a file) */
1429 if (strstrW (quals
, parmS
) == NULL
) {
1430 if (!RemoveDirectory (thisArg
)) WCMD_print_error ();
1432 /* Otherwise use ShFileOp to recursively remove a directory */
1435 SHFILEOPSTRUCT lpDir
;
1438 if (strstrW (quals
, parmQ
) == NULL
) {
1440 WCHAR question
[MAXSTRING
];
1441 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1443 /* Ask for confirmation */
1444 wsprintf(question
, fmt
, thisArg
);
1445 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1447 /* Abort if answer is 'N' */
1454 lpDir
.pFrom
= thisArg
;
1455 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1456 lpDir
.wFunc
= FO_DELETE
;
1457 if (SHFileOperation(&lpDir
)) WCMD_print_error ();
1462 /* Handle no valid args */
1463 if (argsProcessed
== 0) {
1464 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1470 /****************************************************************************
1476 void WCMD_rename (void) {
1481 WCHAR input
[MAX_PATH
];
1482 WCHAR
*dotDst
= NULL
;
1484 WCHAR dir
[MAX_PATH
];
1485 WCHAR fname
[MAX_PATH
];
1486 WCHAR ext
[MAX_PATH
];
1491 /* Must be at least two args */
1492 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1493 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1498 /* Destination cannot contain a drive letter or directory separator */
1499 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1500 SetLastError(ERROR_INVALID_PARAMETER
);
1506 /* Convert partial path to full path */
1507 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1508 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1509 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1510 dotDst
= strchrW(param2
, '.');
1512 /* Split into components */
1513 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1515 hff
= FindFirstFile (input
, &fd
);
1516 while (hff
!= INVALID_HANDLE_VALUE
) {
1517 WCHAR dest
[MAX_PATH
];
1518 WCHAR src
[MAX_PATH
];
1519 WCHAR
*dotSrc
= NULL
;
1522 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1524 /* FIXME: If dest name or extension is *, replace with filename/ext
1525 part otherwise use supplied name. This supports:
1527 ren jim.* fred.* etc
1528 However, windows has a more complex algorithum supporting eg
1529 ?'s and *'s mid name */
1530 dotSrc
= strchrW(fd
.cFileName
, '.');
1532 /* Build src & dest name */
1533 strcpyW(src
, drive
);
1536 dirLen
= strlenW(src
);
1537 strcatW(src
, fd
.cFileName
);
1540 if (param2
[0] == '*') {
1541 strcatW(dest
, fd
.cFileName
);
1542 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1544 strcatW(dest
, param2
);
1545 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1548 /* Build Extension */
1549 if (dotDst
&& (*(dotDst
+1)=='*')) {
1550 if (dotSrc
) strcatW(dest
, dotSrc
);
1551 } else if (dotDst
) {
1552 if (dotDst
) strcatW(dest
, dotDst
);
1555 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1556 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1558 /* Check if file is read only, otherwise move it */
1559 attribs
= GetFileAttributes(src
);
1560 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1561 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1562 SetLastError(ERROR_ACCESS_DENIED
);
1565 status
= MoveFile (src
, dest
);
1569 WCMD_print_error ();
1573 /* Step on to next match */
1574 if (FindNextFile(hff
, &fd
) == 0) {
1576 hff
= INVALID_HANDLE_VALUE
;
1582 /*****************************************************************************
1585 * Make a copy of the environment.
1587 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1597 len
+= (strlenW(&env
[len
]) + 1);
1599 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1602 WINE_ERR("out of memory\n");
1605 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1611 /*****************************************************************************
1614 * setlocal pushes the environment onto a stack
1615 * Save the environment as unicode so we don't screw anything up.
1617 void WCMD_setlocal (const WCHAR
*s
) {
1619 struct env_stack
*env_copy
;
1620 WCHAR cwd
[MAX_PATH
];
1622 /* DISABLEEXTENSIONS ignored */
1624 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1627 WINE_ERR ("out of memory\n");
1631 env
= GetEnvironmentStringsW ();
1633 env_copy
->strings
= WCMD_dupenv (env
);
1634 if (env_copy
->strings
)
1636 env_copy
->next
= saved_environment
;
1637 saved_environment
= env_copy
;
1639 /* Save the current drive letter */
1640 GetCurrentDirectory (MAX_PATH
, cwd
);
1641 env_copy
->u
.cwd
= cwd
[0];
1644 LocalFree (env_copy
);
1646 FreeEnvironmentStringsW (env
);
1650 /*****************************************************************************
1653 * endlocal pops the environment off a stack
1654 * Note: When searching for '=', search from WCHAR position 1, to handle
1655 * special internal environment variables =C:, =D: etc
1657 void WCMD_endlocal (void) {
1658 WCHAR
*env
, *old
, *p
;
1659 struct env_stack
*temp
;
1662 if (!saved_environment
)
1665 /* pop the old environment from the stack */
1666 temp
= saved_environment
;
1667 saved_environment
= temp
->next
;
1669 /* delete the current environment, totally */
1670 env
= GetEnvironmentStringsW ();
1671 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1674 n
= strlenW(&old
[len
]) + 1;
1675 p
= strchrW(&old
[len
] + 1, '=');
1679 SetEnvironmentVariableW (&old
[len
], NULL
);
1684 FreeEnvironmentStringsW (env
);
1686 /* restore old environment */
1687 env
= temp
->strings
;
1690 n
= strlenW(&env
[len
]) + 1;
1691 p
= strchrW(&env
[len
] + 1, '=');
1695 SetEnvironmentVariableW (&env
[len
], p
);
1700 /* Restore current drive letter */
1701 if (IsCharAlpha(temp
->u
.cwd
)) {
1703 WCHAR cwd
[MAX_PATH
];
1704 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
1706 wsprintf(envvar
, fmt
, temp
->u
.cwd
);
1707 if (GetEnvironmentVariable(envvar
, cwd
, MAX_PATH
)) {
1708 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
1709 SetCurrentDirectory(cwd
);
1717 /*****************************************************************************
1718 * WCMD_setshow_attrib
1720 * Display and optionally sets DOS attributes on a file or directory
1722 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1723 * As a result only the Readonly flag is correctly reported, the Archive bit
1724 * is always set and the rest are not implemented. We do the Right Thing anyway.
1726 * FIXME: No SET functionality.
1730 void WCMD_setshow_attrib (void) {
1735 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1737 if (param1
[0] == '-') {
1738 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1742 if (strlenW(param1
) == 0) {
1743 static const WCHAR slashStarW
[] = {'\\','*','\0'};
1745 GetCurrentDirectory (sizeof(param1
)/sizeof(WCHAR
), param1
);
1746 strcatW (param1
, slashStarW
);
1749 hff
= FindFirstFile (param1
, &fd
);
1750 if (hff
== INVALID_HANDLE_VALUE
) {
1751 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), param1
);
1755 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1756 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1757 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1760 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1763 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1766 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1769 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1772 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1775 WCMD_output (fmt
, flags
, fd
.cFileName
);
1776 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1778 } while (FindNextFile(hff
, &fd
) != 0);
1783 /*****************************************************************************
1784 * WCMD_setshow_default
1786 * Set/Show the current default directory
1789 void WCMD_setshow_default (WCHAR
*command
) {
1797 static const WCHAR parmD
[] = {'/','D','\0'};
1799 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
1801 /* Skip /D and trailing whitespace if on the front of the command line */
1802 if (CompareString (LOCALE_USER_DEFAULT
,
1803 NORM_IGNORECASE
| SORT_STRINGSORT
,
1804 command
, 2, parmD
, -1) == 2) {
1806 while (*command
&& *command
==' ') command
++;
1809 GetCurrentDirectory (sizeof(cwd
)/sizeof(WCHAR
), cwd
);
1810 if (strlenW(command
) == 0) {
1811 strcatW (cwd
, newline
);
1815 /* Remove any double quotes, which may be in the
1816 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1819 if (*command
!= '"') *pos
++ = *command
;
1824 /* Search for approprate directory */
1825 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
1826 hff
= FindFirstFile (string
, &fd
);
1827 while (hff
!= INVALID_HANDLE_VALUE
) {
1828 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1829 WCHAR fpath
[MAX_PATH
];
1831 WCHAR dir
[MAX_PATH
];
1832 WCHAR fname
[MAX_PATH
];
1833 WCHAR ext
[MAX_PATH
];
1834 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
1836 /* Convert path into actual directory spec */
1837 GetFullPathName (string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1838 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1841 wsprintf(string
, fmt
, drive
, dir
, fd
.cFileName
);
1844 hff
= INVALID_HANDLE_VALUE
;
1848 /* Step on to next match */
1849 if (FindNextFile(hff
, &fd
) == 0) {
1851 hff
= INVALID_HANDLE_VALUE
;
1856 /* Change to that directory */
1857 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
1859 status
= SetCurrentDirectory (string
);
1862 WCMD_print_error ();
1866 /* Restore old directory if drive letter would change, and
1867 CD x:\directory /D (or pushd c:\directory) not supplied */
1868 if ((strstrW(quals
, parmD
) == NULL
) &&
1869 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
1870 SetCurrentDirectory(cwd
);
1874 /* Set special =C: type environment variable, for drive letter of
1875 change of directory, even if path was restored due to missing
1876 /D (allows changing drive letter when not resident on that
1878 if ((string
[1] == ':') && IsCharAlpha (string
[0])) {
1880 strcpyW(env
, equalW
);
1881 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
1883 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
1884 SetEnvironmentVariable(env
, string
);
1891 /****************************************************************************
1894 * Set/Show the system date
1895 * FIXME: Can't change date yet
1898 void WCMD_setshow_date (void) {
1900 WCHAR curdate
[64], buffer
[64];
1902 static const WCHAR parmT
[] = {'/','T','\0'};
1904 if (strlenW(param1
) == 0) {
1905 if (GetDateFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1906 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
1907 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
1908 if (strstrW (quals
, parmT
) == NULL
) {
1909 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
1910 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
1911 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1913 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1917 else WCMD_print_error ();
1920 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1924 /****************************************************************************
1927 static int WCMD_compare( const void *a
, const void *b
)
1930 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
1931 r
= CompareString( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1932 *str_a
, -1, *str_b
, -1 );
1933 if( r
== CSTR_LESS_THAN
) return -1;
1934 if( r
== CSTR_GREATER_THAN
) return 1;
1938 /****************************************************************************
1939 * WCMD_setshow_sortenv
1941 * sort variables into order for display
1942 * Optionally only display those who start with a stub
1943 * returns the count displayed
1945 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
1947 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
1950 if (stub
) stublen
= strlenW(stub
);
1952 /* count the number of strings, and the total length */
1954 len
+= (strlenW(&s
[len
]) + 1);
1958 /* add the strings to an array */
1959 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
1963 for( i
=1; i
<count
; i
++ )
1964 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
1966 /* sort the array */
1967 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
1970 for( i
=0; i
<count
; i
++ ) {
1971 if (!stub
|| CompareString (LOCALE_USER_DEFAULT
,
1972 NORM_IGNORECASE
| SORT_STRINGSORT
,
1973 str
[i
], stublen
, stub
, -1) == 2) {
1974 /* Don't display special internal variables */
1975 if (str
[i
][0] != '=') {
1976 WCMD_output_asis(str
[i
]);
1977 WCMD_output_asis(newline
);
1984 return displayedcount
;
1987 /****************************************************************************
1990 * Set/Show the environment variables
1993 void WCMD_setshow_env (WCHAR
*s
) {
1998 static const WCHAR parmP
[] = {'/','P','\0'};
2001 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
2002 env
= GetEnvironmentStrings ();
2003 WCMD_setshow_sortenv( env
, NULL
);
2007 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2008 if (CompareString (LOCALE_USER_DEFAULT
,
2009 NORM_IGNORECASE
| SORT_STRINGSORT
,
2010 s
, 2, parmP
, -1) == 2) {
2011 WCHAR string
[MAXSTRING
];
2015 while (*s
&& *s
==' ') s
++;
2017 /* If no parameter, or no '=' sign, return an error */
2018 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2019 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2023 /* Output the prompt */
2025 if (strlenW(p
) != 0) WCMD_output(p
);
2027 /* Read the reply */
2028 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2029 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2031 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2032 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2033 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2034 wine_dbgstr_w(string
));
2035 status
= SetEnvironmentVariable (s
, string
);
2040 p
= strchrW (s
, '=');
2042 env
= GetEnvironmentStrings ();
2043 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2044 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2051 if (strlenW(p
) == 0) p
= NULL
;
2052 status
= SetEnvironmentVariable (s
, p
);
2053 gle
= GetLastError();
2054 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2056 } else if ((!status
)) WCMD_print_error();
2060 /****************************************************************************
2063 * Set/Show the path environment variable
2066 void WCMD_setshow_path (WCHAR
*command
) {
2070 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2071 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2073 if (strlenW(param1
) == 0) {
2074 status
= GetEnvironmentVariable (pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2076 WCMD_output_asis ( pathEqW
);
2077 WCMD_output_asis ( string
);
2078 WCMD_output_asis ( newline
);
2081 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
2085 if (*command
== '=') command
++; /* Skip leading '=' */
2086 status
= SetEnvironmentVariable (pathW
, command
);
2087 if (!status
) WCMD_print_error();
2091 /****************************************************************************
2092 * WCMD_setshow_prompt
2094 * Set or show the command prompt.
2097 void WCMD_setshow_prompt (void) {
2100 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2102 if (strlenW(param1
) == 0) {
2103 SetEnvironmentVariable (promptW
, NULL
);
2107 while ((*s
== '=') || (*s
== ' ')) s
++;
2108 if (strlenW(s
) == 0) {
2109 SetEnvironmentVariable (promptW
, NULL
);
2111 else SetEnvironmentVariable (promptW
, s
);
2115 /****************************************************************************
2118 * Set/Show the system time
2119 * FIXME: Can't change time yet
2122 void WCMD_setshow_time (void) {
2124 WCHAR curtime
[64], buffer
[64];
2127 static const WCHAR parmT
[] = {'/','T','\0'};
2129 if (strlenW(param1
) == 0) {
2131 if (GetTimeFormat (LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2132 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2133 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curtime
);
2134 if (strstrW (quals
, parmT
) == NULL
) {
2135 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2136 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2137 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2139 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2143 else WCMD_print_error ();
2146 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2150 /****************************************************************************
2153 * Shift batch parameters.
2154 * Optional /n says where to start shifting (n=0-8)
2157 void WCMD_shift (WCHAR
*command
) {
2160 if (context
!= NULL
) {
2161 WCHAR
*pos
= strchrW(command
, '/');
2166 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2167 start
= (*(pos
+1) - '0');
2169 SetLastError(ERROR_INVALID_PARAMETER
);
2174 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2175 for (i
=start
;i
<=8;i
++) {
2176 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2178 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2183 /****************************************************************************
2186 * Set the console title
2188 void WCMD_title (WCHAR
*command
) {
2189 SetConsoleTitle(command
);
2192 /****************************************************************************
2195 * Copy a file to standard output.
2198 void WCMD_type (WCHAR
*command
) {
2201 WCHAR
*argN
= command
;
2202 BOOL writeHeaders
= FALSE
;
2204 if (param1
[0] == 0x00) {
2205 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2209 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2211 /* Loop through all args */
2214 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2222 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2223 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2224 FILE_ATTRIBUTE_NORMAL
, NULL
);
2225 if (h
== INVALID_HANDLE_VALUE
) {
2226 WCMD_print_error ();
2227 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2231 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
2232 WCMD_output(fmt
, thisArg
);
2234 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
)) {
2235 if (count
== 0) break; /* ReadFile reports success on EOF! */
2237 WCMD_output_asis (buffer
);
2244 /****************************************************************************
2247 * Output either a file or stdin to screen in pages
2250 void WCMD_more (WCHAR
*command
) {
2253 WCHAR
*argN
= command
;
2254 BOOL useinput
= FALSE
;
2256 WCHAR moreStrPage
[100];
2259 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2260 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2261 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2262 ')',' ','-','-','\n','\0'};
2263 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2265 /* Prefix the NLS more with '-- ', then load the text */
2267 strcpyW(moreStr
, moreStart
);
2268 LoadString (hinst
, WCMD_MORESTR
, &moreStr
[3],
2269 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2271 if (param1
[0] == 0x00) {
2273 /* Wine implements pipes via temporary files, and hence stdin is
2274 effectively reading from the file. This means the prompts for
2275 more are satistied by the next line from the input (file). To
2276 avoid this, ensure stdin is to the console */
2277 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2278 HANDLE hConIn
= CreateFile(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2279 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2280 FILE_ATTRIBUTE_NORMAL
, 0);
2281 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2283 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2284 once you get in this bit unless due to a pipe, its going to end badly... */
2286 wsprintf(moreStrPage
, moreFmt
, moreStr
);
2288 WCMD_enter_paged_mode(moreStrPage
);
2289 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2290 if (count
== 0) break; /* ReadFile reports success on EOF! */
2292 WCMD_output_asis (buffer
);
2294 WCMD_leave_paged_mode();
2296 /* Restore stdin to what it was */
2297 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2298 CloseHandle(hConIn
);
2302 BOOL needsPause
= FALSE
;
2304 /* Loop through all args */
2305 WCMD_enter_paged_mode(moreStrPage
);
2308 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2316 wsprintf(moreStrPage
, moreFmt2
, moreStr
, 100);
2317 WCMD_leave_paged_mode();
2318 WCMD_output_asis(moreStrPage
);
2319 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2320 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2321 WCMD_enter_paged_mode(moreStrPage
);
2325 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2326 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2327 FILE_ATTRIBUTE_NORMAL
, NULL
);
2328 if (h
== INVALID_HANDLE_VALUE
) {
2329 WCMD_print_error ();
2330 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2334 ULONG64 fileLen
= 0;
2335 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2337 /* Get the file size */
2338 GetFileAttributesEx(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2339 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2342 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2343 if (count
== 0) break; /* ReadFile reports success on EOF! */
2347 /* Update % count (would be used in WCMD_output_asis as prompt) */
2348 wsprintf(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2350 WCMD_output_asis (buffer
);
2356 WCMD_leave_paged_mode();
2360 /****************************************************************************
2363 * Display verify flag.
2364 * FIXME: We don't actually do anything with the verify flag other than toggle
2368 void WCMD_verify (WCHAR
*command
) {
2372 count
= strlenW(command
);
2374 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2375 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2378 if (lstrcmpiW(command
, onW
) == 0) {
2382 else if (lstrcmpiW(command
, offW
) == 0) {
2386 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
2389 /****************************************************************************
2392 * Display version info.
2395 void WCMD_version (void) {
2397 WCMD_output (version_string
);
2401 /****************************************************************************
2404 * Display volume info and/or set volume label. Returns 0 if error.
2407 int WCMD_volume (int mode
, WCHAR
*path
) {
2409 DWORD count
, serial
;
2410 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2413 if (strlenW(path
) == 0) {
2414 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2416 WCMD_print_error ();
2419 status
= GetVolumeInformation (NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2420 &serial
, NULL
, NULL
, NULL
, 0);
2423 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2424 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2425 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2428 wsprintf (curdir
, fmt
, path
);
2429 status
= GetVolumeInformation (curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2434 WCMD_print_error ();
2437 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2438 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2440 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2441 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2442 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2444 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2445 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2447 if (strlenW(path
) != 0) {
2448 if (!SetVolumeLabel (curdir
, string
)) WCMD_print_error ();
2451 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
2457 /**************************************************************************
2460 * Exit either the process, or just this batch program
2464 void WCMD_exit (CMD_LIST
**cmdList
) {
2466 static const WCHAR parmB
[] = {'/','B','\0'};
2467 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2469 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2471 context
-> skip_rest
= TRUE
;
2478 /**************************************************************************
2481 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2484 * Returns True if Y (or A) answer is selected
2485 * If optionAll contains a pointer, ALL is allowed, and if answered
2489 BOOL
WCMD_ask_confirm (WCHAR
*message
, BOOL showSureText
, BOOL
*optionAll
) {
2491 WCHAR msgbuffer
[MAXSTRING
];
2492 WCHAR Ybuffer
[MAXSTRING
];
2493 WCHAR Nbuffer
[MAXSTRING
];
2494 WCHAR Abuffer
[MAXSTRING
];
2495 WCHAR answer
[MAX_PATH
] = {'\0'};
2498 /* Load the translated 'Are you sure', plus valid answers */
2499 LoadString (hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2500 LoadString (hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
2501 LoadString (hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
2502 LoadString (hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
2504 /* Loop waiting on a Y or N */
2505 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
2506 static const WCHAR startBkt
[] = {' ','(','\0'};
2507 static const WCHAR endBkt
[] = {')','?','\0'};
2509 WCMD_output_asis (message
);
2511 WCMD_output_asis (msgbuffer
);
2513 WCMD_output_asis (startBkt
);
2514 WCMD_output_asis (Ybuffer
);
2515 WCMD_output_asis (fslashW
);
2516 WCMD_output_asis (Nbuffer
);
2518 WCMD_output_asis (fslashW
);
2519 WCMD_output_asis (Abuffer
);
2521 WCMD_output_asis (endBkt
);
2522 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
,
2523 sizeof(answer
)/sizeof(WCHAR
), &count
, NULL
);
2524 answer
[0] = toupperW(answer
[0]);
2527 /* Return the answer */
2528 return ((answer
[0] == Ybuffer
[0]) ||
2529 (optionAll
&& (answer
[0] == Abuffer
[0])));
2532 /*****************************************************************************
2535 * Lists or sets file associations (assoc = TRUE)
2536 * Lists or sets file types (assoc = FALSE)
2538 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2541 DWORD accessOptions
= KEY_READ
;
2543 LONG rc
= ERROR_SUCCESS
;
2544 WCHAR keyValue
[MAXSTRING
];
2545 DWORD valueLen
= MAXSTRING
;
2547 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2548 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2550 /* See if parameter includes '=' */
2552 newValue
= strchrW(command
, '=');
2553 if (newValue
) accessOptions
|= KEY_WRITE
;
2555 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2556 if (RegOpenKeyEx(HKEY_CLASSES_ROOT
, nullW
, 0,
2557 accessOptions
, &key
) != ERROR_SUCCESS
) {
2558 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2562 /* If no parameters then list all associations */
2563 if (*command
== 0x00) {
2566 /* Enumerate all the keys */
2567 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2568 WCHAR keyName
[MAXSTRING
];
2571 /* Find the next value */
2572 nameLen
= MAXSTRING
;
2573 rc
= RegEnumKeyEx(key
, index
++,
2575 NULL
, NULL
, NULL
, NULL
);
2577 if (rc
== ERROR_SUCCESS
) {
2579 /* Only interested in extension ones if assoc, or others
2581 if ((keyName
[0] == '.' && assoc
) ||
2582 (!(keyName
[0] == '.') && (!assoc
)))
2584 WCHAR subkey
[MAXSTRING
];
2585 strcpyW(subkey
, keyName
);
2586 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2588 if (RegOpenKeyEx(key
, subkey
, 0,
2589 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2591 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2592 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2593 (LPBYTE
)keyValue
, &valueLen
);
2594 WCMD_output_asis(keyName
);
2595 WCMD_output_asis(equalW
);
2596 /* If no default value found, leave line empty after '=' */
2597 if (rc
== ERROR_SUCCESS
) {
2598 WCMD_output_asis(keyValue
);
2600 WCMD_output_asis(newline
);
2605 RegCloseKey(readKey
);
2609 /* Parameter supplied - if no '=' on command line, its a query */
2610 if (newValue
== NULL
) {
2612 WCHAR subkey
[MAXSTRING
];
2614 /* Query terminates the parameter at the first space */
2615 strcpyW(keyValue
, command
);
2616 space
= strchrW(keyValue
, ' ');
2617 if (space
) *space
=0x00;
2619 /* Set up key name */
2620 strcpyW(subkey
, keyValue
);
2621 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2623 if (RegOpenKeyEx(key
, subkey
, 0,
2624 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2626 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2627 (LPBYTE
)keyValue
, &valueLen
);
2628 WCMD_output_asis(command
);
2629 WCMD_output_asis(equalW
);
2630 /* If no default value found, leave line empty after '=' */
2631 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2632 WCMD_output_asis(newline
);
2633 RegCloseKey(readKey
);
2636 WCHAR msgbuffer
[MAXSTRING
];
2637 WCHAR outbuffer
[MAXSTRING
];
2639 /* Load the translated 'File association not found' */
2641 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2643 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2645 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2646 WCMD_output_asis(outbuffer
);
2650 /* Not a query - its a set or clear of a value */
2653 WCHAR subkey
[MAXSTRING
];
2655 /* Get pointer to new value */
2659 /* Set up key name */
2660 strcpyW(subkey
, command
);
2661 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2663 /* If nothing after '=' then clear value - only valid for ASSOC */
2664 if (*newValue
== 0x00) {
2666 if (assoc
) rc
= RegDeleteKey(key
, command
);
2667 if (assoc
&& rc
== ERROR_SUCCESS
) {
2668 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2670 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2675 WCHAR msgbuffer
[MAXSTRING
];
2676 WCHAR outbuffer
[MAXSTRING
];
2678 /* Load the translated 'File association not found' */
2680 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
,
2681 sizeof(msgbuffer
)/sizeof(WCHAR
));
2683 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
,
2684 sizeof(msgbuffer
)/sizeof(WCHAR
));
2686 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2687 WCMD_output_asis(outbuffer
);
2691 /* It really is a set value = contents */
2693 rc
= RegCreateKeyEx(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2694 accessOptions
, NULL
, &readKey
, NULL
);
2695 if (rc
== ERROR_SUCCESS
) {
2696 rc
= RegSetValueEx(readKey
, NULL
, 0, REG_SZ
,
2697 (LPBYTE
)newValue
, strlenW(newValue
));
2698 RegCloseKey(readKey
);
2701 if (rc
!= ERROR_SUCCESS
) {
2705 WCMD_output_asis(command
);
2706 WCMD_output_asis(equalW
);
2707 WCMD_output_asis(newValue
);
2708 WCMD_output_asis(newline
);
2718 /****************************************************************************
2721 * Clear the terminal screen.
2724 void WCMD_color (void) {
2726 /* Emulate by filling the screen from the top left to bottom right with
2727 spaces, then moving the cursor to the top left afterwards */
2728 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2729 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2731 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2732 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR
));
2736 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2742 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2747 /* Convert the color hex digits */
2748 if (param1
[0] == 0x00) {
2749 color
= defaultColor
;
2751 color
= strtoulW(param1
, NULL
, 16);
2754 /* Fail if fg == bg color */
2755 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2760 /* Set the current screen contents and ensure all future writes
2761 remain this color */
2762 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2763 SetConsoleTextAttribute(hStdOut
, color
);