2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 void WCMD_execute (WCHAR *orig_command, WCHAR *parameter, WCHAR *substitution, CMD_LIST **cmdList);
45 struct env_stack *saved_environment;
46 struct env_stack *pushd_directories;
48 extern HINSTANCE hinst;
49 extern WCHAR inbuilt[][10];
50 extern int echo_mode, verify_mode, defaultColor;
51 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
52 extern BATCH_CONTEXT *context;
53 extern DWORD errorlevel;
55 static const WCHAR dotW[] = {'.','\0'};
56 static const WCHAR dotdotW[] = {'.','.','\0'};
57 static const WCHAR slashW[] = {'\\','\0'};
58 static const WCHAR starW[] = {'*','\0'};
59 static const WCHAR equalW[] = {'=','\0'};
60 static const WCHAR fslashW[] = {'/','\0'};
61 static const WCHAR onW[] = {'O','N','\0'};
62 static const WCHAR offW[] = {'O','F','F','\0'};
63 static const WCHAR parmY[] = {'/','Y','\0'};
64 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
65 static const WCHAR nullW[] = {'\0'};
67 /****************************************************************************
70 * Clear the terminal screen.
73 void WCMD_clear_screen (void) {
75 /* Emulate by filling the screen from the top left to bottom right with
76 spaces, then moving the cursor to the top left afterwards */
77 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
78 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
80 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
85 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
89 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
90 SetConsoleCursorPosition(hStdOut, topLeft);
94 /****************************************************************************
97 * Change the default i/o device (ie redirect STDin/STDout).
100 void WCMD_change_tty (void) {
102 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
106 /****************************************************************************
109 * Copy a file or wildcarded set.
110 * FIXME: No wildcard support
113 void WCMD_copy (void) {
118 WCHAR outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
120 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 if (param1[0] == 0x00) {
123 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
127 if ((strchrW(param1,'*') != NULL) && (strchrW(param1,'%') != NULL)) {
128 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
132 /* If no destination supplied, assume current directory */
133 if (param2[0] == 0x00) {
134 strcpyW(param2, dotW);
137 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
138 if (outpath[strlenW(outpath) - 1] == '\\')
139 outpath[strlenW(outpath) - 1] = '\0';
140 hff = FindFirstFile (outpath, &fd);
141 if (hff != INVALID_HANDLE_VALUE) {
142 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
143 GetFullPathName (param1, sizeof(inpath)/sizeof(WCHAR), inpath, &infile);
144 strcatW (outpath, slashW);
145 strcatW (outpath, infile);
150 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
151 if (strstrW (quals, parmNoY))
153 else if (strstrW (quals, parmY))
156 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
157 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
161 hff = FindFirstFile (outpath, &fd);
162 if (hff != INVALID_HANDLE_VALUE) {
163 WCHAR buffer[MAXSTRING];
167 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
168 force = WCMD_ask_confirm(buffer, FALSE, NULL);
173 status = CopyFile (param1, outpath, FALSE);
174 if (!status) WCMD_print_error ();
178 /****************************************************************************
181 * Create a directory.
183 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
184 * they do not already exist.
187 static BOOL create_full_path(WCHAR* path)
193 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
194 strcpyW(new_path,path);
196 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
197 new_path[len - 1] = 0;
199 while (!CreateDirectory(new_path,NULL))
202 DWORD last_error = GetLastError();
203 if (last_error == ERROR_ALREADY_EXISTS)
206 if (last_error != ERROR_PATH_NOT_FOUND)
212 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
218 len = slash - new_path;
220 if (!create_full_path(new_path))
225 new_path[len] = '\\';
227 HeapFree(GetProcessHeap(),0,new_path);
231 void WCMD_create_dir (void) {
233 if (param1[0] == 0x00) {
234 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
237 if (!create_full_path(param1)) WCMD_print_error ();
240 /****************************************************************************
243 * Delete a file or wildcarded set.
246 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
247 * - Each set is a pattern, eg /ahr /as-r means
248 * readonly+hidden OR nonreadonly system files
249 * - The '-' applies to a single field, ie /a:-hr means read only
253 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
256 int argsProcessed = 0;
257 WCHAR *argN = command;
258 BOOL foundAny = FALSE;
259 static const WCHAR parmA[] = {'/','A','\0'};
260 static const WCHAR parmQ[] = {'/','Q','\0'};
261 static const WCHAR parmP[] = {'/','P','\0'};
262 static const WCHAR parmS[] = {'/','S','\0'};
263 static const WCHAR parmF[] = {'/','F','\0'};
265 /* If not recursing, clear error flag */
266 if (expectDir) errorlevel = 0;
268 /* Loop through all args */
270 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
271 WCHAR argCopy[MAX_PATH];
273 if (argN && argN[0] != '/') {
277 WCHAR fpath[MAX_PATH];
279 BOOL handleParm = TRUE;
281 static const WCHAR anyExt[]= {'.','*','\0'};
283 strcpyW(argCopy, thisArg);
284 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
285 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
288 /* If filename part of parameter is * or *.*, prompt unless
290 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
294 WCHAR fname[MAX_PATH];
297 /* Convert path into actual directory spec */
298 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
299 WCMD_splitpath(fpath, drive, dir, fname, ext);
301 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
302 if ((strcmpW(fname, starW) == 0) &&
303 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
305 WCHAR question[MAXSTRING];
306 static const WCHAR fmt[] = {'%','s',' ','\0'};
308 /* Note: Flag as found, to avoid file not found message */
311 /* Ask for confirmation */
312 wsprintf(question, fmt, fpath);
313 ok = WCMD_ask_confirm(question, TRUE, NULL);
315 /* Abort if answer is 'N' */
320 /* First, try to delete in the current directory */
321 hff = FindFirstFile (argCopy, &fd);
322 if (hff == INVALID_HANDLE_VALUE) {
328 /* Support del <dirname> by just deleting all files dirname\* */
329 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
330 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
331 WCHAR modifiedParm[MAX_PATH];
332 static const WCHAR slashStar[] = {'\\','*','\0'};
334 strcpyW(modifiedParm, argCopy);
335 strcatW(modifiedParm, slashStar);
338 WCMD_delete(modifiedParm, FALSE);
340 } else if (handleParm) {
342 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
343 strcpyW (fpath, argCopy);
345 p = strrchrW (fpath, '\\');
348 strcatW (fpath, fd.cFileName);
350 else strcpyW (fpath, fd.cFileName);
351 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
353 WCHAR *nextA = strstrW (quals, parmA);
355 /* Handle attribute matching (/A) */
358 while (nextA != NULL && !ok) {
360 WCHAR *thisA = (nextA+2);
363 /* Skip optional : */
364 if (*thisA == ':') thisA++;
366 /* Parse each of the /A[:]xxx in turn */
367 while (*thisA && *thisA != '/') {
369 BOOL attribute = FALSE;
371 /* Match negation of attribute first */
377 /* Match attribute */
379 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
381 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
383 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
385 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
388 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
391 /* Now check result, keeping a running boolean about whether it
392 matches all parsed attribues so far */
393 if (attribute && !negate) {
395 } else if (!attribute && negate) {
403 /* Save the running total as the final result */
406 /* Step on to next /A set */
407 nextA = strstrW (nextA+1, parmA);
411 /* /P means prompt for each file */
412 if (ok && strstrW (quals, parmP) != NULL) {
413 WCHAR question[MAXSTRING];
415 /* Ask for confirmation */
416 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
417 ok = WCMD_ask_confirm(question, FALSE, NULL);
420 /* Only proceed if ok to */
423 /* If file is read only, and /F supplied, delete it */
424 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
425 strstrW (quals, parmF) != NULL) {
426 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
429 /* Now do the delete */
430 if (!DeleteFile (fpath)) WCMD_print_error ();
434 } while (FindNextFile(hff, &fd) != 0);
438 /* Now recurse into all subdirectories handling the parameter in the same way */
439 if (strstrW (quals, parmS) != NULL) {
441 WCHAR thisDir[MAX_PATH];
446 WCHAR fname[MAX_PATH];
449 /* Convert path into actual directory spec */
450 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
451 WCMD_splitpath(thisDir, drive, dir, fname, ext);
453 strcpyW(thisDir, drive);
454 strcatW(thisDir, dir);
455 cPos = strlenW(thisDir);
457 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
459 /* Append '*' to the directory */
461 thisDir[cPos+1] = 0x00;
463 hff = FindFirstFile (thisDir, &fd);
465 /* Remove residual '*' */
466 thisDir[cPos] = 0x00;
468 if (hff != INVALID_HANDLE_VALUE) {
469 DIRECTORY_STACK *allDirs = NULL;
470 DIRECTORY_STACK *lastEntry = NULL;
473 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
474 (strcmpW(fd.cFileName, dotdotW) != 0) &&
475 (strcmpW(fd.cFileName, dotW) != 0)) {
477 DIRECTORY_STACK *nextDir;
478 WCHAR subParm[MAX_PATH];
480 /* Work out search parameter in sub dir */
481 strcpyW (subParm, thisDir);
482 strcatW (subParm, fd.cFileName);
483 strcatW (subParm, slashW);
484 strcatW (subParm, fname);
485 strcatW (subParm, ext);
486 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
488 /* Allocate memory, add to list */
489 nextDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
490 if (allDirs == NULL) allDirs = nextDir;
491 if (lastEntry != NULL) lastEntry->next = nextDir;
493 nextDir->next = NULL;
494 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
495 (strlenW(subParm)+1) * sizeof(WCHAR));
496 strcpyW(nextDir->dirName, subParm);
498 } while (FindNextFile(hff, &fd) != 0);
501 /* Go through each subdir doing the delete */
502 while (allDirs != NULL) {
503 DIRECTORY_STACK *tempDir;
505 tempDir = allDirs->next;
506 found |= WCMD_delete (allDirs->dirName, FALSE);
508 HeapFree(GetProcessHeap(),0,allDirs->dirName);
509 HeapFree(GetProcessHeap(),0,allDirs);
514 /* Keep running total to see if any found, and if not recursing
515 issue error message */
519 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
526 /* Handle no valid args */
527 if (argsProcessed == 0) {
528 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
534 /****************************************************************************
537 * Echo input to the screen (or not). We don't try to emulate the bugs
538 * in DOS (try typing "ECHO ON AGAIN" for an example).
541 void WCMD_echo (const WCHAR *command) {
545 if ((command[0] == '.') && (command[1] == 0)) {
546 WCMD_output (newline);
551 count = strlenW(command);
553 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
554 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
557 if (lstrcmpiW(command, onW) == 0) {
561 if (lstrcmpiW(command, offW) == 0) {
565 WCMD_output_asis (command);
566 WCMD_output (newline);
570 /**************************************************************************
573 * Batch file loop processing.
574 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
575 * will probably work here, but the reverse is not necessarily the case...
578 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
583 WCHAR set[MAX_PATH], param[MAX_PATH];
585 const WCHAR inW[] = {'i', 'n', '\0'};
586 const WCHAR doW[] = {'d', 'o', '\0'};
588 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
589 || lstrcmpiW (WCMD_parameter (p, 3, NULL), doW)
590 || (param1[0] != '%')) {
591 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
594 lstrcpynW (set, WCMD_parameter (p, 2, NULL), sizeof(set)/sizeof(WCHAR));
595 WCMD_parameter (p, 4, &cmd);
596 strcpyW (param, param1);
599 * If the parameter within the set has a wildcard then search for matching files
600 * otherwise do a literal substitution.
604 while (*(item = WCMD_parameter (set, i, NULL))) {
605 static const WCHAR wildcards[] = {'*','?','\0'};
606 if (strpbrkW (item, wildcards)) {
607 hff = FindFirstFile (item, &fd);
608 if (hff == INVALID_HANDLE_VALUE) {
612 WCMD_execute (cmd, param, fd.cFileName, cmdList);
613 } while (FindNextFile(hff, &fd) != 0);
617 WCMD_execute (cmd, param, item, cmdList);
623 /*****************************************************************************
626 * Execute a command after substituting variable text for the supplied parameter
629 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
631 WCHAR *new_cmd, *p, *s, *dup;
634 size = strlenW (orig_cmd);
635 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
636 dup = s = WCMD_strdupW(orig_cmd);
638 while ((p = strstrW (s, param))) {
640 size += strlenW (subst);
641 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
642 strcatW (new_cmd, s);
643 strcatW (new_cmd, subst);
644 s = p + strlenW (param);
646 strcatW (new_cmd, s);
647 WCMD_process_command (new_cmd, cmdList);
649 LocalFree ((HANDLE)new_cmd);
653 /**************************************************************************
656 * Simple on-line help. Help text is stored in the resource file.
659 void WCMD_give_help (WCHAR *command) {
663 command = WCMD_strtrim_leading_spaces(command);
664 if (strlenW(command) == 0) {
665 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
668 for (i=0; i<=WCMD_EXIT; i++) {
669 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
670 param1, -1, inbuilt[i], -1) == 2) {
671 WCMD_output_asis (WCMD_LoadMessage(i));
675 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
680 /****************************************************************************
683 * Batch file jump instruction. Not the most efficient algorithm ;-)
684 * Prints error message if the specified label cannot be found - the file pointer is
685 * then at EOF, effectively stopping the batch file.
686 * FIXME: DOS is supposed to allow labels with spaces - we don't.
689 void WCMD_goto (CMD_LIST **cmdList) {
691 WCHAR string[MAX_PATH];
693 /* Do not process any more parts of a processed multipart or multilines command */
696 if (param1[0] == 0x00) {
697 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
700 if (context != NULL) {
701 WCHAR *paramStart = param1;
702 static const WCHAR eofW[] = {':','e','o','f','\0'};
704 /* Handle special :EOF label */
705 if (lstrcmpiW (eofW, param1) == 0) {
706 context -> skip_rest = TRUE;
710 /* Support goto :label as well as goto label */
711 if (*paramStart == ':') paramStart++;
713 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
714 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
715 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
717 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
722 /*****************************************************************************
725 * Push a directory onto the stack
728 void WCMD_pushd (WCHAR *command) {
729 struct env_stack *curdir;
731 static const WCHAR parmD[] = {'/','D','\0'};
733 if (strchrW(command, '/') != NULL) {
734 SetLastError(ERROR_INVALID_PARAMETER);
739 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
740 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
741 if( !curdir || !thisdir ) {
744 WINE_ERR ("out of memory\n");
748 /* Change directory using CD code with /D parameter */
749 strcpyW(quals, parmD);
750 GetCurrentDirectoryW (1024, thisdir);
752 WCMD_setshow_default(command);
758 curdir -> next = pushd_directories;
759 curdir -> strings = thisdir;
760 if (pushd_directories == NULL) {
761 curdir -> u.stackdepth = 1;
763 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
765 pushd_directories = curdir;
770 /*****************************************************************************
773 * Pop a directory from the stack
776 void WCMD_popd (void) {
777 struct env_stack *temp = pushd_directories;
779 if (!pushd_directories)
782 /* pop the old environment from the stack, and make it the current dir */
783 pushd_directories = temp->next;
784 SetCurrentDirectoryW(temp->strings);
785 LocalFree (temp->strings);
789 /****************************************************************************
792 * Batch file conditional.
793 * FIXME: Much more syntax checking needed!
796 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
798 int negate = 0, test = 0;
799 WCHAR condition[MAX_PATH], *command, *s;
800 static const WCHAR notW[] = {'n','o','t','\0'};
801 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
802 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
803 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
804 static const WCHAR eqeqW[] = {'=','=','\0'};
806 if (!lstrcmpiW (param1, notW)) {
808 strcpyW (condition, param2);
811 strcpyW (condition, param1);
813 if (!lstrcmpiW (condition, errlvlW)) {
814 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
815 WCMD_parameter (p, 2+negate, &command);
817 else if (!lstrcmpiW (condition, existW)) {
818 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
821 WCMD_parameter (p, 2+negate, &command);
823 else if (!lstrcmpiW (condition, defdW)) {
824 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
827 WCMD_parameter (p, 2+negate, &command);
829 else if ((s = strstrW (p, eqeqW))) {
831 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
832 WCMD_parameter (s, 1, &command);
835 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
838 if (test != negate) {
839 command = WCMD_strdupW(command);
840 WCMD_process_command (command, cmdList);
845 /****************************************************************************
848 * Move a file, directory tree or wildcarded set of files.
851 void WCMD_move (void) {
856 WCHAR input[MAX_PATH];
857 WCHAR output[MAX_PATH];
860 WCHAR fname[MAX_PATH];
863 if (param1[0] == 0x00) {
864 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
868 /* If no destination supplied, assume current directory */
869 if (param2[0] == 0x00) {
870 strcpyW(param2, dotW);
873 /* If 2nd parm is directory, then use original filename */
874 /* Convert partial path to full path */
875 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
876 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
877 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
878 wine_dbgstr_w(param1), wine_dbgstr_w(output));
880 /* Split into components */
881 WCMD_splitpath(input, drive, dir, fname, ext);
883 hff = FindFirstFile (input, &fd);
884 while (hff != INVALID_HANDLE_VALUE) {
885 WCHAR dest[MAX_PATH];
889 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
891 /* Build src & dest name */
895 /* See if dest is an existing directory */
896 attribs = GetFileAttributes(output);
897 if (attribs != INVALID_FILE_ATTRIBUTES &&
898 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
899 strcpyW(dest, output);
900 strcatW(dest, slashW);
901 strcatW(dest, fd.cFileName);
903 strcpyW(dest, output);
906 strcatW(src, fd.cFileName);
908 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
909 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
911 /* Check if file is read only, otherwise move it */
912 attribs = GetFileAttributes(src);
913 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
914 (attribs & FILE_ATTRIBUTE_READONLY)) {
915 SetLastError(ERROR_ACCESS_DENIED);
920 /* If destination exists, prompt unless /Y supplied */
921 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
923 WCHAR copycmd[MAXSTRING];
926 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
927 if (strstrW (quals, parmNoY))
929 else if (strstrW (quals, parmY))
932 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
933 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
934 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
935 && ! lstrcmpiW (copycmd, parmY));
938 /* Prompt if overwriting */
940 WCHAR question[MAXSTRING];
943 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
945 /* Ask for confirmation */
946 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
947 ok = WCMD_ask_confirm(question, FALSE, NULL);
949 /* So delete the destination prior to the move */
951 if (!DeleteFile (dest)) {
961 status = MoveFile (src, dest);
963 status = 1; /* Anything other than 0 to prevent error msg below */
972 /* Step on to next match */
973 if (FindNextFile(hff, &fd) == 0) {
975 hff = INVALID_HANDLE_VALUE;
981 /****************************************************************************
984 * Wait for keyboard input.
987 void WCMD_pause (void) {
992 WCMD_output (anykey);
993 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
994 sizeof(string)/sizeof(WCHAR), &count, NULL);
997 /****************************************************************************
1000 * Delete a directory.
1003 void WCMD_remove_dir (WCHAR *command) {
1006 int argsProcessed = 0;
1007 WCHAR *argN = command;
1008 static const WCHAR parmS[] = {'/','S','\0'};
1009 static const WCHAR parmQ[] = {'/','Q','\0'};
1011 /* Loop through all args */
1013 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1014 if (argN && argN[0] != '/') {
1015 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1016 wine_dbgstr_w(quals));
1019 /* If subdirectory search not supplied, just try to remove
1020 and report error if it fails (eg if it contains a file) */
1021 if (strstrW (quals, parmS) == NULL) {
1022 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1024 /* Otherwise use ShFileOp to recursively remove a directory */
1027 SHFILEOPSTRUCT lpDir;
1030 if (strstrW (quals, parmQ) == NULL) {
1032 WCHAR question[MAXSTRING];
1033 static const WCHAR fmt[] = {'%','s',' ','\0'};
1035 /* Ask for confirmation */
1036 wsprintf(question, fmt, thisArg);
1037 ok = WCMD_ask_confirm(question, TRUE, NULL);
1039 /* Abort if answer is 'N' */
1046 lpDir.pFrom = thisArg;
1047 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1048 lpDir.wFunc = FO_DELETE;
1049 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1054 /* Handle no valid args */
1055 if (argsProcessed == 0) {
1056 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1062 /****************************************************************************
1068 void WCMD_rename (void) {
1073 WCHAR input[MAX_PATH];
1074 WCHAR *dotDst = NULL;
1076 WCHAR dir[MAX_PATH];
1077 WCHAR fname[MAX_PATH];
1078 WCHAR ext[MAX_PATH];
1083 /* Must be at least two args */
1084 if (param1[0] == 0x00 || param2[0] == 0x00) {
1085 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1090 /* Destination cannot contain a drive letter or directory separator */
1091 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1092 SetLastError(ERROR_INVALID_PARAMETER);
1098 /* Convert partial path to full path */
1099 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1100 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1101 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1102 dotDst = strchrW(param2, '.');
1104 /* Split into components */
1105 WCMD_splitpath(input, drive, dir, fname, ext);
1107 hff = FindFirstFile (input, &fd);
1108 while (hff != INVALID_HANDLE_VALUE) {
1109 WCHAR dest[MAX_PATH];
1110 WCHAR src[MAX_PATH];
1111 WCHAR *dotSrc = NULL;
1114 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1116 /* FIXME: If dest name or extension is *, replace with filename/ext
1117 part otherwise use supplied name. This supports:
1119 ren jim.* fred.* etc
1120 However, windows has a more complex algorithum supporting eg
1121 ?'s and *'s mid name */
1122 dotSrc = strchrW(fd.cFileName, '.');
1124 /* Build src & dest name */
1125 strcpyW(src, drive);
1128 dirLen = strlenW(src);
1129 strcatW(src, fd.cFileName);
1132 if (param2[0] == '*') {
1133 strcatW(dest, fd.cFileName);
1134 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1136 strcatW(dest, param2);
1137 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1140 /* Build Extension */
1141 if (dotDst && (*(dotDst+1)=='*')) {
1142 if (dotSrc) strcatW(dest, dotSrc);
1143 } else if (dotDst) {
1144 if (dotDst) strcatW(dest, dotDst);
1147 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1148 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1150 /* Check if file is read only, otherwise move it */
1151 attribs = GetFileAttributes(src);
1152 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1153 (attribs & FILE_ATTRIBUTE_READONLY)) {
1154 SetLastError(ERROR_ACCESS_DENIED);
1157 status = MoveFile (src, dest);
1161 WCMD_print_error ();
1165 /* Step on to next match */
1166 if (FindNextFile(hff, &fd) == 0) {
1168 hff = INVALID_HANDLE_VALUE;
1174 /*****************************************************************************
1177 * Make a copy of the environment.
1179 static WCHAR *WCMD_dupenv( const WCHAR *env )
1189 len += (strlenW(&env[len]) + 1);
1191 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1194 WINE_ERR("out of memory\n");
1197 memcpy (env_copy, env, len*sizeof (WCHAR));
1203 /*****************************************************************************
1206 * setlocal pushes the environment onto a stack
1207 * Save the environment as unicode so we don't screw anything up.
1209 void WCMD_setlocal (const WCHAR *s) {
1211 struct env_stack *env_copy;
1212 WCHAR cwd[MAX_PATH];
1214 /* DISABLEEXTENSIONS ignored */
1216 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1219 WINE_ERR ("out of memory\n");
1223 env = GetEnvironmentStringsW ();
1225 env_copy->strings = WCMD_dupenv (env);
1226 if (env_copy->strings)
1228 env_copy->next = saved_environment;
1229 saved_environment = env_copy;
1231 /* Save the current drive letter */
1232 GetCurrentDirectory (MAX_PATH, cwd);
1233 env_copy->u.cwd = cwd[0];
1236 LocalFree (env_copy);
1238 FreeEnvironmentStringsW (env);
1242 /*****************************************************************************
1245 * endlocal pops the environment off a stack
1246 * Note: When searching for '=', search from WCHAR position 1, to handle
1247 * special internal environment variables =C:, =D: etc
1249 void WCMD_endlocal (void) {
1250 WCHAR *env, *old, *p;
1251 struct env_stack *temp;
1254 if (!saved_environment)
1257 /* pop the old environment from the stack */
1258 temp = saved_environment;
1259 saved_environment = temp->next;
1261 /* delete the current environment, totally */
1262 env = GetEnvironmentStringsW ();
1263 old = WCMD_dupenv (GetEnvironmentStringsW ());
1266 n = strlenW(&old[len]) + 1;
1267 p = strchrW(&old[len] + 1, '=');
1271 SetEnvironmentVariableW (&old[len], NULL);
1276 FreeEnvironmentStringsW (env);
1278 /* restore old environment */
1279 env = temp->strings;
1282 n = strlenW(&env[len]) + 1;
1283 p = strchrW(&env[len] + 1, '=');
1287 SetEnvironmentVariableW (&env[len], p);
1292 /* Restore current drive letter */
1293 if (IsCharAlpha(temp->u.cwd)) {
1295 WCHAR cwd[MAX_PATH];
1296 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1298 wsprintf(envvar, fmt, temp->u.cwd);
1299 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1300 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1301 SetCurrentDirectory(cwd);
1309 /*****************************************************************************
1310 * WCMD_setshow_attrib
1312 * Display and optionally sets DOS attributes on a file or directory
1314 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1315 * As a result only the Readonly flag is correctly reported, the Archive bit
1316 * is always set and the rest are not implemented. We do the Right Thing anyway.
1318 * FIXME: No SET functionality.
1322 void WCMD_setshow_attrib (void) {
1327 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1329 if (param1[0] == '-') {
1330 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1334 if (strlenW(param1) == 0) {
1335 static const WCHAR slashStarW[] = {'\\','*','\0'};
1337 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1338 strcatW (param1, slashStarW);
1341 hff = FindFirstFile (param1, &fd);
1342 if (hff == INVALID_HANDLE_VALUE) {
1343 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1347 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1348 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1349 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1352 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1355 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1358 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1361 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1364 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1367 WCMD_output (fmt, flags, fd.cFileName);
1368 for (count=0; count < 8; count++) flags[count] = ' ';
1370 } while (FindNextFile(hff, &fd) != 0);
1375 /*****************************************************************************
1376 * WCMD_setshow_default
1378 * Set/Show the current default directory
1381 void WCMD_setshow_default (WCHAR *command) {
1389 static const WCHAR parmD[] = {'/','D','\0'};
1391 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1393 /* Skip /D and trailing whitespace if on the front of the command line */
1394 if (CompareString (LOCALE_USER_DEFAULT,
1395 NORM_IGNORECASE | SORT_STRINGSORT,
1396 command, 2, parmD, -1) == 2) {
1398 while (*command && *command==' ') command++;
1401 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1402 if (strlenW(command) == 0) {
1403 strcatW (cwd, newline);
1407 /* Remove any double quotes, which may be in the
1408 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1411 if (*command != '"') *pos++ = *command;
1416 /* Search for approprate directory */
1417 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1418 hff = FindFirstFile (string, &fd);
1419 while (hff != INVALID_HANDLE_VALUE) {
1420 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1421 WCHAR fpath[MAX_PATH];
1423 WCHAR dir[MAX_PATH];
1424 WCHAR fname[MAX_PATH];
1425 WCHAR ext[MAX_PATH];
1426 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1428 /* Convert path into actual directory spec */
1429 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1430 WCMD_splitpath(fpath, drive, dir, fname, ext);
1433 wsprintf(string, fmt, drive, dir, fd.cFileName);
1436 hff = INVALID_HANDLE_VALUE;
1440 /* Step on to next match */
1441 if (FindNextFile(hff, &fd) == 0) {
1443 hff = INVALID_HANDLE_VALUE;
1448 /* Change to that directory */
1449 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1451 status = SetCurrentDirectory (string);
1454 WCMD_print_error ();
1458 /* Restore old directory if drive letter would change, and
1459 CD x:\directory /D (or pushd c:\directory) not supplied */
1460 if ((strstrW(quals, parmD) == NULL) &&
1461 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1462 SetCurrentDirectory(cwd);
1466 /* Set special =C: type environment variable, for drive letter of
1467 change of directory, even if path was restored due to missing
1468 /D (allows changing drive letter when not resident on that
1470 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1472 strcpyW(env, equalW);
1473 memcpy(env+1, string, 2 * sizeof(WCHAR));
1475 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1476 SetEnvironmentVariable(env, string);
1483 /****************************************************************************
1486 * Set/Show the system date
1487 * FIXME: Can't change date yet
1490 void WCMD_setshow_date (void) {
1492 WCHAR curdate[64], buffer[64];
1494 static const WCHAR parmT[] = {'/','T','\0'};
1496 if (strlenW(param1) == 0) {
1497 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1498 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1499 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1500 if (strstrW (quals, parmT) == NULL) {
1501 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1502 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1503 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1505 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1509 else WCMD_print_error ();
1512 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1516 /****************************************************************************
1519 static int WCMD_compare( const void *a, const void *b )
1522 const WCHAR * const *str_a = a, * const *str_b = b;
1523 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1524 *str_a, -1, *str_b, -1 );
1525 if( r == CSTR_LESS_THAN ) return -1;
1526 if( r == CSTR_GREATER_THAN ) return 1;
1530 /****************************************************************************
1531 * WCMD_setshow_sortenv
1533 * sort variables into order for display
1534 * Optionally only display those who start with a stub
1535 * returns the count displayed
1537 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1539 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1542 if (stub) stublen = strlenW(stub);
1544 /* count the number of strings, and the total length */
1546 len += (strlenW(&s[len]) + 1);
1550 /* add the strings to an array */
1551 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1555 for( i=1; i<count; i++ )
1556 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1558 /* sort the array */
1559 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1562 for( i=0; i<count; i++ ) {
1563 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1564 NORM_IGNORECASE | SORT_STRINGSORT,
1565 str[i], stublen, stub, -1) == 2) {
1566 /* Don't display special internal variables */
1567 if (str[i][0] != '=') {
1568 WCMD_output_asis(str[i]);
1569 WCMD_output_asis(newline);
1576 return displayedcount;
1579 /****************************************************************************
1582 * Set/Show the environment variables
1585 void WCMD_setshow_env (WCHAR *s) {
1590 static const WCHAR parmP[] = {'/','P','\0'};
1593 if (param1[0] == 0x00 && quals[0] == 0x00) {
1594 env = GetEnvironmentStrings ();
1595 WCMD_setshow_sortenv( env, NULL );
1599 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1600 if (CompareString (LOCALE_USER_DEFAULT,
1601 NORM_IGNORECASE | SORT_STRINGSORT,
1602 s, 2, parmP, -1) == 2) {
1603 WCHAR string[MAXSTRING];
1607 while (*s && *s==' ') s++;
1609 /* If no parameter, or no '=' sign, return an error */
1610 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1611 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1615 /* Output the prompt */
1617 if (strlenW(p) != 0) WCMD_output(p);
1619 /* Read the reply */
1620 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1621 sizeof(string)/sizeof(WCHAR), &count, NULL);
1623 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1624 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1625 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1626 wine_dbgstr_w(string));
1627 status = SetEnvironmentVariable (s, string);
1632 p = strchrW (s, '=');
1634 env = GetEnvironmentStrings ();
1635 if (WCMD_setshow_sortenv( env, s ) == 0) {
1636 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1643 if (strlenW(p) == 0) p = NULL;
1644 status = SetEnvironmentVariable (s, p);
1645 gle = GetLastError();
1646 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1648 } else if ((!status)) WCMD_print_error();
1652 /****************************************************************************
1655 * Set/Show the path environment variable
1658 void WCMD_setshow_path (WCHAR *command) {
1662 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1663 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1665 if (strlenW(param1) == 0) {
1666 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1668 WCMD_output_asis ( pathEqW);
1669 WCMD_output_asis ( string);
1670 WCMD_output_asis ( newline);
1673 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1677 if (*command == '=') command++; /* Skip leading '=' */
1678 status = SetEnvironmentVariable (pathW, command);
1679 if (!status) WCMD_print_error();
1683 /****************************************************************************
1684 * WCMD_setshow_prompt
1686 * Set or show the command prompt.
1689 void WCMD_setshow_prompt (void) {
1692 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1694 if (strlenW(param1) == 0) {
1695 SetEnvironmentVariable (promptW, NULL);
1699 while ((*s == '=') || (*s == ' ')) s++;
1700 if (strlenW(s) == 0) {
1701 SetEnvironmentVariable (promptW, NULL);
1703 else SetEnvironmentVariable (promptW, s);
1707 /****************************************************************************
1710 * Set/Show the system time
1711 * FIXME: Can't change time yet
1714 void WCMD_setshow_time (void) {
1716 WCHAR curtime[64], buffer[64];
1719 static const WCHAR parmT[] = {'/','T','\0'};
1721 if (strlenW(param1) == 0) {
1723 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1724 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1725 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1726 if (strstrW (quals, parmT) == NULL) {
1727 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1728 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1729 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1731 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1735 else WCMD_print_error ();
1738 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1742 /****************************************************************************
1745 * Shift batch parameters.
1746 * Optional /n says where to start shifting (n=0-8)
1749 void WCMD_shift (WCHAR *command) {
1752 if (context != NULL) {
1753 WCHAR *pos = strchrW(command, '/');
1758 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1759 start = (*(pos+1) - '0');
1761 SetLastError(ERROR_INVALID_PARAMETER);
1766 WINE_TRACE("Shifting variables, starting at %d\n", start);
1767 for (i=start;i<=8;i++) {
1768 context -> shift_count[i] = context -> shift_count[i+1] + 1;
1770 context -> shift_count[9] = context -> shift_count[9] + 1;
1775 /****************************************************************************
1778 * Set the console title
1780 void WCMD_title (WCHAR *command) {
1781 SetConsoleTitle(command);
1784 /****************************************************************************
1787 * Copy a file to standard output.
1790 void WCMD_type (WCHAR *command) {
1793 WCHAR *argN = command;
1794 BOOL writeHeaders = FALSE;
1796 if (param1[0] == 0x00) {
1797 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1801 if (param2[0] != 0x00) writeHeaders = TRUE;
1803 /* Loop through all args */
1806 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1814 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1815 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1816 FILE_ATTRIBUTE_NORMAL, NULL);
1817 if (h == INVALID_HANDLE_VALUE) {
1818 WCMD_print_error ();
1819 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1823 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
1824 WCMD_output(fmt, thisArg);
1826 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
1827 if (count == 0) break; /* ReadFile reports success on EOF! */
1829 WCMD_output_asis (buffer);
1836 /****************************************************************************
1839 * Output either a file or stdin to screen in pages
1842 void WCMD_more (WCHAR *command) {
1845 WCHAR *argN = command;
1846 BOOL useinput = FALSE;
1848 WCHAR moreStrPage[100];
1851 static const WCHAR moreStart[] = {'-','-',' ','\0'};
1852 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
1853 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
1854 ')',' ','-','-','\n','\0'};
1855 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
1857 /* Prefix the NLS more with '-- ', then load the text */
1859 strcpyW(moreStr, moreStart);
1860 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
1861 (sizeof(moreStr)/sizeof(WCHAR))-3);
1863 if (param1[0] == 0x00) {
1865 /* Wine implements pipes via temporary files, and hence stdin is
1866 effectively reading from the file. This means the prompts for
1867 more are satistied by the next line from the input (file). To
1868 avoid this, ensure stdin is to the console */
1869 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
1870 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
1871 FILE_SHARE_READ, NULL, OPEN_EXISTING,
1872 FILE_ATTRIBUTE_NORMAL, 0);
1873 SetStdHandle(STD_INPUT_HANDLE, hConIn);
1875 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1876 once you get in this bit unless due to a pipe, its going to end badly... */
1878 wsprintf(moreStrPage, moreFmt, moreStr);
1880 WCMD_enter_paged_mode(moreStrPage);
1881 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1882 if (count == 0) break; /* ReadFile reports success on EOF! */
1884 WCMD_output_asis (buffer);
1886 WCMD_leave_paged_mode();
1888 /* Restore stdin to what it was */
1889 SetStdHandle(STD_INPUT_HANDLE, hstdin);
1890 CloseHandle(hConIn);
1894 BOOL needsPause = FALSE;
1896 /* Loop through all args */
1897 WCMD_enter_paged_mode(moreStrPage);
1900 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1908 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
1909 WCMD_leave_paged_mode();
1910 WCMD_output_asis(moreStrPage);
1911 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1912 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1913 WCMD_enter_paged_mode(moreStrPage);
1917 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1918 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1919 FILE_ATTRIBUTE_NORMAL, NULL);
1920 if (h == INVALID_HANDLE_VALUE) {
1921 WCMD_print_error ();
1922 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1926 ULONG64 fileLen = 0;
1927 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
1929 /* Get the file size */
1930 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
1931 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
1934 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1935 if (count == 0) break; /* ReadFile reports success on EOF! */
1939 /* Update % count (would be used in WCMD_output_asis as prompt) */
1940 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
1942 WCMD_output_asis (buffer);
1948 WCMD_leave_paged_mode();
1952 /****************************************************************************
1955 * Display verify flag.
1956 * FIXME: We don't actually do anything with the verify flag other than toggle
1960 void WCMD_verify (WCHAR *command) {
1964 count = strlenW(command);
1966 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
1967 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
1970 if (lstrcmpiW(command, onW) == 0) {
1974 else if (lstrcmpiW(command, offW) == 0) {
1978 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
1981 /****************************************************************************
1984 * Display version info.
1987 void WCMD_version (void) {
1989 WCMD_output (version_string);
1993 /****************************************************************************
1996 * Display volume info and/or set volume label. Returns 0 if error.
1999 int WCMD_volume (int mode, WCHAR *path) {
2001 DWORD count, serial;
2002 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2005 if (strlenW(path) == 0) {
2006 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2008 WCMD_print_error ();
2011 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2012 &serial, NULL, NULL, NULL, 0);
2015 static const WCHAR fmt[] = {'%','s','\\','\0'};
2016 if ((path[1] != ':') || (strlenW(path) != 2)) {
2017 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2020 wsprintf (curdir, fmt, path);
2021 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2026 WCMD_print_error ();
2029 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2030 curdir[0], label, HIWORD(serial), LOWORD(serial));
2032 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2033 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2034 sizeof(string)/sizeof(WCHAR), &count, NULL);
2036 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2037 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2039 if (strlenW(path) != 0) {
2040 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2043 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2049 /**************************************************************************
2052 * Exit either the process, or just this batch program
2056 void WCMD_exit (CMD_LIST **cmdList) {
2058 static const WCHAR parmB[] = {'/','B','\0'};
2059 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2061 if (context && lstrcmpiW(quals, parmB) == 0) {
2063 context -> skip_rest = TRUE;
2070 /**************************************************************************
2073 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2076 * Returns True if Y (or A) answer is selected
2077 * If optionAll contains a pointer, ALL is allowed, and if answered
2081 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2083 WCHAR msgbuffer[MAXSTRING];
2084 WCHAR Ybuffer[MAXSTRING];
2085 WCHAR Nbuffer[MAXSTRING];
2086 WCHAR Abuffer[MAXSTRING];
2087 WCHAR answer[MAX_PATH] = {'\0'};
2090 /* Load the translated 'Are you sure', plus valid answers */
2091 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2092 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2093 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2094 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2096 /* Loop waiting on a Y or N */
2097 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2098 static const WCHAR startBkt[] = {' ','(','\0'};
2099 static const WCHAR endBkt[] = {')','?','\0'};
2101 WCMD_output_asis (message);
2103 WCMD_output_asis (msgbuffer);
2105 WCMD_output_asis (startBkt);
2106 WCMD_output_asis (Ybuffer);
2107 WCMD_output_asis (fslashW);
2108 WCMD_output_asis (Nbuffer);
2110 WCMD_output_asis (fslashW);
2111 WCMD_output_asis (Abuffer);
2113 WCMD_output_asis (endBkt);
2114 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2115 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2116 answer[0] = toupper(answer[0]);
2119 /* Return the answer */
2120 return ((answer[0] == Ybuffer[0]) ||
2121 (optionAll && (answer[0] == Abuffer[0])));
2124 /*****************************************************************************
2127 * Lists or sets file associations (assoc = TRUE)
2128 * Lists or sets file types (assoc = FALSE)
2130 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2133 DWORD accessOptions = KEY_READ;
2135 LONG rc = ERROR_SUCCESS;
2136 WCHAR keyValue[MAXSTRING];
2137 DWORD valueLen = MAXSTRING;
2139 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2140 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2142 /* See if parameter includes '=' */
2144 newValue = strchrW(command, '=');
2145 if (newValue) accessOptions |= KEY_WRITE;
2147 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2148 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2149 accessOptions, &key) != ERROR_SUCCESS) {
2150 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2154 /* If no parameters then list all associations */
2155 if (*command == 0x00) {
2158 /* Enumerate all the keys */
2159 while (rc != ERROR_NO_MORE_ITEMS) {
2160 WCHAR keyName[MAXSTRING];
2163 /* Find the next value */
2164 nameLen = MAXSTRING;
2165 rc = RegEnumKeyEx(key, index++,
2167 NULL, NULL, NULL, NULL);
2169 if (rc == ERROR_SUCCESS) {
2171 /* Only interested in extension ones if assoc, or others
2173 if ((keyName[0] == '.' && assoc) ||
2174 (!(keyName[0] == '.') && (!assoc)))
2176 WCHAR subkey[MAXSTRING];
2177 strcpyW(subkey, keyName);
2178 if (!assoc) strcatW(subkey, shOpCmdW);
2180 if (RegOpenKeyEx(key, subkey, 0,
2181 accessOptions, &readKey) == ERROR_SUCCESS) {
2183 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2184 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2185 (LPBYTE)keyValue, &valueLen);
2186 WCMD_output_asis(keyName);
2187 WCMD_output_asis(equalW);
2188 /* If no default value found, leave line empty after '=' */
2189 if (rc == ERROR_SUCCESS) {
2190 WCMD_output_asis(keyValue);
2192 WCMD_output_asis(newline);
2197 RegCloseKey(readKey);
2201 /* Parameter supplied - if no '=' on command line, its a query */
2202 if (newValue == NULL) {
2204 WCHAR subkey[MAXSTRING];
2206 /* Query terminates the parameter at the first space */
2207 strcpyW(keyValue, command);
2208 space = strchrW(keyValue, ' ');
2209 if (space) *space=0x00;
2211 /* Set up key name */
2212 strcpyW(subkey, keyValue);
2213 if (!assoc) strcatW(subkey, shOpCmdW);
2215 if (RegOpenKeyEx(key, subkey, 0,
2216 accessOptions, &readKey) == ERROR_SUCCESS) {
2218 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2219 (LPBYTE)keyValue, &valueLen);
2220 WCMD_output_asis(command);
2221 WCMD_output_asis(equalW);
2222 /* If no default value found, leave line empty after '=' */
2223 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2224 WCMD_output_asis(newline);
2225 RegCloseKey(readKey);
2228 WCHAR msgbuffer[MAXSTRING];
2229 WCHAR outbuffer[MAXSTRING];
2231 /* Load the translated 'File association not found' */
2233 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2235 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2237 wsprintf(outbuffer, msgbuffer, keyValue);
2238 WCMD_output_asis(outbuffer);
2242 /* Not a query - its a set or clear of a value */
2245 WCHAR subkey[MAXSTRING];
2247 /* Get pointer to new value */
2251 /* Set up key name */
2252 strcpyW(subkey, command);
2253 if (!assoc) strcatW(subkey, shOpCmdW);
2255 /* If nothing after '=' then clear value - only valid for ASSOC */
2256 if (*newValue == 0x00) {
2258 if (assoc) rc = RegDeleteKey(key, command);
2259 if (assoc && rc == ERROR_SUCCESS) {
2260 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2262 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2267 WCHAR msgbuffer[MAXSTRING];
2268 WCHAR outbuffer[MAXSTRING];
2270 /* Load the translated 'File association not found' */
2272 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2273 sizeof(msgbuffer)/sizeof(WCHAR));
2275 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2276 sizeof(msgbuffer)/sizeof(WCHAR));
2278 wsprintf(outbuffer, msgbuffer, keyValue);
2279 WCMD_output_asis(outbuffer);
2283 /* It really is a set value = contents */
2285 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2286 accessOptions, NULL, &readKey, NULL);
2287 if (rc == ERROR_SUCCESS) {
2288 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2289 (LPBYTE)newValue, strlenW(newValue));
2290 RegCloseKey(readKey);
2293 if (rc != ERROR_SUCCESS) {
2297 WCMD_output_asis(command);
2298 WCMD_output_asis(equalW);
2299 WCMD_output_asis(newValue);
2300 WCMD_output_asis(newline);
2310 /****************************************************************************
2313 * Clear the terminal screen.
2316 void WCMD_color (void) {
2318 /* Emulate by filling the screen from the top left to bottom right with
2319 spaces, then moving the cursor to the top left afterwards */
2320 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2321 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2323 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2324 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2328 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2334 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2339 /* Convert the color hex digits */
2340 if (param1[0] == 0x00) {
2341 color = defaultColor;
2343 color = strtoulW(param1, NULL, 16);
2346 /* Fail if fg == bg color */
2347 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2352 /* Set the current screen contents and ensure all future writes
2353 remain this color */
2354 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2355 SetConsoleTextAttribute(hStdOut, color);