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: Add support for a+b+c type syntax
114 void WCMD_copy (void) {
119 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 BOOL copyToDir = FALSE;
123 BOOL copyFromDir = FALSE;
124 WCHAR srcspec[MAX_PATH];
128 WCHAR fname[MAX_PATH];
131 if (param1[0] == 0x00) {
132 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
136 /* Convert source into full spec */
137 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
138 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
139 if (srcpath[strlenW(srcpath) - 1] == '\\')
140 srcpath[strlenW(srcpath) - 1] = '\0';
142 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
143 attribs = GetFileAttributes(srcpath);
147 strcpyW(srcspec, srcpath);
149 /* If a directory, then add \* on the end when searching */
150 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
151 strcatW(srcpath, slashW);
153 strcatW(srcspec, slashW);
154 strcatW(srcspec, starW);
156 WCMD_splitpath(srcpath, drive, dir, fname, ext);
157 strcpyW(srcpath, drive);
158 strcatW(srcpath, dir);
161 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
163 /* If no destination supplied, assume current directory */
164 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
165 if (param2[0] == 0x00) {
166 strcpyW(param2, dotW);
169 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
170 if (outpath[strlenW(outpath) - 1] == '\\')
171 outpath[strlenW(outpath) - 1] = '\0';
172 attribs = GetFileAttributes(outpath);
173 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
174 strcatW (outpath, slashW);
177 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
178 wine_dbgstr_w(outpath), copyToDir);
180 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
181 if (strstrW (quals, parmNoY))
183 else if (strstrW (quals, parmY))
186 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
187 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
190 /* Loop through all source files */
191 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
192 hff = FindFirstFile (srcspec, &fd);
193 if (hff != INVALID_HANDLE_VALUE) {
195 WCHAR outname[MAX_PATH];
196 WCHAR srcname[MAX_PATH];
197 BOOL overwrite = force;
199 /* Destination is either supplied filename, or source name in
200 supplied destination directory */
201 strcpyW(outname, outpath);
202 if (copyToDir) strcatW(outname, fd.cFileName);
203 strcpyW(srcname, srcpath);
204 strcatW(srcname, fd.cFileName);
206 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
207 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
209 /* Skip . and .., and directories */
210 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
212 WINE_TRACE("Skipping directories\n");
215 /* Prompt before overwriting */
216 else if (!overwrite) {
217 attribs = GetFileAttributes(outname);
218 if (attribs != INVALID_FILE_ATTRIBUTES) {
219 WCHAR buffer[MAXSTRING];
220 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
221 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
223 else overwrite = TRUE;
226 /* Do the copy as appropriate */
228 status = CopyFile (srcname, outname, FALSE);
229 if (!status) WCMD_print_error ();
232 } while (FindNextFile(hff, &fd) != 0);
235 status = ERROR_FILE_NOT_FOUND;
240 /****************************************************************************
243 * Create a directory.
245 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
246 * they do not already exist.
249 static BOOL create_full_path(WCHAR* path)
255 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
256 strcpyW(new_path,path);
258 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
259 new_path[len - 1] = 0;
261 while (!CreateDirectory(new_path,NULL))
264 DWORD last_error = GetLastError();
265 if (last_error == ERROR_ALREADY_EXISTS)
268 if (last_error != ERROR_PATH_NOT_FOUND)
274 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
280 len = slash - new_path;
282 if (!create_full_path(new_path))
287 new_path[len] = '\\';
289 HeapFree(GetProcessHeap(),0,new_path);
293 void WCMD_create_dir (void) {
295 if (param1[0] == 0x00) {
296 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
299 if (!create_full_path(param1)) WCMD_print_error ();
302 /****************************************************************************
305 * Delete a file or wildcarded set.
308 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
309 * - Each set is a pattern, eg /ahr /as-r means
310 * readonly+hidden OR nonreadonly system files
311 * - The '-' applies to a single field, ie /a:-hr means read only
315 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
318 int argsProcessed = 0;
319 WCHAR *argN = command;
320 BOOL foundAny = FALSE;
321 static const WCHAR parmA[] = {'/','A','\0'};
322 static const WCHAR parmQ[] = {'/','Q','\0'};
323 static const WCHAR parmP[] = {'/','P','\0'};
324 static const WCHAR parmS[] = {'/','S','\0'};
325 static const WCHAR parmF[] = {'/','F','\0'};
327 /* If not recursing, clear error flag */
328 if (expectDir) errorlevel = 0;
330 /* Loop through all args */
332 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
333 WCHAR argCopy[MAX_PATH];
335 if (argN && argN[0] != '/') {
339 WCHAR fpath[MAX_PATH];
341 BOOL handleParm = TRUE;
343 static const WCHAR anyExt[]= {'.','*','\0'};
345 strcpyW(argCopy, thisArg);
346 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
347 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
350 /* If filename part of parameter is * or *.*, prompt unless
352 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
356 WCHAR fname[MAX_PATH];
359 /* Convert path into actual directory spec */
360 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
361 WCMD_splitpath(fpath, drive, dir, fname, ext);
363 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
364 if ((strcmpW(fname, starW) == 0) &&
365 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
367 WCHAR question[MAXSTRING];
368 static const WCHAR fmt[] = {'%','s',' ','\0'};
370 /* Note: Flag as found, to avoid file not found message */
373 /* Ask for confirmation */
374 wsprintf(question, fmt, fpath);
375 ok = WCMD_ask_confirm(question, TRUE, NULL);
377 /* Abort if answer is 'N' */
382 /* First, try to delete in the current directory */
383 hff = FindFirstFile (argCopy, &fd);
384 if (hff == INVALID_HANDLE_VALUE) {
390 /* Support del <dirname> by just deleting all files dirname\* */
391 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
392 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
393 WCHAR modifiedParm[MAX_PATH];
394 static const WCHAR slashStar[] = {'\\','*','\0'};
396 strcpyW(modifiedParm, argCopy);
397 strcatW(modifiedParm, slashStar);
400 WCMD_delete(modifiedParm, FALSE);
402 } else if (handleParm) {
404 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405 strcpyW (fpath, argCopy);
407 p = strrchrW (fpath, '\\');
410 strcatW (fpath, fd.cFileName);
412 else strcpyW (fpath, fd.cFileName);
413 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
415 WCHAR *nextA = strstrW (quals, parmA);
417 /* Handle attribute matching (/A) */
420 while (nextA != NULL && !ok) {
422 WCHAR *thisA = (nextA+2);
425 /* Skip optional : */
426 if (*thisA == ':') thisA++;
428 /* Parse each of the /A[:]xxx in turn */
429 while (*thisA && *thisA != '/') {
431 BOOL attribute = FALSE;
433 /* Match negation of attribute first */
439 /* Match attribute */
441 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
443 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
445 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
447 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
450 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
453 /* Now check result, keeping a running boolean about whether it
454 matches all parsed attribues so far */
455 if (attribute && !negate) {
457 } else if (!attribute && negate) {
465 /* Save the running total as the final result */
468 /* Step on to next /A set */
469 nextA = strstrW (nextA+1, parmA);
473 /* /P means prompt for each file */
474 if (ok && strstrW (quals, parmP) != NULL) {
475 WCHAR question[MAXSTRING];
477 /* Ask for confirmation */
478 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
479 ok = WCMD_ask_confirm(question, FALSE, NULL);
482 /* Only proceed if ok to */
485 /* If file is read only, and /F supplied, delete it */
486 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
487 strstrW (quals, parmF) != NULL) {
488 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
491 /* Now do the delete */
492 if (!DeleteFile (fpath)) WCMD_print_error ();
496 } while (FindNextFile(hff, &fd) != 0);
500 /* Now recurse into all subdirectories handling the parameter in the same way */
501 if (strstrW (quals, parmS) != NULL) {
503 WCHAR thisDir[MAX_PATH];
508 WCHAR fname[MAX_PATH];
511 /* Convert path into actual directory spec */
512 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
513 WCMD_splitpath(thisDir, drive, dir, fname, ext);
515 strcpyW(thisDir, drive);
516 strcatW(thisDir, dir);
517 cPos = strlenW(thisDir);
519 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
521 /* Append '*' to the directory */
523 thisDir[cPos+1] = 0x00;
525 hff = FindFirstFile (thisDir, &fd);
527 /* Remove residual '*' */
528 thisDir[cPos] = 0x00;
530 if (hff != INVALID_HANDLE_VALUE) {
531 DIRECTORY_STACK *allDirs = NULL;
532 DIRECTORY_STACK *lastEntry = NULL;
535 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
536 (strcmpW(fd.cFileName, dotdotW) != 0) &&
537 (strcmpW(fd.cFileName, dotW) != 0)) {
539 DIRECTORY_STACK *nextDir;
540 WCHAR subParm[MAX_PATH];
542 /* Work out search parameter in sub dir */
543 strcpyW (subParm, thisDir);
544 strcatW (subParm, fd.cFileName);
545 strcatW (subParm, slashW);
546 strcatW (subParm, fname);
547 strcatW (subParm, ext);
548 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
550 /* Allocate memory, add to list */
551 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
552 if (allDirs == NULL) allDirs = nextDir;
553 if (lastEntry != NULL) lastEntry->next = nextDir;
555 nextDir->next = NULL;
556 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
557 (strlenW(subParm)+1) * sizeof(WCHAR));
558 strcpyW(nextDir->dirName, subParm);
560 } while (FindNextFile(hff, &fd) != 0);
563 /* Go through each subdir doing the delete */
564 while (allDirs != NULL) {
565 DIRECTORY_STACK *tempDir;
567 tempDir = allDirs->next;
568 found |= WCMD_delete (allDirs->dirName, FALSE);
570 HeapFree(GetProcessHeap(),0,allDirs->dirName);
571 HeapFree(GetProcessHeap(),0,allDirs);
576 /* Keep running total to see if any found, and if not recursing
577 issue error message */
581 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
588 /* Handle no valid args */
589 if (argsProcessed == 0) {
590 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
596 /****************************************************************************
599 * Echo input to the screen (or not). We don't try to emulate the bugs
600 * in DOS (try typing "ECHO ON AGAIN" for an example).
603 void WCMD_echo (const WCHAR *command) {
607 if ((command[0] == '.') && (command[1] == 0)) {
608 WCMD_output (newline);
613 count = strlenW(command);
615 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
616 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
619 if (lstrcmpiW(command, onW) == 0) {
623 if (lstrcmpiW(command, offW) == 0) {
627 WCMD_output_asis (command);
628 WCMD_output (newline);
632 /**************************************************************************
635 * Batch file loop processing.
637 * On entry: cmdList contains the syntax up to the set
638 * next cmdList and all in that bracket contain the set data
639 * next cmdlist contains the DO cmd
640 * following that is either brackets or && entries (as per if)
644 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
649 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
650 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
651 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
657 BOOL expandDirs = FALSE;
658 BOOL useNumbers = FALSE;
659 BOOL doRecursive = FALSE;
660 BOOL doFileset = FALSE;
661 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
663 CMD_LIST *thisCmdStart;
666 /* Handle optional qualifiers (multiple are allowed) */
667 while (*curPos && *curPos == '/') {
668 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
670 switch (toupperW(*curPos)) {
671 case 'D': curPos++; expandDirs = TRUE; break;
672 case 'L': curPos++; useNumbers = TRUE; break;
674 /* Recursive is special case - /R can have an optional path following it */
675 /* filenamesets are another special case - /F can have an optional options following it */
679 BOOL isRecursive = (*curPos == 'R');
681 if (isRecursive) doRecursive = TRUE;
682 else doFileset = TRUE;
684 /* Skip whitespace */
686 while (*curPos && *curPos==' ') curPos++;
688 /* Next parm is either qualifier, path/options or variable -
689 only care about it if it is the path/options */
690 if (*curPos && *curPos != '/' && *curPos != '%') {
691 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
692 else WINE_FIXME("/F needs to handle options\n");
697 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
701 /* Skip whitespace between qualifiers */
702 while (*curPos && *curPos==' ') curPos++;
705 /* Skip whitespace before variable */
706 while (*curPos && *curPos==' ') curPos++;
708 /* Ensure line continues with variable */
709 if (!*curPos || *curPos != '%') {
710 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
714 /* Variable should follow */
716 while (curPos[i] && curPos[i]!=' ') i++;
717 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
719 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
722 /* Skip whitespace before IN */
723 while (*curPos && *curPos==' ') curPos++;
725 /* Ensure line continues with IN */
726 if (!*curPos || lstrcmpiW (curPos, inW)) {
727 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
731 /* Save away where the set of data starts and the variable */
732 thisDepth = (*cmdList)->bracketDepth;
733 *cmdList = (*cmdList)->nextcommand;
734 setStart = (*cmdList);
736 /* Skip until the close bracket */
737 WINE_TRACE("Searching %p as the set\n", *cmdList);
739 (*cmdList)->command != NULL &&
740 (*cmdList)->bracketDepth > thisDepth) {
741 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
742 *cmdList = (*cmdList)->nextcommand;
745 /* Skip the close bracket, if there is one */
746 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
748 /* Syntax error if missing close bracket, or nothing following it
749 and once we have the complete set, we expect a DO */
750 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
751 if ((*cmdList == NULL) ||
752 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
753 (*cmdList)->command, 3, doW, -1) != 2)) {
754 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
758 /* Save away the starting position for the commands (and offset for the
762 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
766 /* Loop through all set entries */
768 thisSet->command != NULL &&
769 thisSet->bracketDepth >= thisDepth) {
771 /* Loop through all entries on the same line */
775 WINE_TRACE("Processing for set %p\n", thisSet);
777 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
780 * If the parameter within the set has a wildcard then search for matching files
781 * otherwise do a literal substitution.
783 static const WCHAR wildcards[] = {'*','?','\0'};
784 thisCmdStart = cmdStart;
787 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
789 if (!useNumbers && !doFileset) {
790 if (strpbrkW (item, wildcards)) {
791 hff = FindFirstFile (item, &fd);
792 if (hff != INVALID_HANDLE_VALUE) {
794 BOOL isDirectory = FALSE;
796 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
798 /* Handle as files or dirs appropriately, but ignore . and .. */
799 if (isDirectory == expandDirs &&
800 (strcmpW(fd.cFileName, dotdotW) != 0) &&
801 (strcmpW(fd.cFileName, dotW) != 0))
803 thisCmdStart = cmdStart;
804 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
805 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
806 fd.cFileName, FALSE, TRUE);
809 } while (FindNextFile(hff, &fd) != 0);
813 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
816 } else if (useNumbers) {
817 /* Convert the first 3 numbers to signed longs and save */
818 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
819 /* else ignore them! */
821 /* Filesets - either a list of files, or a command to run and parse the output */
822 } else if (doFileset && *itemStart != '"') {
825 WCHAR temp_file[MAX_PATH];
827 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
828 wine_dbgstr_w(item));
830 /* If backquote or single quote, we need to launch that command
831 and parse the results - use a temporary file */
832 if (*itemStart == '`' || *itemStart == '\'') {
834 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
835 static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
836 static const WCHAR cmdW[] = {'C','M','D','\0'};
838 /* Remove trailing character */
839 itemStart[strlenW(itemStart)-1] = 0x00;
841 /* Get temp filename */
842 GetTempPath (sizeof(temp_path)/sizeof(WCHAR), temp_path);
843 GetTempFileName (temp_path, cmdW, 0, temp_file);
845 /* Execute program and redirect output */
846 wsprintf (temp_cmd, redirOut, (itemStart+1), temp_file);
847 WCMD_execute (temp_cmd, NULL, NULL, NULL);
849 /* Open the file, read line by line and process */
850 input = CreateFile (temp_file, GENERIC_READ, FILE_SHARE_READ,
851 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
854 /* Open the file, read line by line and process */
855 input = CreateFile (item, GENERIC_READ, FILE_SHARE_READ,
856 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
859 /* Process the input file */
860 if (input == INVALID_HANDLE_VALUE) {
862 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
864 return; /* FOR loop aborts at first failure here */
868 WCHAR buffer[MAXSTRING] = {'\0'};
871 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
873 /* Skip blank lines*/
874 parm = WCMD_parameter (buffer, 0, &where);
875 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
876 wine_dbgstr_w(buffer));
879 /* FIXME: The following should be moved into its own routine and
880 reused for the string literal parsing below */
881 thisCmdStart = cmdStart;
882 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
883 cmdEnd = thisCmdStart;
892 /* Delete the temporary file */
893 if (*itemStart == '`' || *itemStart == '\'') {
894 DeleteFile (temp_file);
897 /* Filesets - A string literal */
898 } else if (doFileset && *itemStart == '"') {
899 WCHAR buffer[MAXSTRING] = {'\0'};
902 /* Skip blank lines, and re-extract parameter now string has quotes removed */
903 strcpyW(buffer, item);
904 parm = WCMD_parameter (buffer, 0, &where);
905 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
906 wine_dbgstr_w(buffer));
909 /* FIXME: The following should be moved into its own routine and
910 reused for the string literal parsing below */
911 thisCmdStart = cmdStart;
912 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
913 cmdEnd = thisCmdStart;
917 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
918 cmdEnd = thisCmdStart;
922 /* Move onto the next set line */
923 thisSet = thisSet->nextcommand;
926 /* If /L is provided, now run the for loop */
929 static const WCHAR fmt[] = {'%','d','\0'};
931 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
932 numbers[0], numbers[2], numbers[1]);
934 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
937 sprintfW(thisNum, fmt, i);
938 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
940 thisCmdStart = cmdStart;
941 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
942 cmdEnd = thisCmdStart;
946 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
947 all processing, OR it should be pointing to the end of && processing OR
948 it should be pointing at the NULL end of bracket for the DO. The return
949 value needs to be the NEXT command to execute, which it either is, or
950 we need to step over the closing bracket */
952 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
956 /*****************************************************************************
959 * Execute a command, and any && or bracketed follow on to the command. The
960 * first command to be executed may not be at the front of the
961 * commands->thiscommand string (eg. it may point after a DO or ELSE)
963 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
964 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
966 CMD_LIST *curPosition = *cmdList;
967 int myDepth = (*cmdList)->bracketDepth;
969 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
970 cmdList, wine_dbgstr_w(firstcmd),
971 wine_dbgstr_w(variable), wine_dbgstr_w(value),
974 /* Skip leading whitespace between condition and the command */
975 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
977 /* Process the first command, if there is one */
978 if (conditionTRUE && firstcmd && *firstcmd) {
979 WCHAR *command = WCMD_strdupW(firstcmd);
980 WCMD_execute (firstcmd, variable, value, cmdList);
985 /* If it didn't move the position, step to next command */
986 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
988 /* Process any other parts of the command */
990 BOOL processThese = TRUE;
992 if (isIF) processThese = conditionTRUE;
995 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
997 /* execute all appropriate commands */
998 curPosition = *cmdList;
1000 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
1002 (*cmdList)->isAmphersand,
1003 (*cmdList)->bracketDepth, myDepth);
1005 /* Execute any appended to the statement with &&'s */
1006 if ((*cmdList)->isAmphersand) {
1008 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
1010 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1012 /* Execute any appended to the statement with (...) */
1013 } else if ((*cmdList)->bracketDepth > myDepth) {
1015 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1016 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1018 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1020 /* End of the command - does 'ELSE ' follow as the next command? */
1022 if (isIF && CompareString (LOCALE_USER_DEFAULT,
1023 NORM_IGNORECASE | SORT_STRINGSORT,
1024 (*cmdList)->command, 5, ifElse, -1) == 2) {
1026 /* Swap between if and else processing */
1027 processThese = !processThese;
1029 /* Process the ELSE part */
1031 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1033 /* Skip leading whitespace between condition and the command */
1034 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1036 WCMD_execute (cmd, variable, value, cmdList);
1039 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1041 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1050 /**************************************************************************
1053 * Simple on-line help. Help text is stored in the resource file.
1056 void WCMD_give_help (WCHAR *command) {
1060 command = WCMD_strtrim_leading_spaces(command);
1061 if (strlenW(command) == 0) {
1062 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1065 for (i=0; i<=WCMD_EXIT; i++) {
1066 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1067 param1, -1, inbuilt[i], -1) == 2) {
1068 WCMD_output_asis (WCMD_LoadMessage(i));
1072 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1077 /****************************************************************************
1080 * Batch file jump instruction. Not the most efficient algorithm ;-)
1081 * Prints error message if the specified label cannot be found - the file pointer is
1082 * then at EOF, effectively stopping the batch file.
1083 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1086 void WCMD_goto (CMD_LIST **cmdList) {
1088 WCHAR string[MAX_PATH];
1090 /* Do not process any more parts of a processed multipart or multilines command */
1091 if (cmdList) *cmdList = NULL;
1093 if (param1[0] == 0x00) {
1094 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1097 if (context != NULL) {
1098 WCHAR *paramStart = param1;
1099 static const WCHAR eofW[] = {':','e','o','f','\0'};
1101 /* Handle special :EOF label */
1102 if (lstrcmpiW (eofW, param1) == 0) {
1103 context -> skip_rest = TRUE;
1107 /* Support goto :label as well as goto label */
1108 if (*paramStart == ':') paramStart++;
1110 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1111 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1112 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1114 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1119 /*****************************************************************************
1122 * Push a directory onto the stack
1125 void WCMD_pushd (WCHAR *command) {
1126 struct env_stack *curdir;
1128 static const WCHAR parmD[] = {'/','D','\0'};
1130 if (strchrW(command, '/') != NULL) {
1131 SetLastError(ERROR_INVALID_PARAMETER);
1136 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1137 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1138 if( !curdir || !thisdir ) {
1141 WINE_ERR ("out of memory\n");
1145 /* Change directory using CD code with /D parameter */
1146 strcpyW(quals, parmD);
1147 GetCurrentDirectoryW (1024, thisdir);
1149 WCMD_setshow_default(command);
1155 curdir -> next = pushd_directories;
1156 curdir -> strings = thisdir;
1157 if (pushd_directories == NULL) {
1158 curdir -> u.stackdepth = 1;
1160 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1162 pushd_directories = curdir;
1167 /*****************************************************************************
1170 * Pop a directory from the stack
1173 void WCMD_popd (void) {
1174 struct env_stack *temp = pushd_directories;
1176 if (!pushd_directories)
1179 /* pop the old environment from the stack, and make it the current dir */
1180 pushd_directories = temp->next;
1181 SetCurrentDirectoryW(temp->strings);
1182 LocalFree (temp->strings);
1186 /****************************************************************************
1189 * Batch file conditional.
1191 * On entry, cmdlist will point to command containing the IF, and optionally
1192 * the first command to execute (if brackets not found)
1193 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1194 * If ('s were found, execute all within that bracket
1195 * Command may optionally be followed by an ELSE - need to skip instructions
1196 * in the else using the same logic
1198 * FIXME: Much more syntax checking needed!
1201 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1203 int negate = 0, test = 0;
1204 WCHAR condition[MAX_PATH], *command, *s;
1205 static const WCHAR notW[] = {'n','o','t','\0'};
1206 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1207 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1208 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1209 static const WCHAR eqeqW[] = {'=','=','\0'};
1211 if (!lstrcmpiW (param1, notW)) {
1213 strcpyW (condition, param2);
1216 strcpyW (condition, param1);
1218 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1220 if (!lstrcmpiW (condition, errlvlW)) {
1221 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1222 WCMD_parameter (p, 2+negate, &command);
1224 else if (!lstrcmpiW (condition, existW)) {
1225 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1228 WCMD_parameter (p, 2+negate, &command);
1230 else if (!lstrcmpiW (condition, defdW)) {
1231 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1234 WCMD_parameter (p, 2+negate, &command);
1236 else if ((s = strstrW (p, eqeqW))) {
1238 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1239 WCMD_parameter (s, 1, &command);
1242 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1246 /* Process rest of IF statement which is on the same line
1247 Note: This may process all or some of the cmdList (eg a GOTO) */
1248 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1251 /****************************************************************************
1254 * Move a file, directory tree or wildcarded set of files.
1257 void WCMD_move (void) {
1262 WCHAR input[MAX_PATH];
1263 WCHAR output[MAX_PATH];
1265 WCHAR dir[MAX_PATH];
1266 WCHAR fname[MAX_PATH];
1267 WCHAR ext[MAX_PATH];
1269 if (param1[0] == 0x00) {
1270 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1274 /* If no destination supplied, assume current directory */
1275 if (param2[0] == 0x00) {
1276 strcpyW(param2, dotW);
1279 /* If 2nd parm is directory, then use original filename */
1280 /* Convert partial path to full path */
1281 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1282 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1283 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1284 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1286 /* Split into components */
1287 WCMD_splitpath(input, drive, dir, fname, ext);
1289 hff = FindFirstFile (input, &fd);
1290 while (hff != INVALID_HANDLE_VALUE) {
1291 WCHAR dest[MAX_PATH];
1292 WCHAR src[MAX_PATH];
1295 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1297 /* Build src & dest name */
1298 strcpyW(src, drive);
1301 /* See if dest is an existing directory */
1302 attribs = GetFileAttributes(output);
1303 if (attribs != INVALID_FILE_ATTRIBUTES &&
1304 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1305 strcpyW(dest, output);
1306 strcatW(dest, slashW);
1307 strcatW(dest, fd.cFileName);
1309 strcpyW(dest, output);
1312 strcatW(src, fd.cFileName);
1314 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1315 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1317 /* Check if file is read only, otherwise move it */
1318 attribs = GetFileAttributes(src);
1319 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1320 (attribs & FILE_ATTRIBUTE_READONLY)) {
1321 SetLastError(ERROR_ACCESS_DENIED);
1326 /* If destination exists, prompt unless /Y supplied */
1327 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1329 WCHAR copycmd[MAXSTRING];
1332 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1333 if (strstrW (quals, parmNoY))
1335 else if (strstrW (quals, parmY))
1338 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1339 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1340 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1341 && ! lstrcmpiW (copycmd, parmY));
1344 /* Prompt if overwriting */
1346 WCHAR question[MAXSTRING];
1349 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1351 /* Ask for confirmation */
1352 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1353 ok = WCMD_ask_confirm(question, FALSE, NULL);
1355 /* So delete the destination prior to the move */
1357 if (!DeleteFile (dest)) {
1358 WCMD_print_error ();
1367 status = MoveFile (src, dest);
1369 status = 1; /* Anything other than 0 to prevent error msg below */
1374 WCMD_print_error ();
1378 /* Step on to next match */
1379 if (FindNextFile(hff, &fd) == 0) {
1381 hff = INVALID_HANDLE_VALUE;
1387 /****************************************************************************
1390 * Wait for keyboard input.
1393 void WCMD_pause (void) {
1398 WCMD_output (anykey);
1399 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1400 sizeof(string)/sizeof(WCHAR), &count, NULL);
1403 /****************************************************************************
1406 * Delete a directory.
1409 void WCMD_remove_dir (WCHAR *command) {
1412 int argsProcessed = 0;
1413 WCHAR *argN = command;
1414 static const WCHAR parmS[] = {'/','S','\0'};
1415 static const WCHAR parmQ[] = {'/','Q','\0'};
1417 /* Loop through all args */
1419 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1420 if (argN && argN[0] != '/') {
1421 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1422 wine_dbgstr_w(quals));
1425 /* If subdirectory search not supplied, just try to remove
1426 and report error if it fails (eg if it contains a file) */
1427 if (strstrW (quals, parmS) == NULL) {
1428 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1430 /* Otherwise use ShFileOp to recursively remove a directory */
1433 SHFILEOPSTRUCT lpDir;
1436 if (strstrW (quals, parmQ) == NULL) {
1438 WCHAR question[MAXSTRING];
1439 static const WCHAR fmt[] = {'%','s',' ','\0'};
1441 /* Ask for confirmation */
1442 wsprintf(question, fmt, thisArg);
1443 ok = WCMD_ask_confirm(question, TRUE, NULL);
1445 /* Abort if answer is 'N' */
1452 lpDir.pFrom = thisArg;
1453 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1454 lpDir.wFunc = FO_DELETE;
1455 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1460 /* Handle no valid args */
1461 if (argsProcessed == 0) {
1462 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1468 /****************************************************************************
1474 void WCMD_rename (void) {
1479 WCHAR input[MAX_PATH];
1480 WCHAR *dotDst = NULL;
1482 WCHAR dir[MAX_PATH];
1483 WCHAR fname[MAX_PATH];
1484 WCHAR ext[MAX_PATH];
1489 /* Must be at least two args */
1490 if (param1[0] == 0x00 || param2[0] == 0x00) {
1491 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1496 /* Destination cannot contain a drive letter or directory separator */
1497 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1498 SetLastError(ERROR_INVALID_PARAMETER);
1504 /* Convert partial path to full path */
1505 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1506 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1507 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1508 dotDst = strchrW(param2, '.');
1510 /* Split into components */
1511 WCMD_splitpath(input, drive, dir, fname, ext);
1513 hff = FindFirstFile (input, &fd);
1514 while (hff != INVALID_HANDLE_VALUE) {
1515 WCHAR dest[MAX_PATH];
1516 WCHAR src[MAX_PATH];
1517 WCHAR *dotSrc = NULL;
1520 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1522 /* FIXME: If dest name or extension is *, replace with filename/ext
1523 part otherwise use supplied name. This supports:
1525 ren jim.* fred.* etc
1526 However, windows has a more complex algorithum supporting eg
1527 ?'s and *'s mid name */
1528 dotSrc = strchrW(fd.cFileName, '.');
1530 /* Build src & dest name */
1531 strcpyW(src, drive);
1534 dirLen = strlenW(src);
1535 strcatW(src, fd.cFileName);
1538 if (param2[0] == '*') {
1539 strcatW(dest, fd.cFileName);
1540 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1542 strcatW(dest, param2);
1543 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1546 /* Build Extension */
1547 if (dotDst && (*(dotDst+1)=='*')) {
1548 if (dotSrc) strcatW(dest, dotSrc);
1549 } else if (dotDst) {
1550 if (dotDst) strcatW(dest, dotDst);
1553 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1554 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1556 /* Check if file is read only, otherwise move it */
1557 attribs = GetFileAttributes(src);
1558 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1559 (attribs & FILE_ATTRIBUTE_READONLY)) {
1560 SetLastError(ERROR_ACCESS_DENIED);
1563 status = MoveFile (src, dest);
1567 WCMD_print_error ();
1571 /* Step on to next match */
1572 if (FindNextFile(hff, &fd) == 0) {
1574 hff = INVALID_HANDLE_VALUE;
1580 /*****************************************************************************
1583 * Make a copy of the environment.
1585 static WCHAR *WCMD_dupenv( const WCHAR *env )
1595 len += (strlenW(&env[len]) + 1);
1597 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1600 WINE_ERR("out of memory\n");
1603 memcpy (env_copy, env, len*sizeof (WCHAR));
1609 /*****************************************************************************
1612 * setlocal pushes the environment onto a stack
1613 * Save the environment as unicode so we don't screw anything up.
1615 void WCMD_setlocal (const WCHAR *s) {
1617 struct env_stack *env_copy;
1618 WCHAR cwd[MAX_PATH];
1620 /* DISABLEEXTENSIONS ignored */
1622 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1625 WINE_ERR ("out of memory\n");
1629 env = GetEnvironmentStringsW ();
1631 env_copy->strings = WCMD_dupenv (env);
1632 if (env_copy->strings)
1634 env_copy->next = saved_environment;
1635 saved_environment = env_copy;
1637 /* Save the current drive letter */
1638 GetCurrentDirectory (MAX_PATH, cwd);
1639 env_copy->u.cwd = cwd[0];
1642 LocalFree (env_copy);
1644 FreeEnvironmentStringsW (env);
1648 /*****************************************************************************
1651 * endlocal pops the environment off a stack
1652 * Note: When searching for '=', search from WCHAR position 1, to handle
1653 * special internal environment variables =C:, =D: etc
1655 void WCMD_endlocal (void) {
1656 WCHAR *env, *old, *p;
1657 struct env_stack *temp;
1660 if (!saved_environment)
1663 /* pop the old environment from the stack */
1664 temp = saved_environment;
1665 saved_environment = temp->next;
1667 /* delete the current environment, totally */
1668 env = GetEnvironmentStringsW ();
1669 old = WCMD_dupenv (GetEnvironmentStringsW ());
1672 n = strlenW(&old[len]) + 1;
1673 p = strchrW(&old[len] + 1, '=');
1677 SetEnvironmentVariableW (&old[len], NULL);
1682 FreeEnvironmentStringsW (env);
1684 /* restore old environment */
1685 env = temp->strings;
1688 n = strlenW(&env[len]) + 1;
1689 p = strchrW(&env[len] + 1, '=');
1693 SetEnvironmentVariableW (&env[len], p);
1698 /* Restore current drive letter */
1699 if (IsCharAlpha(temp->u.cwd)) {
1701 WCHAR cwd[MAX_PATH];
1702 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1704 wsprintf(envvar, fmt, temp->u.cwd);
1705 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1706 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1707 SetCurrentDirectory(cwd);
1715 /*****************************************************************************
1716 * WCMD_setshow_attrib
1718 * Display and optionally sets DOS attributes on a file or directory
1720 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1721 * As a result only the Readonly flag is correctly reported, the Archive bit
1722 * is always set and the rest are not implemented. We do the Right Thing anyway.
1724 * FIXME: No SET functionality.
1728 void WCMD_setshow_attrib (void) {
1733 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1735 if (param1[0] == '-') {
1736 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1740 if (strlenW(param1) == 0) {
1741 static const WCHAR slashStarW[] = {'\\','*','\0'};
1743 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1744 strcatW (param1, slashStarW);
1747 hff = FindFirstFile (param1, &fd);
1748 if (hff == INVALID_HANDLE_VALUE) {
1749 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1753 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1754 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1755 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1758 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1761 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1764 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1767 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1770 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1773 WCMD_output (fmt, flags, fd.cFileName);
1774 for (count=0; count < 8; count++) flags[count] = ' ';
1776 } while (FindNextFile(hff, &fd) != 0);
1781 /*****************************************************************************
1782 * WCMD_setshow_default
1784 * Set/Show the current default directory
1787 void WCMD_setshow_default (WCHAR *command) {
1795 static const WCHAR parmD[] = {'/','D','\0'};
1797 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1799 /* Skip /D and trailing whitespace if on the front of the command line */
1800 if (CompareString (LOCALE_USER_DEFAULT,
1801 NORM_IGNORECASE | SORT_STRINGSORT,
1802 command, 2, parmD, -1) == 2) {
1804 while (*command && *command==' ') command++;
1807 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1808 if (strlenW(command) == 0) {
1809 strcatW (cwd, newline);
1813 /* Remove any double quotes, which may be in the
1814 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1817 if (*command != '"') *pos++ = *command;
1822 /* Search for approprate directory */
1823 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1824 hff = FindFirstFile (string, &fd);
1825 while (hff != INVALID_HANDLE_VALUE) {
1826 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1827 WCHAR fpath[MAX_PATH];
1829 WCHAR dir[MAX_PATH];
1830 WCHAR fname[MAX_PATH];
1831 WCHAR ext[MAX_PATH];
1832 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1834 /* Convert path into actual directory spec */
1835 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1836 WCMD_splitpath(fpath, drive, dir, fname, ext);
1839 wsprintf(string, fmt, drive, dir, fd.cFileName);
1842 hff = INVALID_HANDLE_VALUE;
1846 /* Step on to next match */
1847 if (FindNextFile(hff, &fd) == 0) {
1849 hff = INVALID_HANDLE_VALUE;
1854 /* Change to that directory */
1855 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1857 status = SetCurrentDirectory (string);
1860 WCMD_print_error ();
1864 /* Restore old directory if drive letter would change, and
1865 CD x:\directory /D (or pushd c:\directory) not supplied */
1866 if ((strstrW(quals, parmD) == NULL) &&
1867 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1868 SetCurrentDirectory(cwd);
1872 /* Set special =C: type environment variable, for drive letter of
1873 change of directory, even if path was restored due to missing
1874 /D (allows changing drive letter when not resident on that
1876 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1878 strcpyW(env, equalW);
1879 memcpy(env+1, string, 2 * sizeof(WCHAR));
1881 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1882 SetEnvironmentVariable(env, string);
1889 /****************************************************************************
1892 * Set/Show the system date
1893 * FIXME: Can't change date yet
1896 void WCMD_setshow_date (void) {
1898 WCHAR curdate[64], buffer[64];
1900 static const WCHAR parmT[] = {'/','T','\0'};
1902 if (strlenW(param1) == 0) {
1903 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1904 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1905 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1906 if (strstrW (quals, parmT) == NULL) {
1907 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1908 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1909 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1911 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1915 else WCMD_print_error ();
1918 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1922 /****************************************************************************
1925 static int WCMD_compare( const void *a, const void *b )
1928 const WCHAR * const *str_a = a, * const *str_b = b;
1929 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1930 *str_a, -1, *str_b, -1 );
1931 if( r == CSTR_LESS_THAN ) return -1;
1932 if( r == CSTR_GREATER_THAN ) return 1;
1936 /****************************************************************************
1937 * WCMD_setshow_sortenv
1939 * sort variables into order for display
1940 * Optionally only display those who start with a stub
1941 * returns the count displayed
1943 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1945 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1948 if (stub) stublen = strlenW(stub);
1950 /* count the number of strings, and the total length */
1952 len += (strlenW(&s[len]) + 1);
1956 /* add the strings to an array */
1957 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1961 for( i=1; i<count; i++ )
1962 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1964 /* sort the array */
1965 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1968 for( i=0; i<count; i++ ) {
1969 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1970 NORM_IGNORECASE | SORT_STRINGSORT,
1971 str[i], stublen, stub, -1) == 2) {
1972 /* Don't display special internal variables */
1973 if (str[i][0] != '=') {
1974 WCMD_output_asis(str[i]);
1975 WCMD_output_asis(newline);
1982 return displayedcount;
1985 /****************************************************************************
1988 * Set/Show the environment variables
1991 void WCMD_setshow_env (WCHAR *s) {
1996 static const WCHAR parmP[] = {'/','P','\0'};
1999 if (param1[0] == 0x00 && quals[0] == 0x00) {
2000 env = GetEnvironmentStrings ();
2001 WCMD_setshow_sortenv( env, NULL );
2005 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2006 if (CompareString (LOCALE_USER_DEFAULT,
2007 NORM_IGNORECASE | SORT_STRINGSORT,
2008 s, 2, parmP, -1) == 2) {
2009 WCHAR string[MAXSTRING];
2013 while (*s && *s==' ') s++;
2015 /* If no parameter, or no '=' sign, return an error */
2016 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2017 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2021 /* Output the prompt */
2023 if (strlenW(p) != 0) WCMD_output(p);
2025 /* Read the reply */
2026 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2027 sizeof(string)/sizeof(WCHAR), &count, NULL);
2029 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2030 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2031 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2032 wine_dbgstr_w(string));
2033 status = SetEnvironmentVariable (s, string);
2038 p = strchrW (s, '=');
2040 env = GetEnvironmentStrings ();
2041 if (WCMD_setshow_sortenv( env, s ) == 0) {
2042 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2049 if (strlenW(p) == 0) p = NULL;
2050 status = SetEnvironmentVariable (s, p);
2051 gle = GetLastError();
2052 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2054 } else if ((!status)) WCMD_print_error();
2058 /****************************************************************************
2061 * Set/Show the path environment variable
2064 void WCMD_setshow_path (WCHAR *command) {
2068 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2069 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2071 if (strlenW(param1) == 0) {
2072 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2074 WCMD_output_asis ( pathEqW);
2075 WCMD_output_asis ( string);
2076 WCMD_output_asis ( newline);
2079 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2083 if (*command == '=') command++; /* Skip leading '=' */
2084 status = SetEnvironmentVariable (pathW, command);
2085 if (!status) WCMD_print_error();
2089 /****************************************************************************
2090 * WCMD_setshow_prompt
2092 * Set or show the command prompt.
2095 void WCMD_setshow_prompt (void) {
2098 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2100 if (strlenW(param1) == 0) {
2101 SetEnvironmentVariable (promptW, NULL);
2105 while ((*s == '=') || (*s == ' ')) s++;
2106 if (strlenW(s) == 0) {
2107 SetEnvironmentVariable (promptW, NULL);
2109 else SetEnvironmentVariable (promptW, s);
2113 /****************************************************************************
2116 * Set/Show the system time
2117 * FIXME: Can't change time yet
2120 void WCMD_setshow_time (void) {
2122 WCHAR curtime[64], buffer[64];
2125 static const WCHAR parmT[] = {'/','T','\0'};
2127 if (strlenW(param1) == 0) {
2129 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2130 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2131 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
2132 if (strstrW (quals, parmT) == NULL) {
2133 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2134 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2135 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2137 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2141 else WCMD_print_error ();
2144 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2148 /****************************************************************************
2151 * Shift batch parameters.
2152 * Optional /n says where to start shifting (n=0-8)
2155 void WCMD_shift (WCHAR *command) {
2158 if (context != NULL) {
2159 WCHAR *pos = strchrW(command, '/');
2164 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2165 start = (*(pos+1) - '0');
2167 SetLastError(ERROR_INVALID_PARAMETER);
2172 WINE_TRACE("Shifting variables, starting at %d\n", start);
2173 for (i=start;i<=8;i++) {
2174 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2176 context -> shift_count[9] = context -> shift_count[9] + 1;
2181 /****************************************************************************
2184 * Set the console title
2186 void WCMD_title (WCHAR *command) {
2187 SetConsoleTitle(command);
2190 /****************************************************************************
2193 * Copy a file to standard output.
2196 void WCMD_type (WCHAR *command) {
2199 WCHAR *argN = command;
2200 BOOL writeHeaders = FALSE;
2202 if (param1[0] == 0x00) {
2203 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2207 if (param2[0] != 0x00) writeHeaders = TRUE;
2209 /* Loop through all args */
2212 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2220 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2221 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2222 FILE_ATTRIBUTE_NORMAL, NULL);
2223 if (h == INVALID_HANDLE_VALUE) {
2224 WCMD_print_error ();
2225 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2229 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2230 WCMD_output(fmt, thisArg);
2232 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2233 if (count == 0) break; /* ReadFile reports success on EOF! */
2235 WCMD_output_asis (buffer);
2242 /****************************************************************************
2245 * Output either a file or stdin to screen in pages
2248 void WCMD_more (WCHAR *command) {
2251 WCHAR *argN = command;
2252 BOOL useinput = FALSE;
2254 WCHAR moreStrPage[100];
2257 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2258 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2259 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2260 ')',' ','-','-','\n','\0'};
2261 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2263 /* Prefix the NLS more with '-- ', then load the text */
2265 strcpyW(moreStr, moreStart);
2266 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2267 (sizeof(moreStr)/sizeof(WCHAR))-3);
2269 if (param1[0] == 0x00) {
2271 /* Wine implements pipes via temporary files, and hence stdin is
2272 effectively reading from the file. This means the prompts for
2273 more are satistied by the next line from the input (file). To
2274 avoid this, ensure stdin is to the console */
2275 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2276 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2277 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2278 FILE_ATTRIBUTE_NORMAL, 0);
2279 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2281 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2282 once you get in this bit unless due to a pipe, its going to end badly... */
2284 wsprintf(moreStrPage, moreFmt, moreStr);
2286 WCMD_enter_paged_mode(moreStrPage);
2287 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2288 if (count == 0) break; /* ReadFile reports success on EOF! */
2290 WCMD_output_asis (buffer);
2292 WCMD_leave_paged_mode();
2294 /* Restore stdin to what it was */
2295 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2296 CloseHandle(hConIn);
2300 BOOL needsPause = FALSE;
2302 /* Loop through all args */
2303 WCMD_enter_paged_mode(moreStrPage);
2306 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2314 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2315 WCMD_leave_paged_mode();
2316 WCMD_output_asis(moreStrPage);
2317 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2318 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2319 WCMD_enter_paged_mode(moreStrPage);
2323 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2324 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2325 FILE_ATTRIBUTE_NORMAL, NULL);
2326 if (h == INVALID_HANDLE_VALUE) {
2327 WCMD_print_error ();
2328 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2332 ULONG64 fileLen = 0;
2333 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2335 /* Get the file size */
2336 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2337 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2340 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2341 if (count == 0) break; /* ReadFile reports success on EOF! */
2345 /* Update % count (would be used in WCMD_output_asis as prompt) */
2346 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2348 WCMD_output_asis (buffer);
2354 WCMD_leave_paged_mode();
2358 /****************************************************************************
2361 * Display verify flag.
2362 * FIXME: We don't actually do anything with the verify flag other than toggle
2366 void WCMD_verify (WCHAR *command) {
2370 count = strlenW(command);
2372 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2373 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2376 if (lstrcmpiW(command, onW) == 0) {
2380 else if (lstrcmpiW(command, offW) == 0) {
2384 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2387 /****************************************************************************
2390 * Display version info.
2393 void WCMD_version (void) {
2395 WCMD_output (version_string);
2399 /****************************************************************************
2402 * Display volume info and/or set volume label. Returns 0 if error.
2405 int WCMD_volume (int mode, WCHAR *path) {
2407 DWORD count, serial;
2408 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2411 if (strlenW(path) == 0) {
2412 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2414 WCMD_print_error ();
2417 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2418 &serial, NULL, NULL, NULL, 0);
2421 static const WCHAR fmt[] = {'%','s','\\','\0'};
2422 if ((path[1] != ':') || (strlenW(path) != 2)) {
2423 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2426 wsprintf (curdir, fmt, path);
2427 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2432 WCMD_print_error ();
2435 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2436 curdir[0], label, HIWORD(serial), LOWORD(serial));
2438 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2439 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2440 sizeof(string)/sizeof(WCHAR), &count, NULL);
2442 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2443 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2445 if (strlenW(path) != 0) {
2446 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2449 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2455 /**************************************************************************
2458 * Exit either the process, or just this batch program
2462 void WCMD_exit (CMD_LIST **cmdList) {
2464 static const WCHAR parmB[] = {'/','B','\0'};
2465 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2467 if (context && lstrcmpiW(quals, parmB) == 0) {
2469 context -> skip_rest = TRUE;
2476 /**************************************************************************
2479 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2482 * Returns True if Y (or A) answer is selected
2483 * If optionAll contains a pointer, ALL is allowed, and if answered
2487 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2489 WCHAR msgbuffer[MAXSTRING];
2490 WCHAR Ybuffer[MAXSTRING];
2491 WCHAR Nbuffer[MAXSTRING];
2492 WCHAR Abuffer[MAXSTRING];
2493 WCHAR answer[MAX_PATH] = {'\0'};
2496 /* Load the translated 'Are you sure', plus valid answers */
2497 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2498 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2499 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2500 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2502 /* Loop waiting on a Y or N */
2503 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2504 static const WCHAR startBkt[] = {' ','(','\0'};
2505 static const WCHAR endBkt[] = {')','?','\0'};
2507 WCMD_output_asis (message);
2509 WCMD_output_asis (msgbuffer);
2511 WCMD_output_asis (startBkt);
2512 WCMD_output_asis (Ybuffer);
2513 WCMD_output_asis (fslashW);
2514 WCMD_output_asis (Nbuffer);
2516 WCMD_output_asis (fslashW);
2517 WCMD_output_asis (Abuffer);
2519 WCMD_output_asis (endBkt);
2520 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2521 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2522 answer[0] = toupperW(answer[0]);
2525 /* Return the answer */
2526 return ((answer[0] == Ybuffer[0]) ||
2527 (optionAll && (answer[0] == Abuffer[0])));
2530 /*****************************************************************************
2533 * Lists or sets file associations (assoc = TRUE)
2534 * Lists or sets file types (assoc = FALSE)
2536 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2539 DWORD accessOptions = KEY_READ;
2541 LONG rc = ERROR_SUCCESS;
2542 WCHAR keyValue[MAXSTRING];
2543 DWORD valueLen = MAXSTRING;
2545 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2546 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2548 /* See if parameter includes '=' */
2550 newValue = strchrW(command, '=');
2551 if (newValue) accessOptions |= KEY_WRITE;
2553 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2554 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2555 accessOptions, &key) != ERROR_SUCCESS) {
2556 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2560 /* If no parameters then list all associations */
2561 if (*command == 0x00) {
2564 /* Enumerate all the keys */
2565 while (rc != ERROR_NO_MORE_ITEMS) {
2566 WCHAR keyName[MAXSTRING];
2569 /* Find the next value */
2570 nameLen = MAXSTRING;
2571 rc = RegEnumKeyEx(key, index++,
2573 NULL, NULL, NULL, NULL);
2575 if (rc == ERROR_SUCCESS) {
2577 /* Only interested in extension ones if assoc, or others
2579 if ((keyName[0] == '.' && assoc) ||
2580 (!(keyName[0] == '.') && (!assoc)))
2582 WCHAR subkey[MAXSTRING];
2583 strcpyW(subkey, keyName);
2584 if (!assoc) strcatW(subkey, shOpCmdW);
2586 if (RegOpenKeyEx(key, subkey, 0,
2587 accessOptions, &readKey) == ERROR_SUCCESS) {
2589 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2590 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2591 (LPBYTE)keyValue, &valueLen);
2592 WCMD_output_asis(keyName);
2593 WCMD_output_asis(equalW);
2594 /* If no default value found, leave line empty after '=' */
2595 if (rc == ERROR_SUCCESS) {
2596 WCMD_output_asis(keyValue);
2598 WCMD_output_asis(newline);
2603 RegCloseKey(readKey);
2607 /* Parameter supplied - if no '=' on command line, its a query */
2608 if (newValue == NULL) {
2610 WCHAR subkey[MAXSTRING];
2612 /* Query terminates the parameter at the first space */
2613 strcpyW(keyValue, command);
2614 space = strchrW(keyValue, ' ');
2615 if (space) *space=0x00;
2617 /* Set up key name */
2618 strcpyW(subkey, keyValue);
2619 if (!assoc) strcatW(subkey, shOpCmdW);
2621 if (RegOpenKeyEx(key, subkey, 0,
2622 accessOptions, &readKey) == ERROR_SUCCESS) {
2624 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2625 (LPBYTE)keyValue, &valueLen);
2626 WCMD_output_asis(command);
2627 WCMD_output_asis(equalW);
2628 /* If no default value found, leave line empty after '=' */
2629 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2630 WCMD_output_asis(newline);
2631 RegCloseKey(readKey);
2634 WCHAR msgbuffer[MAXSTRING];
2635 WCHAR outbuffer[MAXSTRING];
2637 /* Load the translated 'File association not found' */
2639 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2641 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2643 wsprintf(outbuffer, msgbuffer, keyValue);
2644 WCMD_output_asis(outbuffer);
2648 /* Not a query - its a set or clear of a value */
2651 WCHAR subkey[MAXSTRING];
2653 /* Get pointer to new value */
2657 /* Set up key name */
2658 strcpyW(subkey, command);
2659 if (!assoc) strcatW(subkey, shOpCmdW);
2661 /* If nothing after '=' then clear value - only valid for ASSOC */
2662 if (*newValue == 0x00) {
2664 if (assoc) rc = RegDeleteKey(key, command);
2665 if (assoc && rc == ERROR_SUCCESS) {
2666 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2668 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2673 WCHAR msgbuffer[MAXSTRING];
2674 WCHAR outbuffer[MAXSTRING];
2676 /* Load the translated 'File association not found' */
2678 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2679 sizeof(msgbuffer)/sizeof(WCHAR));
2681 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2682 sizeof(msgbuffer)/sizeof(WCHAR));
2684 wsprintf(outbuffer, msgbuffer, keyValue);
2685 WCMD_output_asis(outbuffer);
2689 /* It really is a set value = contents */
2691 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2692 accessOptions, NULL, &readKey, NULL);
2693 if (rc == ERROR_SUCCESS) {
2694 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2695 (LPBYTE)newValue, strlenW(newValue));
2696 RegCloseKey(readKey);
2699 if (rc != ERROR_SUCCESS) {
2703 WCMD_output_asis(command);
2704 WCMD_output_asis(equalW);
2705 WCMD_output_asis(newValue);
2706 WCMD_output_asis(newline);
2716 /****************************************************************************
2719 * Clear the terminal screen.
2722 void WCMD_color (void) {
2724 /* Emulate by filling the screen from the top left to bottom right with
2725 spaces, then moving the cursor to the top left afterwards */
2726 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2727 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2729 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2730 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2734 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2740 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2745 /* Convert the color hex digits */
2746 if (param1[0] == 0x00) {
2747 color = defaultColor;
2749 color = strtoulW(param1, NULL, 16);
2752 /* Fail if fg == bg color */
2753 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2758 /* Set the current screen contents and ensure all future writes
2759 remain this color */
2760 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2761 SetConsoleTextAttribute(hStdOut, color);