aboutsummaryrefslogtreecommitdiff
path: root/paludis/util/process.cc
diff options
context:
space:
mode:
authorAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2010-08-19 17:02:21 +0100
committerAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2010-08-21 14:58:15 +0100
commitef7126699addb2a5a3bf74dfba5fd5c1f44b5b93 (patch)
tree6e6f5c1b1ccfc5c395ed254083a8e5f61bcb76f2 /paludis/util/process.cc
parentadda76229c6a59054e137fcc3120891d2c1aa9c8 (diff)
downloadpaludis-ef7126699addb2a5a3bf74dfba5fd5c1f44b5b93.tar.gz
paludis-ef7126699addb2a5a3bf74dfba5fd5c1f44b5b93.tar.xz
New, less horrible way of running processes
Diffstat (limited to 'paludis/util/process.cc')
-rw-r--r--paludis/util/process.cc355
1 files changed, 355 insertions, 0 deletions
diff --git a/paludis/util/process.cc b/paludis/util/process.cc
new file mode 100644
index 000000000..b6d83f364
--- /dev/null
+++ b/paludis/util/process.cc
@@ -0,0 +1,355 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2010 Ciaran McCreesh
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis 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 General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <paludis/util/process.hh>
+#include <paludis/util/pimp-impl.hh>
+#include <paludis/util/thread.hh>
+#include <paludis/util/pipe.hh>
+
+#include <iostream>
+#include <functional>
+#include <algorithm>
+#include <vector>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+
+using namespace paludis;
+
+ProcessError::ProcessError(const std::string & s) throw () :
+ Exception(s)
+{
+}
+
+namespace paludis
+{
+ template <>
+ struct Imp<ProcessCommand>
+ {
+ std::vector<std::string> args;
+
+ Imp(std::vector<std::string> && i) :
+ args(i)
+ {
+ }
+ };
+}
+
+ProcessCommand::ProcessCommand(const std::initializer_list<std::string> & i) :
+ Pimp<ProcessCommand>(std::vector<std::string>(i))
+{
+}
+
+ProcessCommand::ProcessCommand(ProcessCommand && other) :
+ Pimp<ProcessCommand>(std::move(other._imp->args))
+{
+}
+
+ProcessCommand::~ProcessCommand() = default;
+
+void
+ProcessCommand::exec()
+{
+ if (_imp->args.size() < 1)
+ throw ProcessError("No command specified");
+
+ /* no need to worry about free()ing this lot, since if our execvp fails we
+ * call _exit() shortly afterwards */
+
+ char ** argv(new char * [_imp->args.size() + 1]);
+ argv[_imp->args.size()] = 0;
+ for (auto v_begin(_imp->args.begin()), v(v_begin), v_end(_imp->args.end()) ;
+ v != v_end ; ++v)
+ {
+ argv[v - v_begin] = new char [v->length() + 1];
+ argv[v - v_begin][v->length()] = '\0';
+ std::copy(v->begin(), v->end(), argv[v - v_begin]);
+ }
+
+ execvp(_imp->args[0].c_str(), argv);
+
+ throw ProcessError("execvp failed");
+}
+
+namespace paludis
+{
+ struct RunningProcessThread
+ {
+ Pipe ctl_pipe;
+
+ std::ostream * capture_stdout;
+ std::unique_ptr<Pipe> capture_stdout_pipe;
+
+ std::ostream * capture_stderr;
+ std::unique_ptr<Pipe> capture_stderr_pipe;
+
+ /* must be last, so the thread gets join()ed before its FDs vanish */
+ std::unique_ptr<Thread> thread;
+
+ RunningProcessThread() :
+ ctl_pipe(true),
+ capture_stdout(0),
+ capture_stderr(0)
+ {
+ }
+
+ void thread_func();
+
+ void start();
+ };
+}
+
+void
+RunningProcessThread::thread_func()
+{
+ bool done(false);
+ while (! done)
+ {
+ fd_set read_fds, write_fds;
+ int max_fd(0);
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+
+ FD_SET(ctl_pipe.read_fd(), &read_fds);
+ max_fd = std::max(max_fd, ctl_pipe.read_fd());
+
+ if (capture_stdout_pipe)
+ {
+ FD_SET(capture_stdout_pipe->read_fd(), &read_fds);
+ max_fd = std::max(max_fd, capture_stdout_pipe->read_fd());
+ }
+
+ if (capture_stderr_pipe)
+ {
+ FD_SET(capture_stderr_pipe->read_fd(), &read_fds);
+ max_fd = std::max(max_fd, capture_stderr_pipe->read_fd());
+ }
+
+ int retval(::pselect(max_fd + 1, &read_fds, &write_fds, 0, 0, 0));
+ if (-1 == retval)
+ throw ProcessError("pselect() failed");
+
+ bool done_anything(false);
+ char buf[4096];
+
+ if (capture_stdout_pipe && FD_ISSET(capture_stdout_pipe->read_fd(), &read_fds))
+ {
+ int n(::read(capture_stdout_pipe->read_fd(), &buf, sizeof(buf)));
+ if (-1 == n)
+ throw ProcessError("read() capture_stdout_pipe read_fd failed");
+ else if (0 != n)
+ capture_stdout->write(buf, n);
+ done_anything = true;
+ }
+
+ if (capture_stderr_pipe && FD_ISSET(capture_stderr_pipe->read_fd(), &read_fds))
+ {
+ int n(::read(capture_stderr_pipe->read_fd(), &buf, sizeof(buf)));
+ if (-1 == n)
+ throw ProcessError("read() capture_stderr_pipe read_fd failed");
+ else if (0 != n)
+ capture_stderr->write(buf, n);
+ done_anything = true;
+ }
+
+ if (done_anything)
+ continue;
+
+ /* don't do this until nothing else has anything to do */
+ if (FD_ISSET(ctl_pipe.read_fd(), &read_fds))
+ {
+ char c('?');
+ if (1 != ::read(ctl_pipe.read_fd(), &c, 1))
+ throw ProcessError("read() on our ctl pipe failed");
+ else if (c != 'x')
+ throw ProcessError("read() on our ctl pipe gave '" + std::string(1, c) + "' not 'x'");
+ done = true;
+ }
+ }
+}
+
+void
+RunningProcessThread::start()
+{
+ thread.reset(new Thread(std::bind(&RunningProcessThread::thread_func, this)));
+}
+
+namespace paludis
+{
+ template <>
+ struct Imp<Process>
+ {
+ ProcessCommand command;
+
+ bool need_thread;
+ std::ostream * capture_stdout;
+ std::ostream * capture_stderr;
+
+ Imp(ProcessCommand && c) :
+ command(std::move(c)),
+ need_thread(false),
+ capture_stdout(0),
+ capture_stderr(0)
+ {
+ }
+ };
+}
+
+Process::Process(ProcessCommand && c) :
+ Pimp<Process>(c)
+{
+}
+
+Process::~Process()
+{
+}
+
+RunningProcessHandle
+Process::run()
+{
+ std::unique_ptr<RunningProcessThread> thread;
+ if (_imp->need_thread)
+ {
+ thread.reset(new RunningProcessThread{});
+
+ if (_imp->capture_stdout)
+ {
+ thread->capture_stdout = _imp->capture_stdout;
+ thread->capture_stdout_pipe.reset(new Pipe(true));
+ }
+
+ if (_imp->capture_stderr)
+ {
+ thread->capture_stderr = _imp->capture_stderr;
+ thread->capture_stderr_pipe.reset(new Pipe(true));
+ }
+ }
+
+ pid_t child(fork());
+ if (-1 == child)
+ throw ProcessError("fork() failed");
+ else if (0 == child)
+ {
+ try
+ {
+ if (thread && thread->capture_stdout_pipe)
+ {
+ if (-1 == ::dup2(thread->capture_stdout_pipe->write_fd(), STDOUT_FILENO))
+ throw ProcessError("dup2() failed");
+ }
+
+ if (thread && thread->capture_stderr_pipe)
+ {
+ if (-1 == ::dup2(thread->capture_stderr_pipe->write_fd(), STDERR_FILENO))
+ throw ProcessError("dup2() failed");
+ }
+
+ _imp->command.exec();
+ }
+ catch (const ProcessError & e)
+ {
+ std::cerr << "Eek! child thread got error '" << e.message() << "'" << std::endl;
+ }
+ _exit(1);
+ }
+ else
+ {
+ if (thread)
+ thread->start();
+ return RunningProcessHandle(child, thread);
+ }
+}
+
+Process &
+Process::capture_stdout(std::ostream & s)
+{
+ _imp->need_thread = true;
+ _imp->capture_stdout = &s;
+ return *this;
+}
+
+Process &
+Process::capture_stderr(std::ostream & s)
+{
+ _imp->need_thread = true;
+ _imp->capture_stderr = &s;
+ return *this;
+}
+
+namespace paludis
+{
+ template <>
+ struct Imp<RunningProcessHandle>
+ {
+ pid_t pid;
+ std::unique_ptr<RunningProcessThread> thread;
+
+ Imp(pid_t p, std::unique_ptr<RunningProcessThread> && t) :
+ pid(p),
+ thread(std::move(t))
+ {
+ }
+ };
+}
+
+RunningProcessHandle::RunningProcessHandle(pid_t p, std::unique_ptr<RunningProcessThread> && t) :
+ Pimp<RunningProcessHandle>(p, t)
+{
+}
+
+RunningProcessHandle::~RunningProcessHandle()
+{
+ if (-1 != _imp->pid)
+ {
+ int dummy PALUDIS_ATTRIBUTE((unused)) (wait());
+ throw ProcessError("Didn't wait()");
+ }
+}
+
+RunningProcessHandle::RunningProcessHandle(RunningProcessHandle && other) :
+ Pimp<RunningProcessHandle>(other._imp->pid, other._imp->thread)
+{
+ _imp->pid = -1;
+}
+
+int
+RunningProcessHandle::wait()
+{
+ if (-1 == _imp->pid)
+ throw ProcessError("Already wait()ed");
+
+ int status(0);
+ if (-1 == ::waitpid(_imp->pid, &status, 0))
+ throw ProcessError("waitpid() returned -1");
+ _imp->pid = -1;
+
+ if (_imp->thread)
+ {
+ char c('x');
+ if (1 != ::write(_imp->thread->ctl_pipe.write_fd(), &c, 1))
+ throw ProcessError("write() on our ctl pipe failed");
+
+ _imp->thread.reset();
+ }
+
+ return (WIFSIGNALED(status) ? WTERMSIG(status) + 128 : WEXITSTATUS(status));
+}
+