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 != '"') {
826 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
827 wine_dbgstr_w(item));
829 /* Open the file, read line by line and process */
830 input = CreateFile (item, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
831 FILE_ATTRIBUTE_NORMAL, NULL);
833 if (input == INVALID_HANDLE_VALUE) {
835 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
837 return; /* FOR loop aborts at first failure here */
841 WCHAR buffer[MAXSTRING] = {'\0'};
844 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
846 /* Skip blank lines*/
847 parm = WCMD_parameter (buffer, 0, &where);
848 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
849 wine_dbgstr_w(buffer));
852 /* FIXME: The following should be moved into its own routine and
853 reused for the string literal parsing below */
854 thisCmdStart = cmdStart;
855 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
856 cmdEnd = thisCmdStart;
865 /* Filesets - A string literal */
866 } else if (doFileset && *itemStart == '"') {
867 WCHAR buffer[MAXSTRING] = {'\0'};
870 /* Skip blank lines, and re-extract parameter now string has quotes removed */
871 strcpyW(buffer, item);
872 parm = WCMD_parameter (buffer, 0, &where);
873 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
874 wine_dbgstr_w(buffer));
877 /* FIXME: The following should be moved into its own routine and
878 reused for the string literal parsing below */
879 thisCmdStart = cmdStart;
880 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
881 cmdEnd = thisCmdStart;
885 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
886 cmdEnd = thisCmdStart;
890 /* Move onto the next set line */
891 thisSet = thisSet->nextcommand;
894 /* If /L is provided, now run the for loop */
897 static const WCHAR fmt[] = {'%','d','\0'};
899 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
900 numbers[0], numbers[2], numbers[1]);
902 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
905 sprintfW(thisNum, fmt, i);
906 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
908 thisCmdStart = cmdStart;
909 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
910 cmdEnd = thisCmdStart;
914 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
915 all processing, OR it should be pointing to the end of && processing OR
916 it should be pointing at the NULL end of bracket for the DO. The return
917 value needs to be the NEXT command to execute, which it either is, or
918 we need to step over the closing bracket */
920 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
924 /*****************************************************************************
927 * Execute a command, and any && or bracketed follow on to the command. The
928 * first command to be executed may not be at the front of the
929 * commands->thiscommand string (eg. it may point after a DO or ELSE)
931 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
932 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
934 CMD_LIST *curPosition = *cmdList;
935 int myDepth = (*cmdList)->bracketDepth;
937 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
938 cmdList, wine_dbgstr_w(firstcmd),
939 wine_dbgstr_w(variable), wine_dbgstr_w(value),
942 /* Skip leading whitespace between condition and the command */
943 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
945 /* Process the first command, if there is one */
946 if (conditionTRUE && firstcmd && *firstcmd) {
947 WCHAR *command = WCMD_strdupW(firstcmd);
948 WCMD_execute (firstcmd, variable, value, cmdList);
953 /* If it didn't move the position, step to next command */
954 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
956 /* Process any other parts of the command */
958 BOOL processThese = TRUE;
960 if (isIF) processThese = conditionTRUE;
963 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
965 /* execute all appropriate commands */
966 curPosition = *cmdList;
968 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
970 (*cmdList)->isAmphersand,
971 (*cmdList)->bracketDepth, myDepth);
973 /* Execute any appended to the statement with &&'s */
974 if ((*cmdList)->isAmphersand) {
976 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
978 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
980 /* Execute any appended to the statement with (...) */
981 } else if ((*cmdList)->bracketDepth > myDepth) {
983 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
984 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
986 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
988 /* End of the command - does 'ELSE ' follow as the next command? */
990 if (isIF && CompareString (LOCALE_USER_DEFAULT,
991 NORM_IGNORECASE | SORT_STRINGSORT,
992 (*cmdList)->command, 5, ifElse, -1) == 2) {
994 /* Swap between if and else processing */
995 processThese = !processThese;
997 /* Process the ELSE part */
999 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1001 /* Skip leading whitespace between condition and the command */
1002 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1004 WCMD_execute (cmd, variable, value, cmdList);
1007 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1009 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1018 /**************************************************************************
1021 * Simple on-line help. Help text is stored in the resource file.
1024 void WCMD_give_help (WCHAR *command) {
1028 command = WCMD_strtrim_leading_spaces(command);
1029 if (strlenW(command) == 0) {
1030 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1033 for (i=0; i<=WCMD_EXIT; i++) {
1034 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1035 param1, -1, inbuilt[i], -1) == 2) {
1036 WCMD_output_asis (WCMD_LoadMessage(i));
1040 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
1045 /****************************************************************************
1048 * Batch file jump instruction. Not the most efficient algorithm ;-)
1049 * Prints error message if the specified label cannot be found - the file pointer is
1050 * then at EOF, effectively stopping the batch file.
1051 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1054 void WCMD_goto (CMD_LIST **cmdList) {
1056 WCHAR string[MAX_PATH];
1058 /* Do not process any more parts of a processed multipart or multilines command */
1059 if (cmdList) *cmdList = NULL;
1061 if (param1[0] == 0x00) {
1062 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1065 if (context != NULL) {
1066 WCHAR *paramStart = param1;
1067 static const WCHAR eofW[] = {':','e','o','f','\0'};
1069 /* Handle special :EOF label */
1070 if (lstrcmpiW (eofW, param1) == 0) {
1071 context -> skip_rest = TRUE;
1075 /* Support goto :label as well as goto label */
1076 if (*paramStart == ':') paramStart++;
1078 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1079 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1080 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1082 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1087 /*****************************************************************************
1090 * Push a directory onto the stack
1093 void WCMD_pushd (WCHAR *command) {
1094 struct env_stack *curdir;
1096 static const WCHAR parmD[] = {'/','D','\0'};
1098 if (strchrW(command, '/') != NULL) {
1099 SetLastError(ERROR_INVALID_PARAMETER);
1104 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1105 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1106 if( !curdir || !thisdir ) {
1109 WINE_ERR ("out of memory\n");
1113 /* Change directory using CD code with /D parameter */
1114 strcpyW(quals, parmD);
1115 GetCurrentDirectoryW (1024, thisdir);
1117 WCMD_setshow_default(command);
1123 curdir -> next = pushd_directories;
1124 curdir -> strings = thisdir;
1125 if (pushd_directories == NULL) {
1126 curdir -> u.stackdepth = 1;
1128 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1130 pushd_directories = curdir;
1135 /*****************************************************************************
1138 * Pop a directory from the stack
1141 void WCMD_popd (void) {
1142 struct env_stack *temp = pushd_directories;
1144 if (!pushd_directories)
1147 /* pop the old environment from the stack, and make it the current dir */
1148 pushd_directories = temp->next;
1149 SetCurrentDirectoryW(temp->strings);
1150 LocalFree (temp->strings);
1154 /****************************************************************************
1157 * Batch file conditional.
1159 * On entry, cmdlist will point to command containing the IF, and optionally
1160 * the first command to execute (if brackets not found)
1161 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1162 * If ('s were found, execute all within that bracket
1163 * Command may optionally be followed by an ELSE - need to skip instructions
1164 * in the else using the same logic
1166 * FIXME: Much more syntax checking needed!
1169 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1171 int negate = 0, test = 0;
1172 WCHAR condition[MAX_PATH], *command, *s;
1173 static const WCHAR notW[] = {'n','o','t','\0'};
1174 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1175 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1176 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1177 static const WCHAR eqeqW[] = {'=','=','\0'};
1179 if (!lstrcmpiW (param1, notW)) {
1181 strcpyW (condition, param2);
1184 strcpyW (condition, param1);
1186 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1188 if (!lstrcmpiW (condition, errlvlW)) {
1189 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1190 WCMD_parameter (p, 2+negate, &command);
1192 else if (!lstrcmpiW (condition, existW)) {
1193 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1196 WCMD_parameter (p, 2+negate, &command);
1198 else if (!lstrcmpiW (condition, defdW)) {
1199 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1202 WCMD_parameter (p, 2+negate, &command);
1204 else if ((s = strstrW (p, eqeqW))) {
1206 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1207 WCMD_parameter (s, 1, &command);
1210 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1214 /* Process rest of IF statement which is on the same line
1215 Note: This may process all or some of the cmdList (eg a GOTO) */
1216 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1219 /****************************************************************************
1222 * Move a file, directory tree or wildcarded set of files.
1225 void WCMD_move (void) {
1230 WCHAR input[MAX_PATH];
1231 WCHAR output[MAX_PATH];
1233 WCHAR dir[MAX_PATH];
1234 WCHAR fname[MAX_PATH];
1235 WCHAR ext[MAX_PATH];
1237 if (param1[0] == 0x00) {
1238 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1242 /* If no destination supplied, assume current directory */
1243 if (param2[0] == 0x00) {
1244 strcpyW(param2, dotW);
1247 /* If 2nd parm is directory, then use original filename */
1248 /* Convert partial path to full path */
1249 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1250 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1251 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1252 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1254 /* Split into components */
1255 WCMD_splitpath(input, drive, dir, fname, ext);
1257 hff = FindFirstFile (input, &fd);
1258 while (hff != INVALID_HANDLE_VALUE) {
1259 WCHAR dest[MAX_PATH];
1260 WCHAR src[MAX_PATH];
1263 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1265 /* Build src & dest name */
1266 strcpyW(src, drive);
1269 /* See if dest is an existing directory */
1270 attribs = GetFileAttributes(output);
1271 if (attribs != INVALID_FILE_ATTRIBUTES &&
1272 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1273 strcpyW(dest, output);
1274 strcatW(dest, slashW);
1275 strcatW(dest, fd.cFileName);
1277 strcpyW(dest, output);
1280 strcatW(src, fd.cFileName);
1282 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1283 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1285 /* Check if file is read only, otherwise move it */
1286 attribs = GetFileAttributes(src);
1287 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1288 (attribs & FILE_ATTRIBUTE_READONLY)) {
1289 SetLastError(ERROR_ACCESS_DENIED);
1294 /* If destination exists, prompt unless /Y supplied */
1295 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1297 WCHAR copycmd[MAXSTRING];
1300 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1301 if (strstrW (quals, parmNoY))
1303 else if (strstrW (quals, parmY))
1306 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1307 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1308 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1309 && ! lstrcmpiW (copycmd, parmY));
1312 /* Prompt if overwriting */
1314 WCHAR question[MAXSTRING];
1317 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1319 /* Ask for confirmation */
1320 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1321 ok = WCMD_ask_confirm(question, FALSE, NULL);
1323 /* So delete the destination prior to the move */
1325 if (!DeleteFile (dest)) {
1326 WCMD_print_error ();
1335 status = MoveFile (src, dest);
1337 status = 1; /* Anything other than 0 to prevent error msg below */
1342 WCMD_print_error ();
1346 /* Step on to next match */
1347 if (FindNextFile(hff, &fd) == 0) {
1349 hff = INVALID_HANDLE_VALUE;
1355 /****************************************************************************
1358 * Wait for keyboard input.
1361 void WCMD_pause (void) {
1366 WCMD_output (anykey);
1367 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1368 sizeof(string)/sizeof(WCHAR), &count, NULL);
1371 /****************************************************************************
1374 * Delete a directory.
1377 void WCMD_remove_dir (WCHAR *command) {
1380 int argsProcessed = 0;
1381 WCHAR *argN = command;
1382 static const WCHAR parmS[] = {'/','S','\0'};
1383 static const WCHAR parmQ[] = {'/','Q','\0'};
1385 /* Loop through all args */
1387 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1388 if (argN && argN[0] != '/') {
1389 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1390 wine_dbgstr_w(quals));
1393 /* If subdirectory search not supplied, just try to remove
1394 and report error if it fails (eg if it contains a file) */
1395 if (strstrW (quals, parmS) == NULL) {
1396 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1398 /* Otherwise use ShFileOp to recursively remove a directory */
1401 SHFILEOPSTRUCT lpDir;
1404 if (strstrW (quals, parmQ) == NULL) {
1406 WCHAR question[MAXSTRING];
1407 static const WCHAR fmt[] = {'%','s',' ','\0'};
1409 /* Ask for confirmation */
1410 wsprintf(question, fmt, thisArg);
1411 ok = WCMD_ask_confirm(question, TRUE, NULL);
1413 /* Abort if answer is 'N' */
1420 lpDir.pFrom = thisArg;
1421 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1422 lpDir.wFunc = FO_DELETE;
1423 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1428 /* Handle no valid args */
1429 if (argsProcessed == 0) {
1430 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1436 /****************************************************************************
1442 void WCMD_rename (void) {
1447 WCHAR input[MAX_PATH];
1448 WCHAR *dotDst = NULL;
1450 WCHAR dir[MAX_PATH];
1451 WCHAR fname[MAX_PATH];
1452 WCHAR ext[MAX_PATH];
1457 /* Must be at least two args */
1458 if (param1[0] == 0x00 || param2[0] == 0x00) {
1459 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1464 /* Destination cannot contain a drive letter or directory separator */
1465 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1466 SetLastError(ERROR_INVALID_PARAMETER);
1472 /* Convert partial path to full path */
1473 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1474 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1475 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1476 dotDst = strchrW(param2, '.');
1478 /* Split into components */
1479 WCMD_splitpath(input, drive, dir, fname, ext);
1481 hff = FindFirstFile (input, &fd);
1482 while (hff != INVALID_HANDLE_VALUE) {
1483 WCHAR dest[MAX_PATH];
1484 WCHAR src[MAX_PATH];
1485 WCHAR *dotSrc = NULL;
1488 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1490 /* FIXME: If dest name or extension is *, replace with filename/ext
1491 part otherwise use supplied name. This supports:
1493 ren jim.* fred.* etc
1494 However, windows has a more complex algorithum supporting eg
1495 ?'s and *'s mid name */
1496 dotSrc = strchrW(fd.cFileName, '.');
1498 /* Build src & dest name */
1499 strcpyW(src, drive);
1502 dirLen = strlenW(src);
1503 strcatW(src, fd.cFileName);
1506 if (param2[0] == '*') {
1507 strcatW(dest, fd.cFileName);
1508 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1510 strcatW(dest, param2);
1511 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1514 /* Build Extension */
1515 if (dotDst && (*(dotDst+1)=='*')) {
1516 if (dotSrc) strcatW(dest, dotSrc);
1517 } else if (dotDst) {
1518 if (dotDst) strcatW(dest, dotDst);
1521 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1522 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1524 /* Check if file is read only, otherwise move it */
1525 attribs = GetFileAttributes(src);
1526 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1527 (attribs & FILE_ATTRIBUTE_READONLY)) {
1528 SetLastError(ERROR_ACCESS_DENIED);
1531 status = MoveFile (src, dest);
1535 WCMD_print_error ();
1539 /* Step on to next match */
1540 if (FindNextFile(hff, &fd) == 0) {
1542 hff = INVALID_HANDLE_VALUE;
1548 /*****************************************************************************
1551 * Make a copy of the environment.
1553 static WCHAR *WCMD_dupenv( const WCHAR *env )
1563 len += (strlenW(&env[len]) + 1);
1565 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1568 WINE_ERR("out of memory\n");
1571 memcpy (env_copy, env, len*sizeof (WCHAR));
1577 /*****************************************************************************
1580 * setlocal pushes the environment onto a stack
1581 * Save the environment as unicode so we don't screw anything up.
1583 void WCMD_setlocal (const WCHAR *s) {
1585 struct env_stack *env_copy;
1586 WCHAR cwd[MAX_PATH];
1588 /* DISABLEEXTENSIONS ignored */
1590 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1593 WINE_ERR ("out of memory\n");
1597 env = GetEnvironmentStringsW ();
1599 env_copy->strings = WCMD_dupenv (env);
1600 if (env_copy->strings)
1602 env_copy->next = saved_environment;
1603 saved_environment = env_copy;
1605 /* Save the current drive letter */
1606 GetCurrentDirectory (MAX_PATH, cwd);
1607 env_copy->u.cwd = cwd[0];
1610 LocalFree (env_copy);
1612 FreeEnvironmentStringsW (env);
1616 /*****************************************************************************
1619 * endlocal pops the environment off a stack
1620 * Note: When searching for '=', search from WCHAR position 1, to handle
1621 * special internal environment variables =C:, =D: etc
1623 void WCMD_endlocal (void) {
1624 WCHAR *env, *old, *p;
1625 struct env_stack *temp;
1628 if (!saved_environment)
1631 /* pop the old environment from the stack */
1632 temp = saved_environment;
1633 saved_environment = temp->next;
1635 /* delete the current environment, totally */
1636 env = GetEnvironmentStringsW ();
1637 old = WCMD_dupenv (GetEnvironmentStringsW ());
1640 n = strlenW(&old[len]) + 1;
1641 p = strchrW(&old[len] + 1, '=');
1645 SetEnvironmentVariableW (&old[len], NULL);
1650 FreeEnvironmentStringsW (env);
1652 /* restore old environment */
1653 env = temp->strings;
1656 n = strlenW(&env[len]) + 1;
1657 p = strchrW(&env[len] + 1, '=');
1661 SetEnvironmentVariableW (&env[len], p);
1666 /* Restore current drive letter */
1667 if (IsCharAlpha(temp->u.cwd)) {
1669 WCHAR cwd[MAX_PATH];
1670 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1672 wsprintf(envvar, fmt, temp->u.cwd);
1673 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1674 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1675 SetCurrentDirectory(cwd);
1683 /*****************************************************************************
1684 * WCMD_setshow_attrib
1686 * Display and optionally sets DOS attributes on a file or directory
1688 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1689 * As a result only the Readonly flag is correctly reported, the Archive bit
1690 * is always set and the rest are not implemented. We do the Right Thing anyway.
1692 * FIXME: No SET functionality.
1696 void WCMD_setshow_attrib (void) {
1701 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1703 if (param1[0] == '-') {
1704 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1708 if (strlenW(param1) == 0) {
1709 static const WCHAR slashStarW[] = {'\\','*','\0'};
1711 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1712 strcatW (param1, slashStarW);
1715 hff = FindFirstFile (param1, &fd);
1716 if (hff == INVALID_HANDLE_VALUE) {
1717 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1721 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1722 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1723 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1726 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1729 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1732 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1735 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1738 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1741 WCMD_output (fmt, flags, fd.cFileName);
1742 for (count=0; count < 8; count++) flags[count] = ' ';
1744 } while (FindNextFile(hff, &fd) != 0);
1749 /*****************************************************************************
1750 * WCMD_setshow_default
1752 * Set/Show the current default directory
1755 void WCMD_setshow_default (WCHAR *command) {
1763 static const WCHAR parmD[] = {'/','D','\0'};
1765 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1767 /* Skip /D and trailing whitespace if on the front of the command line */
1768 if (CompareString (LOCALE_USER_DEFAULT,
1769 NORM_IGNORECASE | SORT_STRINGSORT,
1770 command, 2, parmD, -1) == 2) {
1772 while (*command && *command==' ') command++;
1775 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1776 if (strlenW(command) == 0) {
1777 strcatW (cwd, newline);
1781 /* Remove any double quotes, which may be in the
1782 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1785 if (*command != '"') *pos++ = *command;
1790 /* Search for approprate directory */
1791 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1792 hff = FindFirstFile (string, &fd);
1793 while (hff != INVALID_HANDLE_VALUE) {
1794 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1795 WCHAR fpath[MAX_PATH];
1797 WCHAR dir[MAX_PATH];
1798 WCHAR fname[MAX_PATH];
1799 WCHAR ext[MAX_PATH];
1800 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1802 /* Convert path into actual directory spec */
1803 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1804 WCMD_splitpath(fpath, drive, dir, fname, ext);
1807 wsprintf(string, fmt, drive, dir, fd.cFileName);
1810 hff = INVALID_HANDLE_VALUE;
1814 /* Step on to next match */
1815 if (FindNextFile(hff, &fd) == 0) {
1817 hff = INVALID_HANDLE_VALUE;
1822 /* Change to that directory */
1823 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1825 status = SetCurrentDirectory (string);
1828 WCMD_print_error ();
1832 /* Restore old directory if drive letter would change, and
1833 CD x:\directory /D (or pushd c:\directory) not supplied */
1834 if ((strstrW(quals, parmD) == NULL) &&
1835 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1836 SetCurrentDirectory(cwd);
1840 /* Set special =C: type environment variable, for drive letter of
1841 change of directory, even if path was restored due to missing
1842 /D (allows changing drive letter when not resident on that
1844 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1846 strcpyW(env, equalW);
1847 memcpy(env+1, string, 2 * sizeof(WCHAR));
1849 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1850 SetEnvironmentVariable(env, string);
1857 /****************************************************************************
1860 * Set/Show the system date
1861 * FIXME: Can't change date yet
1864 void WCMD_setshow_date (void) {
1866 WCHAR curdate[64], buffer[64];
1868 static const WCHAR parmT[] = {'/','T','\0'};
1870 if (strlenW(param1) == 0) {
1871 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1872 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1873 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1874 if (strstrW (quals, parmT) == NULL) {
1875 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1876 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1877 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1879 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1883 else WCMD_print_error ();
1886 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1890 /****************************************************************************
1893 static int WCMD_compare( const void *a, const void *b )
1896 const WCHAR * const *str_a = a, * const *str_b = b;
1897 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1898 *str_a, -1, *str_b, -1 );
1899 if( r == CSTR_LESS_THAN ) return -1;
1900 if( r == CSTR_GREATER_THAN ) return 1;
1904 /****************************************************************************
1905 * WCMD_setshow_sortenv
1907 * sort variables into order for display
1908 * Optionally only display those who start with a stub
1909 * returns the count displayed
1911 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1913 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1916 if (stub) stublen = strlenW(stub);
1918 /* count the number of strings, and the total length */
1920 len += (strlenW(&s[len]) + 1);
1924 /* add the strings to an array */
1925 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1929 for( i=1; i<count; i++ )
1930 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1932 /* sort the array */
1933 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1936 for( i=0; i<count; i++ ) {
1937 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1938 NORM_IGNORECASE | SORT_STRINGSORT,
1939 str[i], stublen, stub, -1) == 2) {
1940 /* Don't display special internal variables */
1941 if (str[i][0] != '=') {
1942 WCMD_output_asis(str[i]);
1943 WCMD_output_asis(newline);
1950 return displayedcount;
1953 /****************************************************************************
1956 * Set/Show the environment variables
1959 void WCMD_setshow_env (WCHAR *s) {
1964 static const WCHAR parmP[] = {'/','P','\0'};
1967 if (param1[0] == 0x00 && quals[0] == 0x00) {
1968 env = GetEnvironmentStrings ();
1969 WCMD_setshow_sortenv( env, NULL );
1973 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1974 if (CompareString (LOCALE_USER_DEFAULT,
1975 NORM_IGNORECASE | SORT_STRINGSORT,
1976 s, 2, parmP, -1) == 2) {
1977 WCHAR string[MAXSTRING];
1981 while (*s && *s==' ') s++;
1983 /* If no parameter, or no '=' sign, return an error */
1984 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1985 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1989 /* Output the prompt */
1991 if (strlenW(p) != 0) WCMD_output(p);
1993 /* Read the reply */
1994 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1995 sizeof(string)/sizeof(WCHAR), &count, NULL);
1997 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1998 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1999 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2000 wine_dbgstr_w(string));
2001 status = SetEnvironmentVariable (s, string);
2006 p = strchrW (s, '=');
2008 env = GetEnvironmentStrings ();
2009 if (WCMD_setshow_sortenv( env, s ) == 0) {
2010 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2017 if (strlenW(p) == 0) p = NULL;
2018 status = SetEnvironmentVariable (s, p);
2019 gle = GetLastError();
2020 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2022 } else if ((!status)) WCMD_print_error();
2026 /****************************************************************************
2029 * Set/Show the path environment variable
2032 void WCMD_setshow_path (WCHAR *command) {
2036 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2037 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2039 if (strlenW(param1) == 0) {
2040 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
2042 WCMD_output_asis ( pathEqW);
2043 WCMD_output_asis ( string);
2044 WCMD_output_asis ( newline);
2047 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2051 if (*command == '=') command++; /* Skip leading '=' */
2052 status = SetEnvironmentVariable (pathW, command);
2053 if (!status) WCMD_print_error();
2057 /****************************************************************************
2058 * WCMD_setshow_prompt
2060 * Set or show the command prompt.
2063 void WCMD_setshow_prompt (void) {
2066 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2068 if (strlenW(param1) == 0) {
2069 SetEnvironmentVariable (promptW, NULL);
2073 while ((*s == '=') || (*s == ' ')) s++;
2074 if (strlenW(s) == 0) {
2075 SetEnvironmentVariable (promptW, NULL);
2077 else SetEnvironmentVariable (promptW, s);
2081 /****************************************************************************
2084 * Set/Show the system time
2085 * FIXME: Can't change time yet
2088 void WCMD_setshow_time (void) {
2090 WCHAR curtime[64], buffer[64];
2093 static const WCHAR parmT[] = {'/','T','\0'};
2095 if (strlenW(param1) == 0) {
2097 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
2098 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2099 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
2100 if (strstrW (quals, parmT) == NULL) {
2101 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2102 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2103 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2105 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2109 else WCMD_print_error ();
2112 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2116 /****************************************************************************
2119 * Shift batch parameters.
2120 * Optional /n says where to start shifting (n=0-8)
2123 void WCMD_shift (WCHAR *command) {
2126 if (context != NULL) {
2127 WCHAR *pos = strchrW(command, '/');
2132 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2133 start = (*(pos+1) - '0');
2135 SetLastError(ERROR_INVALID_PARAMETER);
2140 WINE_TRACE("Shifting variables, starting at %d\n", start);
2141 for (i=start;i<=8;i++) {
2142 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2144 context -> shift_count[9] = context -> shift_count[9] + 1;
2149 /****************************************************************************
2152 * Set the console title
2154 void WCMD_title (WCHAR *command) {
2155 SetConsoleTitle(command);
2158 /****************************************************************************
2161 * Copy a file to standard output.
2164 void WCMD_type (WCHAR *command) {
2167 WCHAR *argN = command;
2168 BOOL writeHeaders = FALSE;
2170 if (param1[0] == 0x00) {
2171 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2175 if (param2[0] != 0x00) writeHeaders = TRUE;
2177 /* Loop through all args */
2180 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2188 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2189 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2190 FILE_ATTRIBUTE_NORMAL, NULL);
2191 if (h == INVALID_HANDLE_VALUE) {
2192 WCMD_print_error ();
2193 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2197 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2198 WCMD_output(fmt, thisArg);
2200 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2201 if (count == 0) break; /* ReadFile reports success on EOF! */
2203 WCMD_output_asis (buffer);
2210 /****************************************************************************
2213 * Output either a file or stdin to screen in pages
2216 void WCMD_more (WCHAR *command) {
2219 WCHAR *argN = command;
2220 BOOL useinput = FALSE;
2222 WCHAR moreStrPage[100];
2225 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2226 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2227 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2228 ')',' ','-','-','\n','\0'};
2229 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2231 /* Prefix the NLS more with '-- ', then load the text */
2233 strcpyW(moreStr, moreStart);
2234 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2235 (sizeof(moreStr)/sizeof(WCHAR))-3);
2237 if (param1[0] == 0x00) {
2239 /* Wine implements pipes via temporary files, and hence stdin is
2240 effectively reading from the file. This means the prompts for
2241 more are satistied by the next line from the input (file). To
2242 avoid this, ensure stdin is to the console */
2243 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2244 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2245 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2246 FILE_ATTRIBUTE_NORMAL, 0);
2247 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2249 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2250 once you get in this bit unless due to a pipe, its going to end badly... */
2252 wsprintf(moreStrPage, moreFmt, moreStr);
2254 WCMD_enter_paged_mode(moreStrPage);
2255 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2256 if (count == 0) break; /* ReadFile reports success on EOF! */
2258 WCMD_output_asis (buffer);
2260 WCMD_leave_paged_mode();
2262 /* Restore stdin to what it was */
2263 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2264 CloseHandle(hConIn);
2268 BOOL needsPause = FALSE;
2270 /* Loop through all args */
2271 WCMD_enter_paged_mode(moreStrPage);
2274 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2282 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2283 WCMD_leave_paged_mode();
2284 WCMD_output_asis(moreStrPage);
2285 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2286 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2287 WCMD_enter_paged_mode(moreStrPage);
2291 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2292 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2293 FILE_ATTRIBUTE_NORMAL, NULL);
2294 if (h == INVALID_HANDLE_VALUE) {
2295 WCMD_print_error ();
2296 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2300 ULONG64 fileLen = 0;
2301 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2303 /* Get the file size */
2304 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2305 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2308 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2309 if (count == 0) break; /* ReadFile reports success on EOF! */
2313 /* Update % count (would be used in WCMD_output_asis as prompt) */
2314 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2316 WCMD_output_asis (buffer);
2322 WCMD_leave_paged_mode();
2326 /****************************************************************************
2329 * Display verify flag.
2330 * FIXME: We don't actually do anything with the verify flag other than toggle
2334 void WCMD_verify (WCHAR *command) {
2338 count = strlenW(command);
2340 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2341 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2344 if (lstrcmpiW(command, onW) == 0) {
2348 else if (lstrcmpiW(command, offW) == 0) {
2352 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2355 /****************************************************************************
2358 * Display version info.
2361 void WCMD_version (void) {
2363 WCMD_output (version_string);
2367 /****************************************************************************
2370 * Display volume info and/or set volume label. Returns 0 if error.
2373 int WCMD_volume (int mode, WCHAR *path) {
2375 DWORD count, serial;
2376 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2379 if (strlenW(path) == 0) {
2380 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2382 WCMD_print_error ();
2385 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2386 &serial, NULL, NULL, NULL, 0);
2389 static const WCHAR fmt[] = {'%','s','\\','\0'};
2390 if ((path[1] != ':') || (strlenW(path) != 2)) {
2391 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2394 wsprintf (curdir, fmt, path);
2395 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2400 WCMD_print_error ();
2403 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2404 curdir[0], label, HIWORD(serial), LOWORD(serial));
2406 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2407 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2408 sizeof(string)/sizeof(WCHAR), &count, NULL);
2410 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2411 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2413 if (strlenW(path) != 0) {
2414 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2417 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2423 /**************************************************************************
2426 * Exit either the process, or just this batch program
2430 void WCMD_exit (CMD_LIST **cmdList) {
2432 static const WCHAR parmB[] = {'/','B','\0'};
2433 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2435 if (context && lstrcmpiW(quals, parmB) == 0) {
2437 context -> skip_rest = TRUE;
2444 /**************************************************************************
2447 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2450 * Returns True if Y (or A) answer is selected
2451 * If optionAll contains a pointer, ALL is allowed, and if answered
2455 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2457 WCHAR msgbuffer[MAXSTRING];
2458 WCHAR Ybuffer[MAXSTRING];
2459 WCHAR Nbuffer[MAXSTRING];
2460 WCHAR Abuffer[MAXSTRING];
2461 WCHAR answer[MAX_PATH] = {'\0'};
2464 /* Load the translated 'Are you sure', plus valid answers */
2465 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2466 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2467 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2468 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2470 /* Loop waiting on a Y or N */
2471 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2472 static const WCHAR startBkt[] = {' ','(','\0'};
2473 static const WCHAR endBkt[] = {')','?','\0'};
2475 WCMD_output_asis (message);
2477 WCMD_output_asis (msgbuffer);
2479 WCMD_output_asis (startBkt);
2480 WCMD_output_asis (Ybuffer);
2481 WCMD_output_asis (fslashW);
2482 WCMD_output_asis (Nbuffer);
2484 WCMD_output_asis (fslashW);
2485 WCMD_output_asis (Abuffer);
2487 WCMD_output_asis (endBkt);
2488 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2489 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2490 answer[0] = toupperW(answer[0]);
2493 /* Return the answer */
2494 return ((answer[0] == Ybuffer[0]) ||
2495 (optionAll && (answer[0] == Abuffer[0])));
2498 /*****************************************************************************
2501 * Lists or sets file associations (assoc = TRUE)
2502 * Lists or sets file types (assoc = FALSE)
2504 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2507 DWORD accessOptions = KEY_READ;
2509 LONG rc = ERROR_SUCCESS;
2510 WCHAR keyValue[MAXSTRING];
2511 DWORD valueLen = MAXSTRING;
2513 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2514 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2516 /* See if parameter includes '=' */
2518 newValue = strchrW(command, '=');
2519 if (newValue) accessOptions |= KEY_WRITE;
2521 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2522 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2523 accessOptions, &key) != ERROR_SUCCESS) {
2524 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2528 /* If no parameters then list all associations */
2529 if (*command == 0x00) {
2532 /* Enumerate all the keys */
2533 while (rc != ERROR_NO_MORE_ITEMS) {
2534 WCHAR keyName[MAXSTRING];
2537 /* Find the next value */
2538 nameLen = MAXSTRING;
2539 rc = RegEnumKeyEx(key, index++,
2541 NULL, NULL, NULL, NULL);
2543 if (rc == ERROR_SUCCESS) {
2545 /* Only interested in extension ones if assoc, or others
2547 if ((keyName[0] == '.' && assoc) ||
2548 (!(keyName[0] == '.') && (!assoc)))
2550 WCHAR subkey[MAXSTRING];
2551 strcpyW(subkey, keyName);
2552 if (!assoc) strcatW(subkey, shOpCmdW);
2554 if (RegOpenKeyEx(key, subkey, 0,
2555 accessOptions, &readKey) == ERROR_SUCCESS) {
2557 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2558 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2559 (LPBYTE)keyValue, &valueLen);
2560 WCMD_output_asis(keyName);
2561 WCMD_output_asis(equalW);
2562 /* If no default value found, leave line empty after '=' */
2563 if (rc == ERROR_SUCCESS) {
2564 WCMD_output_asis(keyValue);
2566 WCMD_output_asis(newline);
2571 RegCloseKey(readKey);
2575 /* Parameter supplied - if no '=' on command line, its a query */
2576 if (newValue == NULL) {
2578 WCHAR subkey[MAXSTRING];
2580 /* Query terminates the parameter at the first space */
2581 strcpyW(keyValue, command);
2582 space = strchrW(keyValue, ' ');
2583 if (space) *space=0x00;
2585 /* Set up key name */
2586 strcpyW(subkey, keyValue);
2587 if (!assoc) strcatW(subkey, shOpCmdW);
2589 if (RegOpenKeyEx(key, subkey, 0,
2590 accessOptions, &readKey) == ERROR_SUCCESS) {
2592 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2593 (LPBYTE)keyValue, &valueLen);
2594 WCMD_output_asis(command);
2595 WCMD_output_asis(equalW);
2596 /* If no default value found, leave line empty after '=' */
2597 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2598 WCMD_output_asis(newline);
2599 RegCloseKey(readKey);
2602 WCHAR msgbuffer[MAXSTRING];
2603 WCHAR outbuffer[MAXSTRING];
2605 /* Load the translated 'File association not found' */
2607 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2609 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2611 wsprintf(outbuffer, msgbuffer, keyValue);
2612 WCMD_output_asis(outbuffer);
2616 /* Not a query - its a set or clear of a value */
2619 WCHAR subkey[MAXSTRING];
2621 /* Get pointer to new value */
2625 /* Set up key name */
2626 strcpyW(subkey, command);
2627 if (!assoc) strcatW(subkey, shOpCmdW);
2629 /* If nothing after '=' then clear value - only valid for ASSOC */
2630 if (*newValue == 0x00) {
2632 if (assoc) rc = RegDeleteKey(key, command);
2633 if (assoc && rc == ERROR_SUCCESS) {
2634 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2636 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2641 WCHAR msgbuffer[MAXSTRING];
2642 WCHAR outbuffer[MAXSTRING];
2644 /* Load the translated 'File association not found' */
2646 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2647 sizeof(msgbuffer)/sizeof(WCHAR));
2649 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2650 sizeof(msgbuffer)/sizeof(WCHAR));
2652 wsprintf(outbuffer, msgbuffer, keyValue);
2653 WCMD_output_asis(outbuffer);
2657 /* It really is a set value = contents */
2659 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2660 accessOptions, NULL, &readKey, NULL);
2661 if (rc == ERROR_SUCCESS) {
2662 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2663 (LPBYTE)newValue, strlenW(newValue));
2664 RegCloseKey(readKey);
2667 if (rc != ERROR_SUCCESS) {
2671 WCMD_output_asis(command);
2672 WCMD_output_asis(equalW);
2673 WCMD_output_asis(newValue);
2674 WCMD_output_asis(newline);
2684 /****************************************************************************
2687 * Clear the terminal screen.
2690 void WCMD_color (void) {
2692 /* Emulate by filling the screen from the top left to bottom right with
2693 spaces, then moving the cursor to the top left afterwards */
2694 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2695 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2697 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2698 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2702 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2708 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2713 /* Convert the color hex digits */
2714 if (param1[0] == 0x00) {
2715 color = defaultColor;
2717 color = strtoulW(param1, NULL, 16);
2720 /* Fail if fg == bg color */
2721 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2726 /* Set the current screen contents and ensure all future writes
2727 remain this color */
2728 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2729 SetConsoleTextAttribute(hStdOut, color);