summaryrefslogtreecommitdiffstats
path: root/mmc_updater/src/ProcessUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mmc_updater/src/ProcessUtils.cpp')
-rw-r--r--mmc_updater/src/ProcessUtils.cpp536
1 files changed, 536 insertions, 0 deletions
diff --git a/mmc_updater/src/ProcessUtils.cpp b/mmc_updater/src/ProcessUtils.cpp
new file mode 100644
index 00000000..3b9ffac2
--- /dev/null
+++ b/mmc_updater/src/ProcessUtils.cpp
@@ -0,0 +1,536 @@
+#include "ProcessUtils.h"
+
+#include "FileUtils.h"
+#include "Platform.h"
+#include "StringUtils.h"
+#include "Log.h"
+
+#include <string.h>
+#include <vector>
+#include <iostream>
+
+#ifdef PLATFORM_WINDOWS
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <errno.h>
+#endif
+
+#ifdef PLATFORM_MAC
+#include <Security/Security.h>
+#include <mach-o/dyld.h>
+#endif
+
+PLATFORM_PID ProcessUtils::currentProcessId()
+{
+#ifdef PLATFORM_UNIX
+ return getpid();
+#else
+ return GetCurrentProcessId();
+#endif
+}
+
+int ProcessUtils::runSync(const std::string& executable,
+ const std::list<std::string>& args)
+{
+#ifdef PLATFORM_UNIX
+ return runSyncUnix(executable,args);
+#else
+ return runWindows(executable,args,RunSync);
+#endif
+}
+
+#ifdef PLATFORM_UNIX
+int ProcessUtils::runSyncUnix(const std::string& executable,
+ const std::list<std::string>& args)
+{
+ PLATFORM_PID pid = runAsyncUnix(executable,args);
+ int status = 0;
+ if (waitpid(pid,&status,0) != -1)
+ {
+ if (WIFEXITED(status))
+ {
+ return static_cast<char>(WEXITSTATUS(status));
+ }
+ else
+ {
+ LOG(Warn,"Child exited abnormally");
+ return -1;
+ }
+ }
+ else
+ {
+ LOG(Warn,"Failed to get exit status of child " + intToStr(pid));
+ return WaitFailed;
+ }
+}
+#endif
+
+void ProcessUtils::runAsync(const std::string& executable,
+ const std::list<std::string>& args)
+{
+#ifdef PLATFORM_WINDOWS
+ runWindows(executable,args,RunAsync);
+#elif defined(PLATFORM_UNIX)
+ runAsyncUnix(executable,args);
+#endif
+}
+
+int ProcessUtils::runElevated(const std::string& executable,
+ const std::list<std::string>& args,
+ const std::string& task)
+{
+#ifdef PLATFORM_WINDOWS
+ (void)task;
+ return runElevatedWindows(executable,args);
+#elif defined(PLATFORM_MAC)
+ (void)task;
+ return runElevatedMac(executable,args);
+#elif defined(PLATFORM_LINUX)
+ return runElevatedLinux(executable,args,task);
+#endif
+}
+
+bool ProcessUtils::waitForProcess(PLATFORM_PID pid)
+{
+#ifdef PLATFORM_UNIX
+ pid_t result = ::waitpid(pid, 0, 0);
+ if (result < 0)
+ {
+ LOG(Error,"waitpid() failed with error: " + std::string(strerror(errno)));
+ }
+ return result > 0;
+#elif defined(PLATFORM_WINDOWS)
+ HANDLE hProc;
+
+ if (!(hProc = OpenProcess(SYNCHRONIZE, FALSE, pid)))
+ {
+ LOG(Error,"Unable to get process handle for pid " + intToStr(pid) + " last error " + intToStr(GetLastError()));
+ return false;
+ }
+
+ DWORD dwRet = WaitForSingleObject(hProc, INFINITE);
+ CloseHandle(hProc);
+
+ if (dwRet == WAIT_FAILED)
+ {
+ LOG(Error,"WaitForSingleObject failed with error " + intToStr(GetLastError()));
+ }
+
+ return (dwRet == WAIT_OBJECT_0);
+#endif
+}
+
+#ifdef PLATFORM_LINUX
+int ProcessUtils::runElevatedLinux(const std::string& executable,
+ const std::list<std::string>& args,
+ const std::string& _task)
+{
+ std::string task(_task);
+ if (task.empty())
+ {
+ task = FileUtils::fileName(executable.c_str());
+ }
+
+ // try available graphical sudo instances until we find one that works.
+ // The different sudo front-ends have different behaviors with respect to error codes:
+ //
+ // - 'kdesudo': return 1 if the user enters the wrong password 3 times or if
+ // they cancel elevation
+ //
+ // - recent 'gksudo' versions: return 1 if the user enters the wrong password
+ // : return -1 if the user cancels elevation
+ //
+ // - older 'gksudo' versions : return 0 if the user cancels elevation
+
+ std::vector<std::string> sudos;
+
+ if (getenv("KDE_SESSION_VERSION"))
+ {
+ sudos.push_back("kdesudo");
+ }
+ sudos.push_back("gksudo");
+
+ for (unsigned int i=0; i < sudos.size(); i++)
+ {
+ const std::string& sudoBinary = sudos.at(i);
+
+ std::list<std::string> sudoArgs;
+ sudoArgs.push_back("-u");
+ sudoArgs.push_back("root");
+
+ if (sudoBinary == "kdesudo")
+ {
+ sudoArgs.push_back("-d");
+ sudoArgs.push_back("--comment");
+ std::string sudoMessage = task + " needs administrative privileges. Please enter your password.";
+ sudoArgs.push_back(sudoMessage);
+ }
+ else if (sudoBinary == "gksudo")
+ {
+ sudoArgs.push_back("--description");
+ sudoArgs.push_back(task);
+ }
+ else
+ {
+ sudoArgs.push_back(task);
+ }
+
+ sudoArgs.push_back("--");
+ sudoArgs.push_back(executable);
+ std::copy(args.begin(),args.end(),std::back_inserter(sudoArgs));
+
+ int result = ProcessUtils::runSync(sudoBinary,sudoArgs);
+
+ LOG(Info,"Tried to use sudo " + sudoBinary + " with response " + intToStr(result));
+
+ if (result != RunFailed)
+ {
+ return result;
+ break;
+ }
+ }
+ return RunElevatedFailed;
+}
+#endif
+
+#ifdef PLATFORM_MAC
+int ProcessUtils::runElevatedMac(const std::string& executable,
+ const std::list<std::string>& args)
+{
+ // request elevation using the Security Service.
+ //
+ // This only works when the application is being run directly
+ // from the Mac. Attempting to run the app via a remote SSH session
+ // (for example) will fail with an interaction-not-allowed error
+
+ OSStatus status;
+ AuthorizationRef authorizationRef;
+
+ status = AuthorizationCreate(
+ NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults,
+ &authorizationRef);
+
+ AuthorizationItem right = { kAuthorizationRightExecute, 0, NULL, 0 };
+ AuthorizationRights rights = { 1, &right };
+
+ AuthorizationFlags flags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights;
+
+ if (status == errAuthorizationSuccess)
+ {
+ status = AuthorizationCopyRights(authorizationRef, &rights, NULL,
+ flags, NULL);
+
+ if (status == errAuthorizationSuccess)
+ {
+ char** argv;
+ argv = (char**) malloc(sizeof(char*) * args.size() + 1);
+
+ unsigned int i = 0;
+ for (std::list<std::string>::const_iterator iter = args.begin(); iter != args.end(); iter++)
+ {
+ argv[i] = strdup(iter->c_str());
+ ++i;
+ }
+ argv[i] = NULL;
+
+ FILE* pipe = NULL;
+
+ char* tool = strdup(executable.c_str());
+
+ status = AuthorizationExecuteWithPrivileges(authorizationRef, tool,
+ kAuthorizationFlagDefaults, argv, &pipe);
+
+ if (status == errAuthorizationSuccess)
+ {
+ // AuthorizationExecuteWithPrivileges does not provide a way to get the process ID
+ // of the child process.
+ //
+ // Discussions on Apple development forums suggest two approaches for working around this,
+ //
+ // - Modify the child process to sent its process ID back to the parent via
+ // the pipe passed to AuthorizationExecuteWithPrivileges.
+ //
+ // - Use the generic Unix wait() call.
+ //
+ // This code uses wait(), which is simpler, but suffers from the problem that wait() waits
+ // for any child process, not necessarily the specific process launched
+ // by AuthorizationExecuteWithPrivileges.
+ //
+ // Apple's documentation (see 'Authorization Services Programming Guide') suggests
+ // installing files in an installer as a legitimate use for
+ // AuthorizationExecuteWithPrivileges but in general strongly recommends
+ // not using this call and discusses a number of other alternatives
+ // for performing privileged operations,
+ // which we could consider in future.
+
+ int childStatus;
+ pid_t childPid = wait(&childStatus);
+
+ if (childStatus != 0)
+ {
+ LOG(Error,"elevated process failed with status " + intToStr(childStatus) + " pid "
+ + intToStr(childPid));
+ }
+ else
+ {
+ LOG(Info,"elevated process succeded with pid " + intToStr(childPid));
+ }
+
+ return childStatus;
+ }
+ else
+ {
+ LOG(Error,"failed to launch elevated process " + intToStr(status));
+ return RunElevatedFailed;
+ }
+
+ // If we want to know more information about what has happened:
+ // http://developer.apple.com/mac/library/documentation/Security/Reference/authorization_ref/Reference/reference.html#//apple_ref/doc/uid/TP30000826-CH4g-CJBEABHG
+ free(tool);
+ for (i = 0; i < args.size(); i++)
+ {
+ free(argv[i]);
+ }
+ }
+ else
+ {
+ LOG(Error,"failed to get rights to launch elevated process. status: " + intToStr(status));
+ return RunElevatedFailed;
+ }
+ }
+ else
+ {
+ return RunElevatedFailed;
+ }
+}
+#endif
+
+// convert a list of arguments in a space-separated string.
+// Arguments containing spaces are enclosed in quotes
+std::string quoteArgs(const std::list<std::string>& arguments)
+{
+ std::string quotedArgs;
+ for (std::list<std::string>::const_iterator iter = arguments.begin();
+ iter != arguments.end();
+ iter++)
+ {
+ std::string arg = *iter;
+
+ bool isQuoted = !arg.empty() &&
+ arg.at(0) == '"' &&
+ arg.at(arg.size()-1) == '"';
+
+ if (!isQuoted && arg.find(' ') != std::string::npos)
+ {
+ arg.insert(0,"\"");
+ arg.append("\"");
+ }
+ quotedArgs += arg;
+ quotedArgs += " ";
+ }
+ return quotedArgs;
+}
+
+#ifdef PLATFORM_WINDOWS
+int ProcessUtils::runElevatedWindows(const std::string& executable,
+ const std::list<std::string>& arguments)
+{
+ std::string args = quoteArgs(arguments);
+
+ SHELLEXECUTEINFO executeInfo;
+ ZeroMemory(&executeInfo,sizeof(executeInfo));
+ executeInfo.cbSize = sizeof(SHELLEXECUTEINFO);
+ executeInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
+ // request UAC elevation
+ executeInfo.lpVerb = "runas";
+ executeInfo.lpFile = executable.c_str();
+ executeInfo.lpParameters = args.c_str();
+ executeInfo.nShow = SW_SHOWNORMAL;
+
+ LOG(Info,"Attempting to execute " + executable + " with administrator priviledges");
+ if (!ShellExecuteEx(&executeInfo))
+ {
+ LOG(Error,"Failed to start with admin priviledges using ShellExecuteEx()");
+ return RunElevatedFailed;
+ }
+
+ WaitForSingleObject(executeInfo.hProcess, INFINITE);
+
+ // this assumes the process succeeded - we need to check whether
+ // this is actually the case.
+ return 0;
+}
+#endif
+
+#ifdef PLATFORM_UNIX
+PLATFORM_PID ProcessUtils::runAsyncUnix(const std::string& executable,
+ const std::list<std::string>& args)
+{
+ pid_t child = fork();
+ if (child == 0)
+ {
+ // in child process
+ char** argBuffer = new char*[args.size() + 2];
+ argBuffer[0] = strdup(executable.c_str());
+ int i = 1;
+ for (std::list<std::string>::const_iterator iter = args.begin(); iter != args.end(); iter++)
+ {
+ argBuffer[i] = strdup(iter->c_str());
+ ++i;
+ }
+ argBuffer[i] = 0;
+
+ if (execvp(executable.c_str(),argBuffer) == -1)
+ {
+ LOG(Error,"error starting child: " + std::string(strerror(errno)));
+ exit(RunFailed);
+ }
+ }
+ else
+ {
+ LOG(Info,"Started child process " + intToStr(child));
+ }
+ return child;
+}
+#endif
+
+#ifdef PLATFORM_WINDOWS
+int ProcessUtils::runWindows(const std::string& _executable,
+ const std::list<std::string>& _args,
+ RunMode runMode)
+{
+ // most Windows API functions allow back and forward slashes to be
+ // used interchangeably. However, an application started with
+ // CreateProcess() may fail to find Side-by-Side library dependencies
+ // in the same directory as the executable if forward slashes are
+ // used as path separators, so convert the path to use back slashes here.
+ //
+ // This may be related to LoadLibrary() requiring backslashes instead
+ // of forward slashes.
+ std::string executable = FileUtils::toWindowsPathSeparators(_executable);
+
+ std::list<std::string> args(_args);
+ args.push_front(executable);
+ std::string commandLine = quoteArgs(args);
+
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo,sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo,sizeof(processInfo));
+
+ char* commandLineStr = strdup(commandLine.c_str());
+ bool result = CreateProcess(
+ executable.c_str(),
+ commandLineStr,
+ 0 /* process attributes */,
+ 0 /* thread attributes */,
+ false /* inherit handles */,
+ NORMAL_PRIORITY_CLASS /* creation flags */,
+ 0 /* environment */,
+ 0 /* current directory */,
+ &startupInfo /* startup info */,
+ &processInfo /* process information */
+ );
+
+ if (!result)
+ {
+ LOG(Error,"Failed to start child process. " + executable + " Last error: " + intToStr(GetLastError()));
+ return RunFailed;
+ }
+ else
+ {
+ if (runMode == RunSync)
+ {
+ if (WaitForSingleObject(processInfo.hProcess,INFINITE) == WAIT_OBJECT_0)
+ {
+ DWORD status = WaitFailed;
+ if (GetExitCodeProcess(processInfo.hProcess,&status) != 0)
+ {
+ LOG(Error,"Failed to get exit code for process");
+ }
+ return status;
+ }
+ else
+ {
+ LOG(Error,"Failed to wait for process to finish");
+ return WaitFailed;
+ }
+ }
+ else
+ {
+ // process is being run asynchronously - return zero as if it had
+ // succeeded
+ return 0;
+ }
+ }
+}
+#endif
+
+std::string ProcessUtils::currentProcessPath()
+{
+#ifdef PLATFORM_LINUX
+ std::string path = FileUtils::canonicalPath("/proc/self/exe");
+ LOG(Info,"Current process path " + path);
+ return path;
+#elif defined(PLATFORM_MAC)
+ uint32_t bufferSize = PATH_MAX;
+ char buffer[bufferSize];
+ _NSGetExecutablePath(buffer,&bufferSize);
+ return buffer;
+#else
+ char fileName[MAX_PATH];
+ GetModuleFileName(0 /* get path of current process */,fileName,MAX_PATH);
+ return fileName;
+#endif
+}
+
+#ifdef PLATFORM_WINDOWS
+void ProcessUtils::convertWindowsCommandLine(LPCWSTR commandLine, int& argc, char**& argv)
+{
+ argc = 0;
+ LPWSTR* argvUnicode = CommandLineToArgvW(commandLine,&argc);
+
+ argv = new char*[argc];
+ for (int i=0; i < argc; i++)
+ {
+ const int BUFFER_SIZE = 4096;
+ char buffer[BUFFER_SIZE];
+
+ int length = WideCharToMultiByte(CP_ACP,
+ 0 /* flags */,
+ argvUnicode[i],
+ -1, /* argvUnicode is null terminated */
+ buffer,
+ BUFFER_SIZE,
+ 0,
+ false);
+
+ // note: if WideCharToMultiByte() fails it will return zero,
+ // in which case we store a zero-length argument in argv
+ if (length == 0)
+ {
+ argv[i] = new char[1];
+ argv[i][0] = '\0';
+ }
+ else
+ {
+ // if the input string to WideCharToMultiByte is null-terminated,
+ // the output is also null-terminated
+ argv[i] = new char[length];
+ strncpy(argv[i],buffer,length);
+ }
+ }
+ LocalFree(argvUnicode);
+}
+#endif
+