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 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
56 static const WCHAR dotW[] = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[] = {'\\','\0'};
59 static const WCHAR starW[] = {'*','\0'};
60 static const WCHAR equalW[] = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[] = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
68 /****************************************************************************
71 * Clear the terminal screen.
74 void WCMD_clear_screen (void) {
76 /* Emulate by filling the screen from the top left to bottom right with
77 spaces, then moving the cursor to the top left afterwards */
78 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
81 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
86 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
90 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91 SetConsoleCursorPosition(hStdOut, topLeft);
95 /****************************************************************************
98 * Change the default i/o device (ie redirect STDin/STDout).
101 void WCMD_change_tty (void) {
103 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
107 /****************************************************************************
110 * Copy a file or wildcarded set.
111 * FIXME: No wildcard support
114 void WCMD_copy (void) {
119 WCHAR outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
123 if (param1[0] == 0x00) {
124 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
128 if ((strchrW(param1,'*') != NULL) && (strchrW(param1,'%') != NULL)) {
129 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
133 /* If no destination supplied, assume current directory */
134 if (param2[0] == 0x00) {
135 strcpyW(param2, dotW);
138 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
139 if (outpath[strlenW(outpath) - 1] == '\\')
140 outpath[strlenW(outpath) - 1] = '\0';
141 hff = FindFirstFile (outpath, &fd);
142 if (hff != INVALID_HANDLE_VALUE) {
143 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
144 GetFullPathName (param1, sizeof(inpath)/sizeof(WCHAR), inpath, &infile);
145 strcatW (outpath, slashW);
146 strcatW (outpath, infile);
151 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
152 if (strstrW (quals, parmNoY))
154 else if (strstrW (quals, parmY))
157 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
158 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
162 hff = FindFirstFile (outpath, &fd);
163 if (hff != INVALID_HANDLE_VALUE) {
164 WCHAR buffer[MAXSTRING];
168 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
169 force = WCMD_ask_confirm(buffer, FALSE, NULL);
174 status = CopyFile (param1, outpath, FALSE);
175 if (!status) WCMD_print_error ();
179 /****************************************************************************
182 * Create a directory.
184 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
185 * they do not already exist.
188 static BOOL create_full_path(WCHAR* path)
194 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
195 strcpyW(new_path,path);
197 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
198 new_path[len - 1] = 0;
200 while (!CreateDirectory(new_path,NULL))
203 DWORD last_error = GetLastError();
204 if (last_error == ERROR_ALREADY_EXISTS)
207 if (last_error != ERROR_PATH_NOT_FOUND)
213 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
219 len = slash - new_path;
221 if (!create_full_path(new_path))
226 new_path[len] = '\\';
228 HeapFree(GetProcessHeap(),0,new_path);
232 void WCMD_create_dir (void) {
234 if (param1[0] == 0x00) {
235 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
238 if (!create_full_path(param1)) WCMD_print_error ();
241 /****************************************************************************
244 * Delete a file or wildcarded set.
247 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
248 * - Each set is a pattern, eg /ahr /as-r means
249 * readonly+hidden OR nonreadonly system files
250 * - The '-' applies to a single field, ie /a:-hr means read only
254 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
257 int argsProcessed = 0;
258 WCHAR *argN = command;
259 BOOL foundAny = FALSE;
260 static const WCHAR parmA[] = {'/','A','\0'};
261 static const WCHAR parmQ[] = {'/','Q','\0'};
262 static const WCHAR parmP[] = {'/','P','\0'};
263 static const WCHAR parmS[] = {'/','S','\0'};
264 static const WCHAR parmF[] = {'/','F','\0'};
266 /* If not recursing, clear error flag */
267 if (expectDir) errorlevel = 0;
269 /* Loop through all args */
271 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
272 WCHAR argCopy[MAX_PATH];
274 if (argN && argN[0] != '/') {
278 WCHAR fpath[MAX_PATH];
280 BOOL handleParm = TRUE;
282 static const WCHAR anyExt[]= {'.','*','\0'};
284 strcpyW(argCopy, thisArg);
285 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
286 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
289 /* If filename part of parameter is * or *.*, prompt unless
291 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
295 WCHAR fname[MAX_PATH];
298 /* Convert path into actual directory spec */
299 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
300 WCMD_splitpath(fpath, drive, dir, fname, ext);
302 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
303 if ((strcmpW(fname, starW) == 0) &&
304 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
306 WCHAR question[MAXSTRING];
307 static const WCHAR fmt[] = {'%','s',' ','\0'};
309 /* Note: Flag as found, to avoid file not found message */
312 /* Ask for confirmation */
313 wsprintf(question, fmt, fpath);
314 ok = WCMD_ask_confirm(question, TRUE, NULL);
316 /* Abort if answer is 'N' */
321 /* First, try to delete in the current directory */
322 hff = FindFirstFile (argCopy, &fd);
323 if (hff == INVALID_HANDLE_VALUE) {
329 /* Support del <dirname> by just deleting all files dirname\* */
330 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
331 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
332 WCHAR modifiedParm[MAX_PATH];
333 static const WCHAR slashStar[] = {'\\','*','\0'};
335 strcpyW(modifiedParm, argCopy);
336 strcatW(modifiedParm, slashStar);
339 WCMD_delete(modifiedParm, FALSE);
341 } else if (handleParm) {
343 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
344 strcpyW (fpath, argCopy);
346 p = strrchrW (fpath, '\\');
349 strcatW (fpath, fd.cFileName);
351 else strcpyW (fpath, fd.cFileName);
352 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
354 WCHAR *nextA = strstrW (quals, parmA);
356 /* Handle attribute matching (/A) */
359 while (nextA != NULL && !ok) {
361 WCHAR *thisA = (nextA+2);
364 /* Skip optional : */
365 if (*thisA == ':') thisA++;
367 /* Parse each of the /A[:]xxx in turn */
368 while (*thisA && *thisA != '/') {
370 BOOL attribute = FALSE;
372 /* Match negation of attribute first */
378 /* Match attribute */
380 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
382 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
384 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
386 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
389 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
392 /* Now check result, keeping a running boolean about whether it
393 matches all parsed attribues so far */
394 if (attribute && !negate) {
396 } else if (!attribute && negate) {
404 /* Save the running total as the final result */
407 /* Step on to next /A set */
408 nextA = strstrW (nextA+1, parmA);
412 /* /P means prompt for each file */
413 if (ok && strstrW (quals, parmP) != NULL) {
414 WCHAR question[MAXSTRING];
416 /* Ask for confirmation */
417 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
418 ok = WCMD_ask_confirm(question, FALSE, NULL);
421 /* Only proceed if ok to */
424 /* If file is read only, and /F supplied, delete it */
425 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
426 strstrW (quals, parmF) != NULL) {
427 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
430 /* Now do the delete */
431 if (!DeleteFile (fpath)) WCMD_print_error ();
435 } while (FindNextFile(hff, &fd) != 0);
439 /* Now recurse into all subdirectories handling the parameter in the same way */
440 if (strstrW (quals, parmS) != NULL) {
442 WCHAR thisDir[MAX_PATH];
447 WCHAR fname[MAX_PATH];
450 /* Convert path into actual directory spec */
451 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
452 WCMD_splitpath(thisDir, drive, dir, fname, ext);
454 strcpyW(thisDir, drive);
455 strcatW(thisDir, dir);
456 cPos = strlenW(thisDir);
458 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
460 /* Append '*' to the directory */
462 thisDir[cPos+1] = 0x00;
464 hff = FindFirstFile (thisDir, &fd);
466 /* Remove residual '*' */
467 thisDir[cPos] = 0x00;
469 if (hff != INVALID_HANDLE_VALUE) {
470 DIRECTORY_STACK *allDirs = NULL;
471 DIRECTORY_STACK *lastEntry = NULL;
474 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
475 (strcmpW(fd.cFileName, dotdotW) != 0) &&
476 (strcmpW(fd.cFileName, dotW) != 0)) {
478 DIRECTORY_STACK *nextDir;
479 WCHAR subParm[MAX_PATH];
481 /* Work out search parameter in sub dir */
482 strcpyW (subParm, thisDir);
483 strcatW (subParm, fd.cFileName);
484 strcatW (subParm, slashW);
485 strcatW (subParm, fname);
486 strcatW (subParm, ext);
487 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
489 /* Allocate memory, add to list */
490 nextDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
491 if (allDirs == NULL) allDirs = nextDir;
492 if (lastEntry != NULL) lastEntry->next = nextDir;
494 nextDir->next = NULL;
495 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
496 (strlenW(subParm)+1) * sizeof(WCHAR));
497 strcpyW(nextDir->dirName, subParm);
499 } while (FindNextFile(hff, &fd) != 0);
502 /* Go through each subdir doing the delete */
503 while (allDirs != NULL) {
504 DIRECTORY_STACK *tempDir;
506 tempDir = allDirs->next;
507 found |= WCMD_delete (allDirs->dirName, FALSE);
509 HeapFree(GetProcessHeap(),0,allDirs->dirName);
510 HeapFree(GetProcessHeap(),0,allDirs);
515 /* Keep running total to see if any found, and if not recursing
516 issue error message */
520 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
527 /* Handle no valid args */
528 if (argsProcessed == 0) {
529 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
535 /****************************************************************************
538 * Echo input to the screen (or not). We don't try to emulate the bugs
539 * in DOS (try typing "ECHO ON AGAIN" for an example).
542 void WCMD_echo (const WCHAR *command) {
546 if ((command[0] == '.') && (command[1] == 0)) {
547 WCMD_output (newline);
552 count = strlenW(command);
554 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
555 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
558 if (lstrcmpiW(command, onW) == 0) {
562 if (lstrcmpiW(command, offW) == 0) {
566 WCMD_output_asis (command);
567 WCMD_output (newline);
571 /**************************************************************************
574 * Batch file loop processing.
576 * On entry: cmdList contains the syntax up to the set
577 * next cmdList and all in that bracket contain the set data
578 * next cmdlist contains the DO cmd
579 * following that is either brackets or && entries (as per if)
581 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
582 * will probably work here, but the reverse is not necessarily the case...
585 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
590 const WCHAR inW[] = {'i', 'n', '\0'};
591 const WCHAR doW[] = {'d', 'o', ' ','\0'};
592 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
598 the first line includes the % variable name as first parm
599 we have been provided with more parts to the command
600 and there is at least some set data
601 and IN as the one after that */
602 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
603 || (*cmdList) == NULL
604 || (*cmdList)->nextcommand == NULL
605 || (param1[0] != '%')
606 || (strlenW(param1) > 3)) {
607 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
611 /* Save away where the set of data starts and the variable */
612 strcpyW(variable, param1);
613 thisDepth = (*cmdList)->bracketDepth;
614 *cmdList = (*cmdList)->nextcommand;
615 setStart = (*cmdList);
617 /* Skip until the close bracket */
618 WINE_TRACE("Searching %p as the set\n", *cmdList);
620 (*cmdList)->command != NULL &&
621 (*cmdList)->bracketDepth > thisDepth) {
622 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
623 *cmdList = (*cmdList)->nextcommand;
626 /* Skip the close bracket, if there is one */
627 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
629 /* Syntax error if missing close bracket, or nothing following it
630 and once we have the complete set, we expect a DO */
631 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
632 if ((*cmdList == NULL) ||
633 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
634 (*cmdList)->command, 3, doW, -1) != 2)) {
635 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
639 /* Save away the starting position for the commands (and offset for the
643 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
646 /* Loop through all set entries */
648 thisSet->command != NULL &&
649 thisSet->bracketDepth >= thisDepth) {
651 /* Loop through all entries on the same line */
654 WINE_TRACE("Processing for set %p\n", thisSet);
656 while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
659 * If the parameter within the set has a wildcard then search for matching files
660 * otherwise do a literal substitution.
662 static const WCHAR wildcards[] = {'*','?','\0'};
663 CMD_LIST *thisCmdStart = cmdStart;
665 WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
666 if (strpbrkW (item, wildcards)) {
667 hff = FindFirstFile (item, &fd);
668 if (hff != INVALID_HANDLE_VALUE) {
670 thisCmdStart = cmdStart;
671 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
672 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
673 fd.cFileName, FALSE, TRUE);
675 } while (FindNextFile(hff, &fd) != 0);
679 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
682 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
683 cmdEnd = thisCmdStart;
687 /* Move onto the next set line */
688 thisSet = thisSet->nextcommand;
691 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
692 all processing, OR it should be pointing to the end of && processing OR
693 it should be pointing at the NULL end of bracket for the DO. The return
694 value needs to be the NEXT command to execute, which it either is, or
695 we need to step over the closing bracket */
697 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
701 /*****************************************************************************
704 * Execute a command, and any && or bracketed follow on to the command. The
705 * first command to be executed may not be at the front of the
706 * commands->thiscommand string (eg. it may point after a DO or ELSE
707 * Returns TRUE if something like exit or goto has aborted all processing
709 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
710 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
712 CMD_LIST *curPosition = *cmdList;
713 int myDepth = (*cmdList)->bracketDepth;
715 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
716 cmdList, wine_dbgstr_w(firstcmd),
717 wine_dbgstr_w(variable), wine_dbgstr_w(value),
720 /* Skip leading whitespace between condition and the command */
721 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
723 /* Process the first command, if there is one */
724 if (conditionTRUE && firstcmd && *firstcmd) {
725 WCHAR *command = WCMD_strdupW(firstcmd);
726 WCMD_execute (firstcmd, variable, value, cmdList);
731 /* If it didnt move the position, step to next command */
732 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
734 /* Process any other parts of the command */
736 BOOL processThese = TRUE;
738 if (isIF) processThese = conditionTRUE;
741 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
743 /* execute all appropriate commands */
744 curPosition = *cmdList;
746 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
748 (*cmdList)->isAmphersand,
749 (*cmdList)->bracketDepth, myDepth);
751 /* Execute any appended to the statement with &&'s */
752 if ((*cmdList)->isAmphersand) {
754 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
756 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
758 /* Execute any appended to the statement with (...) */
759 } else if ((*cmdList)->bracketDepth > myDepth) {
761 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
762 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
764 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
766 /* End of the command - does 'ELSE ' follow as the next command? */
768 if (isIF && CompareString (LOCALE_USER_DEFAULT,
769 NORM_IGNORECASE | SORT_STRINGSORT,
770 (*cmdList)->command, 5, ifElse, -1) == 2) {
772 /* Swap between if and else processing */
773 processThese = !processThese;
775 /* Process the ELSE part */
777 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
779 /* Skip leading whitespace between condition and the command */
780 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
782 WCMD_execute (cmd, variable, value, cmdList);
785 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
787 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
796 /*****************************************************************************
799 * Execute a command after substituting variable text for the supplied parameter
802 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
804 WCHAR *new_cmd, *p, *s, *dup;
808 size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
809 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
810 dup = s = WCMD_strdupW(orig_cmd);
812 while ((p = strstrW (s, param))) {
814 size += strlenW (subst) * sizeof(WCHAR);
815 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
816 strcatW (new_cmd, s);
817 strcatW (new_cmd, subst);
818 s = p + strlenW (param);
820 strcatW (new_cmd, s);
821 WCMD_process_command (new_cmd, cmdList);
823 LocalFree ((HANDLE)new_cmd);
825 WCMD_process_command (orig_cmd, cmdList);
830 /**************************************************************************
833 * Simple on-line help. Help text is stored in the resource file.
836 void WCMD_give_help (WCHAR *command) {
840 command = WCMD_strtrim_leading_spaces(command);
841 if (strlenW(command) == 0) {
842 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
845 for (i=0; i<=WCMD_EXIT; i++) {
846 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
847 param1, -1, inbuilt[i], -1) == 2) {
848 WCMD_output_asis (WCMD_LoadMessage(i));
852 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
857 /****************************************************************************
860 * Batch file jump instruction. Not the most efficient algorithm ;-)
861 * Prints error message if the specified label cannot be found - the file pointer is
862 * then at EOF, effectively stopping the batch file.
863 * FIXME: DOS is supposed to allow labels with spaces - we don't.
866 void WCMD_goto (CMD_LIST **cmdList) {
868 WCHAR string[MAX_PATH];
870 /* Do not process any more parts of a processed multipart or multilines command */
873 if (param1[0] == 0x00) {
874 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
877 if (context != NULL) {
878 WCHAR *paramStart = param1;
879 static const WCHAR eofW[] = {':','e','o','f','\0'};
881 /* Handle special :EOF label */
882 if (lstrcmpiW (eofW, param1) == 0) {
883 context -> skip_rest = TRUE;
887 /* Support goto :label as well as goto label */
888 if (*paramStart == ':') paramStart++;
890 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
891 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
892 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
894 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
899 /*****************************************************************************
902 * Push a directory onto the stack
905 void WCMD_pushd (WCHAR *command) {
906 struct env_stack *curdir;
908 static const WCHAR parmD[] = {'/','D','\0'};
910 if (strchrW(command, '/') != NULL) {
911 SetLastError(ERROR_INVALID_PARAMETER);
916 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
917 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
918 if( !curdir || !thisdir ) {
921 WINE_ERR ("out of memory\n");
925 /* Change directory using CD code with /D parameter */
926 strcpyW(quals, parmD);
927 GetCurrentDirectoryW (1024, thisdir);
929 WCMD_setshow_default(command);
935 curdir -> next = pushd_directories;
936 curdir -> strings = thisdir;
937 if (pushd_directories == NULL) {
938 curdir -> u.stackdepth = 1;
940 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
942 pushd_directories = curdir;
947 /*****************************************************************************
950 * Pop a directory from the stack
953 void WCMD_popd (void) {
954 struct env_stack *temp = pushd_directories;
956 if (!pushd_directories)
959 /* pop the old environment from the stack, and make it the current dir */
960 pushd_directories = temp->next;
961 SetCurrentDirectoryW(temp->strings);
962 LocalFree (temp->strings);
966 /****************************************************************************
969 * Batch file conditional.
971 * On entry, cmdlist will point to command containing the IF, and optionally
972 * the first command to execute (if brackets not found)
973 * If &&'s were found, this may be followed by a record flagged as isAmpersand
974 * If ('s were found, execute all within that bracket
975 * Command may optionally be followed by an ELSE - need to skip instructions
976 * in the else using the same logic
978 * FIXME: Much more syntax checking needed!
981 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
983 int negate = 0, test = 0;
984 WCHAR condition[MAX_PATH], *command, *s;
985 static const WCHAR notW[] = {'n','o','t','\0'};
986 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
987 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
988 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
989 static const WCHAR eqeqW[] = {'=','=','\0'};
991 if (!lstrcmpiW (param1, notW)) {
993 strcpyW (condition, param2);
996 strcpyW (condition, param1);
998 if (!lstrcmpiW (condition, errlvlW)) {
999 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1000 WCMD_parameter (p, 2+negate, &command);
1002 else if (!lstrcmpiW (condition, existW)) {
1003 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1006 WCMD_parameter (p, 2+negate, &command);
1008 else if (!lstrcmpiW (condition, defdW)) {
1009 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1012 WCMD_parameter (p, 2+negate, &command);
1014 else if ((s = strstrW (p, eqeqW))) {
1016 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1017 WCMD_parameter (s, 1, &command);
1020 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1024 /* Process rest of IF statement which is on the same line
1025 Note: This may process all or some of the cmdList (eg a GOTO) */
1026 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1029 /****************************************************************************
1032 * Move a file, directory tree or wildcarded set of files.
1035 void WCMD_move (void) {
1040 WCHAR input[MAX_PATH];
1041 WCHAR output[MAX_PATH];
1043 WCHAR dir[MAX_PATH];
1044 WCHAR fname[MAX_PATH];
1045 WCHAR ext[MAX_PATH];
1047 if (param1[0] == 0x00) {
1048 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1052 /* If no destination supplied, assume current directory */
1053 if (param2[0] == 0x00) {
1054 strcpyW(param2, dotW);
1057 /* If 2nd parm is directory, then use original filename */
1058 /* Convert partial path to full path */
1059 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1060 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1061 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1062 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1064 /* Split into components */
1065 WCMD_splitpath(input, drive, dir, fname, ext);
1067 hff = FindFirstFile (input, &fd);
1068 while (hff != INVALID_HANDLE_VALUE) {
1069 WCHAR dest[MAX_PATH];
1070 WCHAR src[MAX_PATH];
1073 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1075 /* Build src & dest name */
1076 strcpyW(src, drive);
1079 /* See if dest is an existing directory */
1080 attribs = GetFileAttributes(output);
1081 if (attribs != INVALID_FILE_ATTRIBUTES &&
1082 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1083 strcpyW(dest, output);
1084 strcatW(dest, slashW);
1085 strcatW(dest, fd.cFileName);
1087 strcpyW(dest, output);
1090 strcatW(src, fd.cFileName);
1092 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1093 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1095 /* Check if file is read only, otherwise move it */
1096 attribs = GetFileAttributes(src);
1097 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1098 (attribs & FILE_ATTRIBUTE_READONLY)) {
1099 SetLastError(ERROR_ACCESS_DENIED);
1104 /* If destination exists, prompt unless /Y supplied */
1105 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1107 WCHAR copycmd[MAXSTRING];
1110 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1111 if (strstrW (quals, parmNoY))
1113 else if (strstrW (quals, parmY))
1116 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1117 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1118 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1119 && ! lstrcmpiW (copycmd, parmY));
1122 /* Prompt if overwriting */
1124 WCHAR question[MAXSTRING];
1127 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1129 /* Ask for confirmation */
1130 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1131 ok = WCMD_ask_confirm(question, FALSE, NULL);
1133 /* So delete the destination prior to the move */
1135 if (!DeleteFile (dest)) {
1136 WCMD_print_error ();
1145 status = MoveFile (src, dest);
1147 status = 1; /* Anything other than 0 to prevent error msg below */
1152 WCMD_print_error ();
1156 /* Step on to next match */
1157 if (FindNextFile(hff, &fd) == 0) {
1159 hff = INVALID_HANDLE_VALUE;
1165 /****************************************************************************
1168 * Wait for keyboard input.
1171 void WCMD_pause (void) {
1176 WCMD_output (anykey);
1177 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1178 sizeof(string)/sizeof(WCHAR), &count, NULL);
1181 /****************************************************************************
1184 * Delete a directory.
1187 void WCMD_remove_dir (WCHAR *command) {
1190 int argsProcessed = 0;
1191 WCHAR *argN = command;
1192 static const WCHAR parmS[] = {'/','S','\0'};
1193 static const WCHAR parmQ[] = {'/','Q','\0'};
1195 /* Loop through all args */
1197 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1198 if (argN && argN[0] != '/') {
1199 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1200 wine_dbgstr_w(quals));
1203 /* If subdirectory search not supplied, just try to remove
1204 and report error if it fails (eg if it contains a file) */
1205 if (strstrW (quals, parmS) == NULL) {
1206 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1208 /* Otherwise use ShFileOp to recursively remove a directory */
1211 SHFILEOPSTRUCT lpDir;
1214 if (strstrW (quals, parmQ) == NULL) {
1216 WCHAR question[MAXSTRING];
1217 static const WCHAR fmt[] = {'%','s',' ','\0'};
1219 /* Ask for confirmation */
1220 wsprintf(question, fmt, thisArg);
1221 ok = WCMD_ask_confirm(question, TRUE, NULL);
1223 /* Abort if answer is 'N' */
1230 lpDir.pFrom = thisArg;
1231 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1232 lpDir.wFunc = FO_DELETE;
1233 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1238 /* Handle no valid args */
1239 if (argsProcessed == 0) {
1240 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1246 /****************************************************************************
1252 void WCMD_rename (void) {
1257 WCHAR input[MAX_PATH];
1258 WCHAR *dotDst = NULL;
1260 WCHAR dir[MAX_PATH];
1261 WCHAR fname[MAX_PATH];
1262 WCHAR ext[MAX_PATH];
1267 /* Must be at least two args */
1268 if (param1[0] == 0x00 || param2[0] == 0x00) {
1269 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1274 /* Destination cannot contain a drive letter or directory separator */
1275 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1276 SetLastError(ERROR_INVALID_PARAMETER);
1282 /* Convert partial path to full path */
1283 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1284 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1285 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1286 dotDst = strchrW(param2, '.');
1288 /* Split into components */
1289 WCMD_splitpath(input, drive, dir, fname, ext);
1291 hff = FindFirstFile (input, &fd);
1292 while (hff != INVALID_HANDLE_VALUE) {
1293 WCHAR dest[MAX_PATH];
1294 WCHAR src[MAX_PATH];
1295 WCHAR *dotSrc = NULL;
1298 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1300 /* FIXME: If dest name or extension is *, replace with filename/ext
1301 part otherwise use supplied name. This supports:
1303 ren jim.* fred.* etc
1304 However, windows has a more complex algorithum supporting eg
1305 ?'s and *'s mid name */
1306 dotSrc = strchrW(fd.cFileName, '.');
1308 /* Build src & dest name */
1309 strcpyW(src, drive);
1312 dirLen = strlenW(src);
1313 strcatW(src, fd.cFileName);
1316 if (param2[0] == '*') {
1317 strcatW(dest, fd.cFileName);
1318 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1320 strcatW(dest, param2);
1321 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1324 /* Build Extension */
1325 if (dotDst && (*(dotDst+1)=='*')) {
1326 if (dotSrc) strcatW(dest, dotSrc);
1327 } else if (dotDst) {
1328 if (dotDst) strcatW(dest, dotDst);
1331 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1332 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1334 /* Check if file is read only, otherwise move it */
1335 attribs = GetFileAttributes(src);
1336 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1337 (attribs & FILE_ATTRIBUTE_READONLY)) {
1338 SetLastError(ERROR_ACCESS_DENIED);
1341 status = MoveFile (src, dest);
1345 WCMD_print_error ();
1349 /* Step on to next match */
1350 if (FindNextFile(hff, &fd) == 0) {
1352 hff = INVALID_HANDLE_VALUE;
1358 /*****************************************************************************
1361 * Make a copy of the environment.
1363 static WCHAR *WCMD_dupenv( const WCHAR *env )
1373 len += (strlenW(&env[len]) + 1);
1375 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1378 WINE_ERR("out of memory\n");
1381 memcpy (env_copy, env, len*sizeof (WCHAR));
1387 /*****************************************************************************
1390 * setlocal pushes the environment onto a stack
1391 * Save the environment as unicode so we don't screw anything up.
1393 void WCMD_setlocal (const WCHAR *s) {
1395 struct env_stack *env_copy;
1396 WCHAR cwd[MAX_PATH];
1398 /* DISABLEEXTENSIONS ignored */
1400 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1403 WINE_ERR ("out of memory\n");
1407 env = GetEnvironmentStringsW ();
1409 env_copy->strings = WCMD_dupenv (env);
1410 if (env_copy->strings)
1412 env_copy->next = saved_environment;
1413 saved_environment = env_copy;
1415 /* Save the current drive letter */
1416 GetCurrentDirectory (MAX_PATH, cwd);
1417 env_copy->u.cwd = cwd[0];
1420 LocalFree (env_copy);
1422 FreeEnvironmentStringsW (env);
1426 /*****************************************************************************
1429 * endlocal pops the environment off a stack
1430 * Note: When searching for '=', search from WCHAR position 1, to handle
1431 * special internal environment variables =C:, =D: etc
1433 void WCMD_endlocal (void) {
1434 WCHAR *env, *old, *p;
1435 struct env_stack *temp;
1438 if (!saved_environment)
1441 /* pop the old environment from the stack */
1442 temp = saved_environment;
1443 saved_environment = temp->next;
1445 /* delete the current environment, totally */
1446 env = GetEnvironmentStringsW ();
1447 old = WCMD_dupenv (GetEnvironmentStringsW ());
1450 n = strlenW(&old[len]) + 1;
1451 p = strchrW(&old[len] + 1, '=');
1455 SetEnvironmentVariableW (&old[len], NULL);
1460 FreeEnvironmentStringsW (env);
1462 /* restore old environment */
1463 env = temp->strings;
1466 n = strlenW(&env[len]) + 1;
1467 p = strchrW(&env[len] + 1, '=');
1471 SetEnvironmentVariableW (&env[len], p);
1476 /* Restore current drive letter */
1477 if (IsCharAlpha(temp->u.cwd)) {
1479 WCHAR cwd[MAX_PATH];
1480 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1482 wsprintf(envvar, fmt, temp->u.cwd);
1483 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1484 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1485 SetCurrentDirectory(cwd);
1493 /*****************************************************************************
1494 * WCMD_setshow_attrib
1496 * Display and optionally sets DOS attributes on a file or directory
1498 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1499 * As a result only the Readonly flag is correctly reported, the Archive bit
1500 * is always set and the rest are not implemented. We do the Right Thing anyway.
1502 * FIXME: No SET functionality.
1506 void WCMD_setshow_attrib (void) {
1511 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1513 if (param1[0] == '-') {
1514 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1518 if (strlenW(param1) == 0) {
1519 static const WCHAR slashStarW[] = {'\\','*','\0'};
1521 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1522 strcatW (param1, slashStarW);
1525 hff = FindFirstFile (param1, &fd);
1526 if (hff == INVALID_HANDLE_VALUE) {
1527 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1531 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1532 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1533 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1536 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1539 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1542 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1545 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1548 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1551 WCMD_output (fmt, flags, fd.cFileName);
1552 for (count=0; count < 8; count++) flags[count] = ' ';
1554 } while (FindNextFile(hff, &fd) != 0);
1559 /*****************************************************************************
1560 * WCMD_setshow_default
1562 * Set/Show the current default directory
1565 void WCMD_setshow_default (WCHAR *command) {
1573 static const WCHAR parmD[] = {'/','D','\0'};
1575 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1577 /* Skip /D and trailing whitespace if on the front of the command line */
1578 if (CompareString (LOCALE_USER_DEFAULT,
1579 NORM_IGNORECASE | SORT_STRINGSORT,
1580 command, 2, parmD, -1) == 2) {
1582 while (*command && *command==' ') command++;
1585 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1586 if (strlenW(command) == 0) {
1587 strcatW (cwd, newline);
1591 /* Remove any double quotes, which may be in the
1592 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1595 if (*command != '"') *pos++ = *command;
1600 /* Search for approprate directory */
1601 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1602 hff = FindFirstFile (string, &fd);
1603 while (hff != INVALID_HANDLE_VALUE) {
1604 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1605 WCHAR fpath[MAX_PATH];
1607 WCHAR dir[MAX_PATH];
1608 WCHAR fname[MAX_PATH];
1609 WCHAR ext[MAX_PATH];
1610 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1612 /* Convert path into actual directory spec */
1613 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1614 WCMD_splitpath(fpath, drive, dir, fname, ext);
1617 wsprintf(string, fmt, drive, dir, fd.cFileName);
1620 hff = INVALID_HANDLE_VALUE;
1624 /* Step on to next match */
1625 if (FindNextFile(hff, &fd) == 0) {
1627 hff = INVALID_HANDLE_VALUE;
1632 /* Change to that directory */
1633 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1635 status = SetCurrentDirectory (string);
1638 WCMD_print_error ();
1642 /* Restore old directory if drive letter would change, and
1643 CD x:\directory /D (or pushd c:\directory) not supplied */
1644 if ((strstrW(quals, parmD) == NULL) &&
1645 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1646 SetCurrentDirectory(cwd);
1650 /* Set special =C: type environment variable, for drive letter of
1651 change of directory, even if path was restored due to missing
1652 /D (allows changing drive letter when not resident on that
1654 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1656 strcpyW(env, equalW);
1657 memcpy(env+1, string, 2 * sizeof(WCHAR));
1659 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1660 SetEnvironmentVariable(env, string);
1667 /****************************************************************************
1670 * Set/Show the system date
1671 * FIXME: Can't change date yet
1674 void WCMD_setshow_date (void) {
1676 WCHAR curdate[64], buffer[64];
1678 static const WCHAR parmT[] = {'/','T','\0'};
1680 if (strlenW(param1) == 0) {
1681 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1682 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1683 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1684 if (strstrW (quals, parmT) == NULL) {
1685 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1686 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1687 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1689 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1693 else WCMD_print_error ();
1696 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1700 /****************************************************************************
1703 static int WCMD_compare( const void *a, const void *b )
1706 const WCHAR * const *str_a = a, * const *str_b = b;
1707 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1708 *str_a, -1, *str_b, -1 );
1709 if( r == CSTR_LESS_THAN ) return -1;
1710 if( r == CSTR_GREATER_THAN ) return 1;
1714 /****************************************************************************
1715 * WCMD_setshow_sortenv
1717 * sort variables into order for display
1718 * Optionally only display those who start with a stub
1719 * returns the count displayed
1721 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1723 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1726 if (stub) stublen = strlenW(stub);
1728 /* count the number of strings, and the total length */
1730 len += (strlenW(&s[len]) + 1);
1734 /* add the strings to an array */
1735 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1739 for( i=1; i<count; i++ )
1740 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1742 /* sort the array */
1743 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1746 for( i=0; i<count; i++ ) {
1747 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1748 NORM_IGNORECASE | SORT_STRINGSORT,
1749 str[i], stublen, stub, -1) == 2) {
1750 /* Don't display special internal variables */
1751 if (str[i][0] != '=') {
1752 WCMD_output_asis(str[i]);
1753 WCMD_output_asis(newline);
1760 return displayedcount;
1763 /****************************************************************************
1766 * Set/Show the environment variables
1769 void WCMD_setshow_env (WCHAR *s) {
1774 static const WCHAR parmP[] = {'/','P','\0'};
1777 if (param1[0] == 0x00 && quals[0] == 0x00) {
1778 env = GetEnvironmentStrings ();
1779 WCMD_setshow_sortenv( env, NULL );
1783 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1784 if (CompareString (LOCALE_USER_DEFAULT,
1785 NORM_IGNORECASE | SORT_STRINGSORT,
1786 s, 2, parmP, -1) == 2) {
1787 WCHAR string[MAXSTRING];
1791 while (*s && *s==' ') s++;
1793 /* If no parameter, or no '=' sign, return an error */
1794 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1795 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1799 /* Output the prompt */
1801 if (strlenW(p) != 0) WCMD_output(p);
1803 /* Read the reply */
1804 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1805 sizeof(string)/sizeof(WCHAR), &count, NULL);
1807 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1808 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1809 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1810 wine_dbgstr_w(string));
1811 status = SetEnvironmentVariable (s, string);
1816 p = strchrW (s, '=');
1818 env = GetEnvironmentStrings ();
1819 if (WCMD_setshow_sortenv( env, s ) == 0) {
1820 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1827 if (strlenW(p) == 0) p = NULL;
1828 status = SetEnvironmentVariable (s, p);
1829 gle = GetLastError();
1830 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1832 } else if ((!status)) WCMD_print_error();
1836 /****************************************************************************
1839 * Set/Show the path environment variable
1842 void WCMD_setshow_path (WCHAR *command) {
1846 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1847 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1849 if (strlenW(param1) == 0) {
1850 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1852 WCMD_output_asis ( pathEqW);
1853 WCMD_output_asis ( string);
1854 WCMD_output_asis ( newline);
1857 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1861 if (*command == '=') command++; /* Skip leading '=' */
1862 status = SetEnvironmentVariable (pathW, command);
1863 if (!status) WCMD_print_error();
1867 /****************************************************************************
1868 * WCMD_setshow_prompt
1870 * Set or show the command prompt.
1873 void WCMD_setshow_prompt (void) {
1876 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1878 if (strlenW(param1) == 0) {
1879 SetEnvironmentVariable (promptW, NULL);
1883 while ((*s == '=') || (*s == ' ')) s++;
1884 if (strlenW(s) == 0) {
1885 SetEnvironmentVariable (promptW, NULL);
1887 else SetEnvironmentVariable (promptW, s);
1891 /****************************************************************************
1894 * Set/Show the system time
1895 * FIXME: Can't change time yet
1898 void WCMD_setshow_time (void) {
1900 WCHAR curtime[64], buffer[64];
1903 static const WCHAR parmT[] = {'/','T','\0'};
1905 if (strlenW(param1) == 0) {
1907 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1908 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1909 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1910 if (strstrW (quals, parmT) == NULL) {
1911 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1912 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1913 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1915 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1919 else WCMD_print_error ();
1922 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1926 /****************************************************************************
1929 * Shift batch parameters.
1930 * Optional /n says where to start shifting (n=0-8)
1933 void WCMD_shift (WCHAR *command) {
1936 if (context != NULL) {
1937 WCHAR *pos = strchrW(command, '/');
1942 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1943 start = (*(pos+1) - '0');
1945 SetLastError(ERROR_INVALID_PARAMETER);
1950 WINE_TRACE("Shifting variables, starting at %d\n", start);
1951 for (i=start;i<=8;i++) {
1952 context -> shift_count[i] = context -> shift_count[i+1] + 1;
1954 context -> shift_count[9] = context -> shift_count[9] + 1;
1959 /****************************************************************************
1962 * Set the console title
1964 void WCMD_title (WCHAR *command) {
1965 SetConsoleTitle(command);
1968 /****************************************************************************
1971 * Copy a file to standard output.
1974 void WCMD_type (WCHAR *command) {
1977 WCHAR *argN = command;
1978 BOOL writeHeaders = FALSE;
1980 if (param1[0] == 0x00) {
1981 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1985 if (param2[0] != 0x00) writeHeaders = TRUE;
1987 /* Loop through all args */
1990 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1998 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1999 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2000 FILE_ATTRIBUTE_NORMAL, NULL);
2001 if (h == INVALID_HANDLE_VALUE) {
2002 WCMD_print_error ();
2003 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2007 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2008 WCMD_output(fmt, thisArg);
2010 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2011 if (count == 0) break; /* ReadFile reports success on EOF! */
2013 WCMD_output_asis (buffer);
2020 /****************************************************************************
2023 * Output either a file or stdin to screen in pages
2026 void WCMD_more (WCHAR *command) {
2029 WCHAR *argN = command;
2030 BOOL useinput = FALSE;
2032 WCHAR moreStrPage[100];
2035 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2036 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2037 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2038 ')',' ','-','-','\n','\0'};
2039 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2041 /* Prefix the NLS more with '-- ', then load the text */
2043 strcpyW(moreStr, moreStart);
2044 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2045 (sizeof(moreStr)/sizeof(WCHAR))-3);
2047 if (param1[0] == 0x00) {
2049 /* Wine implements pipes via temporary files, and hence stdin is
2050 effectively reading from the file. This means the prompts for
2051 more are satistied by the next line from the input (file). To
2052 avoid this, ensure stdin is to the console */
2053 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2054 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2055 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2056 FILE_ATTRIBUTE_NORMAL, 0);
2057 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2059 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2060 once you get in this bit unless due to a pipe, its going to end badly... */
2062 wsprintf(moreStrPage, moreFmt, moreStr);
2064 WCMD_enter_paged_mode(moreStrPage);
2065 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2066 if (count == 0) break; /* ReadFile reports success on EOF! */
2068 WCMD_output_asis (buffer);
2070 WCMD_leave_paged_mode();
2072 /* Restore stdin to what it was */
2073 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2074 CloseHandle(hConIn);
2078 BOOL needsPause = FALSE;
2080 /* Loop through all args */
2081 WCMD_enter_paged_mode(moreStrPage);
2084 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2092 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2093 WCMD_leave_paged_mode();
2094 WCMD_output_asis(moreStrPage);
2095 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2096 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2097 WCMD_enter_paged_mode(moreStrPage);
2101 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2102 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2103 FILE_ATTRIBUTE_NORMAL, NULL);
2104 if (h == INVALID_HANDLE_VALUE) {
2105 WCMD_print_error ();
2106 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2110 ULONG64 fileLen = 0;
2111 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2113 /* Get the file size */
2114 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2115 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2118 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2119 if (count == 0) break; /* ReadFile reports success on EOF! */
2123 /* Update % count (would be used in WCMD_output_asis as prompt) */
2124 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2126 WCMD_output_asis (buffer);
2132 WCMD_leave_paged_mode();
2136 /****************************************************************************
2139 * Display verify flag.
2140 * FIXME: We don't actually do anything with the verify flag other than toggle
2144 void WCMD_verify (WCHAR *command) {
2148 count = strlenW(command);
2150 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2151 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2154 if (lstrcmpiW(command, onW) == 0) {
2158 else if (lstrcmpiW(command, offW) == 0) {
2162 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2165 /****************************************************************************
2168 * Display version info.
2171 void WCMD_version (void) {
2173 WCMD_output (version_string);
2177 /****************************************************************************
2180 * Display volume info and/or set volume label. Returns 0 if error.
2183 int WCMD_volume (int mode, WCHAR *path) {
2185 DWORD count, serial;
2186 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2189 if (strlenW(path) == 0) {
2190 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2192 WCMD_print_error ();
2195 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2196 &serial, NULL, NULL, NULL, 0);
2199 static const WCHAR fmt[] = {'%','s','\\','\0'};
2200 if ((path[1] != ':') || (strlenW(path) != 2)) {
2201 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2204 wsprintf (curdir, fmt, path);
2205 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2210 WCMD_print_error ();
2213 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2214 curdir[0], label, HIWORD(serial), LOWORD(serial));
2216 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2217 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2218 sizeof(string)/sizeof(WCHAR), &count, NULL);
2220 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2221 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2223 if (strlenW(path) != 0) {
2224 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2227 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2233 /**************************************************************************
2236 * Exit either the process, or just this batch program
2240 void WCMD_exit (CMD_LIST **cmdList) {
2242 static const WCHAR parmB[] = {'/','B','\0'};
2243 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2245 if (context && lstrcmpiW(quals, parmB) == 0) {
2247 context -> skip_rest = TRUE;
2254 /**************************************************************************
2257 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2260 * Returns True if Y (or A) answer is selected
2261 * If optionAll contains a pointer, ALL is allowed, and if answered
2265 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2267 WCHAR msgbuffer[MAXSTRING];
2268 WCHAR Ybuffer[MAXSTRING];
2269 WCHAR Nbuffer[MAXSTRING];
2270 WCHAR Abuffer[MAXSTRING];
2271 WCHAR answer[MAX_PATH] = {'\0'};
2274 /* Load the translated 'Are you sure', plus valid answers */
2275 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2276 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2277 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2278 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2280 /* Loop waiting on a Y or N */
2281 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2282 static const WCHAR startBkt[] = {' ','(','\0'};
2283 static const WCHAR endBkt[] = {')','?','\0'};
2285 WCMD_output_asis (message);
2287 WCMD_output_asis (msgbuffer);
2289 WCMD_output_asis (startBkt);
2290 WCMD_output_asis (Ybuffer);
2291 WCMD_output_asis (fslashW);
2292 WCMD_output_asis (Nbuffer);
2294 WCMD_output_asis (fslashW);
2295 WCMD_output_asis (Abuffer);
2297 WCMD_output_asis (endBkt);
2298 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2299 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2300 answer[0] = toupper(answer[0]);
2303 /* Return the answer */
2304 return ((answer[0] == Ybuffer[0]) ||
2305 (optionAll && (answer[0] == Abuffer[0])));
2308 /*****************************************************************************
2311 * Lists or sets file associations (assoc = TRUE)
2312 * Lists or sets file types (assoc = FALSE)
2314 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2317 DWORD accessOptions = KEY_READ;
2319 LONG rc = ERROR_SUCCESS;
2320 WCHAR keyValue[MAXSTRING];
2321 DWORD valueLen = MAXSTRING;
2323 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2324 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2326 /* See if parameter includes '=' */
2328 newValue = strchrW(command, '=');
2329 if (newValue) accessOptions |= KEY_WRITE;
2331 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2332 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2333 accessOptions, &key) != ERROR_SUCCESS) {
2334 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2338 /* If no parameters then list all associations */
2339 if (*command == 0x00) {
2342 /* Enumerate all the keys */
2343 while (rc != ERROR_NO_MORE_ITEMS) {
2344 WCHAR keyName[MAXSTRING];
2347 /* Find the next value */
2348 nameLen = MAXSTRING;
2349 rc = RegEnumKeyEx(key, index++,
2351 NULL, NULL, NULL, NULL);
2353 if (rc == ERROR_SUCCESS) {
2355 /* Only interested in extension ones if assoc, or others
2357 if ((keyName[0] == '.' && assoc) ||
2358 (!(keyName[0] == '.') && (!assoc)))
2360 WCHAR subkey[MAXSTRING];
2361 strcpyW(subkey, keyName);
2362 if (!assoc) strcatW(subkey, shOpCmdW);
2364 if (RegOpenKeyEx(key, subkey, 0,
2365 accessOptions, &readKey) == ERROR_SUCCESS) {
2367 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2368 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2369 (LPBYTE)keyValue, &valueLen);
2370 WCMD_output_asis(keyName);
2371 WCMD_output_asis(equalW);
2372 /* If no default value found, leave line empty after '=' */
2373 if (rc == ERROR_SUCCESS) {
2374 WCMD_output_asis(keyValue);
2376 WCMD_output_asis(newline);
2381 RegCloseKey(readKey);
2385 /* Parameter supplied - if no '=' on command line, its a query */
2386 if (newValue == NULL) {
2388 WCHAR subkey[MAXSTRING];
2390 /* Query terminates the parameter at the first space */
2391 strcpyW(keyValue, command);
2392 space = strchrW(keyValue, ' ');
2393 if (space) *space=0x00;
2395 /* Set up key name */
2396 strcpyW(subkey, keyValue);
2397 if (!assoc) strcatW(subkey, shOpCmdW);
2399 if (RegOpenKeyEx(key, subkey, 0,
2400 accessOptions, &readKey) == ERROR_SUCCESS) {
2402 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2403 (LPBYTE)keyValue, &valueLen);
2404 WCMD_output_asis(command);
2405 WCMD_output_asis(equalW);
2406 /* If no default value found, leave line empty after '=' */
2407 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2408 WCMD_output_asis(newline);
2409 RegCloseKey(readKey);
2412 WCHAR msgbuffer[MAXSTRING];
2413 WCHAR outbuffer[MAXSTRING];
2415 /* Load the translated 'File association not found' */
2417 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2419 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2421 wsprintf(outbuffer, msgbuffer, keyValue);
2422 WCMD_output_asis(outbuffer);
2426 /* Not a query - its a set or clear of a value */
2429 WCHAR subkey[MAXSTRING];
2431 /* Get pointer to new value */
2435 /* Set up key name */
2436 strcpyW(subkey, command);
2437 if (!assoc) strcatW(subkey, shOpCmdW);
2439 /* If nothing after '=' then clear value - only valid for ASSOC */
2440 if (*newValue == 0x00) {
2442 if (assoc) rc = RegDeleteKey(key, command);
2443 if (assoc && rc == ERROR_SUCCESS) {
2444 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2446 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2451 WCHAR msgbuffer[MAXSTRING];
2452 WCHAR outbuffer[MAXSTRING];
2454 /* Load the translated 'File association not found' */
2456 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2457 sizeof(msgbuffer)/sizeof(WCHAR));
2459 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2460 sizeof(msgbuffer)/sizeof(WCHAR));
2462 wsprintf(outbuffer, msgbuffer, keyValue);
2463 WCMD_output_asis(outbuffer);
2467 /* It really is a set value = contents */
2469 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2470 accessOptions, NULL, &readKey, NULL);
2471 if (rc == ERROR_SUCCESS) {
2472 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2473 (LPBYTE)newValue, strlenW(newValue));
2474 RegCloseKey(readKey);
2477 if (rc != ERROR_SUCCESS) {
2481 WCMD_output_asis(command);
2482 WCMD_output_asis(equalW);
2483 WCMD_output_asis(newValue);
2484 WCMD_output_asis(newline);
2494 /****************************************************************************
2497 * Clear the terminal screen.
2500 void WCMD_color (void) {
2502 /* Emulate by filling the screen from the top left to bottom right with
2503 spaces, then moving the cursor to the top left afterwards */
2504 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2505 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2507 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2508 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2512 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2518 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2523 /* Convert the color hex digits */
2524 if (param1[0] == 0x00) {
2525 color = defaultColor;
2527 color = strtoulW(param1, NULL, 16);
2530 /* Fail if fg == bg color */
2531 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2536 /* Set the current screen contents and ensure all future writes
2537 remain this color */
2538 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2539 SetConsoleTextAttribute(hStdOut, color);