diff options
Diffstat (limited to 'paludis')
-rw-r--r-- | paludis/util/files.m4 | 1 | ||||
-rw-r--r-- | paludis/util/process-fwd.hh | 35 | ||||
-rw-r--r-- | paludis/util/process.cc | 355 | ||||
-rw-r--r-- | paludis/util/process.hh | 96 | ||||
-rw-r--r-- | paludis/util/process_TEST.cc | 149 |
5 files changed, 636 insertions, 0 deletions
diff --git a/paludis/util/files.m4 b/paludis/util/files.m4 index 9f8e2416b..11ca69c4d 100644 --- a/paludis/util/files.m4 +++ b/paludis/util/files.m4 @@ -62,6 +62,7 @@ add(`output_wrapper', `test', `testscript') add(`pimp', `hh', `impl') add(`pipe', `hh', `cc') add(`pretty_print', `hh', `cc', `test') +add(`process', `hh', `cc', `fwd', `test') add(`pty', `hh', `cc', `test') add(`random', `hh', `cc', `test') add(`realpath', `hh', `cc', `test', `testscript') diff --git a/paludis/util/process-fwd.hh b/paludis/util/process-fwd.hh new file mode 100644 index 000000000..35fcd2ca2 --- /dev/null +++ b/paludis/util/process-fwd.hh @@ -0,0 +1,35 @@ +/* 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 + */ + +#ifndef PALUDIS_GUARD_PALUDIS_UTIL_PROCESS_FWD_HH +#define PALUDIS_GUARD_PALUDIS_UTIL_PROCESS_FWD_HH 1 + +namespace paludis +{ + struct ProcessError; + + struct ProcessCommand; + + struct Process; + + struct RunningProcessThread; + struct RunningProcessHandle; +} + +#endif 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)); +} + diff --git a/paludis/util/process.hh b/paludis/util/process.hh new file mode 100644 index 000000000..b9515f8e8 --- /dev/null +++ b/paludis/util/process.hh @@ -0,0 +1,96 @@ +/* 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 + */ + +#ifndef PALUDIS_GUARD_PALUDIS_UTIL_PROCESS_HH +#define PALUDIS_GUARD_PALUDIS_UTIL_PROCESS_HH 1 + +#include <paludis/util/process-fwd.hh> +#include <paludis/util/attributes.hh> +#include <paludis/util/pimp.hh> +#include <paludis/util/exception.hh> + +#include <string> +#include <iosfwd> +#include <memory> +#include <initializer_list> + +#include <unistd.h> + +namespace paludis +{ + class PALUDIS_VISIBLE ProcessError : + public Exception + { + public: + ProcessError(const std::string &) throw (); + }; + + class PALUDIS_VISIBLE ProcessCommand : + private Pimp<ProcessCommand> + { + public: + /** + * List of arguments, one string per argv value. + **/ + explicit ProcessCommand(const std::initializer_list<std::string> &); + + ProcessCommand(ProcessCommand &&); + ~ProcessCommand(); + + ProcessCommand(const ProcessCommand &) = delete; + ProcessCommand & operator= (const ProcessCommand &) = delete; + + void exec() PALUDIS_ATTRIBUTE((noreturn)); + }; + + class PALUDIS_VISIBLE Process : + private Pimp<Process> + { + public: + explicit Process(ProcessCommand &&); + ~Process(); + + Process(const Process &) = delete; + Process & operator= (const Process &) = delete; + + RunningProcessHandle run() PALUDIS_ATTRIBUTE((warn_unused_result)); + + Process & capture_stdout(std::ostream &); + Process & capture_stderr(std::ostream &); + }; + + class PALUDIS_VISIBLE RunningProcessHandle : + private Pimp<RunningProcessHandle> + { + public: + RunningProcessHandle( + const pid_t, + std::unique_ptr<RunningProcessThread> &&); + + ~RunningProcessHandle(); + RunningProcessHandle(RunningProcessHandle &&); + + RunningProcessHandle(const RunningProcessHandle &) = delete; + RunningProcessHandle & operator= (const RunningProcessHandle &) = delete; + + int wait() PALUDIS_ATTRIBUTE((warn_unused_result)); + }; +} + +#endif diff --git a/paludis/util/process_TEST.cc b/paludis/util/process_TEST.cc new file mode 100644 index 000000000..9bf066610 --- /dev/null +++ b/paludis/util/process_TEST.cc @@ -0,0 +1,149 @@ +/* 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 <test/test_framework.hh> +#include <test/test_runner.hh> +#include <sstream> + +using namespace paludis; +using namespace test; + + +namespace test_cases +{ + struct TrueTest : TestCase + { + TrueTest() : TestCase("true") { } + + void run() + { + Process true_process(ProcessCommand({"true"})); + TEST_CHECK_EQUAL(true_process.run().wait(), 0); + } + } test_true; + + struct FalseTest : TestCase + { + FalseTest() : TestCase("false") { } + + void run() + { + Process false_process(ProcessCommand({"false"})); + TEST_CHECK_EQUAL(false_process.run().wait(), 1); + } + } test_false; + + struct NoWaitTest : TestCase + { + NoWaitTest() : TestCase("no wait") { } + + void run() + { + Process true_process(ProcessCommand({"true"})); + TEST_CHECK_THROWS({ RunningProcessHandle handle(true_process.run()); }, ProcessError); + } + } test_no_wait; + + struct TwoWaitTest : TestCase + { + TwoWaitTest() : TestCase("two wait") { } + + void run() + { + Process true_process(ProcessCommand({"true"})); + + RunningProcessHandle handle(true_process.run()); + TEST_CHECK_EQUAL(handle.wait(), 0); + TEST_CHECK_THROWS(int PALUDIS_ATTRIBUTE((unused)) x(handle.wait()), ProcessError); + } + } test_two_wait; + + struct GrabStdoutTest : TestCase + { + GrabStdoutTest() : TestCase("grab stdout") { } + + void run() + { + std::stringstream stdout_stream; + Process echo_process(ProcessCommand({"echo", "monkey"})); + echo_process.capture_stdout(stdout_stream); + + TEST_CHECK_EQUAL(echo_process.run().wait(), 0); + TEST_CHECK_EQUAL(stdout_stream.str(), "monkey\n"); + } + } test_grab_stdout; + + struct GrabStderrTest : TestCase + { + GrabStderrTest() : TestCase("grab stderr") { } + + void run() + { + std::stringstream stderr_stream; + Process echo_process(ProcessCommand({"sh", "-c", "echo monkey 1>&2"})); + echo_process.capture_stderr(stderr_stream); + + TEST_CHECK_EQUAL(echo_process.run().wait(), 0); + TEST_CHECK_EQUAL(stderr_stream.str(), "monkey\n"); + } + } test_grab_stderr; + + struct GrabStdoutStderrTest : TestCase + { + GrabStdoutStderrTest() : TestCase("grab stdout stderr") { } + + void run() + { + std::stringstream stdout_stream, stderr_stream; + Process echo_process(ProcessCommand({"sh", "-c", "echo monkey 1>&2 ; echo chimp"})); + echo_process.capture_stdout(stdout_stream); + echo_process.capture_stderr(stderr_stream); + + TEST_CHECK_EQUAL(echo_process.run().wait(), 0); + + TEST_CHECK_EQUAL(stdout_stream.str(), "chimp\n"); + TEST_CHECK_EQUAL(stderr_stream.str(), "monkey\n"); + } + } test_grab_stdout_stderr; + + struct GrabStdoutLongTest : TestCase + { + GrabStdoutLongTest() : TestCase("grab stdout long") { } + + void run() + { + std::stringstream stdout_stream; + Process echo_process(ProcessCommand({"seq", "1", "100000"})); + echo_process.capture_stdout(stdout_stream); + + TEST_CHECK_EQUAL(echo_process.run().wait(), 0); + + std::string s; + for (int x(1) ; x <= 100000 ; ++x) + { + TEST_CHECK(std::getline(stdout_stream, s)); + TEST_CHECK_STRINGIFY_EQUAL(s, stringify(x)); + } + + TEST_CHECK(! std::getline(stdout_stream, s)); + } + } test_grab_stdout_long; +} + |