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 /****************************************************************************
165 * Copy a file or wildcarded set.
166 * FIXME: Add support for a+b+c type syntax
169 void WCMD_copy (void) {
174 WCHAR outpath
[MAX_PATH
], srcpath
[MAX_PATH
], copycmd
[4];
176 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
177 BOOL copyToDir
= FALSE
;
178 BOOL copyFromDir
= FALSE
;
179 WCHAR srcspec
[MAX_PATH
];
183 WCHAR fname
[MAX_PATH
];
186 if (param1
[0] == 0x00) {
187 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
191 /* Convert source into full spec */
192 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1
));
193 GetFullPathNameW(param1
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, NULL
);
194 if (srcpath
[strlenW(srcpath
) - 1] == '\\')
195 srcpath
[strlenW(srcpath
) - 1] = '\0';
197 if ((strchrW(srcpath
,'*') == NULL
) && (strchrW(srcpath
,'?') == NULL
)) {
198 attribs
= GetFileAttributesW(srcpath
);
202 strcpyW(srcspec
, srcpath
);
204 /* If a directory, then add \* on the end when searching */
205 if (attribs
& FILE_ATTRIBUTE_DIRECTORY
) {
206 strcatW(srcpath
, slashW
);
208 strcatW(srcspec
, slashW
);
209 strcatW(srcspec
, starW
);
211 WCMD_splitpath(srcpath
, drive
, dir
, fname
, ext
);
212 strcpyW(srcpath
, drive
);
213 strcatW(srcpath
, dir
);
216 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath
));
218 /* If no destination supplied, assume current directory */
219 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2
));
220 if (param2
[0] == 0x00) {
221 strcpyW(param2
, dotW
);
224 GetFullPathNameW(param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
225 if (outpath
[strlenW(outpath
) - 1] == '\\')
226 outpath
[strlenW(outpath
) - 1] = '\0';
227 attribs
= GetFileAttributesW(outpath
);
228 if (attribs
!= INVALID_FILE_ATTRIBUTES
&& (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
229 strcatW (outpath
, slashW
);
232 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
233 wine_dbgstr_w(outpath
), copyToDir
);
235 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
236 if (strstrW (quals
, parmNoY
))
238 else if (strstrW (quals
, parmY
))
241 /* By default, we will force the overwrite in batch mode and ask for
242 * confirmation in interactive mode. */
245 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
246 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
247 * default behavior. */
248 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
249 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
250 if (!lstrcmpiW (copycmd
, parmY
))
252 else if (!lstrcmpiW (copycmd
, parmNoY
))
257 /* Loop through all source files */
258 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec
));
259 hff
= FindFirstFileW(srcspec
, &fd
);
260 if (hff
!= INVALID_HANDLE_VALUE
) {
262 WCHAR outname
[MAX_PATH
];
263 WCHAR srcname
[MAX_PATH
];
264 BOOL overwrite
= force
;
266 /* Destination is either supplied filename, or source name in
267 supplied destination directory */
268 strcpyW(outname
, outpath
);
269 if (copyToDir
) strcatW(outname
, fd
.cFileName
);
270 strcpyW(srcname
, srcpath
);
271 strcatW(srcname
, fd
.cFileName
);
273 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname
));
274 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
276 /* Skip . and .., and directories */
277 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
279 WINE_TRACE("Skipping directories\n");
282 /* Prompt before overwriting */
283 else if (!overwrite
) {
284 attribs
= GetFileAttributesW(outname
);
285 if (attribs
!= INVALID_FILE_ATTRIBUTES
) {
286 WCHAR buffer
[MAXSTRING
];
287 wsprintfW(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
288 overwrite
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
290 else overwrite
= TRUE
;
293 /* Do the copy as appropriate */
295 status
= CopyFileW(srcname
, outname
, FALSE
);
296 if (!status
) WCMD_print_error ();
299 } while (FindNextFileW(hff
, &fd
) != 0);
302 status
= ERROR_FILE_NOT_FOUND
;
307 /****************************************************************************
310 * Create a directory.
312 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
313 * they do not already exist.
316 static BOOL
create_full_path(WCHAR
* path
)
322 new_path
= HeapAlloc(GetProcessHeap(),0,(strlenW(path
) * sizeof(WCHAR
))+1);
323 strcpyW(new_path
,path
);
325 while ((len
= strlenW(new_path
)) && new_path
[len
- 1] == '\\')
326 new_path
[len
- 1] = 0;
328 while (!CreateDirectoryW(new_path
,NULL
))
331 DWORD last_error
= GetLastError();
332 if (last_error
== ERROR_ALREADY_EXISTS
)
335 if (last_error
!= ERROR_PATH_NOT_FOUND
)
341 if (!(slash
= strrchrW(new_path
,'\\')) && ! (slash
= strrchrW(new_path
,'/')))
347 len
= slash
- new_path
;
349 if (!create_full_path(new_path
))
354 new_path
[len
] = '\\';
356 HeapFree(GetProcessHeap(),0,new_path
);
360 void WCMD_create_dir (void) {
362 if (param1
[0] == 0x00) {
363 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
366 if (!create_full_path(param1
)) WCMD_print_error ();
369 /****************************************************************************
372 * Delete a file or wildcarded set.
375 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
376 * - Each set is a pattern, eg /ahr /as-r means
377 * readonly+hidden OR nonreadonly system files
378 * - The '-' applies to a single field, ie /a:-hr means read only
382 BOOL
WCMD_delete (WCHAR
*command
, BOOL expectDir
) {
385 int argsProcessed
= 0;
386 WCHAR
*argN
= command
;
387 BOOL foundAny
= FALSE
;
388 static const WCHAR parmA
[] = {'/','A','\0'};
389 static const WCHAR parmQ
[] = {'/','Q','\0'};
390 static const WCHAR parmP
[] = {'/','P','\0'};
391 static const WCHAR parmS
[] = {'/','S','\0'};
392 static const WCHAR parmF
[] = {'/','F','\0'};
394 /* If not recursing, clear error flag */
395 if (expectDir
) errorlevel
= 0;
397 /* Loop through all args */
399 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
400 WCHAR argCopy
[MAX_PATH
];
402 if (argN
&& argN
[0] != '/') {
406 WCHAR fpath
[MAX_PATH
];
408 BOOL handleParm
= TRUE
;
410 static const WCHAR anyExt
[]= {'.','*','\0'};
412 strcpyW(argCopy
, thisArg
);
413 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
414 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
417 /* If filename part of parameter is * or *.*, prompt unless
419 if ((strstrW (quals
, parmQ
) == NULL
) && (strstrW (quals
, parmP
) == NULL
)) {
423 WCHAR fname
[MAX_PATH
];
426 /* Convert path into actual directory spec */
427 GetFullPathNameW(argCopy
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
428 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
430 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
431 if ((strcmpW(fname
, starW
) == 0) &&
432 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
434 WCHAR question
[MAXSTRING
];
435 static const WCHAR fmt
[] = {'%','s',' ','\0'};
437 /* Note: Flag as found, to avoid file not found message */
440 /* Ask for confirmation */
441 wsprintfW(question
, fmt
, fpath
);
442 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
444 /* Abort if answer is 'N' */
449 /* First, try to delete in the current directory */
450 hff
= FindFirstFileW(argCopy
, &fd
);
451 if (hff
== INVALID_HANDLE_VALUE
) {
457 /* Support del <dirname> by just deleting all files dirname\* */
458 if (handleParm
&& (strchrW(argCopy
,'*') == NULL
) && (strchrW(argCopy
,'?') == NULL
)
459 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
460 WCHAR modifiedParm
[MAX_PATH
];
461 static const WCHAR slashStar
[] = {'\\','*','\0'};
463 strcpyW(modifiedParm
, argCopy
);
464 strcatW(modifiedParm
, slashStar
);
467 WCMD_delete(modifiedParm
, FALSE
);
469 } else if (handleParm
) {
471 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
472 strcpyW (fpath
, argCopy
);
474 p
= strrchrW (fpath
, '\\');
477 strcatW (fpath
, fd
.cFileName
);
479 else strcpyW (fpath
, fd
.cFileName
);
480 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
482 WCHAR
*nextA
= strstrW (quals
, parmA
);
484 /* Handle attribute matching (/A) */
487 while (nextA
!= NULL
&& !ok
) {
489 WCHAR
*thisA
= (nextA
+2);
492 /* Skip optional : */
493 if (*thisA
== ':') thisA
++;
495 /* Parse each of the /A[:]xxx in turn */
496 while (*thisA
&& *thisA
!= '/') {
498 BOOL attribute
= FALSE
;
500 /* Match negation of attribute first */
506 /* Match attribute */
508 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
510 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
512 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
514 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
517 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
520 /* Now check result, keeping a running boolean about whether it
521 matches all parsed attributes so far */
522 if (attribute
&& !negate
) {
524 } else if (!attribute
&& negate
) {
532 /* Save the running total as the final result */
535 /* Step on to next /A set */
536 nextA
= strstrW (nextA
+1, parmA
);
540 /* /P means prompt for each file */
541 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
542 WCHAR question
[MAXSTRING
];
544 /* Ask for confirmation */
545 wsprintfW(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
546 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
549 /* Only proceed if ok to */
552 /* If file is read only, and /F supplied, delete it */
553 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
554 strstrW (quals
, parmF
) != NULL
) {
555 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
558 /* Now do the delete */
559 if (!DeleteFileW(fpath
)) WCMD_print_error ();
563 } while (FindNextFileW(hff
, &fd
) != 0);
567 /* Now recurse into all subdirectories handling the parameter in the same way */
568 if (strstrW (quals
, parmS
) != NULL
) {
570 WCHAR thisDir
[MAX_PATH
];
575 WCHAR fname
[MAX_PATH
];
578 /* Convert path into actual directory spec */
579 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
580 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
582 strcpyW(thisDir
, drive
);
583 strcatW(thisDir
, dir
);
584 cPos
= strlenW(thisDir
);
586 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
588 /* Append '*' to the directory */
590 thisDir
[cPos
+1] = 0x00;
592 hff
= FindFirstFileW(thisDir
, &fd
);
594 /* Remove residual '*' */
595 thisDir
[cPos
] = 0x00;
597 if (hff
!= INVALID_HANDLE_VALUE
) {
598 DIRECTORY_STACK
*allDirs
= NULL
;
599 DIRECTORY_STACK
*lastEntry
= NULL
;
602 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
603 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
604 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
606 DIRECTORY_STACK
*nextDir
;
607 WCHAR subParm
[MAX_PATH
];
609 /* Work out search parameter in sub dir */
610 strcpyW (subParm
, thisDir
);
611 strcatW (subParm
, fd
.cFileName
);
612 strcatW (subParm
, slashW
);
613 strcatW (subParm
, fname
);
614 strcatW (subParm
, ext
);
615 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
617 /* Allocate memory, add to list */
618 nextDir
= HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
619 if (allDirs
== NULL
) allDirs
= nextDir
;
620 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
622 nextDir
->next
= NULL
;
623 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
624 (strlenW(subParm
)+1) * sizeof(WCHAR
));
625 strcpyW(nextDir
->dirName
, subParm
);
627 } while (FindNextFileW(hff
, &fd
) != 0);
630 /* Go through each subdir doing the delete */
631 while (allDirs
!= NULL
) {
632 DIRECTORY_STACK
*tempDir
;
634 tempDir
= allDirs
->next
;
635 found
|= WCMD_delete (allDirs
->dirName
, FALSE
);
637 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
638 HeapFree(GetProcessHeap(),0,allDirs
);
643 /* Keep running total to see if any found, and if not recursing
644 issue error message */
648 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), argCopy
);
655 /* Handle no valid args */
656 if (argsProcessed
== 0) {
657 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
663 /****************************************************************************
666 * Echo input to the screen (or not). We don't try to emulate the bugs
667 * in DOS (try typing "ECHO ON AGAIN" for an example).
670 void WCMD_echo (const WCHAR
*command
) {
674 if ((command
[0] == '.') && (command
[1] == 0)) {
675 WCMD_output (newline
);
680 count
= strlenW(command
);
682 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
683 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
686 if (lstrcmpiW(command
, onW
) == 0) {
690 if (lstrcmpiW(command
, offW
) == 0) {
694 WCMD_output_asis (command
);
695 WCMD_output (newline
);
699 /**************************************************************************
702 * Batch file loop processing.
704 * On entry: cmdList contains the syntax up to the set
705 * next cmdList and all in that bracket contain the set data
706 * next cmdlist contains the DO cmd
707 * following that is either brackets or && entries (as per if)
711 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
716 const WCHAR inW
[] = {'i', 'n', ' ', '\0'};
717 const WCHAR doW
[] = {'d', 'o', ' ', '\0'};
718 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
724 BOOL expandDirs
= FALSE
;
725 BOOL useNumbers
= FALSE
;
726 BOOL doRecursive
= FALSE
;
727 BOOL doFileset
= FALSE
;
728 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
730 CMD_LIST
*thisCmdStart
;
733 /* Handle optional qualifiers (multiple are allowed) */
734 while (*curPos
&& *curPos
== '/') {
735 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos
));
737 switch (toupperW(*curPos
)) {
738 case 'D': curPos
++; expandDirs
= TRUE
; break;
739 case 'L': curPos
++; useNumbers
= TRUE
; break;
741 /* Recursive is special case - /R can have an optional path following it */
742 /* filenamesets are another special case - /F can have an optional options following it */
746 BOOL isRecursive
= (*curPos
== 'R');
748 if (isRecursive
) doRecursive
= TRUE
;
749 else doFileset
= TRUE
;
751 /* Skip whitespace */
753 while (*curPos
&& *curPos
==' ') curPos
++;
755 /* Next parm is either qualifier, path/options or variable -
756 only care about it if it is the path/options */
757 if (*curPos
&& *curPos
!= '/' && *curPos
!= '%') {
758 if (isRecursive
) WINE_FIXME("/R needs to handle supplied root\n");
759 else WINE_FIXME("/F needs to handle options\n");
764 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos
);
768 /* Skip whitespace between qualifiers */
769 while (*curPos
&& *curPos
==' ') curPos
++;
772 /* Skip whitespace before variable */
773 while (*curPos
&& *curPos
==' ') curPos
++;
775 /* Ensure line continues with variable */
776 if (!*curPos
|| *curPos
!= '%') {
777 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
781 /* Variable should follow */
783 while (curPos
[i
] && curPos
[i
]!=' ') i
++;
784 memcpy(&variable
[0], curPos
, i
*sizeof(WCHAR
));
786 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
789 /* Skip whitespace before IN */
790 while (*curPos
&& *curPos
==' ') curPos
++;
792 /* Ensure line continues with IN */
793 if (!*curPos
|| lstrcmpiW (curPos
, inW
)) {
794 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
798 /* Save away where the set of data starts and the variable */
799 thisDepth
= (*cmdList
)->bracketDepth
;
800 *cmdList
= (*cmdList
)->nextcommand
;
801 setStart
= (*cmdList
);
803 /* Skip until the close bracket */
804 WINE_TRACE("Searching %p as the set\n", *cmdList
);
806 (*cmdList
)->command
!= NULL
&&
807 (*cmdList
)->bracketDepth
> thisDepth
) {
808 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
809 *cmdList
= (*cmdList
)->nextcommand
;
812 /* Skip the close bracket, if there is one */
813 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
815 /* Syntax error if missing close bracket, or nothing following it
816 and once we have the complete set, we expect a DO */
817 WINE_TRACE("Looking for 'do' in %p\n", *cmdList
);
818 if ((*cmdList
== NULL
) ||
819 (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
820 (*cmdList
)->command
, 3, doW
, -1) != 2)) {
821 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
825 /* Save away the starting position for the commands (and offset for the
829 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
833 /* Loop through all set entries */
835 thisSet
->command
!= NULL
&&
836 thisSet
->bracketDepth
>= thisDepth
) {
838 /* Loop through all entries on the same line */
842 WINE_TRACE("Processing for set %p\n", thisSet
);
844 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
))) {
847 * If the parameter within the set has a wildcard then search for matching files
848 * otherwise do a literal substitution.
850 static const WCHAR wildcards
[] = {'*','?','\0'};
851 thisCmdStart
= cmdStart
;
854 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
856 if (!useNumbers
&& !doFileset
) {
857 if (strpbrkW (item
, wildcards
)) {
858 hff
= FindFirstFileW(item
, &fd
);
859 if (hff
!= INVALID_HANDLE_VALUE
) {
861 BOOL isDirectory
= FALSE
;
863 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
865 /* Handle as files or dirs appropriately, but ignore . and .. */
866 if (isDirectory
== expandDirs
&&
867 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
868 (strcmpW(fd
.cFileName
, dotW
) != 0))
870 thisCmdStart
= cmdStart
;
871 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
872 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
873 fd
.cFileName
, FALSE
, TRUE
);
876 } while (FindNextFileW(hff
, &fd
) != 0);
880 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
883 } else if (useNumbers
) {
884 /* Convert the first 3 numbers to signed longs and save */
885 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
886 /* else ignore them! */
888 /* Filesets - either a list of files, or a command to run and parse the output */
889 } else if (doFileset
&& *itemStart
!= '"') {
892 WCHAR temp_file
[MAX_PATH
];
894 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
895 wine_dbgstr_w(item
));
897 /* If backquote or single quote, we need to launch that command
898 and parse the results - use a temporary file */
899 if (*itemStart
== '`' || *itemStart
== '\'') {
901 WCHAR temp_path
[MAX_PATH
], temp_cmd
[MAXSTRING
];
902 static const WCHAR redirOut
[] = {'>','%','s','\0'};
903 static const WCHAR cmdW
[] = {'C','M','D','\0'};
905 /* Remove trailing character */
906 itemStart
[strlenW(itemStart
)-1] = 0x00;
908 /* Get temp filename */
909 GetTempPathW(sizeof(temp_path
)/sizeof(WCHAR
), temp_path
);
910 GetTempFileNameW(temp_path
, cmdW
, 0, temp_file
);
912 /* Execute program and redirect output */
913 wsprintfW(temp_cmd
, redirOut
, (itemStart
+1), temp_file
);
914 WCMD_execute (itemStart
, temp_cmd
, NULL
, NULL
, NULL
);
916 /* Open the file, read line by line and process */
917 input
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
918 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
921 /* Open the file, read line by line and process */
922 input
= CreateFileW(item
, GENERIC_READ
, FILE_SHARE_READ
,
923 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
926 /* Process the input file */
927 if (input
== INVALID_HANDLE_VALUE
) {
929 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), item
);
931 return; /* FOR loop aborts at first failure here */
935 WCHAR buffer
[MAXSTRING
] = {'\0'};
938 while (WCMD_fgets (buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
940 /* Skip blank lines*/
941 parm
= WCMD_parameter (buffer
, 0, &where
);
942 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
943 wine_dbgstr_w(buffer
));
946 /* FIXME: The following should be moved into its own routine and
947 reused for the string literal parsing below */
948 thisCmdStart
= cmdStart
;
949 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
950 cmdEnd
= thisCmdStart
;
959 /* Delete the temporary file */
960 if (*itemStart
== '`' || *itemStart
== '\'') {
961 DeleteFileW(temp_file
);
964 /* Filesets - A string literal */
965 } else if (doFileset
&& *itemStart
== '"') {
966 WCHAR buffer
[MAXSTRING
] = {'\0'};
969 /* Skip blank lines, and re-extract parameter now string has quotes removed */
970 strcpyW(buffer
, item
);
971 parm
= WCMD_parameter (buffer
, 0, &where
);
972 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm
),
973 wine_dbgstr_w(buffer
));
976 /* FIXME: The following should be moved into its own routine and
977 reused for the string literal parsing below */
978 thisCmdStart
= cmdStart
;
979 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, parm
, FALSE
, TRUE
);
980 cmdEnd
= thisCmdStart
;
984 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
985 cmdEnd
= thisCmdStart
;
989 /* Move onto the next set line */
990 thisSet
= thisSet
->nextcommand
;
993 /* If /L is provided, now run the for loop */
996 static const WCHAR fmt
[] = {'%','d','\0'};
998 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
999 numbers
[0], numbers
[2], numbers
[1]);
1001 (numbers
[1]<0)? i
>numbers
[2] : i
<numbers
[2];
1004 sprintfW(thisNum
, fmt
, i
);
1005 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
1007 thisCmdStart
= cmdStart
;
1008 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, thisNum
, FALSE
, TRUE
);
1009 cmdEnd
= thisCmdStart
;
1013 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1014 all processing, OR it should be pointing to the end of && processing OR
1015 it should be pointing at the NULL end of bracket for the DO. The return
1016 value needs to be the NEXT command to execute, which it either is, or
1017 we need to step over the closing bracket */
1019 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
1023 /*****************************************************************************
1026 * Execute a command, and any && or bracketed follow on to the command. The
1027 * first command to be executed may not be at the front of the
1028 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1030 void WCMD_part_execute(CMD_LIST
**cmdList
, WCHAR
*firstcmd
, WCHAR
*variable
,
1031 WCHAR
*value
, BOOL isIF
, BOOL conditionTRUE
) {
1033 CMD_LIST
*curPosition
= *cmdList
;
1034 int myDepth
= (*cmdList
)->bracketDepth
;
1036 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1037 cmdList
, wine_dbgstr_w(firstcmd
),
1038 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
1041 /* Skip leading whitespace between condition and the command */
1042 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1044 /* Process the first command, if there is one */
1045 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
1046 WCHAR
*command
= WCMD_strdupW(firstcmd
);
1047 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1048 HeapFree(GetProcessHeap(), 0, command
);
1052 /* If it didn't move the position, step to next command */
1053 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1055 /* Process any other parts of the command */
1057 BOOL processThese
= TRUE
;
1059 if (isIF
) processThese
= conditionTRUE
;
1062 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
1064 /* execute all appropriate commands */
1065 curPosition
= *cmdList
;
1067 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1069 (*cmdList
)->prevDelim
,
1070 (*cmdList
)->bracketDepth
, myDepth
);
1072 /* Execute any statements appended to the line */
1073 /* FIXME: Only if previous call worked for && or failed for || */
1074 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1075 (*cmdList
)->prevDelim
!= CMD_ONSUCCESS
) {
1076 if (processThese
&& (*cmdList
)->command
) {
1077 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
, variable
,
1080 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1082 /* Execute any appended to the statement with (...) */
1083 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1085 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
1086 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1088 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1090 /* End of the command - does 'ELSE ' follow as the next command? */
1092 if (isIF
&& CompareStringW(LOCALE_USER_DEFAULT
,
1093 NORM_IGNORECASE
| SORT_STRINGSORT
,
1094 (*cmdList
)->command
, 5, ifElse
, -1) == 2) {
1096 /* Swap between if and else processing */
1097 processThese
= !processThese
;
1099 /* Process the ELSE part */
1101 WCHAR
*cmd
= ((*cmdList
)->command
) + strlenW(ifElse
);
1103 /* Skip leading whitespace between condition and the command */
1104 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1106 WCMD_execute (cmd
, (*cmdList
)->redirects
, variable
, value
, cmdList
);
1109 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1111 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1120 /**************************************************************************
1123 * Simple on-line help. Help text is stored in the resource file.
1126 void WCMD_give_help (WCHAR
*command
) {
1130 command
= WCMD_strtrim_leading_spaces(command
);
1131 if (strlenW(command
) == 0) {
1132 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
1135 for (i
=0; i
<=WCMD_EXIT
; i
++) {
1136 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1137 param1
, -1, inbuilt
[i
], -1) == 2) {
1138 WCMD_output_asis (WCMD_LoadMessage(i
));
1142 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), param1
);
1147 /****************************************************************************
1150 * Batch file jump instruction. Not the most efficient algorithm ;-)
1151 * Prints error message if the specified label cannot be found - the file pointer is
1152 * then at EOF, effectively stopping the batch file.
1153 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1156 void WCMD_goto (CMD_LIST
**cmdList
) {
1158 WCHAR string
[MAX_PATH
];
1160 /* Do not process any more parts of a processed multipart or multilines command */
1161 if (cmdList
) *cmdList
= NULL
;
1163 if (param1
[0] == 0x00) {
1164 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1167 if (context
!= NULL
) {
1168 WCHAR
*paramStart
= param1
;
1169 static const WCHAR eofW
[] = {':','e','o','f','\0'};
1171 /* Handle special :EOF label */
1172 if (lstrcmpiW (eofW
, param1
) == 0) {
1173 context
-> skip_rest
= TRUE
;
1177 /* Support goto :label as well as goto label */
1178 if (*paramStart
== ':') paramStart
++;
1180 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
1181 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
1182 if ((string
[0] == ':') && (lstrcmpiW (&string
[1], paramStart
) == 0)) return;
1184 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET
));
1189 /*****************************************************************************
1192 * Push a directory onto the stack
1195 void WCMD_pushd (WCHAR
*command
) {
1196 struct env_stack
*curdir
;
1198 static const WCHAR parmD
[] = {'/','D','\0'};
1200 if (strchrW(command
, '/') != NULL
) {
1201 SetLastError(ERROR_INVALID_PARAMETER
);
1206 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1207 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
1208 if( !curdir
|| !thisdir
) {
1211 WINE_ERR ("out of memory\n");
1215 /* Change directory using CD code with /D parameter */
1216 strcpyW(quals
, parmD
);
1217 GetCurrentDirectoryW (1024, thisdir
);
1219 WCMD_setshow_default(command
);
1225 curdir
-> next
= pushd_directories
;
1226 curdir
-> strings
= thisdir
;
1227 if (pushd_directories
== NULL
) {
1228 curdir
-> u
.stackdepth
= 1;
1230 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
1232 pushd_directories
= curdir
;
1237 /*****************************************************************************
1240 * Pop a directory from the stack
1243 void WCMD_popd (void) {
1244 struct env_stack
*temp
= pushd_directories
;
1246 if (!pushd_directories
)
1249 /* pop the old environment from the stack, and make it the current dir */
1250 pushd_directories
= temp
->next
;
1251 SetCurrentDirectoryW(temp
->strings
);
1252 LocalFree (temp
->strings
);
1256 /****************************************************************************
1259 * Batch file conditional.
1261 * On entry, cmdlist will point to command containing the IF, and optionally
1262 * the first command to execute (if brackets not found)
1263 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1264 * If ('s were found, execute all within that bracket
1265 * Command may optionally be followed by an ELSE - need to skip instructions
1266 * in the else using the same logic
1268 * FIXME: Much more syntax checking needed!
1271 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
1273 int negate
= 0, test
= 0;
1274 WCHAR condition
[MAX_PATH
], *command
, *s
;
1275 static const WCHAR notW
[] = {'n','o','t','\0'};
1276 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1277 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
1278 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
1279 static const WCHAR eqeqW
[] = {'=','=','\0'};
1281 if (!lstrcmpiW (param1
, notW
)) {
1283 strcpyW (condition
, param2
);
1286 strcpyW (condition
, param1
);
1288 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
1290 if (!lstrcmpiW (condition
, errlvlW
)) {
1291 if (errorlevel
>= atoiW(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
1292 WCMD_parameter (p
, 2+negate
, &command
);
1294 else if (!lstrcmpiW (condition
, existW
)) {
1295 if (GetFileAttributesW(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
1298 WCMD_parameter (p
, 2+negate
, &command
);
1300 else if (!lstrcmpiW (condition
, defdW
)) {
1301 if (GetEnvironmentVariableW(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
1304 WCMD_parameter (p
, 2+negate
, &command
);
1306 else if ((s
= strstrW (p
, eqeqW
))) {
1308 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1309 WCMD_parameter (s
, 1, &command
);
1312 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1316 /* Process rest of IF statement which is on the same line
1317 Note: This may process all or some of the cmdList (eg a GOTO) */
1318 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1321 /****************************************************************************
1324 * Move a file, directory tree or wildcarded set of files.
1327 void WCMD_move (void) {
1330 WIN32_FIND_DATAW fd
;
1332 WCHAR input
[MAX_PATH
];
1333 WCHAR output
[MAX_PATH
];
1335 WCHAR dir
[MAX_PATH
];
1336 WCHAR fname
[MAX_PATH
];
1337 WCHAR ext
[MAX_PATH
];
1339 if (param1
[0] == 0x00) {
1340 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1344 /* If no destination supplied, assume current directory */
1345 if (param2
[0] == 0x00) {
1346 strcpyW(param2
, dotW
);
1349 /* If 2nd parm is directory, then use original filename */
1350 /* Convert partial path to full path */
1351 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1352 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1353 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1354 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1356 /* Split into components */
1357 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1359 hff
= FindFirstFileW(input
, &fd
);
1360 while (hff
!= INVALID_HANDLE_VALUE
) {
1361 WCHAR dest
[MAX_PATH
];
1362 WCHAR src
[MAX_PATH
];
1365 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1367 /* Build src & dest name */
1368 strcpyW(src
, drive
);
1371 /* See if dest is an existing directory */
1372 attribs
= GetFileAttributesW(output
);
1373 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1374 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1375 strcpyW(dest
, output
);
1376 strcatW(dest
, slashW
);
1377 strcatW(dest
, fd
.cFileName
);
1379 strcpyW(dest
, output
);
1382 strcatW(src
, fd
.cFileName
);
1384 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1385 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1387 /* Check if file is read only, otherwise move it */
1388 attribs
= GetFileAttributesW(src
);
1389 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1390 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1391 SetLastError(ERROR_ACCESS_DENIED
);
1396 /* If destination exists, prompt unless /Y supplied */
1397 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
1399 WCHAR copycmd
[MAXSTRING
];
1402 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1403 if (strstrW (quals
, parmNoY
))
1405 else if (strstrW (quals
, parmY
))
1408 const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1409 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1410 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1411 && ! lstrcmpiW (copycmd
, parmY
));
1414 /* Prompt if overwriting */
1416 WCHAR question
[MAXSTRING
];
1419 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1421 /* Ask for confirmation */
1422 wsprintfW(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1423 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1425 /* So delete the destination prior to the move */
1427 if (!DeleteFileW(dest
)) {
1428 WCMD_print_error ();
1437 status
= MoveFileW(src
, dest
);
1439 status
= 1; /* Anything other than 0 to prevent error msg below */
1444 WCMD_print_error ();
1448 /* Step on to next match */
1449 if (FindNextFileW(hff
, &fd
) == 0) {
1451 hff
= INVALID_HANDLE_VALUE
;
1457 /****************************************************************************
1460 * Wait for keyboard input.
1463 void WCMD_pause (void) {
1468 WCMD_output (anykey
);
1469 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1470 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1473 /****************************************************************************
1476 * Delete a directory.
1479 void WCMD_remove_dir (WCHAR
*command
) {
1482 int argsProcessed
= 0;
1483 WCHAR
*argN
= command
;
1484 static const WCHAR parmS
[] = {'/','S','\0'};
1485 static const WCHAR parmQ
[] = {'/','Q','\0'};
1487 /* Loop through all args */
1489 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1490 if (argN
&& argN
[0] != '/') {
1491 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1492 wine_dbgstr_w(quals
));
1495 /* If subdirectory search not supplied, just try to remove
1496 and report error if it fails (eg if it contains a file) */
1497 if (strstrW (quals
, parmS
) == NULL
) {
1498 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
1500 /* Otherwise use ShFileOp to recursively remove a directory */
1503 SHFILEOPSTRUCTW lpDir
;
1506 if (strstrW (quals
, parmQ
) == NULL
) {
1508 WCHAR question
[MAXSTRING
];
1509 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1511 /* Ask for confirmation */
1512 wsprintfW(question
, fmt
, thisArg
);
1513 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1515 /* Abort if answer is 'N' */
1522 lpDir
.pFrom
= thisArg
;
1523 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1524 lpDir
.wFunc
= FO_DELETE
;
1525 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
1530 /* Handle no valid args */
1531 if (argsProcessed
== 0) {
1532 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1538 /****************************************************************************
1544 void WCMD_rename (void) {
1548 WIN32_FIND_DATAW fd
;
1549 WCHAR input
[MAX_PATH
];
1550 WCHAR
*dotDst
= NULL
;
1552 WCHAR dir
[MAX_PATH
];
1553 WCHAR fname
[MAX_PATH
];
1554 WCHAR ext
[MAX_PATH
];
1559 /* Must be at least two args */
1560 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1561 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1566 /* Destination cannot contain a drive letter or directory separator */
1567 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1568 SetLastError(ERROR_INVALID_PARAMETER
);
1574 /* Convert partial path to full path */
1575 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1576 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1577 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1578 dotDst
= strchrW(param2
, '.');
1580 /* Split into components */
1581 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1583 hff
= FindFirstFileW(input
, &fd
);
1584 while (hff
!= INVALID_HANDLE_VALUE
) {
1585 WCHAR dest
[MAX_PATH
];
1586 WCHAR src
[MAX_PATH
];
1587 WCHAR
*dotSrc
= NULL
;
1590 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1592 /* FIXME: If dest name or extension is *, replace with filename/ext
1593 part otherwise use supplied name. This supports:
1595 ren jim.* fred.* etc
1596 However, windows has a more complex algorithm supporting eg
1597 ?'s and *'s mid name */
1598 dotSrc
= strchrW(fd
.cFileName
, '.');
1600 /* Build src & dest name */
1601 strcpyW(src
, drive
);
1604 dirLen
= strlenW(src
);
1605 strcatW(src
, fd
.cFileName
);
1608 if (param2
[0] == '*') {
1609 strcatW(dest
, fd
.cFileName
);
1610 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1612 strcatW(dest
, param2
);
1613 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1616 /* Build Extension */
1617 if (dotDst
&& (*(dotDst
+1)=='*')) {
1618 if (dotSrc
) strcatW(dest
, dotSrc
);
1619 } else if (dotDst
) {
1620 if (dotDst
) strcatW(dest
, dotDst
);
1623 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1624 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1626 /* Check if file is read only, otherwise move it */
1627 attribs
= GetFileAttributesW(src
);
1628 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1629 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1630 SetLastError(ERROR_ACCESS_DENIED
);
1633 status
= MoveFileW(src
, dest
);
1637 WCMD_print_error ();
1641 /* Step on to next match */
1642 if (FindNextFileW(hff
, &fd
) == 0) {
1644 hff
= INVALID_HANDLE_VALUE
;
1650 /*****************************************************************************
1653 * Make a copy of the environment.
1655 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1665 len
+= (strlenW(&env
[len
]) + 1);
1667 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1670 WINE_ERR("out of memory\n");
1673 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1679 /*****************************************************************************
1682 * setlocal pushes the environment onto a stack
1683 * Save the environment as unicode so we don't screw anything up.
1685 void WCMD_setlocal (const WCHAR
*s
) {
1687 struct env_stack
*env_copy
;
1688 WCHAR cwd
[MAX_PATH
];
1690 /* DISABLEEXTENSIONS ignored */
1692 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1695 WINE_ERR ("out of memory\n");
1699 env
= GetEnvironmentStringsW ();
1701 env_copy
->strings
= WCMD_dupenv (env
);
1702 if (env_copy
->strings
)
1704 env_copy
->next
= saved_environment
;
1705 saved_environment
= env_copy
;
1707 /* Save the current drive letter */
1708 GetCurrentDirectoryW(MAX_PATH
, cwd
);
1709 env_copy
->u
.cwd
= cwd
[0];
1712 LocalFree (env_copy
);
1714 FreeEnvironmentStringsW (env
);
1718 /*****************************************************************************
1721 * endlocal pops the environment off a stack
1722 * Note: When searching for '=', search from WCHAR position 1, to handle
1723 * special internal environment variables =C:, =D: etc
1725 void WCMD_endlocal (void) {
1726 WCHAR
*env
, *old
, *p
;
1727 struct env_stack
*temp
;
1730 if (!saved_environment
)
1733 /* pop the old environment from the stack */
1734 temp
= saved_environment
;
1735 saved_environment
= temp
->next
;
1737 /* delete the current environment, totally */
1738 env
= GetEnvironmentStringsW ();
1739 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1742 n
= strlenW(&old
[len
]) + 1;
1743 p
= strchrW(&old
[len
] + 1, '=');
1747 SetEnvironmentVariableW (&old
[len
], NULL
);
1752 FreeEnvironmentStringsW (env
);
1754 /* restore old environment */
1755 env
= temp
->strings
;
1758 n
= strlenW(&env
[len
]) + 1;
1759 p
= strchrW(&env
[len
] + 1, '=');
1763 SetEnvironmentVariableW (&env
[len
], p
);
1768 /* Restore current drive letter */
1769 if (IsCharAlphaW(temp
->u
.cwd
)) {
1771 WCHAR cwd
[MAX_PATH
];
1772 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
1774 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
1775 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
1776 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
1777 SetCurrentDirectoryW(cwd
);
1785 /*****************************************************************************
1786 * WCMD_setshow_attrib
1788 * Display and optionally sets DOS attributes on a file or directory
1792 void WCMD_setshow_attrib (void) {
1796 WIN32_FIND_DATAW fd
;
1797 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1798 WCHAR
*name
= param1
;
1800 DWORD attrib_clear
=0;
1802 if (param1
[0] == '+' || param1
[0] == '-') {
1804 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1805 switch (param1
[1]) {
1806 case 'H': case 'h': attrib
|= FILE_ATTRIBUTE_HIDDEN
; break;
1807 case 'S': case 's': attrib
|= FILE_ATTRIBUTE_SYSTEM
; break;
1808 case 'R': case 'r': attrib
|= FILE_ATTRIBUTE_READONLY
; break;
1809 case 'A': case 'a': attrib
|= FILE_ATTRIBUTE_ARCHIVE
; break;
1811 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1814 switch (param1
[0]) {
1815 case '+': attrib_set
= attrib
; break;
1816 case '-': attrib_clear
= attrib
; break;
1821 if (strlenW(name
) == 0) {
1822 static const WCHAR slashStarW
[] = {'\\','*','\0'};
1824 GetCurrentDirectoryW(sizeof(param2
)/sizeof(WCHAR
), name
);
1825 strcatW (name
, slashStarW
);
1828 hff
= FindFirstFileW(name
, &fd
);
1829 if (hff
== INVALID_HANDLE_VALUE
) {
1830 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), name
);
1834 if (attrib_set
|| attrib_clear
) {
1835 fd
.dwFileAttributes
&= ~attrib_clear
;
1836 fd
.dwFileAttributes
|= attrib_set
;
1837 if (!fd
.dwFileAttributes
)
1838 fd
.dwFileAttributes
|= FILE_ATTRIBUTE_NORMAL
;
1839 SetFileAttributesW(name
, fd
.dwFileAttributes
);
1841 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1842 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1845 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1848 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1851 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1854 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1857 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1860 WCMD_output (fmt
, flags
, fd
.cFileName
);
1861 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1863 } while (FindNextFileW(hff
, &fd
) != 0);
1868 /*****************************************************************************
1869 * WCMD_setshow_default
1871 * Set/Show the current default directory
1874 void WCMD_setshow_default (WCHAR
*command
) {
1880 WIN32_FIND_DATAW fd
;
1882 static const WCHAR parmD
[] = {'/','D','\0'};
1884 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
1886 /* Skip /D and trailing whitespace if on the front of the command line */
1887 if (CompareStringW(LOCALE_USER_DEFAULT
,
1888 NORM_IGNORECASE
| SORT_STRINGSORT
,
1889 command
, 2, parmD
, -1) == 2) {
1891 while (*command
&& *command
==' ') command
++;
1894 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
1895 if (strlenW(command
) == 0) {
1896 strcatW (cwd
, newline
);
1900 /* Remove any double quotes, which may be in the
1901 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1904 if (*command
!= '"') *pos
++ = *command
;
1909 /* Search for appropriate directory */
1910 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
1911 hff
= FindFirstFileW(string
, &fd
);
1912 while (hff
!= INVALID_HANDLE_VALUE
) {
1913 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1914 WCHAR fpath
[MAX_PATH
];
1916 WCHAR dir
[MAX_PATH
];
1917 WCHAR fname
[MAX_PATH
];
1918 WCHAR ext
[MAX_PATH
];
1919 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
1921 /* Convert path into actual directory spec */
1922 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1923 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1926 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
1929 hff
= INVALID_HANDLE_VALUE
;
1933 /* Step on to next match */
1934 if (FindNextFileW(hff
, &fd
) == 0) {
1936 hff
= INVALID_HANDLE_VALUE
;
1941 /* Change to that directory */
1942 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
1944 status
= SetCurrentDirectoryW(string
);
1947 WCMD_print_error ();
1951 /* Save away the actual new directory, to store as current location */
1952 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
1954 /* Restore old directory if drive letter would change, and
1955 CD x:\directory /D (or pushd c:\directory) not supplied */
1956 if ((strstrW(quals
, parmD
) == NULL
) &&
1957 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
1958 SetCurrentDirectoryW(cwd
);
1962 /* Set special =C: type environment variable, for drive letter of
1963 change of directory, even if path was restored due to missing
1964 /D (allows changing drive letter when not resident on that
1966 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
1968 strcpyW(env
, equalW
);
1969 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
1971 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
1972 SetEnvironmentVariableW(env
, string
);
1979 /****************************************************************************
1982 * Set/Show the system date
1983 * FIXME: Can't change date yet
1986 void WCMD_setshow_date (void) {
1988 WCHAR curdate
[64], buffer
[64];
1990 static const WCHAR parmT
[] = {'/','T','\0'};
1992 if (strlenW(param1
) == 0) {
1993 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1994 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
1995 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
1996 if (strstrW (quals
, parmT
) == NULL
) {
1997 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
1998 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
1999 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2001 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2005 else WCMD_print_error ();
2008 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2012 /****************************************************************************
2015 static int WCMD_compare( const void *a
, const void *b
)
2018 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
2019 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2020 *str_a
, -1, *str_b
, -1 );
2021 if( r
== CSTR_LESS_THAN
) return -1;
2022 if( r
== CSTR_GREATER_THAN
) return 1;
2026 /****************************************************************************
2027 * WCMD_setshow_sortenv
2029 * sort variables into order for display
2030 * Optionally only display those who start with a stub
2031 * returns the count displayed
2033 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
2035 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
2038 if (stub
) stublen
= strlenW(stub
);
2040 /* count the number of strings, and the total length */
2042 len
+= (strlenW(&s
[len
]) + 1);
2046 /* add the strings to an array */
2047 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
2051 for( i
=1; i
<count
; i
++ )
2052 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
2054 /* sort the array */
2055 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
2058 for( i
=0; i
<count
; i
++ ) {
2059 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
2060 NORM_IGNORECASE
| SORT_STRINGSORT
,
2061 str
[i
], stublen
, stub
, -1) == 2) {
2062 /* Don't display special internal variables */
2063 if (str
[i
][0] != '=') {
2064 WCMD_output_asis(str
[i
]);
2065 WCMD_output_asis(newline
);
2072 return displayedcount
;
2075 /****************************************************************************
2078 * Set/Show the environment variables
2081 void WCMD_setshow_env (WCHAR
*s
) {
2086 static const WCHAR parmP
[] = {'/','P','\0'};
2089 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
2090 env
= GetEnvironmentStringsW();
2091 WCMD_setshow_sortenv( env
, NULL
);
2095 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2096 if (CompareStringW(LOCALE_USER_DEFAULT
,
2097 NORM_IGNORECASE
| SORT_STRINGSORT
,
2098 s
, 2, parmP
, -1) == 2) {
2099 WCHAR string
[MAXSTRING
];
2103 while (*s
&& *s
==' ') s
++;
2105 WCMD_opt_s_strip_quotes(s
);
2107 /* If no parameter, or no '=' sign, return an error */
2108 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
2109 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2113 /* Output the prompt */
2115 if (strlenW(p
) != 0) WCMD_output(p
);
2117 /* Read the reply */
2118 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2119 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2121 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2122 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2123 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
2124 wine_dbgstr_w(string
));
2125 status
= SetEnvironmentVariableW(s
, string
);
2132 WCMD_opt_s_strip_quotes(s
);
2133 p
= strchrW (s
, '=');
2135 env
= GetEnvironmentStringsW();
2136 if (WCMD_setshow_sortenv( env
, s
) == 0) {
2137 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
2144 if (strlenW(p
) == 0) p
= NULL
;
2145 status
= SetEnvironmentVariableW(s
, p
);
2146 gle
= GetLastError();
2147 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
2149 } else if ((!status
)) WCMD_print_error();
2153 /****************************************************************************
2156 * Set/Show the path environment variable
2159 void WCMD_setshow_path (WCHAR
*command
) {
2163 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
2164 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
2166 if (strlenW(param1
) == 0) {
2167 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
2169 WCMD_output_asis ( pathEqW
);
2170 WCMD_output_asis ( string
);
2171 WCMD_output_asis ( newline
);
2174 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
2178 if (*command
== '=') command
++; /* Skip leading '=' */
2179 status
= SetEnvironmentVariableW(pathW
, command
);
2180 if (!status
) WCMD_print_error();
2184 /****************************************************************************
2185 * WCMD_setshow_prompt
2187 * Set or show the command prompt.
2190 void WCMD_setshow_prompt (void) {
2193 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
2195 if (strlenW(param1
) == 0) {
2196 SetEnvironmentVariableW(promptW
, NULL
);
2200 while ((*s
== '=') || (*s
== ' ')) s
++;
2201 if (strlenW(s
) == 0) {
2202 SetEnvironmentVariableW(promptW
, NULL
);
2204 else SetEnvironmentVariableW(promptW
, s
);
2208 /****************************************************************************
2211 * Set/Show the system time
2212 * FIXME: Can't change time yet
2215 void WCMD_setshow_time (void) {
2217 WCHAR curtime
[64], buffer
[64];
2220 static const WCHAR parmT
[] = {'/','T','\0'};
2222 if (strlenW(param1
) == 0) {
2224 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
2225 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
2226 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
2227 if (strstrW (quals
, parmT
) == NULL
) {
2228 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
2229 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2230 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2232 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2236 else WCMD_print_error ();
2239 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
2243 /****************************************************************************
2246 * Shift batch parameters.
2247 * Optional /n says where to start shifting (n=0-8)
2250 void WCMD_shift (WCHAR
*command
) {
2253 if (context
!= NULL
) {
2254 WCHAR
*pos
= strchrW(command
, '/');
2259 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
2260 start
= (*(pos
+1) - '0');
2262 SetLastError(ERROR_INVALID_PARAMETER
);
2267 WINE_TRACE("Shifting variables, starting at %d\n", start
);
2268 for (i
=start
;i
<=8;i
++) {
2269 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
2271 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
2276 /****************************************************************************
2279 * Set the console title
2281 void WCMD_title (WCHAR
*command
) {
2282 SetConsoleTitleW(command
);
2285 /****************************************************************************
2288 * Copy a file to standard output.
2291 void WCMD_type (WCHAR
*command
) {
2294 WCHAR
*argN
= command
;
2295 BOOL writeHeaders
= FALSE
;
2297 if (param1
[0] == 0x00) {
2298 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
2302 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
2304 /* Loop through all args */
2307 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2315 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2316 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2317 FILE_ATTRIBUTE_NORMAL
, NULL
);
2318 if (h
== INVALID_HANDLE_VALUE
) {
2319 WCMD_print_error ();
2320 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2324 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
2325 WCMD_output(fmt
, thisArg
);
2327 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
, NULL
)) {
2328 if (count
== 0) break; /* ReadFile reports success on EOF! */
2330 WCMD_output_asis (buffer
);
2334 WCMD_output_asis (newline
);
2339 /****************************************************************************
2342 * Output either a file or stdin to screen in pages
2345 void WCMD_more (WCHAR
*command
) {
2348 WCHAR
*argN
= command
;
2349 BOOL useinput
= FALSE
;
2351 WCHAR moreStrPage
[100];
2354 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2355 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2356 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2357 ')',' ','-','-','\n','\0'};
2358 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2360 /* Prefix the NLS more with '-- ', then load the text */
2362 strcpyW(moreStr
, moreStart
);
2363 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
2364 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2366 if (param1
[0] == 0x00) {
2368 /* Wine implements pipes via temporary files, and hence stdin is
2369 effectively reading from the file. This means the prompts for
2370 more are satisfied by the next line from the input (file). To
2371 avoid this, ensure stdin is to the console */
2372 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2373 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2374 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2375 FILE_ATTRIBUTE_NORMAL
, 0);
2376 WINE_TRACE("No parms - working probably in pipe mode\n");
2377 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2379 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2380 once you get in this bit unless due to a pipe, its going to end badly... */
2382 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
2384 WCMD_enter_paged_mode(moreStrPage
);
2385 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2386 if (count
== 0) break; /* ReadFile reports success on EOF! */
2388 WCMD_output_asis (buffer
);
2390 WCMD_leave_paged_mode();
2392 /* Restore stdin to what it was */
2393 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2394 CloseHandle(hConIn
);
2398 BOOL needsPause
= FALSE
;
2400 /* Loop through all args */
2401 WINE_TRACE("Parms supplied - working through each file\n");
2402 WCMD_enter_paged_mode(moreStrPage
);
2405 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2413 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
2414 WCMD_leave_paged_mode();
2415 WCMD_output_asis(moreStrPage
);
2416 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2417 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2418 WCMD_enter_paged_mode(moreStrPage
);
2422 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2423 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2424 FILE_ATTRIBUTE_NORMAL
, NULL
);
2425 if (h
== INVALID_HANDLE_VALUE
) {
2426 WCMD_print_error ();
2427 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2431 ULONG64 fileLen
= 0;
2432 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2434 /* Get the file size */
2435 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2436 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2439 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2440 if (count
== 0) break; /* ReadFile reports success on EOF! */
2444 /* Update % count (would be used in WCMD_output_asis as prompt) */
2445 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2447 WCMD_output_asis (buffer
);
2453 WCMD_leave_paged_mode();
2457 /****************************************************************************
2460 * Display verify flag.
2461 * FIXME: We don't actually do anything with the verify flag other than toggle
2465 void WCMD_verify (WCHAR
*command
) {
2469 count
= strlenW(command
);
2471 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2472 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2475 if (lstrcmpiW(command
, onW
) == 0) {
2479 else if (lstrcmpiW(command
, offW
) == 0) {
2483 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
2486 /****************************************************************************
2489 * Display version info.
2492 void WCMD_version (void) {
2494 WCMD_output (version_string
);
2498 /****************************************************************************
2501 * Display volume info and/or set volume label. Returns 0 if error.
2504 int WCMD_volume (int mode
, WCHAR
*path
) {
2506 DWORD count
, serial
;
2507 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2510 if (strlenW(path
) == 0) {
2511 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2513 WCMD_print_error ();
2516 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2517 &serial
, NULL
, NULL
, NULL
, 0);
2520 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2521 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2522 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2525 wsprintfW (curdir
, fmt
, path
);
2526 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2531 WCMD_print_error ();
2534 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2535 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2537 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2538 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2539 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2541 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2542 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2544 if (strlenW(path
) != 0) {
2545 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
2548 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
2554 /**************************************************************************
2557 * Exit either the process, or just this batch program
2561 void WCMD_exit (CMD_LIST
**cmdList
) {
2563 static const WCHAR parmB
[] = {'/','B','\0'};
2564 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2566 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2568 context
-> skip_rest
= TRUE
;
2576 /*****************************************************************************
2579 * Lists or sets file associations (assoc = TRUE)
2580 * Lists or sets file types (assoc = FALSE)
2582 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2585 DWORD accessOptions
= KEY_READ
;
2587 LONG rc
= ERROR_SUCCESS
;
2588 WCHAR keyValue
[MAXSTRING
];
2589 DWORD valueLen
= MAXSTRING
;
2591 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2592 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2594 /* See if parameter includes '=' */
2596 newValue
= strchrW(command
, '=');
2597 if (newValue
) accessOptions
|= KEY_WRITE
;
2599 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2600 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
2601 accessOptions
, &key
) != ERROR_SUCCESS
) {
2602 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2606 /* If no parameters then list all associations */
2607 if (*command
== 0x00) {
2610 /* Enumerate all the keys */
2611 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2612 WCHAR keyName
[MAXSTRING
];
2615 /* Find the next value */
2616 nameLen
= MAXSTRING
;
2617 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
2619 if (rc
== ERROR_SUCCESS
) {
2621 /* Only interested in extension ones if assoc, or others
2623 if ((keyName
[0] == '.' && assoc
) ||
2624 (!(keyName
[0] == '.') && (!assoc
)))
2626 WCHAR subkey
[MAXSTRING
];
2627 strcpyW(subkey
, keyName
);
2628 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2630 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2632 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2633 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2634 WCMD_output_asis(keyName
);
2635 WCMD_output_asis(equalW
);
2636 /* If no default value found, leave line empty after '=' */
2637 if (rc
== ERROR_SUCCESS
) {
2638 WCMD_output_asis(keyValue
);
2640 WCMD_output_asis(newline
);
2641 RegCloseKey(readKey
);
2649 /* Parameter supplied - if no '=' on command line, its a query */
2650 if (newValue
== NULL
) {
2652 WCHAR subkey
[MAXSTRING
];
2654 /* Query terminates the parameter at the first space */
2655 strcpyW(keyValue
, command
);
2656 space
= strchrW(keyValue
, ' ');
2657 if (space
) *space
=0x00;
2659 /* Set up key name */
2660 strcpyW(subkey
, keyValue
);
2661 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2663 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2665 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
2666 WCMD_output_asis(command
);
2667 WCMD_output_asis(equalW
);
2668 /* If no default value found, leave line empty after '=' */
2669 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2670 WCMD_output_asis(newline
);
2671 RegCloseKey(readKey
);
2674 WCHAR msgbuffer
[MAXSTRING
];
2675 WCHAR outbuffer
[MAXSTRING
];
2677 /* Load the translated 'File association not found' */
2679 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2681 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2683 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2684 WCMD_output_asis(outbuffer
);
2688 /* Not a query - its a set or clear of a value */
2691 WCHAR subkey
[MAXSTRING
];
2693 /* Get pointer to new value */
2697 /* Set up key name */
2698 strcpyW(subkey
, command
);
2699 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2701 /* If nothing after '=' then clear value - only valid for ASSOC */
2702 if (*newValue
== 0x00) {
2704 if (assoc
) rc
= RegDeleteKeyW(key
, command
);
2705 if (assoc
&& rc
== ERROR_SUCCESS
) {
2706 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2708 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2713 WCHAR msgbuffer
[MAXSTRING
];
2714 WCHAR outbuffer
[MAXSTRING
];
2716 /* Load the translated 'File association not found' */
2718 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
2719 sizeof(msgbuffer
)/sizeof(WCHAR
));
2721 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
2722 sizeof(msgbuffer
)/sizeof(WCHAR
));
2724 wsprintfW(outbuffer
, msgbuffer
, keyValue
);
2725 WCMD_output_asis(outbuffer
);
2729 /* It really is a set value = contents */
2731 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2732 accessOptions
, NULL
, &readKey
, NULL
);
2733 if (rc
== ERROR_SUCCESS
) {
2734 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
2735 (LPBYTE
)newValue
, strlenW(newValue
));
2736 RegCloseKey(readKey
);
2739 if (rc
!= ERROR_SUCCESS
) {
2743 WCMD_output_asis(command
);
2744 WCMD_output_asis(equalW
);
2745 WCMD_output_asis(newValue
);
2746 WCMD_output_asis(newline
);
2756 /****************************************************************************
2759 * Clear the terminal screen.
2762 void WCMD_color (void) {
2764 /* Emulate by filling the screen from the top left to bottom right with
2765 spaces, then moving the cursor to the top left afterwards */
2766 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2767 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2769 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2770 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR
));
2774 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2780 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2785 /* Convert the color hex digits */
2786 if (param1
[0] == 0x00) {
2787 color
= defaultColor
;
2789 color
= strtoulW(param1
, NULL
, 16);
2792 /* Fail if fg == bg color */
2793 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2798 /* Set the current screen contents and ensure all future writes
2799 remain this color */
2800 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2801 SetConsoleTextAttribute(hStdOut
, color
);