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
;
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 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 void WCMD_delete (char *command
) {
245 int argsProcessed
= 0;
246 char *argN
= command
;
248 /* Loop through all args */
250 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
251 if (argN
&& argN
[0] != '/') {
255 char fpath
[MAX_PATH
];
259 WINE_TRACE("del: Processing arg %s (quals:%s)\n", thisArg
, quals
);
262 /* If filename part of parameter is * or *.*, prompt unless
264 if ((strstr (quals
, "/Q") == NULL
) && (strstr (quals
, "/P") == NULL
)) {
268 char fname
[MAX_PATH
];
271 /* Convert path into actual directory spec */
272 GetFullPathName (thisArg
, sizeof(fpath
), fpath
, NULL
);
273 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
275 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
276 if ((strcmp(fname
, "*") == 0) &&
277 (*ext
== 0x00 || (strcmp(ext
, ".*") == 0))) {
279 char question
[MAXSTRING
];
281 /* Ask for confirmation */
282 sprintf(question
, "%s, ", fpath
);
283 ok
= WCMD_ask_confirm(question
, TRUE
);
285 /* Abort if answer is 'N' */
290 hff
= FindFirstFile (thisArg
, &fd
);
291 if (hff
== INVALID_HANDLE_VALUE
) {
292 WCMD_output ("%s :File Not Found\n", thisArg
);
295 /* Support del <dirname> by just deleting all files dirname\* */
296 if ((strchr(thisArg
,'*') == NULL
) && (strchr(thisArg
,'?') == NULL
)
297 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
298 char modifiedParm
[MAX_PATH
];
299 strcpy(modifiedParm
, thisArg
);
300 strcat(modifiedParm
, "\\*");
302 WCMD_delete(modifiedParm
);
307 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
308 strcpy (fpath
, thisArg
);
310 p
= strrchr (fpath
, '\\');
313 strcat (fpath
, fd
.cFileName
);
315 else strcpy (fpath
, fd
.cFileName
);
316 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
318 char *nextA
= strstr (quals
, "/A");
320 /* Handle attribute matching (/A) */
323 while (nextA
!= NULL
&& !ok
) {
325 char *thisA
= (nextA
+2);
328 /* Skip optional : */
329 if (*thisA
== ':') thisA
++;
331 /* Parse each of the /A[:]xxx in turn */
332 while (*thisA
&& *thisA
!= '/') {
334 BOOL attribute
= FALSE
;
336 /* Match negation of attribute first */
342 /* Match attribute */
344 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
346 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
348 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
350 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
353 WCMD_output ("Syntax error\n");
356 /* Now check result, keeping a running boolean about whether it
357 matches all parsed attribues so far */
358 if (attribute
&& !negate
) {
360 } else if (!attribute
&& negate
) {
368 /* Save the running total as the final result */
371 /* Step on to next /A set */
372 nextA
= strstr (nextA
+1, "/A");
376 /* /P means prompt for each file */
377 if (ok
&& strstr (quals
, "/P") != NULL
) {
378 char question
[MAXSTRING
];
380 /* Ask for confirmation */
381 sprintf(question
, "%s, Delete", fpath
);
382 ok
= WCMD_ask_confirm(question
, FALSE
);
385 /* Only proceed if ok to */
388 /* If file is read only, and /F supplied, delete it */
389 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
390 strstr (quals
, "/F") != NULL
) {
391 SetFileAttributes(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
394 /* Now do the delete */
395 if (!DeleteFile (fpath
)) WCMD_print_error ();
399 } while (FindNextFile(hff
, &fd
) != 0);
405 /* Handle no valid args */
406 if (argsProcessed
== 0) {
407 WCMD_output ("Argument missing\n");
412 /****************************************************************************
415 * Echo input to the screen (or not). We don't try to emulate the bugs
416 * in DOS (try typing "ECHO ON AGAIN" for an example).
419 void WCMD_echo (const char *command
) {
421 static const char eon
[] = "Echo is ON\n", eoff
[] = "Echo is OFF\n";
424 if ((command
[0] == '.') && (command
[1] == 0)) {
425 WCMD_output (newline
);
430 count
= strlen(command
);
432 if (echo_mode
) WCMD_output (eon
);
433 else WCMD_output (eoff
);
436 if (lstrcmpi(command
, "ON") == 0) {
440 if (lstrcmpi(command
, "OFF") == 0) {
444 WCMD_output_asis (command
);
445 WCMD_output (newline
);
449 /**************************************************************************
452 * Batch file loop processing.
453 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
454 * will probably work here, but the reverse is not necessarily the case...
457 void WCMD_for (char *p
) {
462 char set
[MAX_PATH
], param
[MAX_PATH
];
465 if (lstrcmpi (WCMD_parameter (p
, 1, NULL
), "in")
466 || lstrcmpi (WCMD_parameter (p
, 3, NULL
), "do")
467 || (param1
[0] != '%')) {
468 WCMD_output ("Syntax error\n");
471 lstrcpyn (set
, WCMD_parameter (p
, 2, NULL
), sizeof(set
));
472 WCMD_parameter (p
, 4, &cmd
);
473 lstrcpy (param
, param1
);
476 * If the parameter within the set has a wildcard then search for matching files
477 * otherwise do a literal substitution.
481 while (*(item
= WCMD_parameter (set
, i
, NULL
))) {
482 if (strpbrk (item
, "*?")) {
483 hff
= FindFirstFile (item
, &fd
);
484 if (hff
== INVALID_HANDLE_VALUE
) {
488 WCMD_execute (cmd
, param
, fd
.cFileName
);
489 } while (FindNextFile(hff
, &fd
) != 0);
493 WCMD_execute (cmd
, param
, item
);
499 /*****************************************************************************
502 * Execute a command after substituting variable text for the supplied parameter
505 void WCMD_execute (char *orig_cmd
, char *param
, char *subst
) {
507 char *new_cmd
, *p
, *s
, *dup
;
510 size
= lstrlen (orig_cmd
);
511 new_cmd
= (char *) LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, size
);
512 dup
= s
= strdup (orig_cmd
);
514 while ((p
= strstr (s
, param
))) {
516 size
+= lstrlen (subst
);
517 new_cmd
= (char *) LocalReAlloc ((HANDLE
)new_cmd
, size
, 0);
519 strcat (new_cmd
, subst
);
520 s
= p
+ lstrlen (param
);
523 WCMD_process_command (new_cmd
);
525 LocalFree ((HANDLE
)new_cmd
);
529 /**************************************************************************
532 * Simple on-line help. Help text is stored in the resource file.
535 void WCMD_give_help (char *command
) {
540 command
= WCMD_strtrim_leading_spaces(command
);
541 if (lstrlen(command
) == 0) {
542 LoadString (hinst
, 1000, buffer
, sizeof(buffer
));
543 WCMD_output_asis (buffer
);
546 for (i
=0; i
<=WCMD_EXIT
; i
++) {
547 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
548 param1
, -1, inbuilt
[i
], -1) == 2) {
549 LoadString (hinst
, i
, buffer
, sizeof(buffer
));
550 WCMD_output_asis (buffer
);
554 WCMD_output ("No help available for %s\n", param1
);
559 /****************************************************************************
562 * Batch file jump instruction. Not the most efficient algorithm ;-)
563 * Prints error message if the specified label cannot be found - the file pointer is
564 * then at EOF, effectively stopping the batch file.
565 * FIXME: DOS is supposed to allow labels with spaces - we don't.
568 void WCMD_goto (void) {
570 char string
[MAX_PATH
];
572 if (param1
[0] == 0x00) {
573 WCMD_output ("Argument missing\n");
576 if (context
!= NULL
) {
577 char *paramStart
= param1
;
579 /* Handle special :EOF label */
580 if (lstrcmpi (":eof", param1
) == 0) {
581 context
-> skip_rest
= TRUE
;
585 /* Support goto :label as well as goto label */
586 if (*paramStart
== ':') paramStart
++;
588 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
589 while (WCMD_fgets (string
, sizeof(string
), context
-> h
)) {
590 if ((string
[0] == ':') && (lstrcmpi (&string
[1], paramStart
) == 0)) return;
592 WCMD_output ("Target to GOTO not found\n");
597 /*****************************************************************************
600 * Push a directory onto the stack
603 void WCMD_pushd (void) {
604 struct env_stack
*curdir
;
608 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
609 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
610 if( !curdir
|| !thisdir
) {
613 WCMD_output ("out of memory\n");
617 GetCurrentDirectoryW (1024, thisdir
);
618 status
= SetCurrentDirectoryA (param1
);
625 curdir
-> next
= pushd_directories
;
626 curdir
-> strings
= thisdir
;
627 if (pushd_directories
== NULL
) {
628 curdir
-> stackdepth
= 1;
630 curdir
-> stackdepth
= pushd_directories
-> stackdepth
+ 1;
632 pushd_directories
= curdir
;
637 /*****************************************************************************
640 * Pop a directory from the stack
643 void WCMD_popd (void) {
644 struct env_stack
*temp
= pushd_directories
;
646 if (!pushd_directories
)
649 /* pop the old environment from the stack, and make it the current dir */
650 pushd_directories
= temp
->next
;
651 SetCurrentDirectoryW(temp
->strings
);
652 LocalFree (temp
->strings
);
656 /****************************************************************************
659 * Batch file conditional.
660 * FIXME: Much more syntax checking needed!
663 void WCMD_if (char *p
) {
665 int negate
= 0, test
= 0;
666 char condition
[MAX_PATH
], *command
, *s
;
668 if (!lstrcmpi (param1
, "not")) {
670 lstrcpy (condition
, param2
);
673 lstrcpy (condition
, param1
);
675 if (!lstrcmpi (condition
, "errorlevel")) {
676 if (errorlevel
>= atoi(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
677 WCMD_parameter (p
, 2+negate
, &command
);
679 else if (!lstrcmpi (condition
, "exist")) {
680 if (GetFileAttributesA(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
683 WCMD_parameter (p
, 2+negate
, &command
);
685 else if (!lstrcmpi (condition
, "defined")) {
686 if (GetEnvironmentVariableA(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
689 WCMD_parameter (p
, 2+negate
, &command
);
691 else if ((s
= strstr (p
, "=="))) {
693 if (!lstrcmpi (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
694 WCMD_parameter (s
, 1, &command
);
697 WCMD_output ("Syntax error\n");
700 if (test
!= negate
) {
701 command
= strdup (command
);
702 WCMD_process_command (command
);
707 /****************************************************************************
710 * Move a file, directory tree or wildcarded set of files.
711 * FIXME: Needs input and output files to be fully specified.
714 void WCMD_move (void) {
717 char outpath
[MAX_PATH
], inpath
[MAX_PATH
], *infile
;
721 if (param1
[0] == 0x00) {
722 WCMD_output ("Argument missing\n");
726 if ((strchr(param1
,'*') != NULL
) || (strchr(param1
,'%') != NULL
)) {
727 WCMD_output ("Wildcards not yet supported\n");
731 /* If no destination supplied, assume current directory */
732 if (param2
[0] == 0x00) {
736 /* If 2nd parm is directory, then use original filename */
737 GetFullPathName (param2
, sizeof(outpath
), outpath
, NULL
);
738 if (outpath
[strlen(outpath
) - 1] == '\\')
739 outpath
[strlen(outpath
) - 1] = '\0';
740 hff
= FindFirstFile (outpath
, &fd
);
741 if (hff
!= INVALID_HANDLE_VALUE
) {
742 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
743 GetFullPathName (param1
, sizeof(inpath
), inpath
, &infile
);
744 strcat (outpath
, "\\");
745 strcat (outpath
, infile
);
750 status
= MoveFile (param1
, outpath
);
751 if (!status
) WCMD_print_error ();
754 /****************************************************************************
757 * Wait for keyboard input.
760 void WCMD_pause (void) {
765 WCMD_output (anykey
);
766 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
769 /****************************************************************************
772 * Delete a directory.
775 void WCMD_remove_dir (char *command
) {
778 int argsProcessed
= 0;
779 char *argN
= command
;
781 /* Loop through all args */
783 char *thisArg
= WCMD_parameter (command
, argno
++, &argN
);
784 if (argN
&& argN
[0] != '/') {
785 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", thisArg
, quals
);
788 /* If subdirectory search not supplied, just try to remove
789 and report error if it fails (eg if it contains a file) */
790 if (strstr (quals
, "/S") == NULL
) {
791 if (!RemoveDirectory (thisArg
)) WCMD_print_error ();
793 /* Otherwise use ShFileOp to recursively remove a directory */
796 SHFILEOPSTRUCT lpDir
;
799 if (strstr (quals
, "/Q") == NULL
) {
801 char question
[MAXSTRING
];
803 /* Ask for confirmation */
804 sprintf(question
, "%s, ", thisArg
);
805 ok
= WCMD_ask_confirm(question
, TRUE
);
807 /* Abort if answer is 'N' */
814 lpDir
.pFrom
= thisArg
;
815 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
816 lpDir
.wFunc
= FO_DELETE
;
817 if (SHFileOperationA(&lpDir
)) WCMD_print_error ();
822 /* Handle no valid args */
823 if (argsProcessed
== 0) {
824 WCMD_output ("Argument missing\n");
830 /****************************************************************************
834 * FIXME: Needs input and output files to be fully specified.
837 void WCMD_rename (void) {
841 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
842 WCMD_output ("Argument missing\n");
845 if ((strchr(param1
,'*') != NULL
) || (strchr(param1
,'%') != NULL
)) {
846 WCMD_output ("Wildcards not yet supported\n");
849 status
= MoveFile (param1
, param2
);
850 if (!status
) WCMD_print_error ();
853 /*****************************************************************************
856 * Make a copy of the environment.
858 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
868 len
+= (lstrlenW(&env
[len
]) + 1);
870 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
873 WCMD_output ("out of memory\n");
876 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
882 /*****************************************************************************
885 * setlocal pushes the environment onto a stack
886 * Save the environment as unicode so we don't screw anything up.
888 void WCMD_setlocal (const char *s
) {
890 struct env_stack
*env_copy
;
892 /* DISABLEEXTENSIONS ignored */
894 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
897 WCMD_output ("out of memory\n");
901 env
= GetEnvironmentStringsW ();
903 env_copy
->strings
= WCMD_dupenv (env
);
904 if (env_copy
->strings
)
906 env_copy
->next
= saved_environment
;
907 saved_environment
= env_copy
;
910 LocalFree (env_copy
);
912 FreeEnvironmentStringsW (env
);
915 /*****************************************************************************
918 static inline WCHAR
*WCMD_strchrW(WCHAR
*str
, WCHAR ch
)
929 /*****************************************************************************
932 * endlocal pops the environment off a stack
934 void WCMD_endlocal (void) {
935 WCHAR
*env
, *old
, *p
;
936 struct env_stack
*temp
;
939 if (!saved_environment
)
942 /* pop the old environment from the stack */
943 temp
= saved_environment
;
944 saved_environment
= temp
->next
;
946 /* delete the current environment, totally */
947 env
= GetEnvironmentStringsW ();
948 old
= WCMD_dupenv (GetEnvironmentStringsW ());
951 n
= lstrlenW(&old
[len
]) + 1;
952 p
= WCMD_strchrW(&old
[len
], '=');
956 SetEnvironmentVariableW (&old
[len
], NULL
);
961 FreeEnvironmentStringsW (env
);
963 /* restore old environment */
967 n
= lstrlenW(&env
[len
]) + 1;
968 p
= WCMD_strchrW(&env
[len
], '=');
972 SetEnvironmentVariableW (&env
[len
], p
);
980 /*****************************************************************************
981 * WCMD_setshow_attrib
983 * Display and optionally sets DOS attributes on a file or directory
985 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
986 * As a result only the Readonly flag is correctly reported, the Archive bit
987 * is always set and the rest are not implemented. We do the Right Thing anyway.
989 * FIXME: No SET functionality.
993 void WCMD_setshow_attrib (void) {
998 char flags
[9] = {" "};
1000 if (param1
[0] == '-') {
1005 if (lstrlen(param1
) == 0) {
1006 GetCurrentDirectory (sizeof(param1
), param1
);
1007 strcat (param1
, "\\*");
1010 hff
= FindFirstFile (param1
, &fd
);
1011 if (hff
== INVALID_HANDLE_VALUE
) {
1012 WCMD_output ("%s: File Not Found\n",param1
);
1016 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1017 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1020 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1023 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1026 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1029 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1032 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1035 WCMD_output ("%s %s\n", flags
, fd
.cFileName
);
1036 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1038 } while (FindNextFile(hff
, &fd
) != 0);
1043 /*****************************************************************************
1044 * WCMD_setshow_default
1046 * Set/Show the current default directory
1049 void WCMD_setshow_default (void) {
1054 if (strlen(param1
) == 0) {
1055 GetCurrentDirectory (sizeof(string
), string
);
1056 strcat (string
, "\n");
1057 WCMD_output (string
);
1060 status
= SetCurrentDirectory (param1
);
1062 WCMD_print_error ();
1069 /****************************************************************************
1072 * Set/Show the system date
1073 * FIXME: Can't change date yet
1076 void WCMD_setshow_date (void) {
1078 char curdate
[64], buffer
[64];
1081 if (lstrlen(param1
) == 0) {
1082 if (GetDateFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1083 curdate
, sizeof(curdate
))) {
1084 WCMD_output ("Current Date is %s\nEnter new date: ", curdate
);
1085 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
), &count
, NULL
);
1090 else WCMD_print_error ();
1097 /****************************************************************************
1100 static int WCMD_compare( const void *a
, const void *b
)
1103 const char * const *str_a
= a
, * const *str_b
= b
;
1104 r
= CompareString( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1105 *str_a
, -1, *str_b
, -1 );
1106 if( r
== CSTR_LESS_THAN
) return -1;
1107 if( r
== CSTR_GREATER_THAN
) return 1;
1111 /****************************************************************************
1112 * WCMD_setshow_sortenv
1114 * sort variables into order for display
1115 * Optionally only display those who start with a stub
1116 * returns the count displayed
1118 static int WCMD_setshow_sortenv(const char *s
, const char *stub
)
1120 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
1123 if (stub
) stublen
= strlen(stub
);
1125 /* count the number of strings, and the total length */
1127 len
+= (lstrlen(&s
[len
]) + 1);
1131 /* add the strings to an array */
1132 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (char*) );
1136 for( i
=1; i
<count
; i
++ )
1137 str
[i
] = str
[i
-1] + lstrlen(str
[i
-1]) + 1;
1139 /* sort the array */
1140 qsort( str
, count
, sizeof (char*), WCMD_compare
);
1143 for( i
=0; i
<count
; i
++ ) {
1144 if (!stub
|| CompareString (LOCALE_USER_DEFAULT
,
1145 NORM_IGNORECASE
| SORT_STRINGSORT
,
1146 str
[i
], stublen
, stub
, -1) == 2) {
1147 WCMD_output_asis(str
[i
]);
1148 WCMD_output_asis("\n");
1154 return displayedcount
;
1157 /****************************************************************************
1160 * Set/Show the environment variables
1163 void WCMD_setshow_env (char *s
) {
1169 if (strlen(param1
) == 0) {
1170 env
= GetEnvironmentStrings ();
1171 WCMD_setshow_sortenv( env
, NULL
);
1174 p
= strchr (s
, '=');
1176 env
= GetEnvironmentStrings ();
1177 if (WCMD_setshow_sortenv( env
, s
) == 0) {
1178 WCMD_output ("Environment variable %s not defined\n", s
);
1184 if (strlen(p
) == 0) p
= NULL
;
1185 status
= SetEnvironmentVariable (s
, p
);
1186 if ((!status
) & (GetLastError() != ERROR_ENVVAR_NOT_FOUND
)) WCMD_print_error();
1190 /****************************************************************************
1193 * Set/Show the path environment variable
1196 void WCMD_setshow_path (char *command
) {
1201 if (strlen(param1
) == 0) {
1202 status
= GetEnvironmentVariable ("PATH", string
, sizeof(string
));
1204 WCMD_output_asis ( "PATH=");
1205 WCMD_output_asis ( string
);
1206 WCMD_output_asis ( "\n");
1209 WCMD_output ("PATH not found\n");
1213 if (*command
== '=') command
++; /* Skip leading '=' */
1214 status
= SetEnvironmentVariable ("PATH", command
);
1215 if (!status
) WCMD_print_error();
1219 /****************************************************************************
1220 * WCMD_setshow_prompt
1222 * Set or show the command prompt.
1225 void WCMD_setshow_prompt (void) {
1229 if (strlen(param1
) == 0) {
1230 SetEnvironmentVariable ("PROMPT", NULL
);
1234 while ((*s
== '=') || (*s
== ' ')) s
++;
1235 if (strlen(s
) == 0) {
1236 SetEnvironmentVariable ("PROMPT", NULL
);
1238 else SetEnvironmentVariable ("PROMPT", s
);
1242 /****************************************************************************
1245 * Set/Show the system time
1246 * FIXME: Can't change time yet
1249 void WCMD_setshow_time (void) {
1251 char curtime
[64], buffer
[64];
1255 if (strlen(param1
) == 0) {
1257 if (GetTimeFormat (LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
1258 curtime
, sizeof(curtime
))) {
1259 WCMD_output ("Current Time is %s\nEnter new time: ", curtime
);
1260 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
), &count
, NULL
);
1265 else WCMD_print_error ();
1272 /****************************************************************************
1275 * Shift batch parameters.
1278 void WCMD_shift (void) {
1280 if (context
!= NULL
) context
-> shift_count
++;
1284 /****************************************************************************
1287 * Set the console title
1289 void WCMD_title (char *command
) {
1290 SetConsoleTitle(command
);
1293 /****************************************************************************
1296 * Copy a file to standard output.
1299 void WCMD_type (void) {
1305 if (param1
[0] == 0x00) {
1306 WCMD_output ("Argument missing\n");
1309 h
= CreateFile (param1
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
1310 FILE_ATTRIBUTE_NORMAL
, NULL
);
1311 if (h
== INVALID_HANDLE_VALUE
) {
1312 WCMD_print_error ();
1315 while (ReadFile (h
, buffer
, sizeof(buffer
), &count
, NULL
)) {
1316 if (count
== 0) break; /* ReadFile reports success on EOF! */
1318 WCMD_output_asis (buffer
);
1323 /****************************************************************************
1326 * Display verify flag.
1327 * FIXME: We don't actually do anything with the verify flag other than toggle
1331 void WCMD_verify (char *command
) {
1333 static const char von
[] = "Verify is ON\n", voff
[] = "Verify is OFF\n";
1336 count
= strlen(command
);
1338 if (verify_mode
) WCMD_output (von
);
1339 else WCMD_output (voff
);
1342 if (lstrcmpi(command
, "ON") == 0) {
1346 else if (lstrcmpi(command
, "OFF") == 0) {
1350 else WCMD_output ("Verify must be ON or OFF\n");
1353 /****************************************************************************
1356 * Display version info.
1359 void WCMD_version (void) {
1361 WCMD_output (version_string
);
1365 /****************************************************************************
1368 * Display volume info and/or set volume label. Returns 0 if error.
1371 int WCMD_volume (int mode
, char *path
) {
1373 DWORD count
, serial
;
1374 char string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
1377 if (lstrlen(path
) == 0) {
1378 status
= GetCurrentDirectory (sizeof(curdir
), curdir
);
1380 WCMD_print_error ();
1383 status
= GetVolumeInformation (NULL
, label
, sizeof(label
), &serial
, NULL
,
1387 if ((path
[1] != ':') || (lstrlen(path
) != 2)) {
1388 WCMD_output_asis("Syntax Error\n\n");
1391 wsprintf (curdir
, "%s\\", path
);
1392 status
= GetVolumeInformation (curdir
, label
, sizeof(label
), &serial
, NULL
,
1396 WCMD_print_error ();
1399 WCMD_output ("Volume in drive %c is %s\nVolume Serial Number is %04x-%04x\n\n",
1400 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
1402 WCMD_output ("Volume label (11 characters, ENTER for none)?");
1403 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
), &count
, NULL
);
1405 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
1406 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
1408 if (lstrlen(path
) != 0) {
1409 if (!SetVolumeLabel (curdir
, string
)) WCMD_print_error ();
1412 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
1418 /**************************************************************************
1421 * Exit either the process, or just this batch program
1425 void WCMD_exit (void) {
1427 int rc
= atoi(param1
); /* Note: atoi of empty parameter is 0 */
1429 if (context
&& lstrcmpi(quals
, "/B") == 0) {
1431 context
-> skip_rest
= TRUE
;
1437 /**************************************************************************
1440 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
1443 * Returns True if Y answer is selected
1446 BOOL
WCMD_ask_confirm (char *message
, BOOL showSureText
) {
1448 char msgbuffer
[MAXSTRING
];
1449 char Ybuffer
[MAXSTRING
];
1450 char Nbuffer
[MAXSTRING
];
1451 char answer
[MAX_PATH
] = "";
1454 /* Load the translated 'Are you sure', plus valid answers */
1455 LoadString (hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
));
1456 LoadString (hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
));
1457 LoadString (hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
));
1459 /* Loop waiting on a Y or N */
1460 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
1461 WCMD_output_asis (message
);
1463 WCMD_output_asis (msgbuffer
);
1465 WCMD_output_asis (" (");
1466 WCMD_output_asis (Ybuffer
);
1467 WCMD_output_asis ("/");
1468 WCMD_output_asis (Nbuffer
);
1469 WCMD_output_asis (")?");
1470 ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
),
1472 answer
[0] = toupper(answer
[0]);
1475 /* Return the answer */
1476 return (answer
[0] == Ybuffer
[0]);
1479 /*****************************************************************************
1482 * Lists or sets file associations
1484 void WCMD_assoc (char *command
) {
1487 DWORD accessOptions
= KEY_READ
;
1489 LONG rc
= ERROR_SUCCESS
;
1490 char keyValue
[MAXSTRING
];
1491 DWORD valueLen
= MAXSTRING
;
1495 /* See if parameter includes '=' */
1497 newValue
= strchr(command
, '=');
1498 if (newValue
) accessOptions
|= KEY_WRITE
;
1500 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
1501 if (RegOpenKeyEx(HKEY_CLASSES_ROOT
, "", 0,
1502 accessOptions
, &key
) != ERROR_SUCCESS
) {
1503 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
1507 /* If no parameters then list all associations */
1508 if (*command
== 0x00) {
1511 /* Enumerate all the keys */
1512 while (rc
!= ERROR_NO_MORE_ITEMS
) {
1513 char keyName
[MAXSTRING
];
1516 /* Find the next value */
1517 nameLen
= MAXSTRING
;
1518 rc
= RegEnumKeyEx(key
, index
++,
1520 NULL
, NULL
, NULL
, NULL
);
1522 if (rc
== ERROR_SUCCESS
) {
1524 /* Only interested in extension ones */
1525 if (keyName
[0] == '.') {
1527 if (RegOpenKeyEx(key
, keyName
, 0,
1528 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
1530 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
1531 (LPBYTE
)keyValue
, &valueLen
);
1532 WCMD_output_asis(keyName
);
1533 WCMD_output_asis("=");
1534 /* If no default value found, leave line empty after '=' */
1535 if (rc
== ERROR_SUCCESS
) {
1536 WCMD_output_asis(keyValue
);
1538 WCMD_output_asis("\n");
1543 RegCloseKey(readKey
);
1547 /* Parameter supplied - if no '=' on command line, its a query */
1548 if (newValue
== NULL
) {
1551 /* Query terminates the parameter at the first space */
1552 strcpy(keyValue
, command
);
1553 space
= strchr(keyValue
, ' ');
1554 if (space
) *space
=0x00;
1556 if (RegOpenKeyEx(key
, keyValue
, 0,
1557 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
1559 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
1560 (LPBYTE
)keyValue
, &valueLen
);
1561 WCMD_output_asis(command
);
1562 WCMD_output_asis("=");
1563 /* If no default value found, leave line empty after '=' */
1564 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
1565 WCMD_output_asis("\n");
1566 RegCloseKey(readKey
);
1569 char msgbuffer
[MAXSTRING
];
1570 char outbuffer
[MAXSTRING
];
1572 /* Load the translated 'File association not found' */
1573 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
));
1574 sprintf(outbuffer
, msgbuffer
, keyValue
);
1575 WCMD_output_asis(outbuffer
);
1579 /* Not a query - its a set or clear of a value */
1582 /* Get pointer to new value */
1586 /* If nothing after '=' then clear value */
1587 if (*newValue
== 0x00) {
1589 rc
= RegDeleteKey(key
, command
);
1590 if (rc
== ERROR_SUCCESS
) {
1591 WINE_TRACE("HKCR Key '%s' deleted\n", command
);
1593 } else if (rc
!= ERROR_FILE_NOT_FOUND
) {
1598 char msgbuffer
[MAXSTRING
];
1599 char outbuffer
[MAXSTRING
];
1601 /* Load the translated 'File association not found' */
1602 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
));
1603 sprintf(outbuffer
, msgbuffer
, keyValue
);
1604 WCMD_output_asis(outbuffer
);
1608 /* It really is a set value = contents */
1610 rc
= RegCreateKeyEx(key
, command
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
1611 accessOptions
, NULL
, &readKey
, NULL
);
1612 if (rc
== ERROR_SUCCESS
) {
1613 rc
= RegSetValueEx(readKey
, NULL
, 0, REG_SZ
,
1614 (LPBYTE
)newValue
, strlen(newValue
));
1615 RegCloseKey(readKey
);
1618 if (rc
!= ERROR_SUCCESS
) {
1622 WCMD_output_asis(command
);
1623 WCMD_output_asis("=");
1624 WCMD_output_asis(newValue
);
1625 WCMD_output_asis("\n");