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 (char *orig_command
, char *parameter
, char *substitution
);
45 struct env_stack
*saved_environment
;
46 struct env_stack
*pushd_directories
;
48 extern HINSTANCE hinst
;
49 extern char *inbuilt
[];
50 extern int echo_mode
, verify_mode
, defaultColor
;
51 extern char quals
[MAX_PATH
], param1
[MAX_PATH
], param2
[MAX_PATH
];
52 extern BATCH_CONTEXT
*context
;
53 extern DWORD errorlevel
;
57 /****************************************************************************
60 * Clear the terminal screen.
63 void WCMD_clear_screen (void) {
65 /* Emulate by filling the screen from the top left to bottom right with
66 spaces, then moving the cursor to the top left afterwards */
67 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
68 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
70 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
75 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
79 FillConsoleOutputCharacter(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
80 SetConsoleCursorPosition(hStdOut
, topLeft
);
84 /****************************************************************************
87 * Change the default i/o device (ie redirect STDin/STDout).
90 void WCMD_change_tty (void) {
96 /****************************************************************************
99 * Copy a file or wildcarded set.
100 * FIXME: No wildcard support
103 void WCMD_copy (void) {
109 static const char overwrite
[] = "Overwrite file (Y/N)?";
110 char string
[8], outpath
[MAX_PATH
], inpath
[MAX_PATH
], *infile
, copycmd
[3];
113 if (param1
[0] == 0x00) {
114 WCMD_output ("Argument missing\n");
118 if ((strchr(param1
,'*') != NULL
) && (strchr(param1
,'%') != NULL
)) {
119 WCMD_output ("Wildcards not yet supported\n");
123 /* If no destination supplied, assume current directory */
124 if (param2
[0] == 0x00) {
128 GetFullPathName (param2
, sizeof(outpath
), outpath
, NULL
);
129 if (outpath
[strlen(outpath
) - 1] == '\\')
130 outpath
[strlen(outpath
) - 1] = '\0';
131 hff
= FindFirstFile (outpath
, &fd
);
132 if (hff
!= INVALID_HANDLE_VALUE
) {
133 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
134 GetFullPathName (param1
, sizeof(inpath
), inpath
, &infile
);
135 strcat (outpath
, "\\");
136 strcat (outpath
, infile
);
141 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
142 if (strstr (quals
, "/-Y"))
144 else if (strstr (quals
, "/Y"))
147 len
= GetEnvironmentVariable ("COPYCMD", copycmd
, sizeof(copycmd
));
148 force
= (len
&& len
< sizeof(copycmd
) && ! lstrcmpi (copycmd
, "/Y"));
152 hff
= FindFirstFile (outpath
, &fd
);
153 if (hff
!= INVALID_HANDLE_VALUE
) {
155 WCMD_output (overwrite
);
156 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
157 if (toupper(string
[0]) == 'Y') force
= TRUE
;
162 status
= CopyFile (param1
, outpath
, FALSE
);
163 if (!status
) WCMD_print_error ();
167 /****************************************************************************
170 * Create a directory.
172 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
173 * they do not already exist.
176 static BOOL
create_full_path(CHAR
* path
)
182 new_path
= HeapAlloc(GetProcessHeap(),0,strlen(path
)+1);
183 strcpy(new_path
,path
);
185 while ((len
= strlen(new_path
)) && new_path
[len
- 1] == '\\')
186 new_path
[len
- 1] = 0;
188 while (!CreateDirectory(new_path
,NULL
))
191 DWORD last_error
= GetLastError();
192 if (last_error
== ERROR_ALREADY_EXISTS
)
195 if (last_error
!= ERROR_PATH_NOT_FOUND
)
201 if (!(slash
= strrchr(new_path
,'\\')) && ! (slash
= strrchr(new_path
,'/')))
207 len
= slash
- new_path
;
209 if (!create_full_path(new_path
))
214 new_path
[len
] = '\\';
216 HeapFree(GetProcessHeap(),0,new_path
);
220 void WCMD_create_dir (void) {
222 if (param1
[0] == 0x00) {
223 WCMD_output ("Argument missing\n");
226 if (!create_full_path(param1
)) WCMD_print_error ();
229 /****************************************************************************
232 * Delete a file or wildcarded set.
235 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
236 * - Each set is a pattern, eg /ahr /as-r means
237 * readonly+hidden OR nonreadonly system files
238 * - The '-' applies to a single field, ie /a:-hr means read only
242 BOOL
WCMD_delete (char *command
, BOOL expectDir
) {
245 int argsProcessed
= 0;
246 char *argN
= command
;
247 BOOL foundAny
= FALSE
;
249 /* If not recursing, clear error flag */
250 if (expectDir
) errorlevel
= 0;
252 /* Loop through all args */
254 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
255 char argCopy
[MAX_PATH
];
257 if (argN
&& argN
[0] != '/') {
261 char fpath
[MAX_PATH
];
263 BOOL handleParm
= TRUE
;
266 strcpy(argCopy
, thisArg
);
267 WINE_TRACE("del: Processing arg %s (quals:%s)\n", argCopy
, quals
);
270 /* If filename part of parameter is * or *.*, prompt unless
272 if ((strstr (quals
, "/Q") == NULL
) && (strstr (quals
, "/P") == NULL
)) {
276 char fname
[MAX_PATH
];
279 /* Convert path into actual directory spec */
280 GetFullPathName (argCopy
, sizeof(fpath
), fpath
, NULL
);
281 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
283 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
284 if ((strcmp(fname
, "*") == 0) &&
285 (*ext
== 0x00 || (strcmp(ext
, ".*") == 0))) {
287 char question
[MAXSTRING
];
289 /* Note: Flag as found, to avoid file not found message */
292 /* Ask for confirmation */
293 sprintf(question
, "%s, ", fpath
);
294 ok
= WCMD_ask_confirm(question
, TRUE
);
296 /* Abort if answer is 'N' */
301 /* First, try to delete in the current directory */
302 hff
= FindFirstFile (argCopy
, &fd
);
303 if (hff
== INVALID_HANDLE_VALUE
) {
309 /* Support del <dirname> by just deleting all files dirname\* */
310 if (handleParm
&& (strchr(argCopy
,'*') == NULL
) && (strchr(argCopy
,'?') == NULL
)
311 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
312 char modifiedParm
[MAX_PATH
];
313 strcpy(modifiedParm
, argCopy
);
314 strcat(modifiedParm
, "\\*");
317 WCMD_delete(modifiedParm
, FALSE
);
319 } else if (handleParm
) {
321 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
322 strcpy (fpath
, argCopy
);
324 p
= strrchr (fpath
, '\\');
327 strcat (fpath
, fd
.cFileName
);
329 else strcpy (fpath
, fd
.cFileName
);
330 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
332 char *nextA
= strstr (quals
, "/A");
334 /* Handle attribute matching (/A) */
337 while (nextA
!= NULL
&& !ok
) {
339 char *thisA
= (nextA
+2);
342 /* Skip optional : */
343 if (*thisA
== ':') thisA
++;
345 /* Parse each of the /A[:]xxx in turn */
346 while (*thisA
&& *thisA
!= '/') {
348 BOOL attribute
= FALSE
;
350 /* Match negation of attribute first */
356 /* Match attribute */
358 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
360 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
362 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
364 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
367 WCMD_output ("Syntax error\n");
370 /* Now check result, keeping a running boolean about whether it
371 matches all parsed attribues so far */
372 if (attribute
&& !negate
) {
374 } else if (!attribute
&& negate
) {
382 /* Save the running total as the final result */
385 /* Step on to next /A set */
386 nextA
= strstr (nextA
+1, "/A");
390 /* /P means prompt for each file */
391 if (ok
&& strstr (quals
, "/P") != NULL
) {
392 char question
[MAXSTRING
];
394 /* Ask for confirmation */
395 sprintf(question
, "%s, Delete", fpath
);
396 ok
= WCMD_ask_confirm(question
, FALSE
);
399 /* Only proceed if ok to */
402 /* If file is read only, and /F supplied, delete it */
403 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
404 strstr (quals
, "/F") != NULL
) {
405 SetFileAttributes(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
408 /* Now do the delete */
409 if (!DeleteFile (fpath
)) WCMD_print_error ();
413 } while (FindNextFile(hff
, &fd
) != 0);
417 /* Now recurse into all subdirectories handling the paramater in the same way */
418 if (strstr (quals
, "/S") != NULL
) {
420 char thisDir
[MAX_PATH
];
425 char fname
[MAX_PATH
];
428 /* Convert path into actual directory spec */
429 GetFullPathName (argCopy
, sizeof(thisDir
), thisDir
, NULL
);
430 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
432 strcpy(thisDir
, drive
);
433 strcat(thisDir
, dir
);
434 cPos
= strlen(thisDir
);
436 WINE_TRACE("Searching recursively in '%s'\n", thisDir
);
438 /* Append '*' to the directory */
440 thisDir
[cPos
+1] = 0x00;
442 hff
= FindFirstFile (thisDir
, &fd
);
444 /* Remove residual '*' */
445 thisDir
[cPos
] = 0x00;
447 if (hff
!= INVALID_HANDLE_VALUE
) {
448 DIRECTORY_STACK
*allDirs
= NULL
;
449 DIRECTORY_STACK
*lastEntry
= NULL
;
452 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
453 (strcmp(fd
.cFileName
, "..") != 0) &&
454 (strcmp(fd
.cFileName
, ".") != 0)) {
456 DIRECTORY_STACK
*nextDir
;
457 char subParm
[MAX_PATH
];
459 /* Work out search parameter in sub dir */
460 strcpy (subParm
, thisDir
);
461 strcat (subParm
, fd
.cFileName
);
462 strcat (subParm
, "\\");
463 strcat (subParm
, fname
);
464 strcat (subParm
, ext
);
465 WINE_TRACE("Recursive, Adding to search list '%s'\n", subParm
);
467 /* Allocate memory, add to list */
468 nextDir
= (DIRECTORY_STACK
*) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
469 if (allDirs
== NULL
) allDirs
= nextDir
;
470 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
472 nextDir
->next
= NULL
;
473 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,(strlen(subParm
)+1));
474 strcpy(nextDir
->dirName
, subParm
);
476 } while (FindNextFile(hff
, &fd
) != 0);
479 /* Go through each subdir doing the delete */
480 while (allDirs
!= NULL
) {
481 DIRECTORY_STACK
*tempDir
;
483 tempDir
= allDirs
->next
;
484 found
|= WCMD_delete (allDirs
->dirName
, FALSE
);
486 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
487 HeapFree(GetProcessHeap(),0,allDirs
);
492 /* Keep running total to see if any found, and if not recursing
493 issue error message */
497 WCMD_output ("%s : File Not Found\n", argCopy
);
504 /* Handle no valid args */
505 if (argsProcessed
== 0) {
506 WCMD_output ("Argument missing\n");
512 /****************************************************************************
515 * Echo input to the screen (or not). We don't try to emulate the bugs
516 * in DOS (try typing "ECHO ON AGAIN" for an example).
519 void WCMD_echo (const char *command
) {
521 static const char eon
[] = "Echo is ON\n", eoff
[] = "Echo is OFF\n";
524 if ((command
[0] == '.') && (command
[1] == 0)) {
525 WCMD_output (newline
);
530 count
= strlen(command
);
532 if (echo_mode
) WCMD_output (eon
);
533 else WCMD_output (eoff
);
536 if (lstrcmpi(command
, "ON") == 0) {
540 if (lstrcmpi(command
, "OFF") == 0) {
544 WCMD_output_asis (command
);
545 WCMD_output (newline
);
549 /**************************************************************************
552 * Batch file loop processing.
553 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
554 * will probably work here, but the reverse is not necessarily the case...
557 void WCMD_for (char *p
) {
562 char set
[MAX_PATH
], param
[MAX_PATH
];
565 if (lstrcmpi (WCMD_parameter (p
, 1, NULL
), "in")
566 || lstrcmpi (WCMD_parameter (p
, 3, NULL
), "do")
567 || (param1
[0] != '%')) {
568 WCMD_output ("Syntax error\n");
571 lstrcpyn (set
, WCMD_parameter (p
, 2, NULL
), sizeof(set
));
572 WCMD_parameter (p
, 4, &cmd
);
573 lstrcpy (param
, param1
);
576 * If the parameter within the set has a wildcard then search for matching files
577 * otherwise do a literal substitution.
581 while (*(item
= WCMD_parameter (set
, i
, NULL
))) {
582 if (strpbrk (item
, "*?")) {
583 hff
= FindFirstFile (item
, &fd
);
584 if (hff
== INVALID_HANDLE_VALUE
) {
588 WCMD_execute (cmd
, param
, fd
.cFileName
);
589 } while (FindNextFile(hff
, &fd
) != 0);
593 WCMD_execute (cmd
, param
, item
);
599 /*****************************************************************************
602 * Execute a command after substituting variable text for the supplied parameter
605 void WCMD_execute (char *orig_cmd
, char *param
, char *subst
) {
607 char *new_cmd
, *p
, *s
, *dup
;
610 size
= lstrlen (orig_cmd
);
611 new_cmd
= (char *) LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, size
);
612 dup
= s
= strdup (orig_cmd
);
614 while ((p
= strstr (s
, param
))) {
616 size
+= lstrlen (subst
);
617 new_cmd
= (char *) LocalReAlloc ((HANDLE
)new_cmd
, size
, 0);
619 strcat (new_cmd
, subst
);
620 s
= p
+ lstrlen (param
);
623 WCMD_process_command (new_cmd
);
625 LocalFree ((HANDLE
)new_cmd
);
629 /**************************************************************************
632 * Simple on-line help. Help text is stored in the resource file.
635 void WCMD_give_help (char *command
) {
640 command
= WCMD_strtrim_leading_spaces(command
);
641 if (lstrlen(command
) == 0) {
642 LoadString (hinst
, 1000, buffer
, sizeof(buffer
));
643 WCMD_output_asis (buffer
);
646 for (i
=0; i
<=WCMD_EXIT
; i
++) {
647 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
648 param1
, -1, inbuilt
[i
], -1) == 2) {
649 LoadString (hinst
, i
, buffer
, sizeof(buffer
));
650 WCMD_output_asis (buffer
);
654 WCMD_output ("No help available for %s\n", param1
);
659 /****************************************************************************
662 * Batch file jump instruction. Not the most efficient algorithm ;-)
663 * Prints error message if the specified label cannot be found - the file pointer is
664 * then at EOF, effectively stopping the batch file.
665 * FIXME: DOS is supposed to allow labels with spaces - we don't.
668 void WCMD_goto (void) {
670 char string
[MAX_PATH
];
672 if (param1
[0] == 0x00) {
673 WCMD_output ("Argument missing\n");
676 if (context
!= NULL
) {
677 char *paramStart
= param1
;
679 /* Handle special :EOF label */
680 if (lstrcmpi (":eof", param1
) == 0) {
681 context
-> skip_rest
= TRUE
;
685 /* Support goto :label as well as goto label */
686 if (*paramStart
== ':') paramStart
++;
688 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
689 while (WCMD_fgets (string
, sizeof(string
), context
-> h
)) {
690 if ((string
[0] == ':') && (lstrcmpi (&string
[1], paramStart
) == 0)) return;
692 WCMD_output ("Target to GOTO not found\n");
697 /*****************************************************************************
700 * Push a directory onto the stack
703 void WCMD_pushd (char *command
) {
704 struct env_stack
*curdir
;
707 if (strchr(command
, '/') != NULL
) {
708 SetLastError(ERROR_INVALID_PARAMETER
);
713 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
714 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
715 if( !curdir
|| !thisdir
) {
718 WCMD_output ("out of memory\n");
722 /* Change directory using CD code with /D parameter */
724 GetCurrentDirectoryW (1024, thisdir
);
726 WCMD_setshow_default(command
);
732 curdir
-> next
= pushd_directories
;
733 curdir
-> strings
= thisdir
;
734 if (pushd_directories
== NULL
) {
735 curdir
-> u
.stackdepth
= 1;
737 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
739 pushd_directories
= curdir
;
744 /*****************************************************************************
747 * Pop a directory from the stack
750 void WCMD_popd (void) {
751 struct env_stack
*temp
= pushd_directories
;
753 if (!pushd_directories
)
756 /* pop the old environment from the stack, and make it the current dir */
757 pushd_directories
= temp
->next
;
758 SetCurrentDirectoryW(temp
->strings
);
759 LocalFree (temp
->strings
);
763 /****************************************************************************
766 * Batch file conditional.
767 * FIXME: Much more syntax checking needed!
770 void WCMD_if (char *p
) {
772 int negate
= 0, test
= 0;
773 char condition
[MAX_PATH
], *command
, *s
;
775 if (!lstrcmpi (param1
, "not")) {
777 lstrcpy (condition
, param2
);
780 lstrcpy (condition
, param1
);
782 if (!lstrcmpi (condition
, "errorlevel")) {
783 if (errorlevel
>= atoi(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
784 WCMD_parameter (p
, 2+negate
, &command
);
786 else if (!lstrcmpi (condition
, "exist")) {
787 if (GetFileAttributesA(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
790 WCMD_parameter (p
, 2+negate
, &command
);
792 else if (!lstrcmpi (condition
, "defined")) {
793 if (GetEnvironmentVariableA(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
796 WCMD_parameter (p
, 2+negate
, &command
);
798 else if ((s
= strstr (p
, "=="))) {
800 if (!lstrcmpi (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
801 WCMD_parameter (s
, 1, &command
);
804 WCMD_output ("Syntax error\n");
807 if (test
!= negate
) {
808 command
= strdup (command
);
809 WCMD_process_command (command
);
814 /****************************************************************************
817 * Move a file, directory tree or wildcarded set of files.
820 void WCMD_move (void) {
825 char input
[MAX_PATH
];
826 char output
[MAX_PATH
];
829 char fname
[MAX_PATH
];
832 if (param1
[0] == 0x00) {
833 WCMD_output ("Argument missing\n");
837 /* If no destination supplied, assume current directory */
838 if (param2
[0] == 0x00) {
842 /* If 2nd parm is directory, then use original filename */
843 /* Convert partial path to full path */
844 GetFullPathName (param1
, sizeof(input
), input
, NULL
);
845 GetFullPathName (param2
, sizeof(output
), output
, NULL
);
846 WINE_TRACE("Move from '%s'('%s') to '%s'\n", input
, param1
, output
);
848 /* Split into components */
849 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
851 hff
= FindFirstFile (input
, &fd
);
852 while (hff
!= INVALID_HANDLE_VALUE
) {
857 WINE_TRACE("Processing file '%s'\n", fd
.cFileName
);
859 /* Build src & dest name */
863 /* See if dest is an existing directory */
864 attribs
= GetFileAttributes(output
);
865 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
866 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
867 strcpy(dest
, output
);
869 strcat(dest
, fd
.cFileName
);
871 strcpy(dest
, output
);
874 strcat(src
, fd
.cFileName
);
876 WINE_TRACE("Source '%s'\n", src
);
877 WINE_TRACE("Dest '%s'\n", dest
);
879 /* Check if file is read only, otherwise move it */
880 attribs
= GetFileAttributesA(src
);
881 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
882 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
883 SetLastError(ERROR_ACCESS_DENIED
);
888 /* If destination exists, prompt unless /Y supplied */
889 if (GetFileAttributesA(dest
) != INVALID_FILE_ATTRIBUTES
) {
891 char copycmd
[MAXSTRING
];
894 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
895 if (strstr (quals
, "/-Y"))
897 else if (strstr (quals
, "/Y"))
900 len
= GetEnvironmentVariable ("COPYCMD", copycmd
, sizeof(copycmd
));
901 force
= (len
&& len
< sizeof(copycmd
) && ! lstrcmpi (copycmd
, "/Y"));
904 /* Prompt if overwriting */
906 char question
[MAXSTRING
];
907 char overwrite
[MAXSTRING
];
909 LoadString (hinst
, WCMD_OVERWRITE
, overwrite
, sizeof(overwrite
));
911 /* Ask for confirmation */
912 sprintf(question
, "%s %s? ", overwrite
, dest
);
913 ok
= WCMD_ask_confirm(question
, TRUE
);
915 /* So delete the destination prior to the move */
917 if (!DeleteFile (dest
)) {
927 status
= MoveFile (src
, dest
);
929 status
= 1; /* Anything other than 0 to prevent error msg below */
938 /* Step on to next match */
939 if (FindNextFile(hff
, &fd
) == 0) {
941 hff
= INVALID_HANDLE_VALUE
;
947 /****************************************************************************
950 * Wait for keyboard input.
953 void WCMD_pause (void) {
958 WCMD_output (anykey
);
959 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
962 /****************************************************************************
965 * Delete a directory.
968 void WCMD_remove_dir (char *command
) {
971 int argsProcessed
= 0;
972 char *argN
= command
;
974 /* Loop through all args */
976 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
977 if (argN
&& argN
[0] != '/') {
978 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", thisArg
, quals
);
981 /* If subdirectory search not supplied, just try to remove
982 and report error if it fails (eg if it contains a file) */
983 if (strstr (quals
, "/S") == NULL
) {
984 if (!RemoveDirectory (thisArg
)) WCMD_print_error ();
986 /* Otherwise use ShFileOp to recursively remove a directory */
989 SHFILEOPSTRUCT lpDir
;
992 if (strstr (quals
, "/Q") == NULL
) {
994 char question
[MAXSTRING
];
996 /* Ask for confirmation */
997 sprintf(question
, "%s, ", thisArg
);
998 ok
= WCMD_ask_confirm(question
, TRUE
);
1000 /* Abort if answer is 'N' */
1007 lpDir
.pFrom
= thisArg
;
1008 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1009 lpDir
.wFunc
= FO_DELETE
;
1010 if (SHFileOperationA(&lpDir
)) WCMD_print_error ();
1015 /* Handle no valid args */
1016 if (argsProcessed
== 0) {
1017 WCMD_output ("Argument missing\n");
1023 /****************************************************************************
1029 void WCMD_rename (void) {
1034 char input
[MAX_PATH
];
1035 char *dotDst
= NULL
;
1038 char fname
[MAX_PATH
];
1044 /* Must be at least two args */
1045 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1046 WCMD_output ("Argument missing\n");
1051 /* Destination cannot contain a drive letter or directory separator */
1052 if ((strchr(param1
,':') != NULL
) || (strchr(param1
,'\\') != NULL
)) {
1053 SetLastError(ERROR_INVALID_PARAMETER
);
1059 /* Convert partial path to full path */
1060 GetFullPathName (param1
, sizeof(input
), input
, NULL
);
1061 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", input
, param1
, param2
);
1062 dotDst
= strchr(param2
, '.');
1064 /* Split into components */
1065 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1067 hff
= FindFirstFile (input
, &fd
);
1068 while (hff
!= INVALID_HANDLE_VALUE
) {
1069 char dest
[MAX_PATH
];
1071 char *dotSrc
= NULL
;
1074 WINE_TRACE("Processing file '%s'\n", fd
.cFileName
);
1076 /* FIXME: If dest name or extension is *, replace with filename/ext
1077 part otherwise use supplied name. This supports:
1079 ren jim.* fred.* etc
1080 However, windows has a more complex algorithum supporting eg
1081 ?'s and *'s mid name */
1082 dotSrc
= strchr(fd
.cFileName
, '.');
1084 /* Build src & dest name */
1088 dirLen
= strlen(src
);
1089 strcat(src
, fd
.cFileName
);
1092 if (param2
[0] == '*') {
1093 strcat(dest
, fd
.cFileName
);
1094 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1096 strcat(dest
, param2
);
1097 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1100 /* Build Extension */
1101 if (dotDst
&& (*(dotDst
+1)=='*')) {
1102 if (dotSrc
) strcat(dest
, dotSrc
);
1103 } else if (dotDst
) {
1104 if (dotDst
) strcat(dest
, dotDst
);
1107 WINE_TRACE("Source '%s'\n", src
);
1108 WINE_TRACE("Dest '%s'\n", dest
);
1110 /* Check if file is read only, otherwise move it */
1111 attribs
= GetFileAttributesA(src
);
1112 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1113 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1114 SetLastError(ERROR_ACCESS_DENIED
);
1117 status
= MoveFile (src
, dest
);
1121 WCMD_print_error ();
1125 /* Step on to next match */
1126 if (FindNextFile(hff
, &fd
) == 0) {
1128 hff
= INVALID_HANDLE_VALUE
;
1134 /*****************************************************************************
1137 * Make a copy of the environment.
1139 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1149 len
+= (lstrlenW(&env
[len
]) + 1);
1151 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1154 WCMD_output ("out of memory\n");
1157 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1163 /*****************************************************************************
1166 * setlocal pushes the environment onto a stack
1167 * Save the environment as unicode so we don't screw anything up.
1169 void WCMD_setlocal (const char *s
) {
1171 struct env_stack
*env_copy
;
1174 /* DISABLEEXTENSIONS ignored */
1176 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1179 WCMD_output ("out of memory\n");
1183 env
= GetEnvironmentStringsW ();
1185 env_copy
->strings
= WCMD_dupenv (env
);
1186 if (env_copy
->strings
)
1188 env_copy
->next
= saved_environment
;
1189 saved_environment
= env_copy
;
1191 /* Save the current drive letter */
1192 GetCurrentDirectory (MAX_PATH
, cwd
);
1193 env_copy
->u
.cwd
= cwd
[0];
1196 LocalFree (env_copy
);
1198 FreeEnvironmentStringsW (env
);
1202 /*****************************************************************************
1205 static inline WCHAR
*WCMD_strchrW(WCHAR
*str
, WCHAR ch
)
1216 /*****************************************************************************
1219 * endlocal pops the environment off a stack
1220 * Note: When searching for '=', search from char position 1, to handle
1221 * special internal environment variables =C:, =D: etc
1223 void WCMD_endlocal (void) {
1224 WCHAR
*env
, *old
, *p
;
1225 struct env_stack
*temp
;
1228 if (!saved_environment
)
1231 /* pop the old environment from the stack */
1232 temp
= saved_environment
;
1233 saved_environment
= temp
->next
;
1235 /* delete the current environment, totally */
1236 env
= GetEnvironmentStringsW ();
1237 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1240 n
= lstrlenW(&old
[len
]) + 1;
1241 p
= WCMD_strchrW(&old
[len
] + 1, '=');
1245 SetEnvironmentVariableW (&old
[len
], NULL
);
1250 FreeEnvironmentStringsW (env
);
1252 /* restore old environment */
1253 env
= temp
->strings
;
1256 n
= lstrlenW(&env
[len
]) + 1;
1257 p
= WCMD_strchrW(&env
[len
] + 1, '=');
1261 SetEnvironmentVariableW (&env
[len
], p
);
1266 /* Restore current drive letter */
1267 if (IsCharAlpha(temp
->u
.cwd
)) {
1270 sprintf(envvar
, "=%c:", temp
->u
.cwd
);
1271 if (GetEnvironmentVariable(envvar
, cwd
, MAX_PATH
)) {
1272 WINE_TRACE("Resetting cwd to %s\n", cwd
);
1273 SetCurrentDirectory(cwd
);
1281 /*****************************************************************************
1282 * WCMD_setshow_attrib
1284 * Display and optionally sets DOS attributes on a file or directory
1286 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1287 * As a result only the Readonly flag is correctly reported, the Archive bit
1288 * is always set and the rest are not implemented. We do the Right Thing anyway.
1290 * FIXME: No SET functionality.
1294 void WCMD_setshow_attrib (void) {
1299 char flags
[9] = {" "};
1301 if (param1
[0] == '-') {
1306 if (lstrlen(param1
) == 0) {
1307 GetCurrentDirectory (sizeof(param1
), param1
);
1308 strcat (param1
, "\\*");
1311 hff
= FindFirstFile (param1
, &fd
);
1312 if (hff
== INVALID_HANDLE_VALUE
) {
1313 WCMD_output ("%s: File Not Found\n",param1
);
1317 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1318 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1321 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1324 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1327 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1330 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1333 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1336 WCMD_output ("%s %s\n", flags
, fd
.cFileName
);
1337 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1339 } while (FindNextFile(hff
, &fd
) != 0);
1344 /*****************************************************************************
1345 * WCMD_setshow_default
1347 * Set/Show the current default directory
1350 void WCMD_setshow_default (char *command
) {
1359 WINE_TRACE("Request change to directory '%s'\n", command
);
1361 /* Skip /D and trailing whitespace if on the front of the command line */
1362 if (CompareString (LOCALE_USER_DEFAULT
,
1363 NORM_IGNORECASE
| SORT_STRINGSORT
,
1364 command
, 2, "/D", -1) == 2) {
1366 while (*command
&& *command
==' ') command
++;
1369 GetCurrentDirectory (sizeof(cwd
), cwd
);
1370 if (strlen(command
) == 0) {
1375 /* Remove any double quotes, which may be in the
1376 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1379 if (*command
!= '"') *pos
++ = *command
;
1384 /* Search for approprate directory */
1385 WINE_TRACE("Looking for directory '%s'\n", string
);
1386 hff
= FindFirstFile (string
, &fd
);
1387 while (hff
!= INVALID_HANDLE_VALUE
) {
1388 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1389 char fpath
[MAX_PATH
];
1392 char fname
[MAX_PATH
];
1395 /* Convert path into actual directory spec */
1396 GetFullPathName (string
, sizeof(fpath
), fpath
, NULL
);
1397 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1400 sprintf(string
, "%s%s%s", drive
, dir
, fd
.cFileName
);
1403 hff
= INVALID_HANDLE_VALUE
;
1407 /* Step on to next match */
1408 if (FindNextFile(hff
, &fd
) == 0) {
1410 hff
= INVALID_HANDLE_VALUE
;
1415 /* Change to that directory */
1416 WINE_TRACE("Really changing to directory '%s'\n", string
);
1418 status
= SetCurrentDirectory (string
);
1421 WCMD_print_error ();
1425 /* Restore old directory if drive letter would change, and
1426 CD x:\directory /D (or pushd c:\directory) not supplied */
1427 if ((strstr(quals
, "/D") == NULL
) &&
1428 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
1429 SetCurrentDirectory(cwd
);
1433 /* Set special =C: type environment variable, for drive letter of
1434 change of directory, even if path was restored due to missing
1435 /D (allows changing drive letter when not resident on that
1437 if ((string
[1] == ':') && IsCharAlpha (string
[0])) {
1440 strncpy(env
+1, string
, 2);
1442 SetEnvironmentVariable(env
, string
);
1449 /****************************************************************************
1452 * Set/Show the system date
1453 * FIXME: Can't change date yet
1456 void WCMD_setshow_date (void) {
1458 char curdate
[64], buffer
[64];
1461 if (lstrlen(param1
) == 0) {
1462 if (GetDateFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1463 curdate
, sizeof(curdate
))) {
1464 WCMD_output ("Current Date is %s\n", curdate
);
1465 if (strstr (quals
, "/T") == NULL
) {
1466 WCMD_output("Enter new date: ");
1467 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
), &count
, NULL
);
1473 else WCMD_print_error ();
1480 /****************************************************************************
1483 static int WCMD_compare( const void *a
, const void *b
)
1486 const char * const *str_a
= a
, * const *str_b
= b
;
1487 r
= CompareString( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1488 *str_a
, -1, *str_b
, -1 );
1489 if( r
== CSTR_LESS_THAN
) return -1;
1490 if( r
== CSTR_GREATER_THAN
) return 1;
1494 /****************************************************************************
1495 * WCMD_setshow_sortenv
1497 * sort variables into order for display
1498 * Optionally only display those who start with a stub
1499 * returns the count displayed
1501 static int WCMD_setshow_sortenv(const char *s
, const char *stub
)
1503 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
1506 if (stub
) stublen
= strlen(stub
);
1508 /* count the number of strings, and the total length */
1510 len
+= (lstrlen(&s
[len
]) + 1);
1514 /* add the strings to an array */
1515 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (char*) );
1519 for( i
=1; i
<count
; i
++ )
1520 str
[i
] = str
[i
-1] + lstrlen(str
[i
-1]) + 1;
1522 /* sort the array */
1523 qsort( str
, count
, sizeof (char*), WCMD_compare
);
1526 for( i
=0; i
<count
; i
++ ) {
1527 if (!stub
|| CompareString (LOCALE_USER_DEFAULT
,
1528 NORM_IGNORECASE
| SORT_STRINGSORT
,
1529 str
[i
], stublen
, stub
, -1) == 2) {
1530 /* Don't display special internal variables */
1531 if (str
[i
][0] != '=') {
1532 WCMD_output_asis(str
[i
]);
1533 WCMD_output_asis("\n");
1540 return displayedcount
;
1543 /****************************************************************************
1546 * Set/Show the environment variables
1549 void WCMD_setshow_env (char *s
) {
1556 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
1557 env
= GetEnvironmentStrings ();
1558 WCMD_setshow_sortenv( env
, NULL
);
1562 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1563 if (CompareString (LOCALE_USER_DEFAULT
,
1564 NORM_IGNORECASE
| SORT_STRINGSORT
,
1565 s
, 2, "/P", -1) == 2) {
1566 char string
[MAXSTRING
];
1570 while (*s
&& *s
==' ') s
++;
1572 /* If no parameter, or no '=' sign, return an error */
1573 if (!(*s
) || ((p
= strchr (s
, '=')) == NULL
)) {
1574 WCMD_output ("Argument missing\n");
1578 /* Output the prompt */
1580 if (strlen(p
) != 0) WCMD_output(p
);
1582 /* Read the reply */
1583 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
1585 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
1586 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
1587 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", s
, string
);
1588 status
= SetEnvironmentVariable (s
, string
);
1593 p
= strchr (s
, '=');
1595 env
= GetEnvironmentStrings ();
1596 if (WCMD_setshow_sortenv( env
, s
) == 0) {
1597 WCMD_output ("Environment variable %s not defined\n", s
);
1604 if (strlen(p
) == 0) p
= NULL
;
1605 status
= SetEnvironmentVariable (s
, p
);
1606 gle
= GetLastError();
1607 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
1609 } else if ((!status
)) WCMD_print_error();
1613 /****************************************************************************
1616 * Set/Show the path environment variable
1619 void WCMD_setshow_path (char *command
) {
1624 if (strlen(param1
) == 0) {
1625 status
= GetEnvironmentVariable ("PATH", string
, sizeof(string
));
1627 WCMD_output_asis ( "PATH=");
1628 WCMD_output_asis ( string
);
1629 WCMD_output_asis ( "\n");
1632 WCMD_output ("PATH not found\n");
1636 if (*command
== '=') command
++; /* Skip leading '=' */
1637 status
= SetEnvironmentVariable ("PATH", command
);
1638 if (!status
) WCMD_print_error();
1642 /****************************************************************************
1643 * WCMD_setshow_prompt
1645 * Set or show the command prompt.
1648 void WCMD_setshow_prompt (void) {
1652 if (strlen(param1
) == 0) {
1653 SetEnvironmentVariable ("PROMPT", NULL
);
1657 while ((*s
== '=') || (*s
== ' ')) s
++;
1658 if (strlen(s
) == 0) {
1659 SetEnvironmentVariable ("PROMPT", NULL
);
1661 else SetEnvironmentVariable ("PROMPT", s
);
1665 /****************************************************************************
1668 * Set/Show the system time
1669 * FIXME: Can't change time yet
1672 void WCMD_setshow_time (void) {
1674 char curtime
[64], buffer
[64];
1678 if (strlen(param1
) == 0) {
1680 if (GetTimeFormat (LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
1681 curtime
, sizeof(curtime
))) {
1682 WCMD_output ("Current Time is %s\n", curtime
);
1683 if (strstr (quals
, "/T") == NULL
) {
1684 WCMD_output ("Enter new time: ", curtime
);
1685 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
), &count
, NULL
);
1691 else WCMD_print_error ();
1698 /****************************************************************************
1701 * Shift batch parameters.
1702 * Optional /n says where to start shifting (n=0-8)
1705 void WCMD_shift (char *command
) {
1708 if (context
!= NULL
) {
1709 char *pos
= strchr(command
, '/');
1714 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
1715 start
= (*(pos
+1) - '0');
1717 SetLastError(ERROR_INVALID_PARAMETER
);
1722 WINE_TRACE("Shifting variables, starting at %d\n", start
);
1723 for (i
=start
;i
<=8;i
++) {
1724 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
1726 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
1731 /****************************************************************************
1734 * Set the console title
1736 void WCMD_title (char *command
) {
1737 SetConsoleTitle(command
);
1740 /****************************************************************************
1743 * Copy a file to standard output.
1746 void WCMD_type (char *command
) {
1749 char *argN
= command
;
1750 BOOL writeHeaders
= FALSE
;
1752 if (param1
[0] == 0x00) {
1753 WCMD_output ("Argument missing\n");
1757 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
1759 /* Loop through all args */
1762 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1770 WINE_TRACE("type: Processing arg '%s'\n", thisArg
);
1771 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1772 FILE_ATTRIBUTE_NORMAL
, NULL
);
1773 if (h
== INVALID_HANDLE_VALUE
) {
1774 WCMD_print_error ();
1775 WCMD_output ("%s :Failed\n", thisArg
);
1779 WCMD_output("\n%s\n\n", thisArg
);
1781 while (ReadFile (h
, buffer
, sizeof(buffer
), &count
, NULL
)) {
1782 if (count
== 0) break; /* ReadFile reports success on EOF! */
1784 WCMD_output_asis (buffer
);
1791 /****************************************************************************
1794 * Output either a file or stdin to screen in pages
1797 void WCMD_more (char *command
) {
1800 char *argN
= command
;
1801 BOOL useinput
= FALSE
;
1803 char moreStrPage
[100];
1807 /* Prefix the NLS more with '-- ', then load the text */
1809 strcpy(moreStr
, "-- ");
1810 LoadString (hinst
, WCMD_MORESTR
, &moreStr
[3], sizeof(moreStr
)-3);
1812 if (param1
[0] == 0x00) {
1814 /* Wine implements pipes via temporary files, and hence stdin is
1815 effectively reading from the file. This means the prompts for
1816 more are satistied by the next line from the input (file). To
1817 avoid this, ensure stdin is to the console */
1818 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
1819 HANDLE hConIn
= CreateFile("CONIN$", GENERIC_READ
| GENERIC_WRITE
,
1820 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1821 FILE_ATTRIBUTE_NORMAL
, 0);
1822 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
1824 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1825 once you get in this bit unless due to a pipe, its going to end badly... */
1827 sprintf(moreStrPage
, "%s --\n", moreStr
);
1829 WCMD_enter_paged_mode(moreStrPage
);
1830 while (ReadFile (hstdin
, buffer
, sizeof(buffer
)-1, &count
, NULL
)) {
1831 if (count
== 0) break; /* ReadFile reports success on EOF! */
1833 WCMD_output_asis (buffer
);
1835 WCMD_leave_paged_mode();
1837 /* Restore stdin to what it was */
1838 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
1839 CloseHandle(hConIn
);
1843 BOOL needsPause
= FALSE
;
1845 /* Loop through all args */
1846 WCMD_enter_paged_mode(moreStrPage
);
1849 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1857 sprintf(moreStrPage
, "%s (100%%) --\n", moreStr
);
1858 WCMD_leave_paged_mode();
1859 WCMD_output_asis(moreStrPage
);
1860 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
), &count
, NULL
);
1861 WCMD_enter_paged_mode(moreStrPage
);
1865 WINE_TRACE("more: Processing arg '%s'\n", thisArg
);
1866 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1867 FILE_ATTRIBUTE_NORMAL
, NULL
);
1868 if (h
== INVALID_HANDLE_VALUE
) {
1869 WCMD_print_error ();
1870 WCMD_output ("%s :Failed\n", thisArg
);
1874 ULONG64 fileLen
= 0;
1875 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
1877 /* Get the file size */
1878 GetFileAttributesEx(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
1879 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
1882 while (ReadFile (h
, buffer
, sizeof(buffer
), &count
, NULL
)) {
1883 if (count
== 0) break; /* ReadFile reports success on EOF! */
1887 /* Update % count (would be used in WCMD_output_asis as prompt) */
1888 sprintf(moreStrPage
, "%s (%2.2d%%) --\n", moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
1890 WCMD_output_asis (buffer
);
1896 WCMD_leave_paged_mode();
1900 /****************************************************************************
1903 * Display verify flag.
1904 * FIXME: We don't actually do anything with the verify flag other than toggle
1908 void WCMD_verify (char *command
) {
1910 static const char von
[] = "Verify is ON\n", voff
[] = "Verify is OFF\n";
1913 count
= strlen(command
);
1915 if (verify_mode
) WCMD_output (von
);
1916 else WCMD_output (voff
);
1919 if (lstrcmpi(command
, "ON") == 0) {
1923 else if (lstrcmpi(command
, "OFF") == 0) {
1927 else WCMD_output ("Verify must be ON or OFF\n");
1930 /****************************************************************************
1933 * Display version info.
1936 void WCMD_version (void) {
1938 WCMD_output (version_string
);
1942 /****************************************************************************
1945 * Display volume info and/or set volume label. Returns 0 if error.
1948 int WCMD_volume (int mode
, char *path
) {
1950 DWORD count
, serial
;
1951 char string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
1954 if (lstrlen(path
) == 0) {
1955 status
= GetCurrentDirectory (sizeof(curdir
), curdir
);
1957 WCMD_print_error ();
1960 status
= GetVolumeInformation (NULL
, label
, sizeof(label
), &serial
, NULL
,
1964 if ((path
[1] != ':') || (lstrlen(path
) != 2)) {
1965 WCMD_output_asis("Syntax Error\n\n");
1968 wsprintf (curdir
, "%s\\", path
);
1969 status
= GetVolumeInformation (curdir
, label
, sizeof(label
), &serial
, NULL
,
1973 WCMD_print_error ();
1976 WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n",
1977 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
1979 WCMD_output ("Volume label (11 characters, ENTER for none)?");
1980 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
1982 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
1983 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
1985 if (lstrlen(path
) != 0) {
1986 if (!SetVolumeLabel (curdir
, string
)) WCMD_print_error ();
1989 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
1995 /**************************************************************************
1998 * Exit either the process, or just this batch program
2002 void WCMD_exit (void) {
2004 int rc
= atoi(param1
); /* Note: atoi of empty parameter is 0 */
2006 if (context
&& lstrcmpi(quals
, "/B") == 0) {
2008 context
-> skip_rest
= TRUE
;
2014 /**************************************************************************
2017 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2020 * Returns True if Y answer is selected
2023 BOOL
WCMD_ask_confirm (char *message
, BOOL showSureText
) {
2025 char msgbuffer
[MAXSTRING
];
2026 char Ybuffer
[MAXSTRING
];
2027 char Nbuffer
[MAXSTRING
];
2028 char answer
[MAX_PATH
] = "";
2031 /* Load the translated 'Are you sure', plus valid answers */
2032 LoadString (hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
));
2033 LoadString (hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
));
2034 LoadString (hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
));
2036 /* Loop waiting on a Y or N */
2037 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
2038 WCMD_output_asis (message
);
2040 WCMD_output_asis (msgbuffer
);
2042 WCMD_output_asis (" (");
2043 WCMD_output_asis (Ybuffer
);
2044 WCMD_output_asis ("/");
2045 WCMD_output_asis (Nbuffer
);
2046 WCMD_output_asis (")?");
2047 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
2049 answer
[0] = toupper(answer
[0]);
2052 /* Return the answer */
2053 return (answer
[0] == Ybuffer
[0]);
2056 /*****************************************************************************
2059 * Lists or sets file associations (assoc = TRUE)
2060 * Lists or sets file types (assoc = FALSE)
2062 void WCMD_assoc (char *command
, BOOL assoc
) {
2065 DWORD accessOptions
= KEY_READ
;
2067 LONG rc
= ERROR_SUCCESS
;
2068 char keyValue
[MAXSTRING
];
2069 DWORD valueLen
= MAXSTRING
;
2073 /* See if parameter includes '=' */
2075 newValue
= strchr(command
, '=');
2076 if (newValue
) accessOptions
|= KEY_WRITE
;
2078 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2079 if (RegOpenKeyEx(HKEY_CLASSES_ROOT
, "", 0,
2080 accessOptions
, &key
) != ERROR_SUCCESS
) {
2081 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2085 /* If no parameters then list all associations */
2086 if (*command
== 0x00) {
2089 /* Enumerate all the keys */
2090 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2091 char keyName
[MAXSTRING
];
2094 /* Find the next value */
2095 nameLen
= MAXSTRING
;
2096 rc
= RegEnumKeyEx(key
, index
++,
2098 NULL
, NULL
, NULL
, NULL
);
2100 if (rc
== ERROR_SUCCESS
) {
2102 /* Only interested in extension ones if assoc, or others
2104 if ((keyName
[0] == '.' && assoc
) ||
2105 (!(keyName
[0] == '.') && (!assoc
)))
2107 char subkey
[MAXSTRING
];
2108 strcpy(subkey
, keyName
);
2109 if (!assoc
) strcat(subkey
, "\\Shell\\Open\\Command");
2111 if (RegOpenKeyEx(key
, subkey
, 0,
2112 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2114 valueLen
= sizeof(keyValue
);
2115 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2116 (LPBYTE
)keyValue
, &valueLen
);
2117 WCMD_output_asis(keyName
);
2118 WCMD_output_asis("=");
2119 /* If no default value found, leave line empty after '=' */
2120 if (rc
== ERROR_SUCCESS
) {
2121 WCMD_output_asis(keyValue
);
2123 WCMD_output_asis("\n");
2128 RegCloseKey(readKey
);
2132 /* Parameter supplied - if no '=' on command line, its a query */
2133 if (newValue
== NULL
) {
2135 char subkey
[MAXSTRING
];
2137 /* Query terminates the parameter at the first space */
2138 strcpy(keyValue
, command
);
2139 space
= strchr(keyValue
, ' ');
2140 if (space
) *space
=0x00;
2142 /* Set up key name */
2143 strcpy(subkey
, keyValue
);
2144 if (!assoc
) strcat(subkey
, "\\Shell\\Open\\Command");
2146 if (RegOpenKeyEx(key
, subkey
, 0,
2147 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2149 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2150 (LPBYTE
)keyValue
, &valueLen
);
2151 WCMD_output_asis(command
);
2152 WCMD_output_asis("=");
2153 /* If no default value found, leave line empty after '=' */
2154 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2155 WCMD_output_asis("\n");
2156 RegCloseKey(readKey
);
2159 char msgbuffer
[MAXSTRING
];
2160 char outbuffer
[MAXSTRING
];
2162 /* Load the translated 'File association not found' */
2164 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
));
2166 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
));
2168 sprintf(outbuffer
, msgbuffer
, keyValue
);
2169 WCMD_output_asis(outbuffer
);
2173 /* Not a query - its a set or clear of a value */
2176 char subkey
[MAXSTRING
];
2178 /* Get pointer to new value */
2182 /* Set up key name */
2183 strcpy(subkey
, command
);
2184 if (!assoc
) strcat(subkey
, "\\Shell\\Open\\Command");
2186 /* If nothing after '=' then clear value - only valid for ASSOC */
2187 if (*newValue
== 0x00) {
2189 if (assoc
) rc
= RegDeleteKey(key
, command
);
2190 if (assoc
&& rc
== ERROR_SUCCESS
) {
2191 WINE_TRACE("HKCR Key '%s' deleted\n", command
);
2193 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2198 char msgbuffer
[MAXSTRING
];
2199 char outbuffer
[MAXSTRING
];
2201 /* Load the translated 'File association not found' */
2203 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
));
2205 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
));
2207 sprintf(outbuffer
, msgbuffer
, keyValue
);
2208 WCMD_output_asis(outbuffer
);
2212 /* It really is a set value = contents */
2214 rc
= RegCreateKeyEx(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2215 accessOptions
, NULL
, &readKey
, NULL
);
2216 if (rc
== ERROR_SUCCESS
) {
2217 rc
= RegSetValueEx(readKey
, NULL
, 0, REG_SZ
,
2218 (LPBYTE
)newValue
, strlen(newValue
));
2219 RegCloseKey(readKey
);
2222 if (rc
!= ERROR_SUCCESS
) {
2226 WCMD_output_asis(command
);
2227 WCMD_output_asis("=");
2228 WCMD_output_asis(newValue
);
2229 WCMD_output_asis("\n");
2239 /****************************************************************************
2242 * Clear the terminal screen.
2245 void WCMD_color (void) {
2247 /* Emulate by filling the screen from the top left to bottom right with
2248 spaces, then moving the cursor to the top left afterwards */
2249 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2250 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2252 if (param1
[0] != 0x00 && strlen(param1
) > 2) {
2253 WCMD_output ("Argument invalid\n");
2257 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2263 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2268 /* Convert the color hex digits */
2269 if (param1
[0] == 0x00) {
2270 color
= defaultColor
;
2272 color
= strtoul(param1
, NULL
, 16);
2275 /* Fail if fg == bg color */
2276 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2281 /* Set the current screen contents and ensure all future writes
2282 remain this color */
2283 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2284 SetConsoleTextAttribute(hStdOut
, color
);