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) {
92 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
96 /****************************************************************************
99 * Copy a file or wildcarded set.
100 * FIXME: No wildcard support
103 void WCMD_copy (void) {
108 char outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
111 if (param1[0] == 0x00) {
112 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
116 if ((strchr(param1,'*') != NULL) && (strchr(param1,'%') != NULL)) {
117 WCMD_output ("Wildcards not yet supported\n");
121 /* If no destination supplied, assume current directory */
122 if (param2[0] == 0x00) {
126 GetFullPathName (param2, sizeof(outpath), outpath, NULL);
127 if (outpath[strlen(outpath) - 1] == '\\')
128 outpath[strlen(outpath) - 1] = '\0';
129 hff = FindFirstFile (outpath, &fd);
130 if (hff != INVALID_HANDLE_VALUE) {
131 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
132 GetFullPathName (param1, sizeof(inpath), inpath, &infile);
133 strcat (outpath, "\\");
134 strcat (outpath, infile);
139 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
140 if (strstr (quals, "/-Y"))
142 else if (strstr (quals, "/Y"))
145 len = GetEnvironmentVariable ("COPYCMD", copycmd, sizeof(copycmd));
146 force = (len && len < sizeof(copycmd) && ! lstrcmpi (copycmd, "/Y"));
150 hff = FindFirstFile (outpath, &fd);
151 if (hff != INVALID_HANDLE_VALUE) {
152 char buffer[MAXSTRING];
156 sprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
157 force = WCMD_ask_confirm(buffer, FALSE, NULL);
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 (WCMD_LoadMessage(WCMD_NOARG));
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, NULL);
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 (WCMD_LoadMessage(WCMD_SYNTAXERR));
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, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
396 ok = WCMD_ask_confirm(question, FALSE, NULL);
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 parameter 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 (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
504 /* Handle no valid args */
505 if (argsProcessed == 0) {
506 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
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) {
523 if ((command[0] == '.') && (command[1] == 0)) {
524 WCMD_output (newline);
529 count = strlen(command);
531 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), "ON");
532 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), "OFF");
535 if (lstrcmpi(command, "ON") == 0) {
539 if (lstrcmpi(command, "OFF") == 0) {
543 WCMD_output_asis (command);
544 WCMD_output (newline);
548 /**************************************************************************
551 * Batch file loop processing.
552 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
553 * will probably work here, but the reverse is not necessarily the case...
556 void WCMD_for (char *p) {
561 char set[MAX_PATH], param[MAX_PATH];
564 if (lstrcmpi (WCMD_parameter (p, 1, NULL), "in")
565 || lstrcmpi (WCMD_parameter (p, 3, NULL), "do")
566 || (param1[0] != '%')) {
567 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
570 lstrcpyn (set, WCMD_parameter (p, 2, NULL), sizeof(set));
571 WCMD_parameter (p, 4, &cmd);
572 lstrcpy (param, param1);
575 * If the parameter within the set has a wildcard then search for matching files
576 * otherwise do a literal substitution.
580 while (*(item = WCMD_parameter (set, i, NULL))) {
581 if (strpbrk (item, "*?")) {
582 hff = FindFirstFile (item, &fd);
583 if (hff == INVALID_HANDLE_VALUE) {
587 WCMD_execute (cmd, param, fd.cFileName);
588 } while (FindNextFile(hff, &fd) != 0);
592 WCMD_execute (cmd, param, item);
598 /*****************************************************************************
601 * Execute a command after substituting variable text for the supplied parameter
604 void WCMD_execute (char *orig_cmd, char *param, char *subst) {
606 char *new_cmd, *p, *s, *dup;
609 size = lstrlen (orig_cmd);
610 new_cmd = (char *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
611 dup = s = strdup (orig_cmd);
613 while ((p = strstr (s, param))) {
615 size += lstrlen (subst);
616 new_cmd = (char *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
618 strcat (new_cmd, subst);
619 s = p + lstrlen (param);
622 WCMD_process_command (new_cmd);
624 LocalFree ((HANDLE)new_cmd);
628 /**************************************************************************
631 * Simple on-line help. Help text is stored in the resource file.
634 void WCMD_give_help (char *command) {
639 command = WCMD_strtrim_leading_spaces(command);
640 if (lstrlen(command) == 0) {
641 LoadString (hinst, 1000, buffer, sizeof(buffer));
642 WCMD_output_asis (buffer);
645 for (i=0; i<=WCMD_EXIT; i++) {
646 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
647 param1, -1, inbuilt[i], -1) == 2) {
648 LoadString (hinst, i, buffer, sizeof(buffer));
649 WCMD_output_asis (buffer);
653 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
658 /****************************************************************************
661 * Batch file jump instruction. Not the most efficient algorithm ;-)
662 * Prints error message if the specified label cannot be found - the file pointer is
663 * then at EOF, effectively stopping the batch file.
664 * FIXME: DOS is supposed to allow labels with spaces - we don't.
667 void WCMD_goto (void) {
669 char string[MAX_PATH];
671 if (param1[0] == 0x00) {
672 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
675 if (context != NULL) {
676 char *paramStart = param1;
678 /* Handle special :EOF label */
679 if (lstrcmpi (":eof", param1) == 0) {
680 context -> skip_rest = TRUE;
684 /* Support goto :label as well as goto label */
685 if (*paramStart == ':') paramStart++;
687 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
688 while (WCMD_fgets (string, sizeof(string), context -> h)) {
689 if ((string[0] == ':') && (lstrcmpi (&string[1], paramStart) == 0)) return;
691 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
696 /*****************************************************************************
699 * Push a directory onto the stack
702 void WCMD_pushd (char *command) {
703 struct env_stack *curdir;
706 if (strchr(command, '/') != NULL) {
707 SetLastError(ERROR_INVALID_PARAMETER);
712 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
713 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
714 if( !curdir || !thisdir ) {
717 WINE_ERR ("out of memory\n");
721 /* Change directory using CD code with /D parameter */
723 GetCurrentDirectoryW (1024, thisdir);
725 WCMD_setshow_default(command);
731 curdir -> next = pushd_directories;
732 curdir -> strings = thisdir;
733 if (pushd_directories == NULL) {
734 curdir -> u.stackdepth = 1;
736 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
738 pushd_directories = curdir;
743 /*****************************************************************************
746 * Pop a directory from the stack
749 void WCMD_popd (void) {
750 struct env_stack *temp = pushd_directories;
752 if (!pushd_directories)
755 /* pop the old environment from the stack, and make it the current dir */
756 pushd_directories = temp->next;
757 SetCurrentDirectoryW(temp->strings);
758 LocalFree (temp->strings);
762 /****************************************************************************
765 * Batch file conditional.
766 * FIXME: Much more syntax checking needed!
769 void WCMD_if (char *p) {
771 int negate = 0, test = 0;
772 char condition[MAX_PATH], *command, *s;
774 if (!lstrcmpi (param1, "not")) {
776 lstrcpy (condition, param2);
779 lstrcpy (condition, param1);
781 if (!lstrcmpi (condition, "errorlevel")) {
782 if (errorlevel >= atoi(WCMD_parameter (p, 1+negate, NULL))) test = 1;
783 WCMD_parameter (p, 2+negate, &command);
785 else if (!lstrcmpi (condition, "exist")) {
786 if (GetFileAttributesA(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
789 WCMD_parameter (p, 2+negate, &command);
791 else if (!lstrcmpi (condition, "defined")) {
792 if (GetEnvironmentVariableA(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
795 WCMD_parameter (p, 2+negate, &command);
797 else if ((s = strstr (p, "=="))) {
799 if (!lstrcmpi (condition, WCMD_parameter (s, 0, NULL))) test = 1;
800 WCMD_parameter (s, 1, &command);
803 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
806 if (test != negate) {
807 command = strdup (command);
808 WCMD_process_command (command);
813 /****************************************************************************
816 * Move a file, directory tree or wildcarded set of files.
819 void WCMD_move (void) {
824 char input[MAX_PATH];
825 char output[MAX_PATH];
828 char fname[MAX_PATH];
831 if (param1[0] == 0x00) {
832 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
836 /* If no destination supplied, assume current directory */
837 if (param2[0] == 0x00) {
841 /* If 2nd parm is directory, then use original filename */
842 /* Convert partial path to full path */
843 GetFullPathName (param1, sizeof(input), input, NULL);
844 GetFullPathName (param2, sizeof(output), output, NULL);
845 WINE_TRACE("Move from '%s'('%s') to '%s'\n", input, param1, output);
847 /* Split into components */
848 WCMD_splitpath(input, drive, dir, fname, ext);
850 hff = FindFirstFile (input, &fd);
851 while (hff != INVALID_HANDLE_VALUE) {
856 WINE_TRACE("Processing file '%s'\n", fd.cFileName);
858 /* Build src & dest name */
862 /* See if dest is an existing directory */
863 attribs = GetFileAttributes(output);
864 if (attribs != INVALID_FILE_ATTRIBUTES &&
865 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
866 strcpy(dest, output);
868 strcat(dest, fd.cFileName);
870 strcpy(dest, output);
873 strcat(src, fd.cFileName);
875 WINE_TRACE("Source '%s'\n", src);
876 WINE_TRACE("Dest '%s'\n", dest);
878 /* Check if file is read only, otherwise move it */
879 attribs = GetFileAttributesA(src);
880 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
881 (attribs & FILE_ATTRIBUTE_READONLY)) {
882 SetLastError(ERROR_ACCESS_DENIED);
887 /* If destination exists, prompt unless /Y supplied */
888 if (GetFileAttributesA(dest) != INVALID_FILE_ATTRIBUTES) {
890 char copycmd[MAXSTRING];
893 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
894 if (strstr (quals, "/-Y"))
896 else if (strstr (quals, "/Y"))
899 len = GetEnvironmentVariable ("COPYCMD", copycmd, sizeof(copycmd));
900 force = (len && len < sizeof(copycmd) && ! lstrcmpi (copycmd, "/Y"));
903 /* Prompt if overwriting */
905 char question[MAXSTRING];
908 strcpy(yesChar, WCMD_LoadMessage(WCMD_YES));
910 /* Ask for confirmation */
911 sprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
912 ok = WCMD_ask_confirm(question, FALSE, NULL);
914 /* So delete the destination prior to the move */
916 if (!DeleteFile (dest)) {
926 status = MoveFile (src, dest);
928 status = 1; /* Anything other than 0 to prevent error msg below */
937 /* Step on to next match */
938 if (FindNextFile(hff, &fd) == 0) {
940 hff = INVALID_HANDLE_VALUE;
946 /****************************************************************************
949 * Wait for keyboard input.
952 void WCMD_pause (void) {
957 WCMD_output (anykey);
958 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
961 /****************************************************************************
964 * Delete a directory.
967 void WCMD_remove_dir (char *command) {
970 int argsProcessed = 0;
971 char *argN = command;
973 /* Loop through all args */
975 char *thisArg = WCMD_parameter (command, argno++, &argN);
976 if (argN && argN[0] != '/') {
977 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", thisArg, quals);
980 /* If subdirectory search not supplied, just try to remove
981 and report error if it fails (eg if it contains a file) */
982 if (strstr (quals, "/S") == NULL) {
983 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
985 /* Otherwise use ShFileOp to recursively remove a directory */
988 SHFILEOPSTRUCT lpDir;
991 if (strstr (quals, "/Q") == NULL) {
993 char question[MAXSTRING];
995 /* Ask for confirmation */
996 sprintf(question, "%s, ", thisArg);
997 ok = WCMD_ask_confirm(question, TRUE, NULL);
999 /* Abort if answer is 'N' */
1006 lpDir.pFrom = thisArg;
1007 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1008 lpDir.wFunc = FO_DELETE;
1009 if (SHFileOperationA(&lpDir)) WCMD_print_error ();
1014 /* Handle no valid args */
1015 if (argsProcessed == 0) {
1016 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1022 /****************************************************************************
1028 void WCMD_rename (void) {
1033 char input[MAX_PATH];
1034 char *dotDst = NULL;
1037 char fname[MAX_PATH];
1043 /* Must be at least two args */
1044 if (param1[0] == 0x00 || param2[0] == 0x00) {
1045 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1050 /* Destination cannot contain a drive letter or directory separator */
1051 if ((strchr(param1,':') != NULL) || (strchr(param1,'\\') != NULL)) {
1052 SetLastError(ERROR_INVALID_PARAMETER);
1058 /* Convert partial path to full path */
1059 GetFullPathName (param1, sizeof(input), input, NULL);
1060 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", input, param1, param2);
1061 dotDst = strchr(param2, '.');
1063 /* Split into components */
1064 WCMD_splitpath(input, drive, dir, fname, ext);
1066 hff = FindFirstFile (input, &fd);
1067 while (hff != INVALID_HANDLE_VALUE) {
1068 char dest[MAX_PATH];
1070 char *dotSrc = NULL;
1073 WINE_TRACE("Processing file '%s'\n", fd.cFileName);
1075 /* FIXME: If dest name or extension is *, replace with filename/ext
1076 part otherwise use supplied name. This supports:
1078 ren jim.* fred.* etc
1079 However, windows has a more complex algorithum supporting eg
1080 ?'s and *'s mid name */
1081 dotSrc = strchr(fd.cFileName, '.');
1083 /* Build src & dest name */
1087 dirLen = strlen(src);
1088 strcat(src, fd.cFileName);
1091 if (param2[0] == '*') {
1092 strcat(dest, fd.cFileName);
1093 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1095 strcat(dest, param2);
1096 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1099 /* Build Extension */
1100 if (dotDst && (*(dotDst+1)=='*')) {
1101 if (dotSrc) strcat(dest, dotSrc);
1102 } else if (dotDst) {
1103 if (dotDst) strcat(dest, dotDst);
1106 WINE_TRACE("Source '%s'\n", src);
1107 WINE_TRACE("Dest '%s'\n", dest);
1109 /* Check if file is read only, otherwise move it */
1110 attribs = GetFileAttributesA(src);
1111 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1112 (attribs & FILE_ATTRIBUTE_READONLY)) {
1113 SetLastError(ERROR_ACCESS_DENIED);
1116 status = MoveFile (src, dest);
1120 WCMD_print_error ();
1124 /* Step on to next match */
1125 if (FindNextFile(hff, &fd) == 0) {
1127 hff = INVALID_HANDLE_VALUE;
1133 /*****************************************************************************
1136 * Make a copy of the environment.
1138 static WCHAR *WCMD_dupenv( const WCHAR *env )
1148 len += (lstrlenW(&env[len]) + 1);
1150 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1153 WINE_ERR("out of memory\n");
1156 memcpy (env_copy, env, len*sizeof (WCHAR));
1162 /*****************************************************************************
1165 * setlocal pushes the environment onto a stack
1166 * Save the environment as unicode so we don't screw anything up.
1168 void WCMD_setlocal (const char *s) {
1170 struct env_stack *env_copy;
1173 /* DISABLEEXTENSIONS ignored */
1175 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1178 WINE_ERR ("out of memory\n");
1182 env = GetEnvironmentStringsW ();
1184 env_copy->strings = WCMD_dupenv (env);
1185 if (env_copy->strings)
1187 env_copy->next = saved_environment;
1188 saved_environment = env_copy;
1190 /* Save the current drive letter */
1191 GetCurrentDirectory (MAX_PATH, cwd);
1192 env_copy->u.cwd = cwd[0];
1195 LocalFree (env_copy);
1197 FreeEnvironmentStringsW (env);
1201 /*****************************************************************************
1204 static inline WCHAR *WCMD_strchrW(WCHAR *str, WCHAR ch)
1215 /*****************************************************************************
1218 * endlocal pops the environment off a stack
1219 * Note: When searching for '=', search from char position 1, to handle
1220 * special internal environment variables =C:, =D: etc
1222 void WCMD_endlocal (void) {
1223 WCHAR *env, *old, *p;
1224 struct env_stack *temp;
1227 if (!saved_environment)
1230 /* pop the old environment from the stack */
1231 temp = saved_environment;
1232 saved_environment = temp->next;
1234 /* delete the current environment, totally */
1235 env = GetEnvironmentStringsW ();
1236 old = WCMD_dupenv (GetEnvironmentStringsW ());
1239 n = lstrlenW(&old[len]) + 1;
1240 p = WCMD_strchrW(&old[len] + 1, '=');
1244 SetEnvironmentVariableW (&old[len], NULL);
1249 FreeEnvironmentStringsW (env);
1251 /* restore old environment */
1252 env = temp->strings;
1255 n = lstrlenW(&env[len]) + 1;
1256 p = WCMD_strchrW(&env[len] + 1, '=');
1260 SetEnvironmentVariableW (&env[len], p);
1265 /* Restore current drive letter */
1266 if (IsCharAlpha(temp->u.cwd)) {
1269 sprintf(envvar, "=%c:", temp->u.cwd);
1270 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1271 WINE_TRACE("Resetting cwd to %s\n", cwd);
1272 SetCurrentDirectory(cwd);
1280 /*****************************************************************************
1281 * WCMD_setshow_attrib
1283 * Display and optionally sets DOS attributes on a file or directory
1285 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1286 * As a result only the Readonly flag is correctly reported, the Archive bit
1287 * is always set and the rest are not implemented. We do the Right Thing anyway.
1289 * FIXME: No SET functionality.
1293 void WCMD_setshow_attrib (void) {
1298 char flags[9] = {" "};
1300 if (param1[0] == '-') {
1301 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1305 if (lstrlen(param1) == 0) {
1306 GetCurrentDirectory (sizeof(param1), param1);
1307 strcat (param1, "\\*");
1310 hff = FindFirstFile (param1, &fd);
1311 if (hff == INVALID_HANDLE_VALUE) {
1312 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1316 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1317 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1320 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1323 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1326 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1329 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1332 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1335 WCMD_output ("%s %s\n", flags, fd.cFileName);
1336 for (count=0; count < 8; count++) flags[count] = ' ';
1338 } while (FindNextFile(hff, &fd) != 0);
1343 /*****************************************************************************
1344 * WCMD_setshow_default
1346 * Set/Show the current default directory
1349 void WCMD_setshow_default (char *command) {
1358 WINE_TRACE("Request change to directory '%s'\n", command);
1360 /* Skip /D and trailing whitespace if on the front of the command line */
1361 if (CompareString (LOCALE_USER_DEFAULT,
1362 NORM_IGNORECASE | SORT_STRINGSORT,
1363 command, 2, "/D", -1) == 2) {
1365 while (*command && *command==' ') command++;
1368 GetCurrentDirectory (sizeof(cwd), cwd);
1369 if (strlen(command) == 0) {
1370 strcat (cwd, newline);
1374 /* Remove any double quotes, which may be in the
1375 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1378 if (*command != '"') *pos++ = *command;
1383 /* Search for approprate directory */
1384 WINE_TRACE("Looking for directory '%s'\n", string);
1385 hff = FindFirstFile (string, &fd);
1386 while (hff != INVALID_HANDLE_VALUE) {
1387 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1388 char fpath[MAX_PATH];
1391 char fname[MAX_PATH];
1394 /* Convert path into actual directory spec */
1395 GetFullPathName (string, sizeof(fpath), fpath, NULL);
1396 WCMD_splitpath(fpath, drive, dir, fname, ext);
1399 sprintf(string, "%s%s%s", drive, dir, fd.cFileName);
1402 hff = INVALID_HANDLE_VALUE;
1406 /* Step on to next match */
1407 if (FindNextFile(hff, &fd) == 0) {
1409 hff = INVALID_HANDLE_VALUE;
1414 /* Change to that directory */
1415 WINE_TRACE("Really changing to directory '%s'\n", string);
1417 status = SetCurrentDirectory (string);
1420 WCMD_print_error ();
1424 /* Restore old directory if drive letter would change, and
1425 CD x:\directory /D (or pushd c:\directory) not supplied */
1426 if ((strstr(quals, "/D") == NULL) &&
1427 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1428 SetCurrentDirectory(cwd);
1432 /* Set special =C: type environment variable, for drive letter of
1433 change of directory, even if path was restored due to missing
1434 /D (allows changing drive letter when not resident on that
1436 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1439 strncpy(env+1, string, 2);
1441 SetEnvironmentVariable(env, string);
1448 /****************************************************************************
1451 * Set/Show the system date
1452 * FIXME: Can't change date yet
1455 void WCMD_setshow_date (void) {
1457 char curdate[64], buffer[64];
1460 if (lstrlen(param1) == 0) {
1461 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1462 curdate, sizeof(curdate))) {
1463 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1464 if (strstr (quals, "/T") == NULL) {
1465 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1466 ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1468 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1472 else WCMD_print_error ();
1475 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1479 /****************************************************************************
1482 static int WCMD_compare( const void *a, const void *b )
1485 const char * const *str_a = a, * const *str_b = b;
1486 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1487 *str_a, -1, *str_b, -1 );
1488 if( r == CSTR_LESS_THAN ) return -1;
1489 if( r == CSTR_GREATER_THAN ) return 1;
1493 /****************************************************************************
1494 * WCMD_setshow_sortenv
1496 * sort variables into order for display
1497 * Optionally only display those who start with a stub
1498 * returns the count displayed
1500 static int WCMD_setshow_sortenv(const char *s, const char *stub)
1502 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1505 if (stub) stublen = strlen(stub);
1507 /* count the number of strings, and the total length */
1509 len += (lstrlen(&s[len]) + 1);
1513 /* add the strings to an array */
1514 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (char*) );
1518 for( i=1; i<count; i++ )
1519 str[i] = str[i-1] + lstrlen(str[i-1]) + 1;
1521 /* sort the array */
1522 qsort( str, count, sizeof (char*), WCMD_compare );
1525 for( i=0; i<count; i++ ) {
1526 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1527 NORM_IGNORECASE | SORT_STRINGSORT,
1528 str[i], stublen, stub, -1) == 2) {
1529 /* Don't display special internal variables */
1530 if (str[i][0] != '=') {
1531 WCMD_output_asis(str[i]);
1532 WCMD_output_asis(newline);
1539 return displayedcount;
1542 /****************************************************************************
1545 * Set/Show the environment variables
1548 void WCMD_setshow_env (char *s) {
1555 if (param1[0] == 0x00 && quals[0] == 0x00) {
1556 env = GetEnvironmentStrings ();
1557 WCMD_setshow_sortenv( env, NULL );
1561 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1562 if (CompareString (LOCALE_USER_DEFAULT,
1563 NORM_IGNORECASE | SORT_STRINGSORT,
1564 s, 2, "/P", -1) == 2) {
1565 char string[MAXSTRING];
1569 while (*s && *s==' ') s++;
1571 /* If no parameter, or no '=' sign, return an error */
1572 if (!(*s) || ((p = strchr (s, '=')) == NULL )) {
1573 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1577 /* Output the prompt */
1579 if (strlen(p) != 0) WCMD_output(p);
1581 /* Read the reply */
1582 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1584 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1585 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1586 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", s, string);
1587 status = SetEnvironmentVariable (s, string);
1592 p = strchr (s, '=');
1594 env = GetEnvironmentStrings ();
1595 if (WCMD_setshow_sortenv( env, s ) == 0) {
1596 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1603 if (strlen(p) == 0) p = NULL;
1604 status = SetEnvironmentVariable (s, p);
1605 gle = GetLastError();
1606 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1608 } else if ((!status)) WCMD_print_error();
1612 /****************************************************************************
1615 * Set/Show the path environment variable
1618 void WCMD_setshow_path (char *command) {
1623 if (strlen(param1) == 0) {
1624 status = GetEnvironmentVariable ("PATH", string, sizeof(string));
1626 WCMD_output_asis ( "PATH=");
1627 WCMD_output_asis ( string);
1628 WCMD_output_asis ( newline);
1631 WCMD_output ("PATH not found\n");
1635 if (*command == '=') command++; /* Skip leading '=' */
1636 status = SetEnvironmentVariable ("PATH", command);
1637 if (!status) WCMD_print_error();
1641 /****************************************************************************
1642 * WCMD_setshow_prompt
1644 * Set or show the command prompt.
1647 void WCMD_setshow_prompt (void) {
1651 if (strlen(param1) == 0) {
1652 SetEnvironmentVariable ("PROMPT", NULL);
1656 while ((*s == '=') || (*s == ' ')) s++;
1657 if (strlen(s) == 0) {
1658 SetEnvironmentVariable ("PROMPT", NULL);
1660 else SetEnvironmentVariable ("PROMPT", s);
1664 /****************************************************************************
1667 * Set/Show the system time
1668 * FIXME: Can't change time yet
1671 void WCMD_setshow_time (void) {
1673 char curtime[64], buffer[64];
1677 if (strlen(param1) == 0) {
1679 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1680 curtime, sizeof(curtime))) {
1681 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1682 if (strstr (quals, "/T") == NULL) {
1683 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1684 ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1686 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1690 else WCMD_print_error ();
1693 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1697 /****************************************************************************
1700 * Shift batch parameters.
1701 * Optional /n says where to start shifting (n=0-8)
1704 void WCMD_shift (char *command) {
1707 if (context != NULL) {
1708 char *pos = strchr(command, '/');
1713 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1714 start = (*(pos+1) - '0');
1716 SetLastError(ERROR_INVALID_PARAMETER);
1721 WINE_TRACE("Shifting variables, starting at %d\n", start);
1722 for (i=start;i<=8;i++) {
1723 context -> shift_count[i] = context -> shift_count[i+1] + 1;
1725 context -> shift_count[9] = context -> shift_count[9] + 1;
1730 /****************************************************************************
1733 * Set the console title
1735 void WCMD_title (char *command) {
1736 SetConsoleTitle(command);
1739 /****************************************************************************
1742 * Copy a file to standard output.
1745 void WCMD_type (char *command) {
1748 char *argN = command;
1749 BOOL writeHeaders = FALSE;
1751 if (param1[0] == 0x00) {
1752 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1756 if (param2[0] != 0x00) writeHeaders = TRUE;
1758 /* Loop through all args */
1761 char *thisArg = WCMD_parameter (command, argno++, &argN);
1769 WINE_TRACE("type: Processing arg '%s'\n", thisArg);
1770 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1771 FILE_ATTRIBUTE_NORMAL, NULL);
1772 if (h == INVALID_HANDLE_VALUE) {
1773 WCMD_print_error ();
1774 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1778 WCMD_output("\n%s\n\n", thisArg);
1780 while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1781 if (count == 0) break; /* ReadFile reports success on EOF! */
1783 WCMD_output_asis (buffer);
1790 /****************************************************************************
1793 * Output either a file or stdin to screen in pages
1796 void WCMD_more (char *command) {
1799 char *argN = command;
1800 BOOL useinput = FALSE;
1802 char moreStrPage[100];
1806 /* Prefix the NLS more with '-- ', then load the text */
1808 strcpy(moreStr, "-- ");
1809 LoadString (hinst, WCMD_MORESTR, &moreStr[3], sizeof(moreStr)-3);
1811 if (param1[0] == 0x00) {
1813 /* Wine implements pipes via temporary files, and hence stdin is
1814 effectively reading from the file. This means the prompts for
1815 more are satistied by the next line from the input (file). To
1816 avoid this, ensure stdin is to the console */
1817 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
1818 HANDLE hConIn = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
1819 FILE_SHARE_READ, NULL, OPEN_EXISTING,
1820 FILE_ATTRIBUTE_NORMAL, 0);
1821 SetStdHandle(STD_INPUT_HANDLE, hConIn);
1823 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1824 once you get in this bit unless due to a pipe, its going to end badly... */
1826 sprintf(moreStrPage, "%s --\n", moreStr);
1828 WCMD_enter_paged_mode(moreStrPage);
1829 while (ReadFile (hstdin, buffer, sizeof(buffer)-1, &count, NULL)) {
1830 if (count == 0) break; /* ReadFile reports success on EOF! */
1832 WCMD_output_asis (buffer);
1834 WCMD_leave_paged_mode();
1836 /* Restore stdin to what it was */
1837 SetStdHandle(STD_INPUT_HANDLE, hstdin);
1838 CloseHandle(hConIn);
1842 BOOL needsPause = FALSE;
1844 /* Loop through all args */
1845 WCMD_enter_paged_mode(moreStrPage);
1848 char *thisArg = WCMD_parameter (command, argno++, &argN);
1856 sprintf(moreStrPage, "%s (100%%) --\n", moreStr);
1857 WCMD_leave_paged_mode();
1858 WCMD_output_asis(moreStrPage);
1859 ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer), &count, NULL);
1860 WCMD_enter_paged_mode(moreStrPage);
1864 WINE_TRACE("more: Processing arg '%s'\n", thisArg);
1865 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1866 FILE_ATTRIBUTE_NORMAL, NULL);
1867 if (h == INVALID_HANDLE_VALUE) {
1868 WCMD_print_error ();
1869 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1873 ULONG64 fileLen = 0;
1874 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
1876 /* Get the file size */
1877 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
1878 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
1881 while (ReadFile (h, buffer, sizeof(buffer), &count, NULL)) {
1882 if (count == 0) break; /* ReadFile reports success on EOF! */
1886 /* Update % count (would be used in WCMD_output_asis as prompt) */
1887 sprintf(moreStrPage, "%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
1889 WCMD_output_asis (buffer);
1895 WCMD_leave_paged_mode();
1899 /****************************************************************************
1902 * Display verify flag.
1903 * FIXME: We don't actually do anything with the verify flag other than toggle
1907 void WCMD_verify (char *command) {
1911 count = strlen(command);
1913 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), "ON");
1914 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), "OFF");
1917 if (lstrcmpi(command, "ON") == 0) {
1921 else if (lstrcmpi(command, "OFF") == 0) {
1925 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
1928 /****************************************************************************
1931 * Display version info.
1934 void WCMD_version (void) {
1936 WCMD_output (version_string);
1940 /****************************************************************************
1943 * Display volume info and/or set volume label. Returns 0 if error.
1946 int WCMD_volume (int mode, char *path) {
1948 DWORD count, serial;
1949 char string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
1952 if (lstrlen(path) == 0) {
1953 status = GetCurrentDirectory (sizeof(curdir), curdir);
1955 WCMD_print_error ();
1958 status = GetVolumeInformation (NULL, label, sizeof(label), &serial, NULL,
1962 if ((path[1] != ':') || (lstrlen(path) != 2)) {
1963 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1966 wsprintf (curdir, "%s\\", path);
1967 status = GetVolumeInformation (curdir, label, sizeof(label), &serial, NULL,
1971 WCMD_print_error ();
1974 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
1975 curdir[0], label, HIWORD(serial), LOWORD(serial));
1977 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
1978 ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string), &count, NULL);
1980 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1981 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1983 if (lstrlen(path) != 0) {
1984 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
1987 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
1993 /**************************************************************************
1996 * Exit either the process, or just this batch program
2000 void WCMD_exit (void) {
2002 int rc = atoi(param1); /* Note: atoi of empty parameter is 0 */
2004 if (context && lstrcmpi(quals, "/B") == 0) {
2006 context -> skip_rest = TRUE;
2012 /**************************************************************************
2015 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2018 * Returns True if Y (or A) answer is selected
2019 * If optionAll contains a pointer, ALL is allowed, and if answered
2023 BOOL WCMD_ask_confirm (char *message, BOOL showSureText, BOOL *optionAll) {
2025 char msgbuffer[MAXSTRING];
2026 char Ybuffer[MAXSTRING];
2027 char Nbuffer[MAXSTRING];
2028 char Abuffer[MAXSTRING];
2029 char answer[MAX_PATH] = "";
2032 /* Load the translated 'Are you sure', plus valid answers */
2033 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer));
2034 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer));
2035 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer));
2036 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer));
2038 /* Loop waiting on a Y or N */
2039 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2040 WCMD_output_asis (message);
2042 WCMD_output_asis (msgbuffer);
2044 WCMD_output_asis (" (");
2045 WCMD_output_asis (Ybuffer);
2046 WCMD_output_asis ("/");
2047 WCMD_output_asis (Nbuffer);
2049 WCMD_output_asis ("/");
2050 WCMD_output_asis (Abuffer);
2052 WCMD_output_asis (")?");
2053 ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer),
2055 answer[0] = toupper(answer[0]);
2058 /* Return the answer */
2059 return ((answer[0] == Ybuffer[0]) ||
2060 (optionAll && (answer[0] == Abuffer[0])));
2063 /*****************************************************************************
2066 * Lists or sets file associations (assoc = TRUE)
2067 * Lists or sets file types (assoc = FALSE)
2069 void WCMD_assoc (char *command, BOOL assoc) {
2072 DWORD accessOptions = KEY_READ;
2074 LONG rc = ERROR_SUCCESS;
2075 char keyValue[MAXSTRING];
2076 DWORD valueLen = MAXSTRING;
2080 /* See if parameter includes '=' */
2082 newValue = strchr(command, '=');
2083 if (newValue) accessOptions |= KEY_WRITE;
2085 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2086 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, "", 0,
2087 accessOptions, &key) != ERROR_SUCCESS) {
2088 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2092 /* If no parameters then list all associations */
2093 if (*command == 0x00) {
2096 /* Enumerate all the keys */
2097 while (rc != ERROR_NO_MORE_ITEMS) {
2098 char keyName[MAXSTRING];
2101 /* Find the next value */
2102 nameLen = MAXSTRING;
2103 rc = RegEnumKeyEx(key, index++,
2105 NULL, NULL, NULL, NULL);
2107 if (rc == ERROR_SUCCESS) {
2109 /* Only interested in extension ones if assoc, or others
2111 if ((keyName[0] == '.' && assoc) ||
2112 (!(keyName[0] == '.') && (!assoc)))
2114 char subkey[MAXSTRING];
2115 strcpy(subkey, keyName);
2116 if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2118 if (RegOpenKeyEx(key, subkey, 0,
2119 accessOptions, &readKey) == ERROR_SUCCESS) {
2121 valueLen = sizeof(keyValue);
2122 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2123 (LPBYTE)keyValue, &valueLen);
2124 WCMD_output_asis(keyName);
2125 WCMD_output_asis("=");
2126 /* If no default value found, leave line empty after '=' */
2127 if (rc == ERROR_SUCCESS) {
2128 WCMD_output_asis(keyValue);
2130 WCMD_output_asis(newline);
2135 RegCloseKey(readKey);
2139 /* Parameter supplied - if no '=' on command line, its a query */
2140 if (newValue == NULL) {
2142 char subkey[MAXSTRING];
2144 /* Query terminates the parameter at the first space */
2145 strcpy(keyValue, command);
2146 space = strchr(keyValue, ' ');
2147 if (space) *space=0x00;
2149 /* Set up key name */
2150 strcpy(subkey, keyValue);
2151 if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2153 if (RegOpenKeyEx(key, subkey, 0,
2154 accessOptions, &readKey) == ERROR_SUCCESS) {
2156 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2157 (LPBYTE)keyValue, &valueLen);
2158 WCMD_output_asis(command);
2159 WCMD_output_asis("=");
2160 /* If no default value found, leave line empty after '=' */
2161 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2162 WCMD_output_asis(newline);
2163 RegCloseKey(readKey);
2166 char msgbuffer[MAXSTRING];
2167 char outbuffer[MAXSTRING];
2169 /* Load the translated 'File association not found' */
2171 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer));
2173 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer));
2175 sprintf(outbuffer, msgbuffer, keyValue);
2176 WCMD_output_asis(outbuffer);
2180 /* Not a query - its a set or clear of a value */
2183 char subkey[MAXSTRING];
2185 /* Get pointer to new value */
2189 /* Set up key name */
2190 strcpy(subkey, command);
2191 if (!assoc) strcat(subkey, "\\Shell\\Open\\Command");
2193 /* If nothing after '=' then clear value - only valid for ASSOC */
2194 if (*newValue == 0x00) {
2196 if (assoc) rc = RegDeleteKey(key, command);
2197 if (assoc && rc == ERROR_SUCCESS) {
2198 WINE_TRACE("HKCR Key '%s' deleted\n", command);
2200 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2205 char msgbuffer[MAXSTRING];
2206 char outbuffer[MAXSTRING];
2208 /* Load the translated 'File association not found' */
2210 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer));
2212 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer));
2214 sprintf(outbuffer, msgbuffer, keyValue);
2215 WCMD_output_asis(outbuffer);
2219 /* It really is a set value = contents */
2221 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2222 accessOptions, NULL, &readKey, NULL);
2223 if (rc == ERROR_SUCCESS) {
2224 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2225 (LPBYTE)newValue, strlen(newValue));
2226 RegCloseKey(readKey);
2229 if (rc != ERROR_SUCCESS) {
2233 WCMD_output_asis(command);
2234 WCMD_output_asis("=");
2235 WCMD_output_asis(newValue);
2236 WCMD_output_asis(newline);
2246 /****************************************************************************
2249 * Clear the terminal screen.
2252 void WCMD_color (void) {
2254 /* Emulate by filling the screen from the top left to bottom right with
2255 spaces, then moving the cursor to the top left afterwards */
2256 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2257 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2259 if (param1[0] != 0x00 && strlen(param1) > 2) {
2260 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2264 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2270 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2275 /* Convert the color hex digits */
2276 if (param1[0] == 0x00) {
2277 color = defaultColor;
2279 color = strtoul(param1, NULL, 16);
2282 /* Fail if fg == bg color */
2283 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2288 /* Set the current screen contents and ensure all future writes
2289 remain this color */
2290 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2291 SetConsoleTextAttribute(hStdOut, color);