From d4ffacd5ebf924a7461fc31dd3cb07e27ac0c7b3 Mon Sep 17 00:00:00 2001 From: Robert Nelson Date: Mon, 25 Sep 2006 09:13:43 +0000 Subject: [PATCH] Fixed the routine that handles executing external programs and scripts so that it is a lot more flexible about the format of the command line. It now translates path names containing spaces to the equivalent short name. It then passes the name with the quotes removed to cmd.exe. git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@3501 91ce42f0-d328-0410-95d8-f526ca767f89 --- bacula/src/win32/compat/compat.cpp | 323 +++++++++++++++++++++++------ 1 file changed, 255 insertions(+), 68 deletions(-) diff --git a/bacula/src/win32/compat/compat.cpp b/bacula/src/win32/compat/compat.cpp index 6df8dcdab4..7ab8a014dd 100644 --- a/bacula/src/win32/compat/compat.cpp +++ b/bacula/src/win32/compat/compat.cpp @@ -27,6 +27,7 @@ #define b_errno_win32 (1<<29) +#define MAX_PATHLENGTH 1024 /* UTF-8 to UCS2 path conversion is expensive, so we cache the conversion. During backup the @@ -1532,88 +1533,274 @@ getArgv0(const char *cmdline) return rval; } +/* + * Extracts the executable or script name from the first string in + * cmdline. + * + * If the name contains blanks then it must be quoted with double quotes, + * otherwise quotes are optional. If the name contains blanks then it + * will be converted to a short name. + * + * The optional quotes will be removed. The result is copied to a malloc'ed + * buffer and returned through the pexe argument. The pargs parameter is set + * to the address of the character in cmdline located after the name. + * + * The malloc'ed buffer returned in *pexe must be freed by the caller. + */ +bool +GetApplicationName(const char *cmdline, char **pexe, const char **pargs) +{ + const char *pExeStart = NULL; /* Start of executable name in cmdline */ + const char *pExeEnd = NULL; /* Character after executable name (separator) */ + + const char *pBasename = NULL; /* Character after last path separator */ + const char *pExtension = NULL; /* Period at start of extension */ + + const char *current = cmdline; + + bool bHasBlanks = false; + bool bQuoted = false; + + /* Skip initial whitespace */ + + while (*current == ' ' || *current == '\t') + { + current++; + } + + /* Calculate start of name and determine if quoted */ + + if (*current == '"') { + pExeStart = ++current; + bQuoted = true; + } else { + pExeStart = current; + bQuoted = false; + } + + *pargs = NULL; + + /* + * Scan command line looking for path separators (/ and \\) and the + * terminator, either a quote or a blank. The location of the + * extension is also noted. + */ + + for ( ; *current != '\0'; current++) + { + if (*current == ' ') { + bHasBlanks = true; + } else if (*current == '.') { + pExtension = current; + } else if ((*current == '\\' || *current == '/') && current[1] != '\0') { + pBasename = ¤t[1]; + pExtension = NULL; + } + + /* Check for terminator, either quote or blank */ + if (bQuoted) { + if (*current != '"') { + continue; + } + } else { + if (*current != ' ') { + continue; + } + } + + /* + * Hit terminator, remember end of name (address of terminator) and + * start of arguments + */ + pExeEnd = current; + + if (bQuoted && *current == '"') { + *pargs = ¤t[1]; + } else { + *pargs = current; + } + + break; + } + + if (pBasename == NULL) { + pBasename = pExeStart; + } + + if (pExeEnd == NULL) { + pExeEnd = current; + } + + if (!bQuoted) { + bHasBlanks = false; + } + + bool bHasPathSeparators = pExeStart != pBasename; + + /* We have the pointers to all the useful parts of the name */ + + /* Default extensions in the order cmd.exe uses to search */ + + static const char ExtensionList[][5] = { ".com", ".exe", ".bat", ".cmd" }; + DWORD dwBasePathLength = pExeEnd - pExeStart; + + if (bHasBlanks) { + DWORD dwShortNameLength = 0; + char *pPathname = (char *)alloca(MAX_PATHLENGTH + 1); + char *pShortPathname = (char *)alloca(MAX_PATHLENGTH + 1); + + pPathname[MAX_PATHLENGTH] = '\0'; + pShortPathname[MAX_PATHLENGTH] = '\0'; + + memcpy(pPathname, pExeStart, dwBasePathLength); + pPathname[dwBasePathLength] = '\0'; + + if (pExtension == NULL) { + /* Try appending extensions */ + for (int index = 0; index < sizeof(ExtensionList) / sizeof(ExtensionList[0]); index++) { + + if (!bHasPathSeparators) { + /* There are no path separators, search in the standard locations */ + dwShortNameLength = SearchPath(NULL, pPathname, ExtensionList[index], MAX_PATHLENGTH, pShortPathname, NULL); + if (dwShortNameLength > 0 && dwShortNameLength <= MAX_PATHLENGTH) { + memcpy(pPathname, pShortPathname, dwShortNameLength); + pPathname[dwShortNameLength] = '\0'; + break; + } + } else { + strcpy(&pPathname[dwBasePathLength], ExtensionList[index]); + + if (GetFileAttributes(pPathname) != INVALID_FILE_ATTRIBUTES) { + break; + } + } + } + } else { + if (!bHasPathSeparators) { + /* There are no path separators, search in the standard locations */ + dwShortNameLength = SearchPath(NULL, pPathname, NULL, MAX_PATHLENGTH, pShortPathname, NULL); + if (dwShortNameLength == 0 || dwShortNameLength > MAX_PATHLENGTH) { + return false; + } + + memcpy(pPathname, pShortPathname, dwShortNameLength); + pPathname[dwShortNameLength] = '\0'; + } + } + + dwShortNameLength = GetShortPathName(pPathname, pShortPathname, MAX_PATHLENGTH); + + if (dwShortNameLength > 0 && dwShortNameLength <= MAX_PATHLENGTH) { + *pexe = (char *)malloc(dwShortNameLength + 1); + if (*pexe != NULL) { + memcpy(*pexe, pShortPathname, dwShortNameLength + 1); + } else { + return false; + } + } else { + return false; + } + } else { + /* There are no blanks so we don't need to munge the name */ + *pexe = (char *)malloc(dwBasePathLength + 1); + if (*pexe != NULL) { + memcpy(*pexe, pExeStart, dwBasePathLength); + (*pexe)[dwBasePathLength] = '\0'; + } else { + return false; + } + } + + return true; +} /** * OK, so it would seem CreateProcess only handles true executables: - * .com or .exe files. - * So test to see whether we're getting a .bat file and if so grab - * $COMSPEC value and pass batch file to it. + * .com or .exe files. So grab $COMSPEC value and pass command line to it. */ HANDLE CreateChildProcess(const char *cmdline, HANDLE in, HANDLE out, HANDLE err) { - static const char *comspec = NULL; - PROCESS_INFORMATION piProcInfo; - STARTUPINFOA siStartInfo; - BOOL bFuncRetn = FALSE; + static const char *comspec = NULL; + PROCESS_INFORMATION piProcInfo; + STARTUPINFOA siStartInfo; + BOOL bFuncRetn = FALSE; - if (comspec == NULL) { - comspec = getenv("COMSPEC"); - } - if (comspec == NULL) // should never happen - return INVALID_HANDLE_VALUE; + if (comspec == NULL) { + comspec = getenv("COMSPEC"); + } + if (comspec == NULL) // should never happen + return INVALID_HANDLE_VALUE; - // Set up members of the PROCESS_INFORMATION structure. - ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); + // Set up members of the PROCESS_INFORMATION structure. + ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); - // Set up members of the STARTUPINFO structure. + // Set up members of the STARTUPINFO structure. - ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); - siStartInfo.cb = sizeof(STARTUPINFO); - // setup new process to use supplied handles for stdin,stdout,stderr - // if supplied handles are not used the send a copy of our STD_HANDLE - // as appropriate - siStartInfo.dwFlags = STARTF_USESTDHANDLES; + ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); + siStartInfo.cb = sizeof(STARTUPINFO); + // setup new process to use supplied handles for stdin,stdout,stderr + // if supplied handles are not used the send a copy of our STD_HANDLE + // as appropriate + siStartInfo.dwFlags = STARTF_USESTDHANDLES; - if (in != INVALID_HANDLE_VALUE) - siStartInfo.hStdInput = in; - else - siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + if (in != INVALID_HANDLE_VALUE) + siStartInfo.hStdInput = in; + else + siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - if (out != INVALID_HANDLE_VALUE) - siStartInfo.hStdOutput = out; - else - siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - if (err != INVALID_HANDLE_VALUE) - siStartInfo.hStdError = err; - else - siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); - // Create the child process. - - char exeFile[256]; - int cmdLen = strlen(cmdline) + strlen(comspec) + 16; - - char *cmdLine = (char *)alloca(cmdLen); - - bstrncpy(exeFile, comspec, sizeof(exeFile)); - bstrncpy(cmdLine, comspec, cmdLen); - bstrncat(cmdLine, " /c ", cmdLen); - bstrncat(cmdLine, cmdline, cmdLen); - - // try to execute program - bFuncRetn = CreateProcessA(exeFile, - cmdLine, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &piProcInfo); // receives PROCESS_INFORMATION - - if (bFuncRetn == 0) { - ErrorExit("CreateProcess failed\n"); - const char *err = errorString(); - Dmsg3(99, "CreateProcess(%s, %s, ...)=%s\n", exeFile, cmdLine, err); - LocalFree((void *)err); - return INVALID_HANDLE_VALUE; - } - // we don't need a handle on the process primary thread so we close - // this now. - CloseHandle(piProcInfo.hThread); + if (out != INVALID_HANDLE_VALUE) + siStartInfo.hStdOutput = out; + else + siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + if (err != INVALID_HANDLE_VALUE) + siStartInfo.hStdError = err; + else + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + // Create the child process. + + char *exeFile; + const char *argStart; + + if (!GetApplicationName(cmdline, &exeFile, &argStart)) { + return INVALID_HANDLE_VALUE; + } + + int cmdLen = strlen(comspec) + 4 + strlen(exeFile) + strlen(argStart) + 1; + + char *cmdLine = (char *)alloca(cmdLen); + + snprintf(cmdLine, cmdLen, "%s /c %s%s", comspec, exeFile, argStart); + + free(exeFile); + + Dmsg2(150, "Calling CreateProcess(%s, %s, ...)\n", comspec, cmdLine); + + // try to execute program + bFuncRetn = CreateProcessA(comspec, + cmdLine, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + + if (bFuncRetn == 0) { + ErrorExit("CreateProcess failed\n"); + const char *err = errorString(); + Dmsg3(99, "CreateProcess(%s, %s, ...)=%s\n", comspec, cmdLine, err); + LocalFree((void *)err); + return INVALID_HANDLE_VALUE; + } + // we don't need a handle on the process primary thread so we close + // this now. + CloseHandle(piProcInfo.hThread); - return piProcInfo.hProcess; + return piProcInfo.hProcess; } -- 2.39.5