Add a Process class for running and interfacing with other programs
authorMikko Rasa <tdb@tdb.fi>
Mon, 14 Oct 2013 19:23:11 +0000 (22:23 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 15 Oct 2013 11:50:53 +0000 (14:50 +0300)
source/core/process.cpp [new file with mode: 0644]
source/core/process.h [new file with mode: 0644]
source/core/process_private.h [new file with mode: 0644]
source/core/unix/process.cpp [new file with mode: 0644]
source/core/unix/process_platform.h [new file with mode: 0644]
source/core/windows/process.cpp [new file with mode: 0644]
source/core/windows/process_platform.h [new file with mode: 0644]

diff --git a/source/core/process.cpp b/source/core/process.cpp
new file mode 100644 (file)
index 0000000..dfbea34
--- /dev/null
@@ -0,0 +1,100 @@
+#include <msp/io/console.h>
+#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 (file)
index 0000000..5413587
--- /dev/null
@@ -0,0 +1,105 @@
+#ifndef MSP_CORE_PROCESS_H_
+#define MSP_CORE_PROCESS_H_
+
+#include <string>
+#include <vector>
+#include <msp/fs/path.h>
+#include <msp/io/base.h>
+
+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<std::string> 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 (file)
index 0000000..f6f4f2e
--- /dev/null
@@ -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 (file)
index 0000000..76141b0
--- /dev/null
@@ -0,0 +1,113 @@
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <msp/core/systemerror.h>
+#include <msp/fs/dir.h>
+#include <msp/io/console.h>
+#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<const char *> argv(args.size()+2);
+                       argv[0] = command.c_str();
+                       for(unsigned i=0; i<args.size(); ++i)
+                               argv[i+1] = args[i].c_str();
+                       argv[args.size()+1] = 0;
+
+                       if(redirect)
+                       {
+                               if(cin)
+                                       IO::cin.redirect(*cin);
+                               if(cout)
+                                       IO::cout.redirect(*cout);
+                               if(cerr)
+                                       IO::cerr.redirect(*cerr);
+                       }
+
+                       if(!work_dir.empty())
+                               FS::chdir(work_dir);
+
+                       if(path_search)
+                               execvp(command.c_str(), const_cast<char *const *>(&argv[0]));
+                       else
+                               execv(command.c_str(), const_cast<char *const *>(&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 (file)
index 0000000..69cf326
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef MSP_CORE_PROCESS_PLATFORM_H_
+#define MSP_CORE_PROCESS_PLATFORM_H_
+
+#include <sys/types.h>
+
+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 (file)
index 0000000..7b1efd6
--- /dev/null
@@ -0,0 +1,137 @@
+#include <windows.h>
+#include <msp/core/systemerror.h>
+#include <msp/io/handle_private.h>
+#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<char *>(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 (file)
index 0000000..4cb793a
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef MSP_CORE_PROCESS_PLATFORM_H_
+#define MSP_CORE_PROCESS_PLATFORM_H_
+
+#include <windows.h>
+
+namespace Msp {
+
+typedef PROCESS_INFORMATION PlatformProcessInformation;
+
+} // namespace Msp
+
+#endif