From ea60f3548d4769c356b796cb27cd690cdfe4b6d9 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Mon, 14 Oct 2013 22:23:11 +0300 Subject: [PATCH] Add a Process class for running and interfacing with other programs --- source/core/process.cpp | 100 ++++++++++++++++++ source/core/process.h | 105 +++++++++++++++++++ source/core/process_private.h | 17 +++ source/core/unix/process.cpp | 113 ++++++++++++++++++++ source/core/unix/process_platform.h | 15 +++ source/core/windows/process.cpp | 137 +++++++++++++++++++++++++ source/core/windows/process_platform.h | 12 +++ 7 files changed, 499 insertions(+) create mode 100644 source/core/process.cpp create mode 100644 source/core/process.h create mode 100644 source/core/process_private.h create mode 100644 source/core/unix/process.cpp create mode 100644 source/core/unix/process_platform.h create mode 100644 source/core/windows/process.cpp create mode 100644 source/core/windows/process_platform.h diff --git a/source/core/process.cpp b/source/core/process.cpp new file mode 100644 index 0000000..dfbea34 --- /dev/null +++ b/source/core/process.cpp @@ -0,0 +1,100 @@ +#include +#include "process.h" +#include "process_private.h" + +using namespace std; + +namespace Msp { + +Process *Process::_self = 0; + +Process::Process(const Private &p): + priv(new Private(p)) +{ + init(); +} + +Process::Process(): + priv(new Private) +{ + init(); +} + +void Process::init() +{ + redirect = false; + cin = 0; + cout = 0; + cerr = 0; + running = false; + finished = false; + exit_code = 0; +} + +Process &Process::self() +{ + if(!_self) + { + Private _priv; + platform_get_self_info(_priv); + _self = new Process(_priv); + } + return *_self; +} + +void Process::set_working_directory(const FS::Path &d) +{ + work_dir = d; +} + +void Process::redirect_cin(IO::Base &io) +{ + do_redirect(cin, io); +} + +void Process::redirect_cout(IO::Base &io) +{ + do_redirect(cout, io); +} + +void Process::redirect_cerr(IO::Base &io) +{ + do_redirect(cerr, io); +} + +void Process::do_redirect(IO::Base *&ptr, IO::Base &io) +{ + if(this==_self) + { + if(&ptr==&cin) + IO::cin.redirect(io); + else if(&ptr==&cout) + IO::cout.redirect(io); + else if(&ptr==&cerr) + IO::cerr.redirect(io); + } + else + { + redirect = true; + ptr = &io; + } +} + +void Process::execute(const string &command, const Arguments &args) +{ + execute(command, true, args); +} + +void Process::execute(const FS::Path &command, const Arguments &args) +{ + execute(command.str(), false, args); +} + +unsigned Process::get_exit_code() const +{ + if(!finished) + throw logic_error("not finished"); + return exit_code; +} + +} // namespace Msp diff --git a/source/core/process.h b/source/core/process.h new file mode 100644 index 0000000..5413587 --- /dev/null +++ b/source/core/process.h @@ -0,0 +1,105 @@ +#ifndef MSP_CORE_PROCESS_H_ +#define MSP_CORE_PROCESS_H_ + +#include +#include +#include +#include + +namespace Msp { + +/** +Provides an interface for running and controlling programs. + +The creation of a new system-level process does not happen immediately when a +Process object is created, but only when exexute is called. If called on the +object obtained with self(), the process is replaced with a new program +immediately. In particular, destructors are not invoked. + +Output redirections can be specified before calling execute. To feed input to +the process or capture its output, use an IO::Pipe. Redirections performed on +the self object take effect immediately. It is recommended to perform such +redirections directly on the Console objects. +*/ +class Process +{ +public: + typedef std::vector Arguments; + +private: + struct Private; + + Private *priv; + FS::Path work_dir; + bool redirect; + IO::Base *cin; + IO::Base *cout; + IO::Base *cerr; + bool running; + bool finished; + unsigned exit_code; + + static Process *_self; + + Process(const Private &); + void init(); +public: + Process(); + ~Process(); + + /** Returns an object referring to the current process. */ + static Process &self(); +private: + static void platform_get_self_info(Private &); + +public: + /** Sets the working directory for the new process. By default, the working + directory is not changed. */ + void set_working_directory(const FS::Path &); + + /** Redirects console input from an I/O object. */ + void redirect_cin(IO::Base &); + + /** Redirects console output to an I/O object. */ + void redirect_cout(IO::Base &); + + /** Redirects error output to an I/O object. */ + void redirect_cerr(IO::Base &); +private: + void do_redirect(IO::Base *&, IO::Base &); + +public: + /** Executes a command, searching for it in the standard locations. */ + void execute(const std::string &, const Arguments &); + + /** Executes a command specified by a full path. */ + void execute(const FS::Path &, const Arguments &); + +private: + void execute(const std::string &, bool, const Arguments &); + +public: + bool is_running() const { return running; } + bool has_finished() const { return finished; } + + /** Checks the status of a running process. If block is true, waits for the + process to finish. */ + bool wait(bool block = true); + + /** Returns the exit code for a finished process. */ + unsigned get_exit_code() const; + + /** Terminates the process. */ + void terminate(); + + /** Brutally murders the process. It is not given a chance to terminate + gracefully. */ + void kill(); + + /** Sends the process an interrupt signal, as if ^C was pressed. */ + void interrupt(); +}; + +} // namespace Msp + +#endif diff --git a/source/core/process_private.h b/source/core/process_private.h new file mode 100644 index 0000000..f6f4f2e --- /dev/null +++ b/source/core/process_private.h @@ -0,0 +1,17 @@ +#ifndef MSP_CORE_PROCESS_PRIVATE_H_ +#define MSP_CORE_PROCESS_PRIVATE_H_ + +#include "process_platform.h" + +namespace Msp { + +struct Process::Private +{ + PlatformProcessInformation info; + + Private(); +}; + +} // namespace Msp + +#endif diff --git a/source/core/unix/process.cpp b/source/core/unix/process.cpp new file mode 100644 index 0000000..76141b0 --- /dev/null +++ b/source/core/unix/process.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include "process.h" +#include "process_private.h" + +using namespace std; + +namespace Msp { + +Process::~Process() +{ } + +void Process::platform_get_self_info(Private &priv) +{ + priv.info.pid = getpid(); +} + +void Process::execute(const string &command, bool path_search, const Arguments &args) +{ + pid_t pid = 0; + if(this!=_self) + pid = fork(); + + if(pid==-1) + throw system_error("fork"); + else if(pid==0) + { + try + { + vector argv(args.size()+2); + argv[0] = command.c_str(); + for(unsigned i=0; i(&argv[0])); + else + execv(command.c_str(), const_cast(&argv[0])); + } + catch(...) + { } + _exit(255); + } + else + { + priv->info.pid = pid; + running = true; + } +} + +bool Process::wait(bool block) +{ + if(!running) + throw logic_error("not running"); + + int status; + int pid = waitpid(priv->info.pid, &status, (block ? 0 : WNOHANG)); + if(pid==-1) + throw system_error("waitpid"); + + if(pid) + { + finished = true; + running = false; + if(WIFEXITED(status)) + exit_code = WEXITSTATUS(status); + else if(WIFSIGNALED(status)) + exit_code = 0x100|WTERMSIG(status); + } + + return finished; +} + +void Process::terminate() +{ + ::kill(priv->info.pid, SIGTERM); +} + +void Process::kill() +{ + ::kill(priv->info.pid, SIGKILL); +} + +void Process::interrupt() +{ + ::kill(priv->info.pid, SIGINT); +} + + +Process::Private::Private() +{ + info.pid = 0; +} + +} // namespace Msp diff --git a/source/core/unix/process_platform.h b/source/core/unix/process_platform.h new file mode 100644 index 0000000..69cf326 --- /dev/null +++ b/source/core/unix/process_platform.h @@ -0,0 +1,15 @@ +#ifndef MSP_CORE_PROCESS_PLATFORM_H_ +#define MSP_CORE_PROCESS_PLATFORM_H_ + +#include + +namespace Msp { + +struct PlatformProcessInformation +{ + pid_t pid; +}; + +} // namespace Msp + +#endif diff --git a/source/core/windows/process.cpp b/source/core/windows/process.cpp new file mode 100644 index 0000000..7b1efd6 --- /dev/null +++ b/source/core/windows/process.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include "process.h" +#include "process_private.h" + +using namespace std; + +namespace { + +string quote_argument(const string &arg) +{ + string result; + bool need_quotes = false; + bool backslash = false; + for(string::const_iterator i=arg.begin(); i!=arg.end(); ++i) + { + if(*i=='\\') + backslash = true; + else if(*i=='"') + { + if(backslash) + result += '\\'; + result += '\\'; + } + else if(*i==' ') + need_quotes = true; + result += *i; + } + + if(need_quotes) + return "\""+result+"\""; + else + return result; +} + +} + +namespace Msp { + +Process::~Process() +{ + CloseHandle(priv->info.hProcess); + CloseHandle(priv->info.hThread); +} + +void Process::platform_get_self_info(Private &priv) +{ + priv.info.hProcess = GetCurrentProcess(); + priv.info.hThread = 0; + priv.info.dwProcessId = GetCurrentProcessId(); + priv.info.dwThreadId = 0; +} + +void Process::execute(const string &command, bool path_search, const Arguments &args) +{ + string cmdline = quote_argument(command); + for(Arguments::const_iterator i=args.begin(); i!=args.end(); ++i) + { + cmdline += ' '; + cmdline += quote_argument(*i); + } + + STARTUPINFO startup; + startup.cb = sizeof(STARTUPINFO); + startup.lpReserved = 0; + startup.lpDesktop = 0; + startup.lpTitle = 0; + startup.dwFlags = 0; + startup.cbReserved2 = 0; + startup.lpReserved2 = 0; + if(redirect) + { + startup.dwFlags |= STARTF_USESTDHANDLES; + HANDLE self_handle = self().priv->info.hProcess; + HANDLE cin_handle = (cin ? *cin->get_handle(IO::M_READ) : GetStdHandle(STD_INPUT_HANDLE)); + DuplicateHandle(self_handle, cin_handle, self_handle, &startup.hStdInput, 0, true, DUPLICATE_SAME_ACCESS); + HANDLE cout_handle = (cout ? *cout->get_handle(IO::M_WRITE) : GetStdHandle(STD_OUTPUT_HANDLE)); + DuplicateHandle(self_handle, cout_handle, self_handle, &startup.hStdOutput, 0, true, DUPLICATE_SAME_ACCESS); + HANDLE cerr_handle = (cerr ? *cerr->get_handle(IO::M_WRITE) : GetStdHandle(STD_ERROR_HANDLE)); + DuplicateHandle(self_handle, cerr_handle, self_handle, &startup.hStdError, 0, true, DUPLICATE_SAME_ACCESS); + } + const char *cmdptr = (path_search ? 0 : command.c_str()); + const char *wd = (work_dir.empty() ? 0 : work_dir.c_str()); + if(!CreateProcess(cmdptr, const_cast(cmdline.c_str()), 0, 0, false, 0, 0, wd, &startup, &priv->info)) + throw system_error("CreateProcess"); + // XXX Should we close the duplicated handles? What if CreateProcess fails? + + running = true; + + if(this==_self) + TerminateProcess(priv->info.hProcess, 0); +} + +bool Process::wait(bool block) +{ + if(!running) + throw logic_error("not running"); + + DWORD ret = WaitForSingleObject(priv->info.hProcess, (block ? INFINITE : 0)); + if(ret==WAIT_FAILED) + throw system_error("WaitForSingleObject"); + + if(ret==WAIT_OBJECT_0) + { + running = false; + finished = true; + } + + return finished; +} + +void Process::terminate() +{ + TerminateProcess(priv->info.hProcess, 1); +} + +void Process::kill() +{ + TerminateProcess(priv->info.hProcess, 1); +} + +void Process::interrupt() +{ + TerminateProcess(priv->info.hProcess, 1); +} + + +Process::Private::Private() +{ + info.hProcess = 0; + info.hThread = 0; + info.dwProcessId = 0; + info.dwThreadId = 0; +} + +} // namespace Msp diff --git a/source/core/windows/process_platform.h b/source/core/windows/process_platform.h new file mode 100644 index 0000000..4cb793a --- /dev/null +++ b/source/core/windows/process_platform.h @@ -0,0 +1,12 @@ +#ifndef MSP_CORE_PROCESS_PLATFORM_H_ +#define MSP_CORE_PROCESS_PLATFORM_H_ + +#include + +namespace Msp { + +typedef PROCESS_INFORMATION PlatformProcessInformation; + +} // namespace Msp + +#endif -- 2.43.0