| /* | |
| * CMD - Wine-compatible command line interface - built-in functions. | |
| * | |
| * Copyright (C) 1999 D A Pickles | |
| * Copyright (C) 2007 J Edmeades | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| /* | |
| * NOTES: | |
| * On entry to each function, global variables quals, param1, param2 contain | |
| * the qualifiers (uppercased and concatenated) and parameters entered, with | |
| * environment-variable and batch parameter substitution already done. | |
| */ | |
| /* | |
| * FIXME: | |
| * - No support for pipes, shell parameters | |
| * - Lots of functionality missing from builtins | |
| * - Messages etc need international support | |
| */ | |
| #define WIN32_LEAN_AND_MEAN | |
| #include "wcmd.h" | |
| #include <shellapi.h> | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(cmd); | |
| static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable, | |
| WCHAR *value, BOOL isIF, BOOL conditionTRUE); | |
| struct env_stack *saved_environment; | |
| struct env_stack *pushd_directories; | |
| extern HINSTANCE hinst; | |
| extern WCHAR inbuilt[][10]; | |
| extern int echo_mode, verify_mode, defaultColor; | |
| extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH]; | |
| extern BATCH_CONTEXT *context; | |
| extern DWORD errorlevel; | |
| static const WCHAR dotW[] = {'.','\0'}; | |
| static const WCHAR dotdotW[] = {'.','.','\0'}; | |
| static const WCHAR slashW[] = {'\\','\0'}; | |
| static const WCHAR starW[] = {'*','\0'}; | |
| static const WCHAR equalW[] = {'=','\0'}; | |
| static const WCHAR fslashW[] = {'/','\0'}; | |
| static const WCHAR onW[] = {'O','N','\0'}; | |
| static const WCHAR offW[] = {'O','F','F','\0'}; | |
| static const WCHAR parmY[] = {'/','Y','\0'}; | |
| static const WCHAR parmNoY[] = {'/','-','Y','\0'}; | |
| static const WCHAR nullW[] = {'\0'}; | |
| /************************************************************************** | |
| * WCMD_ask_confirm | |
| * | |
| * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid | |
| * answer. | |
| * | |
| * Returns True if Y (or A) answer is selected | |
| * If optionAll contains a pointer, ALL is allowed, and if answered | |
| * set to TRUE | |
| * | |
| */ | |
| static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) { | |
| WCHAR msgbuffer[MAXSTRING]; | |
| WCHAR Ybuffer[MAXSTRING]; | |
| WCHAR Nbuffer[MAXSTRING]; | |
| WCHAR Abuffer[MAXSTRING]; | |
| WCHAR answer[MAX_PATH] = {'\0'}; | |
| DWORD count = 0; | |
| /* Load the translated 'Are you sure', plus valid answers */ | |
| LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR)); | |
| LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR)); | |
| LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR)); | |
| LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR)); | |
| /* Loop waiting on a Y or N */ | |
| while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) { | |
| static const WCHAR startBkt[] = {' ','(','\0'}; | |
| static const WCHAR endBkt[] = {')','?','\0'}; | |
| WCMD_output_asis (message); | |
| if (showSureText) { | |
| WCMD_output_asis (msgbuffer); | |
| } | |
| WCMD_output_asis (startBkt); | |
| WCMD_output_asis (Ybuffer); | |
| WCMD_output_asis (fslashW); | |
| WCMD_output_asis (Nbuffer); | |
| if (optionAll) { | |
| WCMD_output_asis (fslashW); | |
| WCMD_output_asis (Abuffer); | |
| } | |
| WCMD_output_asis (endBkt); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer, | |
| sizeof(answer)/sizeof(WCHAR), &count, NULL); | |
| answer[0] = toupperW(answer[0]); | |
| } | |
| /* Return the answer */ | |
| return ((answer[0] == Ybuffer[0]) || | |
| (optionAll && (answer[0] == Abuffer[0]))); | |
| } | |
| /**************************************************************************** | |
| * WCMD_clear_screen | |
| * | |
| * Clear the terminal screen. | |
| */ | |
| void WCMD_clear_screen (void) { | |
| /* Emulate by filling the screen from the top left to bottom right with | |
| spaces, then moving the cursor to the top left afterwards */ | |
| CONSOLE_SCREEN_BUFFER_INFO consoleInfo; | |
| HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); | |
| if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo)) | |
| { | |
| COORD topLeft; | |
| DWORD screenSize; | |
| screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1); | |
| topLeft.X = 0; | |
| topLeft.Y = 0; | |
| FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize); | |
| SetConsoleCursorPosition(hStdOut, topLeft); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_change_tty | |
| * | |
| * Change the default i/o device (ie redirect STDin/STDout). | |
| */ | |
| void WCMD_change_tty (void) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| } | |
| /**************************************************************************** | |
| * WCMD_copy | |
| * | |
| * Copy a file or wildcarded set. | |
| * FIXME: Add support for a+b+c type syntax | |
| */ | |
| void WCMD_copy (void) { | |
| WIN32_FIND_DATAW fd; | |
| HANDLE hff; | |
| BOOL force, status; | |
| WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4]; | |
| DWORD len; | |
| static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'}; | |
| BOOL copyToDir = FALSE; | |
| BOOL copyFromDir = FALSE; | |
| WCHAR srcspec[MAX_PATH]; | |
| DWORD attribs; | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| if (param1[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| /* Convert source into full spec */ | |
| WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1)); | |
| GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL); | |
| if (srcpath[strlenW(srcpath) - 1] == '\\') | |
| srcpath[strlenW(srcpath) - 1] = '\0'; | |
| if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) { | |
| attribs = GetFileAttributesW(srcpath); | |
| } else { | |
| attribs = 0; | |
| } | |
| strcpyW(srcspec, srcpath); | |
| /* If a directory, then add \* on the end when searching */ | |
| if (attribs & FILE_ATTRIBUTE_DIRECTORY) { | |
| strcatW(srcpath, slashW); | |
| copyFromDir = TRUE; | |
| strcatW(srcspec, slashW); | |
| strcatW(srcspec, starW); | |
| } else { | |
| WCMD_splitpath(srcpath, drive, dir, fname, ext); | |
| strcpyW(srcpath, drive); | |
| strcatW(srcpath, dir); | |
| } | |
| WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath)); | |
| /* If no destination supplied, assume current directory */ | |
| WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2)); | |
| if (param2[0] == 0x00) { | |
| strcpyW(param2, dotW); | |
| } | |
| GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL); | |
| if (outpath[strlenW(outpath) - 1] == '\\') | |
| outpath[strlenW(outpath) - 1] = '\0'; | |
| attribs = GetFileAttributesW(outpath); | |
| if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) { | |
| strcatW (outpath, slashW); | |
| copyToDir = TRUE; | |
| } | |
| WINE_TRACE("Copy destination (calculated): '%s'(%d)\n", | |
| wine_dbgstr_w(outpath), copyToDir); | |
| /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */ | |
| if (strstrW (quals, parmNoY)) | |
| force = FALSE; | |
| else if (strstrW (quals, parmY)) | |
| force = TRUE; | |
| else { | |
| /* By default, we will force the overwrite in batch mode and ask for | |
| * confirmation in interactive mode. */ | |
| force = !!context; | |
| /* If COPYCMD is set, then we force the overwrite with /Y and ask for | |
| * confirmation with /-Y. If COPYCMD is neither of those, then we use the | |
| * default behavior. */ | |
| len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR)); | |
| if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) { | |
| if (!lstrcmpiW (copycmd, parmY)) | |
| force = TRUE; | |
| else if (!lstrcmpiW (copycmd, parmNoY)) | |
| force = FALSE; | |
| } | |
| } | |
| /* Loop through all source files */ | |
| WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec)); | |
| hff = FindFirstFileW(srcspec, &fd); | |
| if (hff != INVALID_HANDLE_VALUE) { | |
| do { | |
| WCHAR outname[MAX_PATH]; | |
| WCHAR srcname[MAX_PATH]; | |
| BOOL overwrite = force; | |
| /* Destination is either supplied filename, or source name in | |
| supplied destination directory */ | |
| strcpyW(outname, outpath); | |
| if (copyToDir) strcatW(outname, fd.cFileName); | |
| strcpyW(srcname, srcpath); | |
| strcatW(srcname, fd.cFileName); | |
| WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname)); | |
| WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname)); | |
| /* Skip . and .., and directories */ | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |
| overwrite = FALSE; | |
| WINE_TRACE("Skipping directories\n"); | |
| } | |
| /* Prompt before overwriting */ | |
| else if (!overwrite) { | |
| attribs = GetFileAttributesW(outname); | |
| if (attribs != INVALID_FILE_ATTRIBUTES) { | |
| WCHAR buffer[MAXSTRING]; | |
| wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname); | |
| overwrite = WCMD_ask_confirm(buffer, FALSE, NULL); | |
| } | |
| else overwrite = TRUE; | |
| } | |
| /* Do the copy as appropriate */ | |
| if (overwrite) { | |
| status = CopyFileW(srcname, outname, FALSE); | |
| if (!status) WCMD_print_error (); | |
| } | |
| } while (FindNextFileW(hff, &fd) != 0); | |
| FindClose (hff); | |
| } else { | |
| status = ERROR_FILE_NOT_FOUND; | |
| WCMD_print_error (); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_create_dir | |
| * | |
| * Create a directory. | |
| * | |
| * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if | |
| * they do not already exist. | |
| */ | |
| static BOOL create_full_path(WCHAR* path) | |
| { | |
| int len; | |
| WCHAR *new_path; | |
| BOOL ret = TRUE; | |
| new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR)); | |
| strcpyW(new_path,path); | |
| while ((len = strlenW(new_path)) && new_path[len - 1] == '\\') | |
| new_path[len - 1] = 0; | |
| while (!CreateDirectoryW(new_path,NULL)) | |
| { | |
| WCHAR *slash; | |
| DWORD last_error = GetLastError(); | |
| if (last_error == ERROR_ALREADY_EXISTS) | |
| break; | |
| if (last_error != ERROR_PATH_NOT_FOUND) | |
| { | |
| ret = FALSE; | |
| break; | |
| } | |
| if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/'))) | |
| { | |
| ret = FALSE; | |
| break; | |
| } | |
| len = slash - new_path; | |
| new_path[len] = 0; | |
| if (!create_full_path(new_path)) | |
| { | |
| ret = FALSE; | |
| break; | |
| } | |
| new_path[len] = '\\'; | |
| } | |
| HeapFree(GetProcessHeap(),0,new_path); | |
| return ret; | |
| } | |
| void WCMD_create_dir (void) { | |
| if (param1[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| if (!create_full_path(param1)) WCMD_print_error (); | |
| } | |
| /**************************************************************************** | |
| * WCMD_delete | |
| * | |
| * Delete a file or wildcarded set. | |
| * | |
| * Note on /A: | |
| * - Testing shows /A is repeatable, eg. /a-r /ar matches all files | |
| * - Each set is a pattern, eg /ahr /as-r means | |
| * readonly+hidden OR nonreadonly system files | |
| * - The '-' applies to a single field, ie /a:-hr means read only | |
| * non-hidden files | |
| */ | |
| BOOL WCMD_delete (WCHAR *command, BOOL expectDir) { | |
| int argno = 0; | |
| int argsProcessed = 0; | |
| WCHAR *argN = command; | |
| BOOL foundAny = FALSE; | |
| static const WCHAR parmA[] = {'/','A','\0'}; | |
| static const WCHAR parmQ[] = {'/','Q','\0'}; | |
| static const WCHAR parmP[] = {'/','P','\0'}; | |
| static const WCHAR parmS[] = {'/','S','\0'}; | |
| static const WCHAR parmF[] = {'/','F','\0'}; | |
| /* If not recursing, clear error flag */ | |
| if (expectDir) errorlevel = 0; | |
| /* Loop through all args */ | |
| while (argN) { | |
| WCHAR *thisArg = WCMD_parameter (command, argno++, &argN); | |
| WCHAR argCopy[MAX_PATH]; | |
| if (argN && argN[0] != '/') { | |
| WIN32_FIND_DATAW fd; | |
| HANDLE hff; | |
| WCHAR fpath[MAX_PATH]; | |
| WCHAR *p; | |
| BOOL handleParm = TRUE; | |
| BOOL found = FALSE; | |
| static const WCHAR anyExt[]= {'.','*','\0'}; | |
| strcpyW(argCopy, thisArg); | |
| WINE_TRACE("del: Processing arg %s (quals:%s)\n", | |
| wine_dbgstr_w(argCopy), wine_dbgstr_w(quals)); | |
| argsProcessed++; | |
| /* If filename part of parameter is * or *.*, prompt unless | |
| /Q supplied. */ | |
| if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) { | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| /* Convert path into actual directory spec */ | |
| GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL); | |
| WCMD_splitpath(fpath, drive, dir, fname, ext); | |
| /* Only prompt for * and *.*, not *a, a*, *.a* etc */ | |
| if ((strcmpW(fname, starW) == 0) && | |
| (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) { | |
| BOOL ok; | |
| WCHAR question[MAXSTRING]; | |
| static const WCHAR fmt[] = {'%','s',' ','\0'}; | |
| /* Note: Flag as found, to avoid file not found message */ | |
| found = TRUE; | |
| /* Ask for confirmation */ | |
| wsprintfW(question, fmt, fpath); | |
| ok = WCMD_ask_confirm(question, TRUE, NULL); | |
| /* Abort if answer is 'N' */ | |
| if (!ok) continue; | |
| } | |
| } | |
| /* First, try to delete in the current directory */ | |
| hff = FindFirstFileW(argCopy, &fd); | |
| if (hff == INVALID_HANDLE_VALUE) { | |
| handleParm = FALSE; | |
| } else { | |
| found = TRUE; | |
| } | |
| /* Support del <dirname> by just deleting all files dirname\* */ | |
| if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL) | |
| && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { | |
| WCHAR modifiedParm[MAX_PATH]; | |
| static const WCHAR slashStar[] = {'\\','*','\0'}; | |
| strcpyW(modifiedParm, argCopy); | |
| strcatW(modifiedParm, slashStar); | |
| FindClose(hff); | |
| found = TRUE; | |
| WCMD_delete(modifiedParm, FALSE); | |
| } else if (handleParm) { | |
| /* Build the filename to delete as <supplied directory>\<findfirst filename> */ | |
| strcpyW (fpath, argCopy); | |
| do { | |
| p = strrchrW (fpath, '\\'); | |
| if (p != NULL) { | |
| *++p = '\0'; | |
| strcatW (fpath, fd.cFileName); | |
| } | |
| else strcpyW (fpath, fd.cFileName); | |
| if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { | |
| BOOL ok = TRUE; | |
| WCHAR *nextA = strstrW (quals, parmA); | |
| /* Handle attribute matching (/A) */ | |
| if (nextA != NULL) { | |
| ok = FALSE; | |
| while (nextA != NULL && !ok) { | |
| WCHAR *thisA = (nextA+2); | |
| BOOL stillOK = TRUE; | |
| /* Skip optional : */ | |
| if (*thisA == ':') thisA++; | |
| /* Parse each of the /A[:]xxx in turn */ | |
| while (*thisA && *thisA != '/') { | |
| BOOL negate = FALSE; | |
| BOOL attribute = FALSE; | |
| /* Match negation of attribute first */ | |
| if (*thisA == '-') { | |
| negate=TRUE; | |
| thisA++; | |
| } | |
| /* Match attribute */ | |
| switch (*thisA) { | |
| case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY); | |
| break; | |
| case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN); | |
| break; | |
| case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM); | |
| break; | |
| case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE); | |
| break; | |
| default: | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| } | |
| /* Now check result, keeping a running boolean about whether it | |
| matches all parsed attributes so far */ | |
| if (attribute && !negate) { | |
| stillOK = stillOK; | |
| } else if (!attribute && negate) { | |
| stillOK = stillOK; | |
| } else { | |
| stillOK = FALSE; | |
| } | |
| thisA++; | |
| } | |
| /* Save the running total as the final result */ | |
| ok = stillOK; | |
| /* Step on to next /A set */ | |
| nextA = strstrW (nextA+1, parmA); | |
| } | |
| } | |
| /* /P means prompt for each file */ | |
| if (ok && strstrW (quals, parmP) != NULL) { | |
| WCHAR question[MAXSTRING]; | |
| /* Ask for confirmation */ | |
| wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath); | |
| ok = WCMD_ask_confirm(question, FALSE, NULL); | |
| } | |
| /* Only proceed if ok to */ | |
| if (ok) { | |
| /* If file is read only, and /F supplied, delete it */ | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY && | |
| strstrW (quals, parmF) != NULL) { | |
| SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY); | |
| } | |
| /* Now do the delete */ | |
| if (!DeleteFileW(fpath)) WCMD_print_error (); | |
| } | |
| } | |
| } while (FindNextFileW(hff, &fd) != 0); | |
| FindClose (hff); | |
| } | |
| /* Now recurse into all subdirectories handling the parameter in the same way */ | |
| if (strstrW (quals, parmS) != NULL) { | |
| WCHAR thisDir[MAX_PATH]; | |
| int cPos; | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| /* Convert path into actual directory spec */ | |
| GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL); | |
| WCMD_splitpath(thisDir, drive, dir, fname, ext); | |
| strcpyW(thisDir, drive); | |
| strcatW(thisDir, dir); | |
| cPos = strlenW(thisDir); | |
| WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir)); | |
| /* Append '*' to the directory */ | |
| thisDir[cPos] = '*'; | |
| thisDir[cPos+1] = 0x00; | |
| hff = FindFirstFileW(thisDir, &fd); | |
| /* Remove residual '*' */ | |
| thisDir[cPos] = 0x00; | |
| if (hff != INVALID_HANDLE_VALUE) { | |
| DIRECTORY_STACK *allDirs = NULL; | |
| DIRECTORY_STACK *lastEntry = NULL; | |
| do { | |
| if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && | |
| (strcmpW(fd.cFileName, dotdotW) != 0) && | |
| (strcmpW(fd.cFileName, dotW) != 0)) { | |
| DIRECTORY_STACK *nextDir; | |
| WCHAR subParm[MAX_PATH]; | |
| /* Work out search parameter in sub dir */ | |
| strcpyW (subParm, thisDir); | |
| strcatW (subParm, fd.cFileName); | |
| strcatW (subParm, slashW); | |
| strcatW (subParm, fname); | |
| strcatW (subParm, ext); | |
| WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm)); | |
| /* Allocate memory, add to list */ | |
| nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK)); | |
| if (allDirs == NULL) allDirs = nextDir; | |
| if (lastEntry != NULL) lastEntry->next = nextDir; | |
| lastEntry = nextDir; | |
| nextDir->next = NULL; | |
| nextDir->dirName = HeapAlloc(GetProcessHeap(),0, | |
| (strlenW(subParm)+1) * sizeof(WCHAR)); | |
| strcpyW(nextDir->dirName, subParm); | |
| } | |
| } while (FindNextFileW(hff, &fd) != 0); | |
| FindClose (hff); | |
| /* Go through each subdir doing the delete */ | |
| while (allDirs != NULL) { | |
| DIRECTORY_STACK *tempDir; | |
| tempDir = allDirs->next; | |
| found |= WCMD_delete (allDirs->dirName, FALSE); | |
| HeapFree(GetProcessHeap(),0,allDirs->dirName); | |
| HeapFree(GetProcessHeap(),0,allDirs); | |
| allDirs = tempDir; | |
| } | |
| } | |
| } | |
| /* Keep running total to see if any found, and if not recursing | |
| issue error message */ | |
| if (expectDir) { | |
| if (!found) { | |
| errorlevel = 1; | |
| WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy); | |
| } | |
| } | |
| foundAny |= found; | |
| } | |
| } | |
| /* Handle no valid args */ | |
| if (argsProcessed == 0) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| } | |
| return foundAny; | |
| } | |
| /**************************************************************************** | |
| * WCMD_echo | |
| * | |
| * Echo input to the screen (or not). We don't try to emulate the bugs | |
| * in DOS (try typing "ECHO ON AGAIN" for an example). | |
| */ | |
| void WCMD_echo (const WCHAR *command) { | |
| int count; | |
| if ((command[0] == '.') && (command[1] == 0)) { | |
| WCMD_output (newline); | |
| return; | |
| } | |
| if (command[0]==' ') | |
| command++; | |
| count = strlenW(command); | |
| if (count == 0) { | |
| if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW); | |
| else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW); | |
| return; | |
| } | |
| if (lstrcmpiW(command, onW) == 0) { | |
| echo_mode = 1; | |
| return; | |
| } | |
| if (lstrcmpiW(command, offW) == 0) { | |
| echo_mode = 0; | |
| return; | |
| } | |
| WCMD_output_asis (command); | |
| WCMD_output (newline); | |
| } | |
| /************************************************************************** | |
| * WCMD_for | |
| * | |
| * Batch file loop processing. | |
| * | |
| * On entry: cmdList contains the syntax up to the set | |
| * next cmdList and all in that bracket contain the set data | |
| * next cmdlist contains the DO cmd | |
| * following that is either brackets or && entries (as per if) | |
| * | |
| */ | |
| void WCMD_for (WCHAR *p, CMD_LIST **cmdList) { | |
| WIN32_FIND_DATAW fd; | |
| HANDLE hff; | |
| int i; | |
| const WCHAR inW[] = {'i', 'n', ' ', '\0'}; | |
| const WCHAR doW[] = {'d', 'o', ' ', '\0'}; | |
| CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd; | |
| WCHAR variable[4]; | |
| WCHAR *firstCmd; | |
| int thisDepth; | |
| WCHAR *curPos = p; | |
| BOOL expandDirs = FALSE; | |
| BOOL useNumbers = FALSE; | |
| BOOL doRecursive = FALSE; | |
| BOOL doFileset = FALSE; | |
| LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */ | |
| int itemNum; | |
| CMD_LIST *thisCmdStart; | |
| /* Handle optional qualifiers (multiple are allowed) */ | |
| while (*curPos && *curPos == '/') { | |
| WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos)); | |
| curPos++; | |
| switch (toupperW(*curPos)) { | |
| case 'D': curPos++; expandDirs = TRUE; break; | |
| case 'L': curPos++; useNumbers = TRUE; break; | |
| /* Recursive is special case - /R can have an optional path following it */ | |
| /* filenamesets are another special case - /F can have an optional options following it */ | |
| case 'R': | |
| case 'F': | |
| { | |
| BOOL isRecursive = (*curPos == 'R'); | |
| if (isRecursive) doRecursive = TRUE; | |
| else doFileset = TRUE; | |
| /* Skip whitespace */ | |
| curPos++; | |
| while (*curPos && *curPos==' ') curPos++; | |
| /* Next parm is either qualifier, path/options or variable - | |
| only care about it if it is the path/options */ | |
| if (*curPos && *curPos != '/' && *curPos != '%') { | |
| if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n"); | |
| else WINE_FIXME("/F needs to handle options\n"); | |
| } | |
| break; | |
| } | |
| default: | |
| WINE_FIXME("for qualifier '%c' unhandled\n", *curPos); | |
| curPos++; | |
| } | |
| /* Skip whitespace between qualifiers */ | |
| while (*curPos && *curPos==' ') curPos++; | |
| } | |
| /* Skip whitespace before variable */ | |
| while (*curPos && *curPos==' ') curPos++; | |
| /* Ensure line continues with variable */ | |
| if (!*curPos || *curPos != '%') { | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| return; | |
| } | |
| /* Variable should follow */ | |
| i = 0; | |
| while (curPos[i] && curPos[i]!=' ') i++; | |
| memcpy(&variable[0], curPos, i*sizeof(WCHAR)); | |
| variable[i] = 0x00; | |
| WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable)); | |
| curPos = &curPos[i]; | |
| /* Skip whitespace before IN */ | |
| while (*curPos && *curPos==' ') curPos++; | |
| /* Ensure line continues with IN */ | |
| if (!*curPos || lstrcmpiW (curPos, inW)) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| return; | |
| } | |
| /* Save away where the set of data starts and the variable */ | |
| thisDepth = (*cmdList)->bracketDepth; | |
| *cmdList = (*cmdList)->nextcommand; | |
| setStart = (*cmdList); | |
| /* Skip until the close bracket */ | |
| WINE_TRACE("Searching %p as the set\n", *cmdList); | |
| while (*cmdList && | |
| (*cmdList)->command != NULL && | |
| (*cmdList)->bracketDepth > thisDepth) { | |
| WINE_TRACE("Skipping %p which is part of the set\n", *cmdList); | |
| *cmdList = (*cmdList)->nextcommand; | |
| } | |
| /* Skip the close bracket, if there is one */ | |
| if (*cmdList) *cmdList = (*cmdList)->nextcommand; | |
| /* Syntax error if missing close bracket, or nothing following it | |
| and once we have the complete set, we expect a DO */ | |
| WINE_TRACE("Looking for 'do' in %p\n", *cmdList); | |
| if ((*cmdList == NULL) || | |
| (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, | |
| (*cmdList)->command, 3, doW, -1) != 2)) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| return; | |
| } | |
| /* Save away the starting position for the commands (and offset for the | |
| first one */ | |
| cmdStart = *cmdList; | |
| cmdEnd = *cmdList; | |
| firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */ | |
| itemNum = 0; | |
| thisSet = setStart; | |
| /* Loop through all set entries */ | |
| while (thisSet && | |
| thisSet->command != NULL && | |
| thisSet->bracketDepth >= thisDepth) { | |
| /* Loop through all entries on the same line */ | |
| WCHAR *item; | |
| WCHAR *itemStart; | |
| WINE_TRACE("Processing for set %p\n", thisSet); | |
| i = 0; | |
| while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) { | |
| /* | |
| * If the parameter within the set has a wildcard then search for matching files | |
| * otherwise do a literal substitution. | |
| */ | |
| static const WCHAR wildcards[] = {'*','?','\0'}; | |
| thisCmdStart = cmdStart; | |
| itemNum++; | |
| WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item)); | |
| if (!useNumbers && !doFileset) { | |
| if (strpbrkW (item, wildcards)) { | |
| hff = FindFirstFileW(item, &fd); | |
| if (hff != INVALID_HANDLE_VALUE) { | |
| do { | |
| BOOL isDirectory = FALSE; | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE; | |
| /* Handle as files or dirs appropriately, but ignore . and .. */ | |
| if (isDirectory == expandDirs && | |
| (strcmpW(fd.cFileName, dotdotW) != 0) && | |
| (strcmpW(fd.cFileName, dotW) != 0)) | |
| { | |
| thisCmdStart = cmdStart; | |
| WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName)); | |
| WCMD_part_execute (&thisCmdStart, firstCmd, variable, | |
| fd.cFileName, FALSE, TRUE); | |
| } | |
| } while (FindNextFileW(hff, &fd) != 0); | |
| FindClose (hff); | |
| } | |
| } else { | |
| WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE); | |
| } | |
| } else if (useNumbers) { | |
| /* Convert the first 3 numbers to signed longs and save */ | |
| if (itemNum <=3) numbers[itemNum-1] = atolW(item); | |
| /* else ignore them! */ | |
| /* Filesets - either a list of files, or a command to run and parse the output */ | |
| } else if (doFileset && *itemStart != '"') { | |
| HANDLE input; | |
| WCHAR temp_file[MAX_PATH]; | |
| WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum, | |
| wine_dbgstr_w(item)); | |
| /* If backquote or single quote, we need to launch that command | |
| and parse the results - use a temporary file */ | |
| if (*itemStart == '`' || *itemStart == '\'') { | |
| WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING]; | |
| static const WCHAR redirOut[] = {'>','%','s','\0'}; | |
| static const WCHAR cmdW[] = {'C','M','D','\0'}; | |
| /* Remove trailing character */ | |
| itemStart[strlenW(itemStart)-1] = 0x00; | |
| /* Get temp filename */ | |
| GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path); | |
| GetTempFileNameW(temp_path, cmdW, 0, temp_file); | |
| /* Execute program and redirect output */ | |
| wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file); | |
| WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL); | |
| /* Open the file, read line by line and process */ | |
| input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ, | |
| NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
| } else { | |
| /* Open the file, read line by line and process */ | |
| input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ, | |
| NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
| } | |
| /* Process the input file */ | |
| if (input == INVALID_HANDLE_VALUE) { | |
| WCMD_print_error (); | |
| WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item); | |
| errorlevel = 1; | |
| return; /* FOR loop aborts at first failure here */ | |
| } else { | |
| WCHAR buffer[MAXSTRING] = {'\0'}; | |
| WCHAR *where, *parm; | |
| while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) { | |
| /* Skip blank lines*/ | |
| parm = WCMD_parameter (buffer, 0, &where); | |
| WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm), | |
| wine_dbgstr_w(buffer)); | |
| if (where) { | |
| /* FIXME: The following should be moved into its own routine and | |
| reused for the string literal parsing below */ | |
| thisCmdStart = cmdStart; | |
| WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE); | |
| cmdEnd = thisCmdStart; | |
| } | |
| buffer[0] = 0x00; | |
| } | |
| CloseHandle (input); | |
| } | |
| /* Delete the temporary file */ | |
| if (*itemStart == '`' || *itemStart == '\'') { | |
| DeleteFileW(temp_file); | |
| } | |
| /* Filesets - A string literal */ | |
| } else if (doFileset && *itemStart == '"') { | |
| WCHAR buffer[MAXSTRING] = {'\0'}; | |
| WCHAR *where, *parm; | |
| /* Skip blank lines, and re-extract parameter now string has quotes removed */ | |
| strcpyW(buffer, item); | |
| parm = WCMD_parameter (buffer, 0, &where); | |
| WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm), | |
| wine_dbgstr_w(buffer)); | |
| if (where) { | |
| /* FIXME: The following should be moved into its own routine and | |
| reused for the string literal parsing below */ | |
| thisCmdStart = cmdStart; | |
| WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE); | |
| cmdEnd = thisCmdStart; | |
| } | |
| } | |
| WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd); | |
| cmdEnd = thisCmdStart; | |
| i++; | |
| } | |
| /* Move onto the next set line */ | |
| thisSet = thisSet->nextcommand; | |
| } | |
| /* If /L is provided, now run the for loop */ | |
| if (useNumbers) { | |
| WCHAR thisNum[20]; | |
| static const WCHAR fmt[] = {'%','d','\0'}; | |
| WINE_TRACE("FOR /L provided range from %d to %d step %d\n", | |
| numbers[0], numbers[2], numbers[1]); | |
| for (i=numbers[0]; | |
| (numbers[1]<0)? i>numbers[2] : i<numbers[2]; | |
| i=i + numbers[1]) { | |
| sprintfW(thisNum, fmt, i); | |
| WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum)); | |
| thisCmdStart = cmdStart; | |
| WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE); | |
| cmdEnd = thisCmdStart; | |
| } | |
| } | |
| /* When the loop ends, either something like a GOTO or EXIT /b has terminated | |
| all processing, OR it should be pointing to the end of && processing OR | |
| it should be pointing at the NULL end of bracket for the DO. The return | |
| value needs to be the NEXT command to execute, which it either is, or | |
| we need to step over the closing bracket */ | |
| *cmdList = cmdEnd; | |
| if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand; | |
| } | |
| /***************************************************************************** | |
| * WCMD_part_execute | |
| * | |
| * Execute a command, and any && or bracketed follow on to the command. The | |
| * first command to be executed may not be at the front of the | |
| * commands->thiscommand string (eg. it may point after a DO or ELSE) | |
| */ | |
| void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable, | |
| WCHAR *value, BOOL isIF, BOOL conditionTRUE) { | |
| CMD_LIST *curPosition = *cmdList; | |
| int myDepth = (*cmdList)->bracketDepth; | |
| WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n", | |
| cmdList, wine_dbgstr_w(firstcmd), | |
| wine_dbgstr_w(variable), wine_dbgstr_w(value), | |
| conditionTRUE); | |
| /* Skip leading whitespace between condition and the command */ | |
| while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++; | |
| /* Process the first command, if there is one */ | |
| if (conditionTRUE && firstcmd && *firstcmd) { | |
| WCHAR *command = WCMD_strdupW(firstcmd); | |
| WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList); | |
| HeapFree(GetProcessHeap(), 0, command); | |
| } | |
| /* If it didn't move the position, step to next command */ | |
| if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; | |
| /* Process any other parts of the command */ | |
| if (*cmdList) { | |
| BOOL processThese = TRUE; | |
| if (isIF) processThese = conditionTRUE; | |
| while (*cmdList) { | |
| const WCHAR ifElse[] = {'e','l','s','e',' ','\0'}; | |
| /* execute all appropriate commands */ | |
| curPosition = *cmdList; | |
| WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n", | |
| *cmdList, | |
| (*cmdList)->prevDelim, | |
| (*cmdList)->bracketDepth, myDepth); | |
| /* Execute any statements appended to the line */ | |
| /* FIXME: Only if previous call worked for && or failed for || */ | |
| if ((*cmdList)->prevDelim == CMD_ONFAILURE || | |
| (*cmdList)->prevDelim != CMD_ONSUCCESS) { | |
| if (processThese && (*cmdList)->command) { | |
| WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable, | |
| value, cmdList); | |
| } | |
| if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; | |
| /* Execute any appended to the statement with (...) */ | |
| } else if ((*cmdList)->bracketDepth > myDepth) { | |
| if (processThese) { | |
| *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value); | |
| WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList); | |
| } | |
| if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; | |
| /* End of the command - does 'ELSE ' follow as the next command? */ | |
| } else { | |
| if (isIF && CompareStringW(LOCALE_USER_DEFAULT, | |
| NORM_IGNORECASE | SORT_STRINGSORT, | |
| (*cmdList)->command, 5, ifElse, -1) == 2) { | |
| /* Swap between if and else processing */ | |
| processThese = !processThese; | |
| /* Process the ELSE part */ | |
| if (processThese) { | |
| WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse); | |
| /* Skip leading whitespace between condition and the command */ | |
| while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++; | |
| if (*cmd) { | |
| WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList); | |
| } | |
| } | |
| if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand; | |
| } else { | |
| WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| /************************************************************************** | |
| * WCMD_give_help | |
| * | |
| * Simple on-line help. Help text is stored in the resource file. | |
| */ | |
| void WCMD_give_help (WCHAR *command) { | |
| int i; | |
| command = WCMD_strtrim_leading_spaces(command); | |
| if (strlenW(command) == 0) { | |
| WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP)); | |
| } | |
| else { | |
| for (i=0; i<=WCMD_EXIT; i++) { | |
| if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, | |
| param1, -1, inbuilt[i], -1) == 2) { | |
| WCMD_output_asis (WCMD_LoadMessage(i)); | |
| return; | |
| } | |
| } | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1); | |
| } | |
| return; | |
| } | |
| /**************************************************************************** | |
| * WCMD_go_to | |
| * | |
| * Batch file jump instruction. Not the most efficient algorithm ;-) | |
| * Prints error message if the specified label cannot be found - the file pointer is | |
| * then at EOF, effectively stopping the batch file. | |
| * FIXME: DOS is supposed to allow labels with spaces - we don't. | |
| */ | |
| void WCMD_goto (CMD_LIST **cmdList) { | |
| WCHAR string[MAX_PATH]; | |
| /* Do not process any more parts of a processed multipart or multilines command */ | |
| if (cmdList) *cmdList = NULL; | |
| if (param1[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| if (context != NULL) { | |
| WCHAR *paramStart = param1; | |
| static const WCHAR eofW[] = {':','e','o','f','\0'}; | |
| /* Handle special :EOF label */ | |
| if (lstrcmpiW (eofW, param1) == 0) { | |
| context -> skip_rest = TRUE; | |
| return; | |
| } | |
| /* Support goto :label as well as goto label */ | |
| if (*paramStart == ':') paramStart++; | |
| SetFilePointer (context -> h, 0, NULL, FILE_BEGIN); | |
| while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) { | |
| if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return; | |
| } | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET)); | |
| } | |
| return; | |
| } | |
| /***************************************************************************** | |
| * WCMD_pushd | |
| * | |
| * Push a directory onto the stack | |
| */ | |
| void WCMD_pushd (WCHAR *command) { | |
| struct env_stack *curdir; | |
| WCHAR *thisdir; | |
| static const WCHAR parmD[] = {'/','D','\0'}; | |
| if (strchrW(command, '/') != NULL) { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| WCMD_print_error(); | |
| return; | |
| } | |
| curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack)); | |
| thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR)); | |
| if( !curdir || !thisdir ) { | |
| LocalFree(curdir); | |
| LocalFree(thisdir); | |
| WINE_ERR ("out of memory\n"); | |
| return; | |
| } | |
| /* Change directory using CD code with /D parameter */ | |
| strcpyW(quals, parmD); | |
| GetCurrentDirectoryW (1024, thisdir); | |
| errorlevel = 0; | |
| WCMD_setshow_default(command); | |
| if (errorlevel) { | |
| LocalFree(curdir); | |
| LocalFree(thisdir); | |
| return; | |
| } else { | |
| curdir -> next = pushd_directories; | |
| curdir -> strings = thisdir; | |
| if (pushd_directories == NULL) { | |
| curdir -> u.stackdepth = 1; | |
| } else { | |
| curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1; | |
| } | |
| pushd_directories = curdir; | |
| } | |
| } | |
| /***************************************************************************** | |
| * WCMD_popd | |
| * | |
| * Pop a directory from the stack | |
| */ | |
| void WCMD_popd (void) { | |
| struct env_stack *temp = pushd_directories; | |
| if (!pushd_directories) | |
| return; | |
| /* pop the old environment from the stack, and make it the current dir */ | |
| pushd_directories = temp->next; | |
| SetCurrentDirectoryW(temp->strings); | |
| LocalFree (temp->strings); | |
| LocalFree (temp); | |
| } | |
| /**************************************************************************** | |
| * WCMD_if | |
| * | |
| * Batch file conditional. | |
| * | |
| * On entry, cmdlist will point to command containing the IF, and optionally | |
| * the first command to execute (if brackets not found) | |
| * If &&'s were found, this may be followed by a record flagged as isAmpersand | |
| * If ('s were found, execute all within that bracket | |
| * Command may optionally be followed by an ELSE - need to skip instructions | |
| * in the else using the same logic | |
| * | |
| * FIXME: Much more syntax checking needed! | |
| */ | |
| void WCMD_if (WCHAR *p, CMD_LIST **cmdList) { | |
| int negate = 0, test = 0; | |
| WCHAR condition[MAX_PATH], *command, *s; | |
| static const WCHAR notW[] = {'n','o','t','\0'}; | |
| static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'}; | |
| static const WCHAR existW[] = {'e','x','i','s','t','\0'}; | |
| static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'}; | |
| static const WCHAR eqeqW[] = {'=','=','\0'}; | |
| static const WCHAR parmI[] = {'/','I','\0'}; | |
| if (!lstrcmpiW (param1, notW)) { | |
| negate = 1; | |
| strcpyW (condition, param2); | |
| } | |
| else { | |
| strcpyW (condition, param1); | |
| } | |
| WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition)); | |
| if (!lstrcmpiW (condition, errlvlW)) { | |
| if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1; | |
| WCMD_parameter (p, 2+negate, &command); | |
| } | |
| else if (!lstrcmpiW (condition, existW)) { | |
| if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) { | |
| test = 1; | |
| } | |
| WCMD_parameter (p, 2+negate, &command); | |
| } | |
| else if (!lstrcmpiW (condition, defdW)) { | |
| if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) { | |
| test = 1; | |
| } | |
| WCMD_parameter (p, 2+negate, &command); | |
| } | |
| else if ((s = strstrW (p, eqeqW))) { | |
| s += 2; | |
| if (strstrW (quals, parmI) == NULL) { | |
| if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1; | |
| } | |
| else { | |
| if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1; | |
| } | |
| WCMD_parameter (s, 1, &command); | |
| } | |
| else { | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| return; | |
| } | |
| /* Process rest of IF statement which is on the same line | |
| Note: This may process all or some of the cmdList (eg a GOTO) */ | |
| WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate)); | |
| } | |
| /**************************************************************************** | |
| * WCMD_move | |
| * | |
| * Move a file, directory tree or wildcarded set of files. | |
| */ | |
| void WCMD_move (void) { | |
| int status; | |
| WIN32_FIND_DATAW fd; | |
| HANDLE hff; | |
| WCHAR input[MAX_PATH]; | |
| WCHAR output[MAX_PATH]; | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| if (param1[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| /* If no destination supplied, assume current directory */ | |
| if (param2[0] == 0x00) { | |
| strcpyW(param2, dotW); | |
| } | |
| /* If 2nd parm is directory, then use original filename */ | |
| /* Convert partial path to full path */ | |
| GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL); | |
| GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL); | |
| WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input), | |
| wine_dbgstr_w(param1), wine_dbgstr_w(output)); | |
| /* Split into components */ | |
| WCMD_splitpath(input, drive, dir, fname, ext); | |
| hff = FindFirstFileW(input, &fd); | |
| while (hff != INVALID_HANDLE_VALUE) { | |
| WCHAR dest[MAX_PATH]; | |
| WCHAR src[MAX_PATH]; | |
| DWORD attribs; | |
| WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName)); | |
| /* Build src & dest name */ | |
| strcpyW(src, drive); | |
| strcatW(src, dir); | |
| /* See if dest is an existing directory */ | |
| attribs = GetFileAttributesW(output); | |
| if (attribs != INVALID_FILE_ATTRIBUTES && | |
| (attribs & FILE_ATTRIBUTE_DIRECTORY)) { | |
| strcpyW(dest, output); | |
| strcatW(dest, slashW); | |
| strcatW(dest, fd.cFileName); | |
| } else { | |
| strcpyW(dest, output); | |
| } | |
| strcatW(src, fd.cFileName); | |
| WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src)); | |
| WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest)); | |
| /* Check if file is read only, otherwise move it */ | |
| attribs = GetFileAttributesW(src); | |
| if ((attribs != INVALID_FILE_ATTRIBUTES) && | |
| (attribs & FILE_ATTRIBUTE_READONLY)) { | |
| SetLastError(ERROR_ACCESS_DENIED); | |
| status = 0; | |
| } else { | |
| BOOL ok = TRUE; | |
| /* If destination exists, prompt unless /Y supplied */ | |
| if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) { | |
| BOOL force = FALSE; | |
| WCHAR copycmd[MAXSTRING]; | |
| int len; | |
| /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */ | |
| if (strstrW (quals, parmNoY)) | |
| force = FALSE; | |
| else if (strstrW (quals, parmY)) | |
| force = TRUE; | |
| else { | |
| const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'}; | |
| len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR)); | |
| force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) | |
| && ! lstrcmpiW (copycmd, parmY)); | |
| } | |
| /* Prompt if overwriting */ | |
| if (!force) { | |
| WCHAR question[MAXSTRING]; | |
| WCHAR yesChar[10]; | |
| strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES)); | |
| /* Ask for confirmation */ | |
| wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest); | |
| ok = WCMD_ask_confirm(question, FALSE, NULL); | |
| /* So delete the destination prior to the move */ | |
| if (ok) { | |
| if (!DeleteFileW(dest)) { | |
| WCMD_print_error (); | |
| errorlevel = 1; | |
| ok = FALSE; | |
| } | |
| } | |
| } | |
| } | |
| if (ok) { | |
| status = MoveFileW(src, dest); | |
| } else { | |
| status = 1; /* Anything other than 0 to prevent error msg below */ | |
| } | |
| } | |
| if (!status) { | |
| WCMD_print_error (); | |
| errorlevel = 1; | |
| } | |
| /* Step on to next match */ | |
| if (FindNextFileW(hff, &fd) == 0) { | |
| FindClose(hff); | |
| hff = INVALID_HANDLE_VALUE; | |
| break; | |
| } | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_pause | |
| * | |
| * Wait for keyboard input. | |
| */ | |
| void WCMD_pause (void) { | |
| DWORD count; | |
| WCHAR string[32]; | |
| WCMD_output (anykey); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, | |
| sizeof(string)/sizeof(WCHAR), &count, NULL); | |
| } | |
| /**************************************************************************** | |
| * WCMD_remove_dir | |
| * | |
| * Delete a directory. | |
| */ | |
| void WCMD_remove_dir (WCHAR *command) { | |
| int argno = 0; | |
| int argsProcessed = 0; | |
| WCHAR *argN = command; | |
| static const WCHAR parmS[] = {'/','S','\0'}; | |
| static const WCHAR parmQ[] = {'/','Q','\0'}; | |
| /* Loop through all args */ | |
| while (argN) { | |
| WCHAR *thisArg = WCMD_parameter (command, argno++, &argN); | |
| if (argN && argN[0] != '/') { | |
| WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg), | |
| wine_dbgstr_w(quals)); | |
| argsProcessed++; | |
| /* If subdirectory search not supplied, just try to remove | |
| and report error if it fails (eg if it contains a file) */ | |
| if (strstrW (quals, parmS) == NULL) { | |
| if (!RemoveDirectoryW(thisArg)) WCMD_print_error (); | |
| /* Otherwise use ShFileOp to recursively remove a directory */ | |
| } else { | |
| SHFILEOPSTRUCTW lpDir; | |
| /* Ask first */ | |
| if (strstrW (quals, parmQ) == NULL) { | |
| BOOL ok; | |
| WCHAR question[MAXSTRING]; | |
| static const WCHAR fmt[] = {'%','s',' ','\0'}; | |
| /* Ask for confirmation */ | |
| wsprintfW(question, fmt, thisArg); | |
| ok = WCMD_ask_confirm(question, TRUE, NULL); | |
| /* Abort if answer is 'N' */ | |
| if (!ok) return; | |
| } | |
| /* Do the delete */ | |
| lpDir.hwnd = NULL; | |
| lpDir.pTo = NULL; | |
| lpDir.pFrom = thisArg; | |
| lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; | |
| lpDir.wFunc = FO_DELETE; | |
| if (SHFileOperationW(&lpDir)) WCMD_print_error (); | |
| } | |
| } | |
| } | |
| /* Handle no valid args */ | |
| if (argsProcessed == 0) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_rename | |
| * | |
| * Rename a file. | |
| */ | |
| void WCMD_rename (void) { | |
| int status; | |
| HANDLE hff; | |
| WIN32_FIND_DATAW fd; | |
| WCHAR input[MAX_PATH]; | |
| WCHAR *dotDst = NULL; | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| DWORD attribs; | |
| errorlevel = 0; | |
| /* Must be at least two args */ | |
| if (param1[0] == 0x00 || param2[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| errorlevel = 1; | |
| return; | |
| } | |
| /* Destination cannot contain a drive letter or directory separator */ | |
| if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| WCMD_print_error(); | |
| errorlevel = 1; | |
| return; | |
| } | |
| /* Convert partial path to full path */ | |
| GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL); | |
| WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input), | |
| wine_dbgstr_w(param1), wine_dbgstr_w(param2)); | |
| dotDst = strchrW(param2, '.'); | |
| /* Split into components */ | |
| WCMD_splitpath(input, drive, dir, fname, ext); | |
| hff = FindFirstFileW(input, &fd); | |
| while (hff != INVALID_HANDLE_VALUE) { | |
| WCHAR dest[MAX_PATH]; | |
| WCHAR src[MAX_PATH]; | |
| WCHAR *dotSrc = NULL; | |
| int dirLen; | |
| WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName)); | |
| /* FIXME: If dest name or extension is *, replace with filename/ext | |
| part otherwise use supplied name. This supports: | |
| ren *.fred *.jim | |
| ren jim.* fred.* etc | |
| However, windows has a more complex algorithm supporting eg | |
| ?'s and *'s mid name */ | |
| dotSrc = strchrW(fd.cFileName, '.'); | |
| /* Build src & dest name */ | |
| strcpyW(src, drive); | |
| strcatW(src, dir); | |
| strcpyW(dest, src); | |
| dirLen = strlenW(src); | |
| strcatW(src, fd.cFileName); | |
| /* Build name */ | |
| if (param2[0] == '*') { | |
| strcatW(dest, fd.cFileName); | |
| if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00; | |
| } else { | |
| strcatW(dest, param2); | |
| if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00; | |
| } | |
| /* Build Extension */ | |
| if (dotDst && (*(dotDst+1)=='*')) { | |
| if (dotSrc) strcatW(dest, dotSrc); | |
| } else if (dotDst) { | |
| if (dotDst) strcatW(dest, dotDst); | |
| } | |
| WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src)); | |
| WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest)); | |
| /* Check if file is read only, otherwise move it */ | |
| attribs = GetFileAttributesW(src); | |
| if ((attribs != INVALID_FILE_ATTRIBUTES) && | |
| (attribs & FILE_ATTRIBUTE_READONLY)) { | |
| SetLastError(ERROR_ACCESS_DENIED); | |
| status = 0; | |
| } else { | |
| status = MoveFileW(src, dest); | |
| } | |
| if (!status) { | |
| WCMD_print_error (); | |
| errorlevel = 1; | |
| } | |
| /* Step on to next match */ | |
| if (FindNextFileW(hff, &fd) == 0) { | |
| FindClose(hff); | |
| hff = INVALID_HANDLE_VALUE; | |
| break; | |
| } | |
| } | |
| } | |
| /***************************************************************************** | |
| * WCMD_dupenv | |
| * | |
| * Make a copy of the environment. | |
| */ | |
| static WCHAR *WCMD_dupenv( const WCHAR *env ) | |
| { | |
| WCHAR *env_copy; | |
| int len; | |
| if( !env ) | |
| return NULL; | |
| len = 0; | |
| while ( env[len] ) | |
| len += (strlenW(&env[len]) + 1); | |
| env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) ); | |
| if (!env_copy) | |
| { | |
| WINE_ERR("out of memory\n"); | |
| return env_copy; | |
| } | |
| memcpy (env_copy, env, len*sizeof (WCHAR)); | |
| env_copy[len] = 0; | |
| return env_copy; | |
| } | |
| /***************************************************************************** | |
| * WCMD_setlocal | |
| * | |
| * setlocal pushes the environment onto a stack | |
| * Save the environment as unicode so we don't screw anything up. | |
| */ | |
| void WCMD_setlocal (const WCHAR *s) { | |
| WCHAR *env; | |
| struct env_stack *env_copy; | |
| WCHAR cwd[MAX_PATH]; | |
| /* DISABLEEXTENSIONS ignored */ | |
| env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack)); | |
| if( !env_copy ) | |
| { | |
| WINE_ERR ("out of memory\n"); | |
| return; | |
| } | |
| env = GetEnvironmentStringsW (); | |
| env_copy->strings = WCMD_dupenv (env); | |
| if (env_copy->strings) | |
| { | |
| env_copy->next = saved_environment; | |
| saved_environment = env_copy; | |
| /* Save the current drive letter */ | |
| GetCurrentDirectoryW(MAX_PATH, cwd); | |
| env_copy->u.cwd = cwd[0]; | |
| } | |
| else | |
| LocalFree (env_copy); | |
| FreeEnvironmentStringsW (env); | |
| } | |
| /***************************************************************************** | |
| * WCMD_endlocal | |
| * | |
| * endlocal pops the environment off a stack | |
| * Note: When searching for '=', search from WCHAR position 1, to handle | |
| * special internal environment variables =C:, =D: etc | |
| */ | |
| void WCMD_endlocal (void) { | |
| WCHAR *env, *old, *p; | |
| struct env_stack *temp; | |
| int len, n; | |
| if (!saved_environment) | |
| return; | |
| /* pop the old environment from the stack */ | |
| temp = saved_environment; | |
| saved_environment = temp->next; | |
| /* delete the current environment, totally */ | |
| env = GetEnvironmentStringsW (); | |
| old = WCMD_dupenv (GetEnvironmentStringsW ()); | |
| len = 0; | |
| while (old[len]) { | |
| n = strlenW(&old[len]) + 1; | |
| p = strchrW(&old[len] + 1, '='); | |
| if (p) | |
| { | |
| *p++ = 0; | |
| SetEnvironmentVariableW (&old[len], NULL); | |
| } | |
| len += n; | |
| } | |
| LocalFree (old); | |
| FreeEnvironmentStringsW (env); | |
| /* restore old environment */ | |
| env = temp->strings; | |
| len = 0; | |
| while (env[len]) { | |
| n = strlenW(&env[len]) + 1; | |
| p = strchrW(&env[len] + 1, '='); | |
| if (p) | |
| { | |
| *p++ = 0; | |
| SetEnvironmentVariableW (&env[len], p); | |
| } | |
| len += n; | |
| } | |
| /* Restore current drive letter */ | |
| if (IsCharAlphaW(temp->u.cwd)) { | |
| WCHAR envvar[4]; | |
| WCHAR cwd[MAX_PATH]; | |
| static const WCHAR fmt[] = {'=','%','c',':','\0'}; | |
| wsprintfW(envvar, fmt, temp->u.cwd); | |
| if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) { | |
| WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd)); | |
| SetCurrentDirectoryW(cwd); | |
| } | |
| } | |
| LocalFree (env); | |
| LocalFree (temp); | |
| } | |
| /***************************************************************************** | |
| * WCMD_setshow_attrib | |
| * | |
| * Display and optionally sets DOS attributes on a file or directory | |
| * | |
| */ | |
| void WCMD_setshow_attrib (void) { | |
| DWORD count; | |
| HANDLE hff; | |
| WIN32_FIND_DATAW fd; | |
| WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'}; | |
| WCHAR *name = param1; | |
| DWORD attrib_set=0; | |
| DWORD attrib_clear=0; | |
| if (param1[0] == '+' || param1[0] == '-') { | |
| DWORD attrib = 0; | |
| /* FIXME: the real cmd can handle many more than two args; this should be in a loop */ | |
| switch (param1[1]) { | |
| case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break; | |
| case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break; | |
| case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break; | |
| case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break; | |
| default: | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| return; | |
| } | |
| switch (param1[0]) { | |
| case '+': attrib_set = attrib; break; | |
| case '-': attrib_clear = attrib; break; | |
| } | |
| name = param2; | |
| } | |
| if (strlenW(name) == 0) { | |
| static const WCHAR slashStarW[] = {'\\','*','\0'}; | |
| GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name); | |
| strcatW (name, slashStarW); | |
| } | |
| hff = FindFirstFileW(name, &fd); | |
| if (hff == INVALID_HANDLE_VALUE) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name); | |
| } | |
| else { | |
| do { | |
| if (attrib_set || attrib_clear) { | |
| fd.dwFileAttributes &= ~attrib_clear; | |
| fd.dwFileAttributes |= attrib_set; | |
| if (!fd.dwFileAttributes) | |
| fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; | |
| SetFileAttributesW(name, fd.dwFileAttributes); | |
| } else { | |
| static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'}; | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) { | |
| flags[0] = 'H'; | |
| } | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) { | |
| flags[1] = 'S'; | |
| } | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) { | |
| flags[2] = 'A'; | |
| } | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { | |
| flags[3] = 'R'; | |
| } | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) { | |
| flags[4] = 'T'; | |
| } | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) { | |
| flags[5] = 'C'; | |
| } | |
| WCMD_output (fmt, flags, fd.cFileName); | |
| for (count=0; count < 8; count++) flags[count] = ' '; | |
| } | |
| } while (FindNextFileW(hff, &fd) != 0); | |
| } | |
| FindClose (hff); | |
| } | |
| /***************************************************************************** | |
| * WCMD_setshow_default | |
| * | |
| * Set/Show the current default directory | |
| */ | |
| void WCMD_setshow_default (WCHAR *command) { | |
| BOOL status; | |
| WCHAR string[1024]; | |
| WCHAR cwd[1024]; | |
| WCHAR *pos; | |
| WIN32_FIND_DATAW fd; | |
| HANDLE hff; | |
| static const WCHAR parmD[] = {'/','D','\0'}; | |
| WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command)); | |
| /* Skip /D and trailing whitespace if on the front of the command line */ | |
| if (CompareStringW(LOCALE_USER_DEFAULT, | |
| NORM_IGNORECASE | SORT_STRINGSORT, | |
| command, 2, parmD, -1) == 2) { | |
| command += 2; | |
| while (*command && *command==' ') command++; | |
| } | |
| GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd); | |
| if (strlenW(command) == 0) { | |
| strcatW (cwd, newline); | |
| WCMD_output (cwd); | |
| } | |
| else { | |
| /* Remove any double quotes, which may be in the | |
| middle, eg. cd "C:\Program Files"\Microsoft is ok */ | |
| pos = string; | |
| while (*command) { | |
| if (*command != '"') *pos++ = *command; | |
| command++; | |
| } | |
| *pos = 0x00; | |
| /* Search for appropriate directory */ | |
| WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string)); | |
| hff = FindFirstFileW(string, &fd); | |
| while (hff != INVALID_HANDLE_VALUE) { | |
| if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |
| WCHAR fpath[MAX_PATH]; | |
| WCHAR drive[10]; | |
| WCHAR dir[MAX_PATH]; | |
| WCHAR fname[MAX_PATH]; | |
| WCHAR ext[MAX_PATH]; | |
| static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'}; | |
| /* Convert path into actual directory spec */ | |
| GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL); | |
| WCMD_splitpath(fpath, drive, dir, fname, ext); | |
| /* Rebuild path */ | |
| wsprintfW(string, fmt, drive, dir, fd.cFileName); | |
| FindClose(hff); | |
| hff = INVALID_HANDLE_VALUE; | |
| break; | |
| } | |
| /* Step on to next match */ | |
| if (FindNextFileW(hff, &fd) == 0) { | |
| FindClose(hff); | |
| hff = INVALID_HANDLE_VALUE; | |
| break; | |
| } | |
| } | |
| /* Change to that directory */ | |
| WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string)); | |
| status = SetCurrentDirectoryW(string); | |
| if (!status) { | |
| errorlevel = 1; | |
| WCMD_print_error (); | |
| return; | |
| } else { | |
| /* Save away the actual new directory, to store as current location */ | |
| GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string); | |
| /* Restore old directory if drive letter would change, and | |
| CD x:\directory /D (or pushd c:\directory) not supplied */ | |
| if ((strstrW(quals, parmD) == NULL) && | |
| (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) { | |
| SetCurrentDirectoryW(cwd); | |
| } | |
| } | |
| /* Set special =C: type environment variable, for drive letter of | |
| change of directory, even if path was restored due to missing | |
| /D (allows changing drive letter when not resident on that | |
| drive */ | |
| if ((string[1] == ':') && IsCharAlphaW(string[0])) { | |
| WCHAR env[4]; | |
| strcpyW(env, equalW); | |
| memcpy(env+1, string, 2 * sizeof(WCHAR)); | |
| env[3] = 0x00; | |
| WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string)); | |
| SetEnvironmentVariableW(env, string); | |
| } | |
| } | |
| return; | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_date | |
| * | |
| * Set/Show the system date | |
| * FIXME: Can't change date yet | |
| */ | |
| void WCMD_setshow_date (void) { | |
| WCHAR curdate[64], buffer[64]; | |
| DWORD count; | |
| static const WCHAR parmT[] = {'/','T','\0'}; | |
| if (strlenW(param1) == 0) { | |
| if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, | |
| curdate, sizeof(curdate)/sizeof(WCHAR))) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate); | |
| if (strstrW (quals, parmT) == NULL) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE)); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), | |
| buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL); | |
| if (count > 2) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| } | |
| } | |
| } | |
| else WCMD_print_error (); | |
| } | |
| else { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_compare | |
| */ | |
| static int WCMD_compare( const void *a, const void *b ) | |
| { | |
| int r; | |
| const WCHAR * const *str_a = a, * const *str_b = b; | |
| r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, | |
| *str_a, -1, *str_b, -1 ); | |
| if( r == CSTR_LESS_THAN ) return -1; | |
| if( r == CSTR_GREATER_THAN ) return 1; | |
| return 0; | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_sortenv | |
| * | |
| * sort variables into order for display | |
| * Optionally only display those who start with a stub | |
| * returns the count displayed | |
| */ | |
| static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub) | |
| { | |
| UINT count=0, len=0, i, displayedcount=0, stublen=0; | |
| const WCHAR **str; | |
| if (stub) stublen = strlenW(stub); | |
| /* count the number of strings, and the total length */ | |
| while ( s[len] ) { | |
| len += (strlenW(&s[len]) + 1); | |
| count++; | |
| } | |
| /* add the strings to an array */ | |
| str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) ); | |
| if( !str ) | |
| return 0; | |
| str[0] = s; | |
| for( i=1; i<count; i++ ) | |
| str[i] = str[i-1] + strlenW(str[i-1]) + 1; | |
| /* sort the array */ | |
| qsort( str, count, sizeof (WCHAR*), WCMD_compare ); | |
| /* print it */ | |
| for( i=0; i<count; i++ ) { | |
| if (!stub || CompareStringW(LOCALE_USER_DEFAULT, | |
| NORM_IGNORECASE | SORT_STRINGSORT, | |
| str[i], stublen, stub, -1) == 2) { | |
| /* Don't display special internal variables */ | |
| if (str[i][0] != '=') { | |
| WCMD_output_asis(str[i]); | |
| WCMD_output_asis(newline); | |
| displayedcount++; | |
| } | |
| } | |
| } | |
| LocalFree( str ); | |
| return displayedcount; | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_env | |
| * | |
| * Set/Show the environment variables | |
| */ | |
| void WCMD_setshow_env (WCHAR *s) { | |
| LPVOID env; | |
| WCHAR *p; | |
| int status; | |
| static const WCHAR parmP[] = {'/','P','\0'}; | |
| errorlevel = 0; | |
| if (param1[0] == 0x00 && quals[0] == 0x00) { | |
| env = GetEnvironmentStringsW(); | |
| WCMD_setshow_sortenv( env, NULL ); | |
| return; | |
| } | |
| /* See if /P supplied, and if so echo the prompt, and read in a reply */ | |
| if (CompareStringW(LOCALE_USER_DEFAULT, | |
| NORM_IGNORECASE | SORT_STRINGSORT, | |
| s, 2, parmP, -1) == 2) { | |
| WCHAR string[MAXSTRING]; | |
| DWORD count; | |
| s += 2; | |
| while (*s && *s==' ') s++; | |
| if (*s=='\"') | |
| WCMD_opt_s_strip_quotes(s); | |
| /* If no parameter, or no '=' sign, return an error */ | |
| if (!(*s) || ((p = strchrW (s, '=')) == NULL )) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| /* Output the prompt */ | |
| *p++ = '\0'; | |
| if (strlenW(p) != 0) WCMD_output(p); | |
| /* Read the reply */ | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, | |
| sizeof(string)/sizeof(WCHAR), &count, NULL); | |
| if (count > 1) { | |
| string[count-1] = '\0'; /* ReadFile output is not null-terminated! */ | |
| if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */ | |
| WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s), | |
| wine_dbgstr_w(string)); | |
| status = SetEnvironmentVariableW(s, string); | |
| } | |
| } else { | |
| DWORD gle; | |
| if (*s=='\"') | |
| WCMD_opt_s_strip_quotes(s); | |
| p = strchrW (s, '='); | |
| if (p == NULL) { | |
| env = GetEnvironmentStringsW(); | |
| if (WCMD_setshow_sortenv( env, s ) == 0) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s); | |
| errorlevel = 1; | |
| } | |
| return; | |
| } | |
| *p++ = '\0'; | |
| if (strlenW(p) == 0) p = NULL; | |
| status = SetEnvironmentVariableW(s, p); | |
| gle = GetLastError(); | |
| if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) { | |
| errorlevel = 1; | |
| } else if ((!status)) WCMD_print_error(); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_path | |
| * | |
| * Set/Show the path environment variable | |
| */ | |
| void WCMD_setshow_path (WCHAR *command) { | |
| WCHAR string[1024]; | |
| DWORD status; | |
| static const WCHAR pathW[] = {'P','A','T','H','\0'}; | |
| static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'}; | |
| if (strlenW(param1) == 0) { | |
| status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR)); | |
| if (status != 0) { | |
| WCMD_output_asis ( pathEqW); | |
| WCMD_output_asis ( string); | |
| WCMD_output_asis ( newline); | |
| } | |
| else { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOPATH)); | |
| } | |
| } | |
| else { | |
| if (*command == '=') command++; /* Skip leading '=' */ | |
| status = SetEnvironmentVariableW(pathW, command); | |
| if (!status) WCMD_print_error(); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_prompt | |
| * | |
| * Set or show the command prompt. | |
| */ | |
| void WCMD_setshow_prompt (void) { | |
| WCHAR *s; | |
| static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'}; | |
| if (strlenW(param1) == 0) { | |
| SetEnvironmentVariableW(promptW, NULL); | |
| } | |
| else { | |
| s = param1; | |
| while ((*s == '=') || (*s == ' ')) s++; | |
| if (strlenW(s) == 0) { | |
| SetEnvironmentVariableW(promptW, NULL); | |
| } | |
| else SetEnvironmentVariableW(promptW, s); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_setshow_time | |
| * | |
| * Set/Show the system time | |
| * FIXME: Can't change time yet | |
| */ | |
| void WCMD_setshow_time (void) { | |
| WCHAR curtime[64], buffer[64]; | |
| DWORD count; | |
| SYSTEMTIME st; | |
| static const WCHAR parmT[] = {'/','T','\0'}; | |
| if (strlenW(param1) == 0) { | |
| GetLocalTime(&st); | |
| if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, | |
| curtime, sizeof(curtime)/sizeof(WCHAR))) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime); | |
| if (strstrW (quals, parmT) == NULL) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME)); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, | |
| sizeof(buffer)/sizeof(WCHAR), &count, NULL); | |
| if (count > 2) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| } | |
| } | |
| } | |
| else WCMD_print_error (); | |
| } | |
| else { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NYI)); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_shift | |
| * | |
| * Shift batch parameters. | |
| * Optional /n says where to start shifting (n=0-8) | |
| */ | |
| void WCMD_shift (WCHAR *command) { | |
| int start; | |
| if (context != NULL) { | |
| WCHAR *pos = strchrW(command, '/'); | |
| int i; | |
| if (pos == NULL) { | |
| start = 0; | |
| } else if (*(pos+1)>='0' && *(pos+1)<='8') { | |
| start = (*(pos+1) - '0'); | |
| } else { | |
| SetLastError(ERROR_INVALID_PARAMETER); | |
| WCMD_print_error(); | |
| return; | |
| } | |
| WINE_TRACE("Shifting variables, starting at %d\n", start); | |
| for (i=start;i<=8;i++) { | |
| context -> shift_count[i] = context -> shift_count[i+1] + 1; | |
| } | |
| context -> shift_count[9] = context -> shift_count[9] + 1; | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_title | |
| * | |
| * Set the console title | |
| */ | |
| void WCMD_title (WCHAR *command) { | |
| SetConsoleTitleW(command); | |
| } | |
| /**************************************************************************** | |
| * WCMD_type | |
| * | |
| * Copy a file to standard output. | |
| */ | |
| void WCMD_type (WCHAR *command) { | |
| int argno = 0; | |
| WCHAR *argN = command; | |
| BOOL writeHeaders = FALSE; | |
| if (param1[0] == 0x00) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_NOARG)); | |
| return; | |
| } | |
| if (param2[0] != 0x00) writeHeaders = TRUE; | |
| /* Loop through all args */ | |
| errorlevel = 0; | |
| while (argN) { | |
| WCHAR *thisArg = WCMD_parameter (command, argno++, &argN); | |
| HANDLE h; | |
| WCHAR buffer[512]; | |
| DWORD count; | |
| if (!argN) break; | |
| WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg)); | |
| h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, | |
| FILE_ATTRIBUTE_NORMAL, NULL); | |
| if (h == INVALID_HANDLE_VALUE) { | |
| WCMD_print_error (); | |
| WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg); | |
| errorlevel = 1; | |
| } else { | |
| if (writeHeaders) { | |
| static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'}; | |
| WCMD_output(fmt, thisArg); | |
| } | |
| while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) { | |
| if (count == 0) break; /* ReadFile reports success on EOF! */ | |
| buffer[count] = 0; | |
| WCMD_output_asis (buffer); | |
| } | |
| CloseHandle (h); | |
| if (!writeHeaders) | |
| WCMD_output_asis (newline); | |
| } | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_more | |
| * | |
| * Output either a file or stdin to screen in pages | |
| */ | |
| void WCMD_more (WCHAR *command) { | |
| int argno = 0; | |
| WCHAR *argN = command; | |
| BOOL useinput = FALSE; | |
| WCHAR moreStr[100]; | |
| WCHAR moreStrPage[100]; | |
| WCHAR buffer[512]; | |
| DWORD count; | |
| static const WCHAR moreStart[] = {'-','-',' ','\0'}; | |
| static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'}; | |
| static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%', | |
| ')',' ','-','-','\n','\0'}; | |
| static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'}; | |
| /* Prefix the NLS more with '-- ', then load the text */ | |
| errorlevel = 0; | |
| strcpyW(moreStr, moreStart); | |
| LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], | |
| (sizeof(moreStr)/sizeof(WCHAR))-3); | |
| if (param1[0] == 0x00) { | |
| /* Wine implements pipes via temporary files, and hence stdin is | |
| effectively reading from the file. This means the prompts for | |
| more are satisfied by the next line from the input (file). To | |
| avoid this, ensure stdin is to the console */ | |
| HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE); | |
| HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE, | |
| FILE_SHARE_READ, NULL, OPEN_EXISTING, | |
| FILE_ATTRIBUTE_NORMAL, 0); | |
| WINE_TRACE("No parms - working probably in pipe mode\n"); | |
| SetStdHandle(STD_INPUT_HANDLE, hConIn); | |
| /* Warning: No easy way of ending the stream (ctrl+z on windows) so | |
| once you get in this bit unless due to a pipe, its going to end badly... */ | |
| useinput = TRUE; | |
| wsprintfW(moreStrPage, moreFmt, moreStr); | |
| WCMD_enter_paged_mode(moreStrPage); | |
| while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) { | |
| if (count == 0) break; /* ReadFile reports success on EOF! */ | |
| buffer[count] = 0; | |
| WCMD_output_asis (buffer); | |
| } | |
| WCMD_leave_paged_mode(); | |
| /* Restore stdin to what it was */ | |
| SetStdHandle(STD_INPUT_HANDLE, hstdin); | |
| CloseHandle(hConIn); | |
| return; | |
| } else { | |
| BOOL needsPause = FALSE; | |
| /* Loop through all args */ | |
| WINE_TRACE("Parms supplied - working through each file\n"); | |
| WCMD_enter_paged_mode(moreStrPage); | |
| while (argN) { | |
| WCHAR *thisArg = WCMD_parameter (command, argno++, &argN); | |
| HANDLE h; | |
| if (!argN) break; | |
| if (needsPause) { | |
| /* Wait */ | |
| wsprintfW(moreStrPage, moreFmt2, moreStr, 100); | |
| WCMD_leave_paged_mode(); | |
| WCMD_output_asis(moreStrPage); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer, | |
| sizeof(buffer)/sizeof(WCHAR), &count, NULL); | |
| WCMD_enter_paged_mode(moreStrPage); | |
| } | |
| WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg)); | |
| h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, | |
| FILE_ATTRIBUTE_NORMAL, NULL); | |
| if (h == INVALID_HANDLE_VALUE) { | |
| WCMD_print_error (); | |
| WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg); | |
| errorlevel = 1; | |
| } else { | |
| ULONG64 curPos = 0; | |
| ULONG64 fileLen = 0; | |
| WIN32_FILE_ATTRIBUTE_DATA fileInfo; | |
| /* Get the file size */ | |
| GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo); | |
| fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow; | |
| needsPause = TRUE; | |
| while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) { | |
| if (count == 0) break; /* ReadFile reports success on EOF! */ | |
| buffer[count] = 0; | |
| curPos += count; | |
| /* Update % count (would be used in WCMD_output_asis as prompt) */ | |
| wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen)); | |
| WCMD_output_asis (buffer); | |
| } | |
| CloseHandle (h); | |
| } | |
| } | |
| WCMD_leave_paged_mode(); | |
| } | |
| } | |
| /**************************************************************************** | |
| * WCMD_verify | |
| * | |
| * Display verify flag. | |
| * FIXME: We don't actually do anything with the verify flag other than toggle | |
| * it... | |
| */ | |
| void WCMD_verify (WCHAR *command) { | |
| int count; | |
| count = strlenW(command); | |
| if (count == 0) { | |
| if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW); | |
| else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW); | |
| return; | |
| } | |
| if (lstrcmpiW(command, onW) == 0) { | |
| verify_mode = 1; | |
| return; | |
| } | |
| else if (lstrcmpiW(command, offW) == 0) { | |
| verify_mode = 0; | |
| return; | |
| } | |
| else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR)); | |
| } | |
| /**************************************************************************** | |
| * WCMD_version | |
| * | |
| * Display version info. | |
| */ | |
| void WCMD_version (void) { | |
| WCMD_output (version_string); | |
| } | |
| /**************************************************************************** | |
| * WCMD_volume | |
| * | |
| * Display volume info and/or set volume label. Returns 0 if error. | |
| */ | |
| int WCMD_volume (int mode, WCHAR *path) { | |
| DWORD count, serial; | |
| WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH]; | |
| BOOL status; | |
| if (strlenW(path) == 0) { | |
| status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir); | |
| if (!status) { | |
| WCMD_print_error (); | |
| return 0; | |
| } | |
| status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR), | |
| &serial, NULL, NULL, NULL, 0); | |
| } | |
| else { | |
| static const WCHAR fmt[] = {'%','s','\\','\0'}; | |
| if ((path[1] != ':') || (strlenW(path) != 2)) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR)); | |
| return 0; | |
| } | |
| wsprintfW (curdir, fmt, path); | |
| status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR), | |
| &serial, NULL, | |
| NULL, NULL, 0); | |
| } | |
| if (!status) { | |
| WCMD_print_error (); | |
| return 0; | |
| } | |
| WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL), | |
| curdir[0], label, HIWORD(serial), LOWORD(serial)); | |
| if (mode) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT)); | |
| WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string, | |
| sizeof(string)/sizeof(WCHAR), &count, NULL); | |
| if (count > 1) { | |
| string[count-1] = '\0'; /* ReadFile output is not null-terminated! */ | |
| if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */ | |
| } | |
| if (strlenW(path) != 0) { | |
| if (!SetVolumeLabelW(curdir, string)) WCMD_print_error (); | |
| } | |
| else { | |
| if (!SetVolumeLabelW(NULL, string)) WCMD_print_error (); | |
| } | |
| } | |
| return 1; | |
| } | |
| /************************************************************************** | |
| * WCMD_exit | |
| * | |
| * Exit either the process, or just this batch program | |
| * | |
| */ | |
| void WCMD_exit (CMD_LIST **cmdList) { | |
| static const WCHAR parmB[] = {'/','B','\0'}; | |
| int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */ | |
| if (context && lstrcmpiW(quals, parmB) == 0) { | |
| errorlevel = rc; | |
| context -> skip_rest = TRUE; | |
| *cmdList = NULL; | |
| } else { | |
| ExitProcess(rc); | |
| } | |
| } | |
| /***************************************************************************** | |
| * WCMD_assoc | |
| * | |
| * Lists or sets file associations (assoc = TRUE) | |
| * Lists or sets file types (assoc = FALSE) | |
| */ | |
| void WCMD_assoc (WCHAR *command, BOOL assoc) { | |
| HKEY key; | |
| DWORD accessOptions = KEY_READ; | |
| WCHAR *newValue; | |
| LONG rc = ERROR_SUCCESS; | |
| WCHAR keyValue[MAXSTRING]; | |
| DWORD valueLen = MAXSTRING; | |
| HKEY readKey; | |
| static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\', | |
| 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'}; | |
| /* See if parameter includes '=' */ | |
| errorlevel = 0; | |
| newValue = strchrW(command, '='); | |
| if (newValue) accessOptions |= KEY_WRITE; | |
| /* Open a key to HKEY_CLASSES_ROOT for enumerating */ | |
| if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0, | |
| accessOptions, &key) != ERROR_SUCCESS) { | |
| WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError()); | |
| return; | |
| } | |
| /* If no parameters then list all associations */ | |
| if (*command == 0x00) { | |
| int index = 0; | |
| /* Enumerate all the keys */ | |
| while (rc != ERROR_NO_MORE_ITEMS) { | |
| WCHAR keyName[MAXSTRING]; | |
| DWORD nameLen; | |
| /* Find the next value */ | |
| nameLen = MAXSTRING; | |
| rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL); | |
| if (rc == ERROR_SUCCESS) { | |
| /* Only interested in extension ones if assoc, or others | |
| if not assoc */ | |
| if ((keyName[0] == '.' && assoc) || | |
| (!(keyName[0] == '.') && (!assoc))) | |
| { | |
| WCHAR subkey[MAXSTRING]; | |
| strcpyW(subkey, keyName); | |
| if (!assoc) strcatW(subkey, shOpCmdW); | |
| if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) { | |
| valueLen = sizeof(keyValue)/sizeof(WCHAR); | |
| rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen); | |
| WCMD_output_asis(keyName); | |
| WCMD_output_asis(equalW); | |
| /* If no default value found, leave line empty after '=' */ | |
| if (rc == ERROR_SUCCESS) { | |
| WCMD_output_asis(keyValue); | |
| } | |
| WCMD_output_asis(newline); | |
| RegCloseKey(readKey); | |
| } | |
| } | |
| } | |
| } | |
| } else { | |
| /* Parameter supplied - if no '=' on command line, its a query */ | |
| if (newValue == NULL) { | |
| WCHAR *space; | |
| WCHAR subkey[MAXSTRING]; | |
| /* Query terminates the parameter at the first space */ | |
| strcpyW(keyValue, command); | |
| space = strchrW(keyValue, ' '); | |
| if (space) *space=0x00; | |
| /* Set up key name */ | |
| strcpyW(subkey, keyValue); | |
| if (!assoc) strcatW(subkey, shOpCmdW); | |
| if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) { | |
| rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen); | |
| WCMD_output_asis(command); | |
| WCMD_output_asis(equalW); | |
| /* If no default value found, leave line empty after '=' */ | |
| if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue); | |
| WCMD_output_asis(newline); | |
| RegCloseKey(readKey); | |
| } else { | |
| WCHAR msgbuffer[MAXSTRING]; | |
| WCHAR outbuffer[MAXSTRING]; | |
| /* Load the translated 'File association not found' */ | |
| if (assoc) { | |
| LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR)); | |
| } else { | |
| LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR)); | |
| } | |
| wsprintfW(outbuffer, msgbuffer, keyValue); | |
| WCMD_output_asis(outbuffer); | |
| errorlevel = 2; | |
| } | |
| /* Not a query - its a set or clear of a value */ | |
| } else { | |
| WCHAR subkey[MAXSTRING]; | |
| /* Get pointer to new value */ | |
| *newValue = 0x00; | |
| newValue++; | |
| /* Set up key name */ | |
| strcpyW(subkey, command); | |
| if (!assoc) strcatW(subkey, shOpCmdW); | |
| /* If nothing after '=' then clear value - only valid for ASSOC */ | |
| if (*newValue == 0x00) { | |
| if (assoc) rc = RegDeleteKeyW(key, command); | |
| if (assoc && rc == ERROR_SUCCESS) { | |
| WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command)); | |
| } else if (assoc && rc != ERROR_FILE_NOT_FOUND) { | |
| WCMD_print_error(); | |
| errorlevel = 2; | |
| } else { | |
| WCHAR msgbuffer[MAXSTRING]; | |
| WCHAR outbuffer[MAXSTRING]; | |
| /* Load the translated 'File association not found' */ | |
| if (assoc) { | |
| LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, | |
| sizeof(msgbuffer)/sizeof(WCHAR)); | |
| } else { | |
| LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, | |
| sizeof(msgbuffer)/sizeof(WCHAR)); | |
| } | |
| wsprintfW(outbuffer, msgbuffer, keyValue); | |
| WCMD_output_asis(outbuffer); | |
| errorlevel = 2; | |
| } | |
| /* It really is a set value = contents */ | |
| } else { | |
| rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE, | |
| accessOptions, NULL, &readKey, NULL); | |
| if (rc == ERROR_SUCCESS) { | |
| rc = RegSetValueExW(readKey, NULL, 0, REG_SZ, | |
| (LPBYTE)newValue, strlenW(newValue)); | |
| RegCloseKey(readKey); | |
| } | |
| if (rc != ERROR_SUCCESS) { | |
| WCMD_print_error(); | |
| errorlevel = 2; | |
| } else { | |
| WCMD_output_asis(command); | |
| WCMD_output_asis(equalW); | |
| WCMD_output_asis(newValue); | |
| WCMD_output_asis(newline); | |
| } | |
| } | |
| } | |
| } | |
| /* Clean up */ | |
| RegCloseKey(key); | |
| } | |
| /**************************************************************************** | |
| * WCMD_color | |
| * | |
| * Clear the terminal screen. | |
| */ | |
| void WCMD_color (void) { | |
| /* Emulate by filling the screen from the top left to bottom right with | |
| spaces, then moving the cursor to the top left afterwards */ | |
| CONSOLE_SCREEN_BUFFER_INFO consoleInfo; | |
| HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); | |
| if (param1[0] != 0x00 && strlenW(param1) > 2) { | |
| WCMD_output (WCMD_LoadMessage(WCMD_ARGERR)); | |
| return; | |
| } | |
| if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo)) | |
| { | |
| COORD topLeft; | |
| DWORD screenSize; | |
| DWORD color = 0; | |
| screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1); | |
| topLeft.X = 0; | |
| topLeft.Y = 0; | |
| /* Convert the color hex digits */ | |
| if (param1[0] == 0x00) { | |
| color = defaultColor; | |
| } else { | |
| color = strtoulW(param1, NULL, 16); | |
| } | |
| /* Fail if fg == bg color */ | |
| if (((color & 0xF0) >> 4) == (color & 0x0F)) { | |
| errorlevel = 1; | |
| return; | |
| } | |
| /* Set the current screen contents and ensure all future writes | |
| remain this color */ | |
| FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize); | |
| SetConsoleTextAttribute(hStdOut, color); | |
| } | |
| } |