2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
43 void WCMD_execute (WCHAR
*orig_command
, WCHAR
*parameter
, WCHAR
*substitution
);
45 struct env_stack
*saved_environment
;
46 struct env_stack
*pushd_directories
;
48 extern HINSTANCE hinst
;
49 extern WCHAR inbuilt
[][10];
50 extern int echo_mode
, verify_mode
, defaultColor
;
51 extern WCHAR quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
52 extern BATCH_CONTEXT
*context
;
53 extern DWORD errorlevel
;
55 static const WCHAR dotW
[] = {'.','\0'};
56 static const WCHAR dotdotW
[] = {'.','.','\0'};
57 static const WCHAR slashW
[] = {'\\','\0'};
58 static const WCHAR starW
[] = {'*','\0'};
59 static const WCHAR equalW
[] = {'=','\0'};
60 static const WCHAR fslashW
[] = {'/','\0'};
61 static const WCHAR onW
[] = {'O','N','\0'};
62 static const WCHAR offW
[] = {'O','F','F','\0'};
63 static const WCHAR parmY
[] = {'/','Y','\0'};
64 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
65 static const WCHAR nullW
[] = {'\0'};
67 /****************************************************************************
70 * Clear the terminal screen.
73 void WCMD_clear_screen (void) {
75 /* Emulate by filling the screen from the top left to bottom right with
76 spaces, then moving the cursor to the top left afterwards */
77 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
78 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
80 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
85 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
89 FillConsoleOutputCharacter(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
90 SetConsoleCursorPosition(hStdOut
, topLeft
);
94 /****************************************************************************
97 * Change the default i/o device (ie redirect STDin/STDout).
100 void WCMD_change_tty (void) {
102 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
106 /****************************************************************************
109 * Copy a file or wildcarded set.
110 * FIXME: No wildcard support
113 void WCMD_copy (void) {
118 WCHAR outpath
[MAX_PATH
], inpath
[MAX_PATH
], *infile
, copycmd
[3];
120 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
122 if (param1
[0] == 0x00) {
123 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
127 if ((strchrW(param1
,'*') != NULL
) && (strchrW(param1
,'%') != NULL
)) {
128 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
132 /* If no destination supplied, assume current directory */
133 if (param2
[0] == 0x00) {
134 strcpyW(param2
, dotW
);
137 GetFullPathName (param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
138 if (outpath
[strlenW(outpath
) - 1] == '\\')
139 outpath
[strlenW(outpath
) - 1] = '\0';
140 hff
= FindFirstFile (outpath
, &fd
);
141 if (hff
!= INVALID_HANDLE_VALUE
) {
142 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
143 GetFullPathName (param1
, sizeof(inpath
)/sizeof(WCHAR
), inpath
, &infile
);
144 strcatW (outpath
, slashW
);
145 strcatW (outpath
, infile
);
150 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
151 if (strstrW (quals
, parmNoY
))
153 else if (strstrW (quals
, parmY
))
156 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
157 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
)) && ! lstrcmpiW (copycmd
, parmY
));
161 hff
= FindFirstFile (outpath
, &fd
);
162 if (hff
!= INVALID_HANDLE_VALUE
) {
163 WCHAR buffer
[MAXSTRING
];
167 wsprintf(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outpath
);
168 force
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
173 status
= CopyFile (param1
, outpath
, FALSE
);
174 if (!status
) WCMD_print_error ();
178 /****************************************************************************
181 * Create a directory.
183 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
184 * they do not already exist.
187 static BOOL
create_full_path(WCHAR
* path
)
193 new_path
= HeapAlloc(GetProcessHeap(),0,(strlenW(path
) * sizeof(WCHAR
))+1);
194 strcpyW(new_path
,path
);
196 while ((len
= strlenW(new_path
)) && new_path
[len
- 1] == '\\')
197 new_path
[len
- 1] = 0;
199 while (!CreateDirectory(new_path
,NULL
))
202 DWORD last_error
= GetLastError();
203 if (last_error
== ERROR_ALREADY_EXISTS
)
206 if (last_error
!= ERROR_PATH_NOT_FOUND
)
212 if (!(slash
= strrchrW(new_path
,'\\')) && ! (slash
= strrchrW(new_path
,'/')))
218 len
= slash
- new_path
;
220 if (!create_full_path(new_path
))
225 new_path
[len
] = '\\';
227 HeapFree(GetProcessHeap(),0,new_path
);
231 void WCMD_create_dir (void) {
233 if (param1
[0] == 0x00) {
234 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
237 if (!create_full_path(param1
)) WCMD_print_error ();
240 /****************************************************************************
243 * Delete a file or wildcarded set.
246 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
247 * - Each set is a pattern, eg /ahr /as-r means
248 * readonly+hidden OR nonreadonly system files
249 * - The '-' applies to a single field, ie /a:-hr means read only
253 BOOL
WCMD_delete (WCHAR
*command
, BOOL expectDir
) {
256 int argsProcessed
= 0;
257 WCHAR
*argN
= command
;
258 BOOL foundAny
= FALSE
;
259 static const WCHAR parmA
[] = {'/','A','\0'};
260 static const WCHAR parmQ
[] = {'/','Q','\0'};
261 static const WCHAR parmP
[] = {'/','P','\0'};
262 static const WCHAR parmS
[] = {'/','S','\0'};
263 static const WCHAR parmF
[] = {'/','F','\0'};
265 /* If not recursing, clear error flag */
266 if (expectDir
) errorlevel
= 0;
268 /* Loop through all args */
270 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
271 WCHAR argCopy
[MAX_PATH
];
273 if (argN
&& argN
[0] != '/') {
277 WCHAR fpath
[MAX_PATH
];
279 BOOL handleParm
= TRUE
;
281 static const WCHAR anyExt
[]= {'.','*','\0'};
283 strcpyW(argCopy
, thisArg
);
284 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
285 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
288 /* If filename part of parameter is * or *.*, prompt unless
290 if ((strstrW (quals
, parmQ
) == NULL
) && (strstrW (quals
, parmP
) == NULL
)) {
294 WCHAR fname
[MAX_PATH
];
297 /* Convert path into actual directory spec */
298 GetFullPathName (argCopy
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
299 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
301 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
302 if ((strcmpW(fname
, starW
) == 0) &&
303 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
305 WCHAR question
[MAXSTRING
];
306 static const WCHAR fmt
[] = {'%','s',' ','\0'};
308 /* Note: Flag as found, to avoid file not found message */
311 /* Ask for confirmation */
312 wsprintf(question
, fmt
, fpath
);
313 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
315 /* Abort if answer is 'N' */
320 /* First, try to delete in the current directory */
321 hff
= FindFirstFile (argCopy
, &fd
);
322 if (hff
== INVALID_HANDLE_VALUE
) {
328 /* Support del <dirname> by just deleting all files dirname\* */
329 if (handleParm
&& (strchrW(argCopy
,'*') == NULL
) && (strchrW(argCopy
,'?') == NULL
)
330 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
331 WCHAR modifiedParm
[MAX_PATH
];
332 static const WCHAR slashStar
[] = {'\\','*','\0'};
334 strcpyW(modifiedParm
, argCopy
);
335 strcatW(modifiedParm
, slashStar
);
338 WCMD_delete(modifiedParm
, FALSE
);
340 } else if (handleParm
) {
342 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
343 strcpyW (fpath
, argCopy
);
345 p
= strrchrW (fpath
, '\\');
348 strcatW (fpath
, fd
.cFileName
);
350 else strcpyW (fpath
, fd
.cFileName
);
351 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
353 WCHAR
*nextA
= strstrW (quals
, parmA
);
355 /* Handle attribute matching (/A) */
358 while (nextA
!= NULL
&& !ok
) {
360 WCHAR
*thisA
= (nextA
+2);
363 /* Skip optional : */
364 if (*thisA
== ':') thisA
++;
366 /* Parse each of the /A[:]xxx in turn */
367 while (*thisA
&& *thisA
!= '/') {
369 BOOL attribute
= FALSE
;
371 /* Match negation of attribute first */
377 /* Match attribute */
379 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
381 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
383 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
385 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
388 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
391 /* Now check result, keeping a running boolean about whether it
392 matches all parsed attribues so far */
393 if (attribute
&& !negate
) {
395 } else if (!attribute
&& negate
) {
403 /* Save the running total as the final result */
406 /* Step on to next /A set */
407 nextA
= strstrW (nextA
+1, parmA
);
411 /* /P means prompt for each file */
412 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
413 WCHAR question
[MAXSTRING
];
415 /* Ask for confirmation */
416 wsprintf(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
417 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
420 /* Only proceed if ok to */
423 /* If file is read only, and /F supplied, delete it */
424 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
425 strstrW (quals
, parmF
) != NULL
) {
426 SetFileAttributes(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
429 /* Now do the delete */
430 if (!DeleteFile (fpath
)) WCMD_print_error ();
434 } while (FindNextFile(hff
, &fd
) != 0);
438 /* Now recurse into all subdirectories handling the parameter in the same way */
439 if (strstrW (quals
, parmS
) != NULL
) {
441 WCHAR thisDir
[MAX_PATH
];
446 WCHAR fname
[MAX_PATH
];
449 /* Convert path into actual directory spec */
450 GetFullPathName (argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
451 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
453 strcpyW(thisDir
, drive
);
454 strcatW(thisDir
, dir
);
455 cPos
= strlenW(thisDir
);
457 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
459 /* Append '*' to the directory */
461 thisDir
[cPos
+1] = 0x00;
463 hff
= FindFirstFile (thisDir
, &fd
);
465 /* Remove residual '*' */
466 thisDir
[cPos
] = 0x00;
468 if (hff
!= INVALID_HANDLE_VALUE
) {
469 DIRECTORY_STACK
*allDirs
= NULL
;
470 DIRECTORY_STACK
*lastEntry
= NULL
;
473 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
474 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
475 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
477 DIRECTORY_STACK
*nextDir
;
478 WCHAR subParm
[MAX_PATH
];
480 /* Work out search parameter in sub dir */
481 strcpyW (subParm
, thisDir
);
482 strcatW (subParm
, fd
.cFileName
);
483 strcatW (subParm
, slashW
);
484 strcatW (subParm
, fname
);
485 strcatW (subParm
, ext
);
486 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
488 /* Allocate memory, add to list */
489 nextDir
= (DIRECTORY_STACK
*) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
490 if (allDirs
== NULL
) allDirs
= nextDir
;
491 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
493 nextDir
->next
= NULL
;
494 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
495 (strlenW(subParm
)+1) * sizeof(WCHAR
));
496 strcpyW(nextDir
->dirName
, subParm
);
498 } while (FindNextFile(hff
, &fd
) != 0);
501 /* Go through each subdir doing the delete */
502 while (allDirs
!= NULL
) {
503 DIRECTORY_STACK
*tempDir
;
505 tempDir
= allDirs
->next
;
506 found
|= WCMD_delete (allDirs
->dirName
, FALSE
);
508 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
509 HeapFree(GetProcessHeap(),0,allDirs
);
514 /* Keep running total to see if any found, and if not recursing
515 issue error message */
519 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), argCopy
);
526 /* Handle no valid args */
527 if (argsProcessed
== 0) {
528 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
534 /****************************************************************************
537 * Echo input to the screen (or not). We don't try to emulate the bugs
538 * in DOS (try typing "ECHO ON AGAIN" for an example).
541 void WCMD_echo (const WCHAR
*command
) {
545 if ((command
[0] == '.') && (command
[1] == 0)) {
546 WCMD_output (newline
);
551 count
= strlenW(command
);
553 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
554 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
557 if (lstrcmpiW(command
, onW
) == 0) {
561 if (lstrcmpiW(command
, offW
) == 0) {
565 WCMD_output_asis (command
);
566 WCMD_output (newline
);
570 /**************************************************************************
573 * Batch file loop processing.
574 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
575 * will probably work here, but the reverse is not necessarily the case...
578 void WCMD_for (WCHAR
*p
) {
583 WCHAR set
[MAX_PATH
], param
[MAX_PATH
];
585 const WCHAR inW
[] = {'i', 'n', '\0'};
586 const WCHAR doW
[] = {'d', 'o', '\0'};
588 if (lstrcmpiW (WCMD_parameter (p
, 1, NULL
), inW
)
589 || lstrcmpiW (WCMD_parameter (p
, 3, NULL
), doW
)
590 || (param1
[0] != '%')) {
591 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
594 lstrcpynW (set
, WCMD_parameter (p
, 2, NULL
), sizeof(set
)/sizeof(WCHAR
));
595 WCMD_parameter (p
, 4, &cmd
);
596 strcpyW (param
, param1
);
599 * If the parameter within the set has a wildcard then search for matching files
600 * otherwise do a literal substitution.
604 while (*(item
= WCMD_parameter (set
, i
, NULL
))) {
605 static const WCHAR wildcards
[] = {'*','?','\0'};
606 if (strpbrkW (item
, wildcards
)) {
607 hff
= FindFirstFile (item
, &fd
);
608 if (hff
== INVALID_HANDLE_VALUE
) {
612 WCMD_execute (cmd
, param
, fd
.cFileName
);
613 } while (FindNextFile(hff
, &fd
) != 0);
617 WCMD_execute (cmd
, param
, item
);
623 /*****************************************************************************
626 * Execute a command after substituting variable text for the supplied parameter
629 void WCMD_execute (WCHAR
*orig_cmd
, WCHAR
*param
, WCHAR
*subst
) {
631 WCHAR
*new_cmd
, *p
, *s
, *dup
;
634 size
= strlenW (orig_cmd
);
635 new_cmd
= (WCHAR
*) LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, size
);
636 dup
= s
= WCMD_strdupW(orig_cmd
);
638 while ((p
= strstrW (s
, param
))) {
640 size
+= strlenW (subst
);
641 new_cmd
= (WCHAR
*) LocalReAlloc ((HANDLE
)new_cmd
, size
, 0);
642 strcatW (new_cmd
, s
);
643 strcatW (new_cmd
, subst
);
644 s
= p
+ strlenW (param
);
646 strcatW (new_cmd
, s
);
647 WCMD_process_command (new_cmd
);
649 LocalFree ((HANDLE
)new_cmd
);
653 /**************************************************************************
656 * Simple on-line help. Help text is stored in the resource file.
659 void WCMD_give_help (WCHAR
*command
) {
663 command
= WCMD_strtrim_leading_spaces(command
);
664 if (strlenW(command
) == 0) {
665 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
668 for (i
=0; i
<=WCMD_EXIT
; i
++) {
669 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
670 param1
, -1, inbuilt
[i
], -1) == 2) {
671 WCMD_output_asis (WCMD_LoadMessage(i
));
675 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), param1
);
680 /****************************************************************************
683 * Batch file jump instruction. Not the most efficient algorithm ;-)
684 * Prints error message if the specified label cannot be found - the file pointer is
685 * then at EOF, effectively stopping the batch file.
686 * FIXME: DOS is supposed to allow labels with spaces - we don't.
689 void WCMD_goto (void) {
691 WCHAR string
[MAX_PATH
];
693 if (param1
[0] == 0x00) {
694 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
697 if (context
!= NULL
) {
698 WCHAR
*paramStart
= param1
;
699 static const WCHAR eofW
[] = {':','e','o','f','\0'};
701 /* Handle special :EOF label */
702 if (lstrcmpiW (eofW
, param1
) == 0) {
703 context
-> skip_rest
= TRUE
;
707 /* Support goto :label as well as goto label */
708 if (*paramStart
== ':') paramStart
++;
710 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
711 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
712 if ((string
[0] == ':') && (lstrcmpiW (&string
[1], paramStart
) == 0)) return;
714 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET
));
719 /*****************************************************************************
722 * Push a directory onto the stack
725 void WCMD_pushd (WCHAR
*command
) {
726 struct env_stack
*curdir
;
728 static const WCHAR parmD
[] = {'/','D','\0'};
730 if (strchrW(command
, '/') != NULL
) {
731 SetLastError(ERROR_INVALID_PARAMETER
);
736 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
737 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
738 if( !curdir
|| !thisdir
) {
741 WINE_ERR ("out of memory\n");
745 /* Change directory using CD code with /D parameter */
746 strcpyW(quals
, parmD
);
747 GetCurrentDirectoryW (1024, thisdir
);
749 WCMD_setshow_default(command
);
755 curdir
-> next
= pushd_directories
;
756 curdir
-> strings
= thisdir
;
757 if (pushd_directories
== NULL
) {
758 curdir
-> u
.stackdepth
= 1;
760 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
762 pushd_directories
= curdir
;
767 /*****************************************************************************
770 * Pop a directory from the stack
773 void WCMD_popd (void) {
774 struct env_stack
*temp
= pushd_directories
;
776 if (!pushd_directories
)
779 /* pop the old environment from the stack, and make it the current dir */
780 pushd_directories
= temp
->next
;
781 SetCurrentDirectoryW(temp
->strings
);
782 LocalFree (temp
->strings
);
786 /****************************************************************************
789 * Batch file conditional.
790 * FIXME: Much more syntax checking needed!
793 void WCMD_if (WCHAR
*p
) {
795 int negate
= 0, test
= 0;
796 WCHAR condition
[MAX_PATH
], *command
, *s
;
797 static const WCHAR notW
[] = {'n','o','t','\0'};
798 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
799 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
800 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
801 static const WCHAR eqeqW
[] = {'=','=','\0'};
803 if (!lstrcmpiW (param1
, notW
)) {
805 strcpyW (condition
, param2
);
808 strcpyW (condition
, param1
);
810 if (!lstrcmpiW (condition
, errlvlW
)) {
811 if (errorlevel
>= atoiW(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
812 WCMD_parameter (p
, 2+negate
, &command
);
814 else if (!lstrcmpiW (condition
, existW
)) {
815 if (GetFileAttributes(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
818 WCMD_parameter (p
, 2+negate
, &command
);
820 else if (!lstrcmpiW (condition
, defdW
)) {
821 if (GetEnvironmentVariable(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
824 WCMD_parameter (p
, 2+negate
, &command
);
826 else if ((s
= strstrW (p
, eqeqW
))) {
828 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
829 WCMD_parameter (s
, 1, &command
);
832 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
835 if (test
!= negate
) {
836 command
= WCMD_strdupW(command
);
837 WCMD_process_command (command
);
842 /****************************************************************************
845 * Move a file, directory tree or wildcarded set of files.
848 void WCMD_move (void) {
853 WCHAR input
[MAX_PATH
];
854 WCHAR output
[MAX_PATH
];
857 WCHAR fname
[MAX_PATH
];
860 if (param1
[0] == 0x00) {
861 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
865 /* If no destination supplied, assume current directory */
866 if (param2
[0] == 0x00) {
867 strcpyW(param2
, dotW
);
870 /* If 2nd parm is directory, then use original filename */
871 /* Convert partial path to full path */
872 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
873 GetFullPathName (param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
874 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
875 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
877 /* Split into components */
878 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
880 hff
= FindFirstFile (input
, &fd
);
881 while (hff
!= INVALID_HANDLE_VALUE
) {
882 WCHAR dest
[MAX_PATH
];
886 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
888 /* Build src & dest name */
892 /* See if dest is an existing directory */
893 attribs
= GetFileAttributes(output
);
894 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
895 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
896 strcpyW(dest
, output
);
897 strcatW(dest
, slashW
);
898 strcatW(dest
, fd
.cFileName
);
900 strcpyW(dest
, output
);
903 strcatW(src
, fd
.cFileName
);
905 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
906 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
908 /* Check if file is read only, otherwise move it */
909 attribs
= GetFileAttributes(src
);
910 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
911 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
912 SetLastError(ERROR_ACCESS_DENIED
);
917 /* If destination exists, prompt unless /Y supplied */
918 if (GetFileAttributes(dest
) != INVALID_FILE_ATTRIBUTES
) {
920 WCHAR copycmd
[MAXSTRING
];
923 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
924 if (strstrW (quals
, parmNoY
))
926 else if (strstrW (quals
, parmY
))
929 const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
930 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
931 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
932 && ! lstrcmpiW (copycmd
, parmY
));
935 /* Prompt if overwriting */
937 WCHAR question
[MAXSTRING
];
940 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
942 /* Ask for confirmation */
943 wsprintf(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
944 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
946 /* So delete the destination prior to the move */
948 if (!DeleteFile (dest
)) {
958 status
= MoveFile (src
, dest
);
960 status
= 1; /* Anything other than 0 to prevent error msg below */
969 /* Step on to next match */
970 if (FindNextFile(hff
, &fd
) == 0) {
972 hff
= INVALID_HANDLE_VALUE
;
978 /****************************************************************************
981 * Wait for keyboard input.
984 void WCMD_pause (void) {
989 WCMD_output (anykey
);
990 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
991 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
994 /****************************************************************************
997 * Delete a directory.
1000 void WCMD_remove_dir (WCHAR
*command
) {
1003 int argsProcessed
= 0;
1004 WCHAR
*argN
= command
;
1005 static const WCHAR parmS
[] = {'/','S','\0'};
1006 static const WCHAR parmQ
[] = {'/','Q','\0'};
1008 /* Loop through all args */
1010 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1011 if (argN
&& argN
[0] != '/') {
1012 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1013 wine_dbgstr_w(quals
));
1016 /* If subdirectory search not supplied, just try to remove
1017 and report error if it fails (eg if it contains a file) */
1018 if (strstrW (quals
, parmS
) == NULL
) {
1019 if (!RemoveDirectory (thisArg
)) WCMD_print_error ();
1021 /* Otherwise use ShFileOp to recursively remove a directory */
1024 SHFILEOPSTRUCT lpDir
;
1027 if (strstrW (quals
, parmQ
) == NULL
) {
1029 WCHAR question
[MAXSTRING
];
1030 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1032 /* Ask for confirmation */
1033 wsprintf(question
, fmt
, thisArg
);
1034 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1036 /* Abort if answer is 'N' */
1043 lpDir
.pFrom
= thisArg
;
1044 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1045 lpDir
.wFunc
= FO_DELETE
;
1046 if (SHFileOperation(&lpDir
)) WCMD_print_error ();
1051 /* Handle no valid args */
1052 if (argsProcessed
== 0) {
1053 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1059 /****************************************************************************
1065 void WCMD_rename (void) {
1070 WCHAR input
[MAX_PATH
];
1071 WCHAR
*dotDst
= NULL
;
1073 WCHAR dir
[MAX_PATH
];
1074 WCHAR fname
[MAX_PATH
];
1075 WCHAR ext
[MAX_PATH
];
1080 /* Must be at least two args */
1081 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1082 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1087 /* Destination cannot contain a drive letter or directory separator */
1088 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1089 SetLastError(ERROR_INVALID_PARAMETER
);
1095 /* Convert partial path to full path */
1096 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1097 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1098 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1099 dotDst
= strchrW(param2
, '.');
1101 /* Split into components */
1102 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1104 hff
= FindFirstFile (input
, &fd
);
1105 while (hff
!= INVALID_HANDLE_VALUE
) {
1106 WCHAR dest
[MAX_PATH
];
1107 WCHAR src
[MAX_PATH
];
1108 WCHAR
*dotSrc
= NULL
;
1111 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1113 /* FIXME: If dest name or extension is *, replace with filename/ext
1114 part otherwise use supplied name. This supports:
1116 ren jim.* fred.* etc
1117 However, windows has a more complex algorithum supporting eg
1118 ?'s and *'s mid name */
1119 dotSrc
= strchrW(fd
.cFileName
, '.');
1121 /* Build src & dest name */
1122 strcpyW(src
, drive
);
1125 dirLen
= strlenW(src
);
1126 strcatW(src
, fd
.cFileName
);
1129 if (param2
[0] == '*') {
1130 strcatW(dest
, fd
.cFileName
);
1131 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1133 strcatW(dest
, param2
);
1134 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1137 /* Build Extension */
1138 if (dotDst
&& (*(dotDst
+1)=='*')) {
1139 if (dotSrc
) strcatW(dest
, dotSrc
);
1140 } else if (dotDst
) {
1141 if (dotDst
) strcatW(dest
, dotDst
);
1144 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1145 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1147 /* Check if file is read only, otherwise move it */
1148 attribs
= GetFileAttributes(src
);
1149 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1150 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1151 SetLastError(ERROR_ACCESS_DENIED
);
1154 status
= MoveFile (src
, dest
);
1158 WCMD_print_error ();
1162 /* Step on to next match */
1163 if (FindNextFile(hff
, &fd
) == 0) {
1165 hff
= INVALID_HANDLE_VALUE
;
1171 /*****************************************************************************
1174 * Make a copy of the environment.
1176 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1186 len
+= (strlenW(&env
[len
]) + 1);
1188 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1191 WINE_ERR("out of memory\n");
1194 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1200 /*****************************************************************************
1203 * setlocal pushes the environment onto a stack
1204 * Save the environment as unicode so we don't screw anything up.
1206 void WCMD_setlocal (const WCHAR
*s
) {
1208 struct env_stack
*env_copy
;
1209 WCHAR cwd
[MAX_PATH
];
1211 /* DISABLEEXTENSIONS ignored */
1213 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1216 WINE_ERR ("out of memory\n");
1220 env
= GetEnvironmentStringsW ();
1222 env_copy
->strings
= WCMD_dupenv (env
);
1223 if (env_copy
->strings
)
1225 env_copy
->next
= saved_environment
;
1226 saved_environment
= env_copy
;
1228 /* Save the current drive letter */
1229 GetCurrentDirectory (MAX_PATH
, cwd
);
1230 env_copy
->u
.cwd
= cwd
[0];
1233 LocalFree (env_copy
);
1235 FreeEnvironmentStringsW (env
);
1239 /*****************************************************************************
1242 * endlocal pops the environment off a stack
1243 * Note: When searching for '=', search from WCHAR position 1, to handle
1244 * special internal environment variables =C:, =D: etc
1246 void WCMD_endlocal (void) {
1247 WCHAR
*env
, *old
, *p
;
1248 struct env_stack
*temp
;
1251 if (!saved_environment
)
1254 /* pop the old environment from the stack */
1255 temp
= saved_environment
;
1256 saved_environment
= temp
->next
;
1258 /* delete the current environment, totally */
1259 env
= GetEnvironmentStringsW ();
1260 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1263 n
= strlenW(&old
[len
]) + 1;
1264 p
= strchrW(&old
[len
] + 1, '=');
1268 SetEnvironmentVariableW (&old
[len
], NULL
);
1273 FreeEnvironmentStringsW (env
);
1275 /* restore old environment */
1276 env
= temp
->strings
;
1279 n
= strlenW(&env
[len
]) + 1;
1280 p
= strchrW(&env
[len
] + 1, '=');
1284 SetEnvironmentVariableW (&env
[len
], p
);
1289 /* Restore current drive letter */
1290 if (IsCharAlpha(temp
->u
.cwd
)) {
1292 WCHAR cwd
[MAX_PATH
];
1293 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
1295 wsprintf(envvar
, fmt
, temp
->u
.cwd
);
1296 if (GetEnvironmentVariable(envvar
, cwd
, MAX_PATH
)) {
1297 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
1298 SetCurrentDirectory(cwd
);
1306 /*****************************************************************************
1307 * WCMD_setshow_attrib
1309 * Display and optionally sets DOS attributes on a file or directory
1311 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1312 * As a result only the Readonly flag is correctly reported, the Archive bit
1313 * is always set and the rest are not implemented. We do the Right Thing anyway.
1315 * FIXME: No SET functionality.
1319 void WCMD_setshow_attrib (void) {
1324 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1326 if (param1
[0] == '-') {
1327 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1331 if (strlenW(param1
) == 0) {
1332 static const WCHAR slashStarW
[] = {'\\','*','\0'};
1334 GetCurrentDirectory (sizeof(param1
)/sizeof(WCHAR
), param1
);
1335 strcatW (param1
, slashStarW
);
1338 hff
= FindFirstFile (param1
, &fd
);
1339 if (hff
== INVALID_HANDLE_VALUE
) {
1340 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), param1
);
1344 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1345 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1346 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1349 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1352 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1355 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1358 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1361 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1364 WCMD_output (fmt
, flags
, fd
.cFileName
);
1365 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1367 } while (FindNextFile(hff
, &fd
) != 0);
1372 /*****************************************************************************
1373 * WCMD_setshow_default
1375 * Set/Show the current default directory
1378 void WCMD_setshow_default (WCHAR
*command
) {
1386 static const WCHAR parmD
[] = {'/','D','\0'};
1388 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
1390 /* Skip /D and trailing whitespace if on the front of the command line */
1391 if (CompareString (LOCALE_USER_DEFAULT
,
1392 NORM_IGNORECASE
| SORT_STRINGSORT
,
1393 command
, 2, parmD
, -1) == 2) {
1395 while (*command
&& *command
==' ') command
++;
1398 GetCurrentDirectory (sizeof(cwd
)/sizeof(WCHAR
), cwd
);
1399 if (strlenW(command
) == 0) {
1400 strcatW (cwd
, newline
);
1404 /* Remove any double quotes, which may be in the
1405 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1408 if (*command
!= '"') *pos
++ = *command
;
1413 /* Search for approprate directory */
1414 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
1415 hff
= FindFirstFile (string
, &fd
);
1416 while (hff
!= INVALID_HANDLE_VALUE
) {
1417 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1418 WCHAR fpath
[MAX_PATH
];
1420 WCHAR dir
[MAX_PATH
];
1421 WCHAR fname
[MAX_PATH
];
1422 WCHAR ext
[MAX_PATH
];
1423 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
1425 /* Convert path into actual directory spec */
1426 GetFullPathName (string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1427 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1430 wsprintf(string
, fmt
, drive
, dir
, fd
.cFileName
);
1433 hff
= INVALID_HANDLE_VALUE
;
1437 /* Step on to next match */
1438 if (FindNextFile(hff
, &fd
) == 0) {
1440 hff
= INVALID_HANDLE_VALUE
;
1445 /* Change to that directory */
1446 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
1448 status
= SetCurrentDirectory (string
);
1451 WCMD_print_error ();
1455 /* Restore old directory if drive letter would change, and
1456 CD x:\directory /D (or pushd c:\directory) not supplied */
1457 if ((strstrW(quals
, parmD
) == NULL
) &&
1458 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
1459 SetCurrentDirectory(cwd
);
1463 /* Set special =C: type environment variable, for drive letter of
1464 change of directory, even if path was restored due to missing
1465 /D (allows changing drive letter when not resident on that
1467 if ((string
[1] == ':') && IsCharAlpha (string
[0])) {
1469 strcpyW(env
, equalW
);
1470 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
1472 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
1473 SetEnvironmentVariable(env
, string
);
1480 /****************************************************************************
1483 * Set/Show the system date
1484 * FIXME: Can't change date yet
1487 void WCMD_setshow_date (void) {
1489 WCHAR curdate
[64], buffer
[64];
1491 static const WCHAR parmT
[] = {'/','T','\0'};
1493 if (strlenW(param1
) == 0) {
1494 if (GetDateFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1495 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
1496 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
1497 if (strstrW (quals
, parmT
) == NULL
) {
1498 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
1499 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
1500 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1502 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1506 else WCMD_print_error ();
1509 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1513 /****************************************************************************
1516 static int WCMD_compare( const void *a
, const void *b
)
1519 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
1520 r
= CompareString( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1521 *str_a
, -1, *str_b
, -1 );
1522 if( r
== CSTR_LESS_THAN
) return -1;
1523 if( r
== CSTR_GREATER_THAN
) return 1;
1527 /****************************************************************************
1528 * WCMD_setshow_sortenv
1530 * sort variables into order for display
1531 * Optionally only display those who start with a stub
1532 * returns the count displayed
1534 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
1536 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
1539 if (stub
) stublen
= strlenW(stub
);
1541 /* count the number of strings, and the total length */
1543 len
+= (strlenW(&s
[len
]) + 1);
1547 /* add the strings to an array */
1548 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
1552 for( i
=1; i
<count
; i
++ )
1553 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
1555 /* sort the array */
1556 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
1559 for( i
=0; i
<count
; i
++ ) {
1560 if (!stub
|| CompareString (LOCALE_USER_DEFAULT
,
1561 NORM_IGNORECASE
| SORT_STRINGSORT
,
1562 str
[i
], stublen
, stub
, -1) == 2) {
1563 /* Don't display special internal variables */
1564 if (str
[i
][0] != '=') {
1565 WCMD_output_asis(str
[i
]);
1566 WCMD_output_asis(newline
);
1573 return displayedcount
;
1576 /****************************************************************************
1579 * Set/Show the environment variables
1582 void WCMD_setshow_env (WCHAR
*s
) {
1587 static const WCHAR parmP
[] = {'/','P','\0'};
1590 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
1591 env
= GetEnvironmentStrings ();
1592 WCMD_setshow_sortenv( env
, NULL
);
1596 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1597 if (CompareString (LOCALE_USER_DEFAULT
,
1598 NORM_IGNORECASE
| SORT_STRINGSORT
,
1599 s
, 2, parmP
, -1) == 2) {
1600 WCHAR string
[MAXSTRING
];
1604 while (*s
&& *s
==' ') s
++;
1606 /* If no parameter, or no '=' sign, return an error */
1607 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
1608 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1612 /* Output the prompt */
1614 if (strlenW(p
) != 0) WCMD_output(p
);
1616 /* Read the reply */
1617 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1618 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1620 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
1621 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
1622 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
1623 wine_dbgstr_w(string
));
1624 status
= SetEnvironmentVariable (s
, string
);
1629 p
= strchrW (s
, '=');
1631 env
= GetEnvironmentStrings ();
1632 if (WCMD_setshow_sortenv( env
, s
) == 0) {
1633 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
1640 if (strlenW(p
) == 0) p
= NULL
;
1641 status
= SetEnvironmentVariable (s
, p
);
1642 gle
= GetLastError();
1643 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
1645 } else if ((!status
)) WCMD_print_error();
1649 /****************************************************************************
1652 * Set/Show the path environment variable
1655 void WCMD_setshow_path (WCHAR
*command
) {
1659 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
1660 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
1662 if (strlenW(param1
) == 0) {
1663 status
= GetEnvironmentVariable (pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
1665 WCMD_output_asis ( pathEqW
);
1666 WCMD_output_asis ( string
);
1667 WCMD_output_asis ( newline
);
1670 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
1674 if (*command
== '=') command
++; /* Skip leading '=' */
1675 status
= SetEnvironmentVariable (pathW
, command
);
1676 if (!status
) WCMD_print_error();
1680 /****************************************************************************
1681 * WCMD_setshow_prompt
1683 * Set or show the command prompt.
1686 void WCMD_setshow_prompt (void) {
1689 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
1691 if (strlenW(param1
) == 0) {
1692 SetEnvironmentVariable (promptW
, NULL
);
1696 while ((*s
== '=') || (*s
== ' ')) s
++;
1697 if (strlenW(s
) == 0) {
1698 SetEnvironmentVariable (promptW
, NULL
);
1700 else SetEnvironmentVariable (promptW
, s
);
1704 /****************************************************************************
1707 * Set/Show the system time
1708 * FIXME: Can't change time yet
1711 void WCMD_setshow_time (void) {
1713 WCHAR curtime
[64], buffer
[64];
1716 static const WCHAR parmT
[] = {'/','T','\0'};
1718 if (strlenW(param1
) == 0) {
1720 if (GetTimeFormat (LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
1721 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
1722 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curtime
);
1723 if (strstrW (quals
, parmT
) == NULL
) {
1724 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
1725 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
1726 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1728 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1732 else WCMD_print_error ();
1735 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1739 /****************************************************************************
1742 * Shift batch parameters.
1743 * Optional /n says where to start shifting (n=0-8)
1746 void WCMD_shift (WCHAR
*command
) {
1749 if (context
!= NULL
) {
1750 WCHAR
*pos
= strchrW(command
, '/');
1755 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
1756 start
= (*(pos
+1) - '0');
1758 SetLastError(ERROR_INVALID_PARAMETER
);
1763 WINE_TRACE("Shifting variables, starting at %d\n", start
);
1764 for (i
=start
;i
<=8;i
++) {
1765 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
1767 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
1772 /****************************************************************************
1775 * Set the console title
1777 void WCMD_title (WCHAR
*command
) {
1778 SetConsoleTitle(command
);
1781 /****************************************************************************
1784 * Copy a file to standard output.
1787 void WCMD_type (WCHAR
*command
) {
1790 WCHAR
*argN
= command
;
1791 BOOL writeHeaders
= FALSE
;
1793 if (param1
[0] == 0x00) {
1794 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1798 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
1800 /* Loop through all args */
1803 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1811 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
1812 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1813 FILE_ATTRIBUTE_NORMAL
, NULL
);
1814 if (h
== INVALID_HANDLE_VALUE
) {
1815 WCMD_print_error ();
1816 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
1820 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
1821 WCMD_output(fmt
, thisArg
);
1823 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
)) {
1824 if (count
== 0) break; /* ReadFile reports success on EOF! */
1826 WCMD_output_asis (buffer
);
1833 /****************************************************************************
1836 * Output either a file or stdin to screen in pages
1839 void WCMD_more (WCHAR
*command
) {
1842 WCHAR
*argN
= command
;
1843 BOOL useinput
= FALSE
;
1845 WCHAR moreStrPage
[100];
1848 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
1849 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
1850 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
1851 ')',' ','-','-','\n','\0'};
1852 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
1854 /* Prefix the NLS more with '-- ', then load the text */
1856 strcpyW(moreStr
, moreStart
);
1857 LoadString (hinst
, WCMD_MORESTR
, &moreStr
[3],
1858 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
1860 if (param1
[0] == 0x00) {
1862 /* Wine implements pipes via temporary files, and hence stdin is
1863 effectively reading from the file. This means the prompts for
1864 more are satistied by the next line from the input (file). To
1865 avoid this, ensure stdin is to the console */
1866 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
1867 HANDLE hConIn
= CreateFile(conInW
, GENERIC_READ
| GENERIC_WRITE
,
1868 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1869 FILE_ATTRIBUTE_NORMAL
, 0);
1870 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
1872 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1873 once you get in this bit unless due to a pipe, its going to end badly... */
1875 wsprintf(moreStrPage
, moreFmt
, moreStr
);
1877 WCMD_enter_paged_mode(moreStrPage
);
1878 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
1879 if (count
== 0) break; /* ReadFile reports success on EOF! */
1881 WCMD_output_asis (buffer
);
1883 WCMD_leave_paged_mode();
1885 /* Restore stdin to what it was */
1886 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
1887 CloseHandle(hConIn
);
1891 BOOL needsPause
= FALSE
;
1893 /* Loop through all args */
1894 WCMD_enter_paged_mode(moreStrPage
);
1897 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1905 wsprintf(moreStrPage
, moreFmt2
, moreStr
, 100);
1906 WCMD_leave_paged_mode();
1907 WCMD_output_asis(moreStrPage
);
1908 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
1909 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1910 WCMD_enter_paged_mode(moreStrPage
);
1914 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
1915 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1916 FILE_ATTRIBUTE_NORMAL
, NULL
);
1917 if (h
== INVALID_HANDLE_VALUE
) {
1918 WCMD_print_error ();
1919 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
1923 ULONG64 fileLen
= 0;
1924 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
1926 /* Get the file size */
1927 GetFileAttributesEx(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
1928 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
1931 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
1932 if (count
== 0) break; /* ReadFile reports success on EOF! */
1936 /* Update % count (would be used in WCMD_output_asis as prompt) */
1937 wsprintf(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
1939 WCMD_output_asis (buffer
);
1945 WCMD_leave_paged_mode();
1949 /****************************************************************************
1952 * Display verify flag.
1953 * FIXME: We don't actually do anything with the verify flag other than toggle
1957 void WCMD_verify (WCHAR
*command
) {
1961 count
= strlenW(command
);
1963 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
1964 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
1967 if (lstrcmpiW(command
, onW
) == 0) {
1971 else if (lstrcmpiW(command
, offW
) == 0) {
1975 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
1978 /****************************************************************************
1981 * Display version info.
1984 void WCMD_version (void) {
1986 WCMD_output (version_string
);
1990 /****************************************************************************
1993 * Display volume info and/or set volume label. Returns 0 if error.
1996 int WCMD_volume (int mode
, WCHAR
*path
) {
1998 DWORD count
, serial
;
1999 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2002 if (strlenW(path
) == 0) {
2003 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2005 WCMD_print_error ();
2008 status
= GetVolumeInformation (NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2009 &serial
, NULL
, NULL
, NULL
, 0);
2012 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2013 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2014 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2017 wsprintf (curdir
, fmt
, path
);
2018 status
= GetVolumeInformation (curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2023 WCMD_print_error ();
2026 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2027 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2029 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2030 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2031 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2033 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2034 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2036 if (strlenW(path
) != 0) {
2037 if (!SetVolumeLabel (curdir
, string
)) WCMD_print_error ();
2040 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
2046 /**************************************************************************
2049 * Exit either the process, or just this batch program
2053 void WCMD_exit (void) {
2055 static const WCHAR parmB
[] = {'/','B','\0'};
2056 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2058 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2060 context
-> skip_rest
= TRUE
;
2066 /**************************************************************************
2069 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2072 * Returns True if Y (or A) answer is selected
2073 * If optionAll contains a pointer, ALL is allowed, and if answered
2077 BOOL
WCMD_ask_confirm (WCHAR
*message
, BOOL showSureText
, BOOL
*optionAll
) {
2079 WCHAR msgbuffer
[MAXSTRING
];
2080 WCHAR Ybuffer
[MAXSTRING
];
2081 WCHAR Nbuffer
[MAXSTRING
];
2082 WCHAR Abuffer
[MAXSTRING
];
2083 WCHAR answer
[MAX_PATH
] = {'\0'};
2086 /* Load the translated 'Are you sure', plus valid answers */
2087 LoadString (hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2088 LoadString (hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
2089 LoadString (hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
2090 LoadString (hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
2092 /* Loop waiting on a Y or N */
2093 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
2094 static const WCHAR startBkt
[] = {' ','(','\0'};
2095 static const WCHAR endBkt
[] = {')','?','\0'};
2097 WCMD_output_asis (message
);
2099 WCMD_output_asis (msgbuffer
);
2101 WCMD_output_asis (startBkt
);
2102 WCMD_output_asis (Ybuffer
);
2103 WCMD_output_asis (fslashW
);
2104 WCMD_output_asis (Nbuffer
);
2106 WCMD_output_asis (fslashW
);
2107 WCMD_output_asis (Abuffer
);
2109 WCMD_output_asis (endBkt
);
2110 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
,
2111 sizeof(answer
)/sizeof(WCHAR
), &count
, NULL
);
2112 answer
[0] = toupper(answer
[0]);
2115 /* Return the answer */
2116 return ((answer
[0] == Ybuffer
[0]) ||
2117 (optionAll
&& (answer
[0] == Abuffer
[0])));
2120 /*****************************************************************************
2123 * Lists or sets file associations (assoc = TRUE)
2124 * Lists or sets file types (assoc = FALSE)
2126 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2129 DWORD accessOptions
= KEY_READ
;
2131 LONG rc
= ERROR_SUCCESS
;
2132 WCHAR keyValue
[MAXSTRING
];
2133 DWORD valueLen
= MAXSTRING
;
2135 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2136 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2138 /* See if parameter includes '=' */
2140 newValue
= strchrW(command
, '=');
2141 if (newValue
) accessOptions
|= KEY_WRITE
;
2143 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2144 if (RegOpenKeyEx(HKEY_CLASSES_ROOT
, nullW
, 0,
2145 accessOptions
, &key
) != ERROR_SUCCESS
) {
2146 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2150 /* If no parameters then list all associations */
2151 if (*command
== 0x00) {
2154 /* Enumerate all the keys */
2155 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2156 WCHAR keyName
[MAXSTRING
];
2159 /* Find the next value */
2160 nameLen
= MAXSTRING
;
2161 rc
= RegEnumKeyEx(key
, index
++,
2163 NULL
, NULL
, NULL
, NULL
);
2165 if (rc
== ERROR_SUCCESS
) {
2167 /* Only interested in extension ones if assoc, or others
2169 if ((keyName
[0] == '.' && assoc
) ||
2170 (!(keyName
[0] == '.') && (!assoc
)))
2172 WCHAR subkey
[MAXSTRING
];
2173 strcpyW(subkey
, keyName
);
2174 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2176 if (RegOpenKeyEx(key
, subkey
, 0,
2177 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2179 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2180 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2181 (LPBYTE
)keyValue
, &valueLen
);
2182 WCMD_output_asis(keyName
);
2183 WCMD_output_asis(equalW
);
2184 /* If no default value found, leave line empty after '=' */
2185 if (rc
== ERROR_SUCCESS
) {
2186 WCMD_output_asis(keyValue
);
2188 WCMD_output_asis(newline
);
2193 RegCloseKey(readKey
);
2197 /* Parameter supplied - if no '=' on command line, its a query */
2198 if (newValue
== NULL
) {
2200 WCHAR subkey
[MAXSTRING
];
2202 /* Query terminates the parameter at the first space */
2203 strcpyW(keyValue
, command
);
2204 space
= strchrW(keyValue
, ' ');
2205 if (space
) *space
=0x00;
2207 /* Set up key name */
2208 strcpyW(subkey
, keyValue
);
2209 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2211 if (RegOpenKeyEx(key
, subkey
, 0,
2212 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2214 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2215 (LPBYTE
)keyValue
, &valueLen
);
2216 WCMD_output_asis(command
);
2217 WCMD_output_asis(equalW
);
2218 /* If no default value found, leave line empty after '=' */
2219 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2220 WCMD_output_asis(newline
);
2221 RegCloseKey(readKey
);
2224 WCHAR msgbuffer
[MAXSTRING
];
2225 WCHAR outbuffer
[MAXSTRING
];
2227 /* Load the translated 'File association not found' */
2229 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2231 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2233 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2234 WCMD_output_asis(outbuffer
);
2238 /* Not a query - its a set or clear of a value */
2241 WCHAR subkey
[MAXSTRING
];
2243 /* Get pointer to new value */
2247 /* Set up key name */
2248 strcpyW(subkey
, command
);
2249 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2251 /* If nothing after '=' then clear value - only valid for ASSOC */
2252 if (*newValue
== 0x00) {
2254 if (assoc
) rc
= RegDeleteKey(key
, command
);
2255 if (assoc
&& rc
== ERROR_SUCCESS
) {
2256 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2258 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2263 WCHAR msgbuffer
[MAXSTRING
];
2264 WCHAR outbuffer
[MAXSTRING
];
2266 /* Load the translated 'File association not found' */
2268 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
,
2269 sizeof(msgbuffer
)/sizeof(WCHAR
));
2271 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
,
2272 sizeof(msgbuffer
)/sizeof(WCHAR
));
2274 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2275 WCMD_output_asis(outbuffer
);
2279 /* It really is a set value = contents */
2281 rc
= RegCreateKeyEx(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2282 accessOptions
, NULL
, &readKey
, NULL
);
2283 if (rc
== ERROR_SUCCESS
) {
2284 rc
= RegSetValueEx(readKey
, NULL
, 0, REG_SZ
,
2285 (LPBYTE
)newValue
, strlenW(newValue
));
2286 RegCloseKey(readKey
);
2289 if (rc
!= ERROR_SUCCESS
) {
2293 WCMD_output_asis(command
);
2294 WCMD_output_asis(equalW
);
2295 WCMD_output_asis(newValue
);
2296 WCMD_output_asis(newline
);
2306 /****************************************************************************
2309 * Clear the terminal screen.
2312 void WCMD_color (void) {
2314 /* Emulate by filling the screen from the top left to bottom right with
2315 spaces, then moving the cursor to the top left afterwards */
2316 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2317 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2319 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2320 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR
));
2324 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2330 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2335 /* Convert the color hex digits */
2336 if (param1
[0] == 0x00) {
2337 color
= defaultColor
;
2339 color
= strtoulW(param1
, NULL
, 16);
2342 /* Fail if fg == bg color */
2343 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2348 /* Set the current screen contents and ensure all future writes
2349 remain this color */
2350 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2351 SetConsoleTextAttribute(hStdOut
, color
);