Initial work

This commit is contained in:
Simon Fels 2016-05-26 22:38:57 +02:00 committed by Simon Fels
commit 3222551702
374 changed files with 40290 additions and 0 deletions

View file

@ -0,0 +1,47 @@
# Copyright © 2013 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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, see <http://www.gnu.org/licenses/>.
#
# Authored by: Thomas Voss <thomas.voss@canonical.com>
add_library(
process-cpp
core/posix/backtrace.h
core/posix/backtrace.cpp
core/posix/child_process.cpp
core/posix/exec.cpp
core/posix/fork.cpp
core/posix/process.cpp
core/posix/process_group.cpp
core/posix/signal.cpp
core/posix/signalable.cpp
core/posix/standard_stream.cpp
core/posix/wait.cpp
core/posix/this_process.cpp
core/posix/linux/proc/process/oom_adj.cpp
core/posix/linux/proc/process/oom_score.cpp
core/posix/linux/proc/process/oom_score_adj.cpp
core/posix/linux/proc/process/stat.cpp
core/testing/cross_process_sync.cpp
core/testing/fork_and_run.cpp
)
target_link_libraries(
process-cpp
${Boost_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)

View file

@ -0,0 +1,24 @@
# Copyright © 2013 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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, see <http://www.gnu.org/licenses/>.
#
# Authored by: Thomas Voss <thomas.voss@canonical.com>
add_library(
posix-process
process.cpp
)
add_subdirectory(linux)

View file

@ -0,0 +1,153 @@
/*
* Copyright © 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include "backtrace.h"
#include <cxxabi.h>
#include <execinfo.h>
namespace bt = core::posix::backtrace;
namespace impl
{
std::tuple<std::string, bool> demangle(const std::string& symbol)
{
int status = 1;
auto result = abi::__cxa_demangle(symbol.c_str(),
nullptr,
nullptr,
&status);
if (!result || status != 0)
{
return std::make_tuple(std::string(), false);
}
std::string s{result};
::free(result);
return std::make_tuple(s, true);
}
struct Frame : public bt::Frame
{
struct Symbol : public bt::Frame::Symbol
{
Symbol(const char* symbol) : raw_(symbol)
{
auto first = raw_.find_first_of("(");
auto last = raw_.find_last_of(")");
if (first != std::string::npos && last != std::string::npos)
{
auto mangled_symbol = raw_.substr(first+1,
(last-1) - (first+1));
auto plus = mangled_symbol.find_first_of("+");
if (plus != std::string::npos)
mangled_symbol.erase(plus);
std::tie(demangled_, is_cxx_) = demangle(mangled_symbol);
if (!is_cxx_)
demangled_ = raw_;
}
}
bool is_cxx() const
{
return is_cxx_;
}
std::string demangled() const
{
return demangled_;
}
std::string raw() const
{
return raw_;
}
std::string raw_;
std::string demangled_;
bool is_cxx_ = false;
};
std::size_t depth_;
void* frame_pointer_;
Symbol symbol_;
Frame(std::size_t depth, void* frame_pointer, const char* symbol)
: depth_(depth),
frame_pointer_(frame_pointer),
symbol_(symbol)
{
}
std::size_t depth() const
{
return depth_;
}
virtual void* frame_pointer() const
{
return frame_pointer_;
}
const Symbol& symbol() const
{
return symbol_;
}
};
}
std::shared_ptr<bt::Frame::Symbol> bt::Frame::Symbol::for_testing_from_raw_symbol(const char* symbol)
{
return std::shared_ptr<bt::Frame::Symbol>(new impl::Frame::Symbol(symbol));
}
void bt::visit_with_handler(const bt::FrameHandler& handler)
{
static const unsigned int max_frames=64;
void *frames[max_frames];
auto frame_count = ::backtrace(frames, max_frames);
auto symbols = ::backtrace_symbols(frames, frame_count);
struct Scope
{
Scope(char** symbols) : symbols(symbols)
{
}
~Scope()
{
::free(symbols);
}
char** symbols = nullptr;
} scope{symbols};
for (int i = 0; i < frame_count; i++)
{
impl::Frame frame(i, frames[i], symbols[i]);
if (!handler(frame))
return;
}
}

View file

@ -0,0 +1,122 @@
/*
* Copyright © 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#ifndef CORE_POSIX_BACKTRACE_H_
#define CORE_POSIX_BACKTRACE_H_
#include <core/posix/visibility.h>
#include <functional>
#include <memory>
#include <string>
namespace core
{
namespace posix
{
namespace backtrace
{
/**
* @brief The Frame class models an individual frame of a backtrace.
*/
class Frame
{
public:
/**
* @brief The Symbol class models the symbolic representation of a frame pointer.
*/
class Symbol
{
public:
static std::shared_ptr<Symbol> for_testing_from_raw_symbol(const char* symbol);
Symbol(const Symbol&) = delete;
virtual ~Symbol() = default;
Symbol& operator=(const Symbol&) = delete;
/**
* @brief is_cxx checks whether the symbol refers to a mangled C++ symbol.
* @return true iff the symbol refers to a mangled C++ symbol.
*/
virtual bool is_cxx() const = 0;
/**
* @brief demangled returns the demangled C++ symbol name or raw.
*/
virtual std::string demangled() const = 0;
/**
* @brief raw The raw symbolic representation of a frame pointer.
* @return
*/
virtual std::string raw() const = 0;
protected:
Symbol() = default;
};
Frame(const Frame&) = delete;
virtual ~Frame() = default;
Frame& operator=(const Frame&) = delete;
/**
* @brief depth returns the depth of this frame in the overall backtrace.
*/
virtual std::size_t depth() const = 0;
/**
* @brief frame_pointer returns the the raw frame pointer of this frame.
* @return
*/
virtual void* frame_pointer() const = 0;
/**
* @brief symbol returns the symbolic representation of this frame.
*/
virtual const Symbol& symbol() const = 0;
protected:
Frame() = default;
};
/**
* @brief FrameHandler is the functor invoked for every frame of a backtrace.
*
* A FrameHandler should return true if it wants to continue walking the stack
* or false otherwise.
*/
typedef std::function<bool(const Frame& frame)> FrameHandler;
/**
*@brief visit_with_handler iterates the backtrace of the calling program,
*invoking the handler for every frame.
*
* A FrameHandler should return true if it wants to continue walking the stack
* or false otherwise
*
* @param handler The handler invoked for every frame.
*/
void CORE_POSIX_DLL_PUBLIC visit_with_handler(const FrameHandler& handler);
}
}
}
#endif // CORE_POSIX_BACKTRACE_H_

View file

@ -0,0 +1,397 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/child_process.h>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <atomic>
#include <fstream>
#include <mutex>
#include <unordered_map>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/signalfd.h>
namespace io = boost::iostreams;
namespace
{
struct DeathObserverImpl : public core::posix::ChildProcess::DeathObserver
{
DeathObserverImpl(const std::shared_ptr<core::posix::SignalTrap>& trap)
: on_sig_child_connection
{
trap->signal_raised().connect([this](core::posix::Signal signal)
{
switch (signal)
{
case core::posix::Signal::sig_chld:
on_sig_child();
break;
default:
break;
}
})
}
{
if (!trap->has(core::posix::Signal::sig_chld))
throw std::logic_error(
"DeathObserver::DeathObserverImpl: Given SignalTrap"
" instance does not trap Signal::sig_chld.");
}
bool add(const core::posix::ChildProcess& process) override
{
if (process.pid() == -1)
return false;
std::lock_guard<std::mutex> lg(guard);
bool added = false;
auto new_process = std::make_pair(process.pid(), process);
std::tie(std::ignore, added) = children.insert(new_process);
if (added)
{
// The process may have died between it's instantiation and it
// being added to the children map. Check that it's still alive.
int status{-1};
if (::waitpid(process.pid(), &status, WNOHANG) != 0) // child no longer alive
{
// we missed the SIGCHLD signal so we must now manually
// inform our subscribers.
signals.child_died(new_process.second);
children.erase(new_process.first);
return false;
}
}
return added;
}
bool has(const core::posix::ChildProcess& process) const override
{
std::lock_guard<std::mutex> lg(guard);
return children.count(process.pid()) > 0;
}
const core::Signal<core::posix::ChildProcess>& child_died() const override
{
return signals.child_died;
}
void on_sig_child() override
{
pid_t pid{-1}; int status{-1};
while (true)
{
pid = ::waitpid(0, &status, WNOHANG);
if (pid == -1)
{
if (errno == ECHILD)
{
break; // No more children
}
continue; // Ignore stray SIGCHLD signals
}
else if (pid == 0)
{
break; // No more children
}
else
{
std::lock_guard<std::mutex> lg(guard);
auto it = children.find(pid);
if (it != children.end())
{
if (WIFSIGNALED(status) || WIFEXITED(status))
{
signals.child_died(it->second);
children.erase(it);
}
}
}
}
}
mutable std::mutex guard;
std::unordered_map<pid_t, core::posix::ChildProcess> children;
core::ScopedConnection on_sig_child_connection;
struct
{
core::Signal<core::posix::ChildProcess> child_died;
} signals;
};
}
std::unique_ptr<core::posix::ChildProcess::DeathObserver>
core::posix::ChildProcess::DeathObserver::create_once_with_signal_trap(
std::shared_ptr<core::posix::SignalTrap> trap)
{
static std::atomic<bool> has_been_created_once{false};
if (has_been_created_once.exchange(true))
throw std::runtime_error
{
"DeathObserver::create_once_with_signal_trap: "
"Cannot create more than one instance."
};
try
{
std::unique_ptr<core::posix::ChildProcess::DeathObserver> result
{
new DeathObserverImpl{trap}
};
return result;
} catch(...)
{
// We make sure that a throwing c'tor does not impact our ability to
// retry creation of a DeathObserver instance.
has_been_created_once.store(false);
std::rethrow_exception(std::current_exception());
}
assert(false && "We should never reach here.");
// Silence the compiler.
return std::unique_ptr<core::posix::ChildProcess::DeathObserver>{};
}
namespace core
{
namespace posix
{
ChildProcess::Pipe ChildProcess::Pipe::invalid()
{
static Pipe p;
static std::once_flag flag;
std::call_once(flag, [&]() { p.close_read_fd(); p.close_write_fd(); });
return p;
}
ChildProcess::Pipe::Pipe()
{
int rc = ::pipe(fds);
if (rc == -1)
throw std::system_error(errno, std::system_category());
}
ChildProcess::Pipe::Pipe(const ChildProcess::Pipe& rhs) : fds{-1, -1}
{
if (rhs.fds[0] != -1)
fds[0] = ::dup(rhs.fds[0]);
if (rhs.fds[1] != -1)
fds[1] = ::dup(rhs.fds[1]);
}
ChildProcess::Pipe::~Pipe()
{
if (fds[0] != -1)
::close(fds[0]);
if (fds[1] != -1)
::close(fds[1]);
}
int ChildProcess::Pipe::read_fd() const
{
return fds[0];
}
void ChildProcess::Pipe::close_read_fd()
{
if (fds[0] != -1)
{
::close(fds[0]);
fds[0] = -1;
}
}
int ChildProcess::Pipe::write_fd() const
{
return fds[1];
}
void ChildProcess::Pipe::close_write_fd()
{
if (fds[1] != -1)
{
::close(fds[1]);
fds[1] = -1;
}
}
ChildProcess::Pipe& ChildProcess::Pipe::operator=(const ChildProcess::Pipe& rhs)
{
if (fds[0] != -1)
::close(fds[0]);
if (fds[1] != -1)
::close(fds[1]);
if (rhs.fds[0] != -1)
fds[0] = ::dup(rhs.fds[0]);
else
fds[0] = -1;
if (rhs.fds[1] != -1)
fds[1] = ::dup(rhs.fds[1]);
else
fds[1] = -1;
return *this;
}
struct ChildProcess::Private
{
// stdin and stdout are always "relative" to the childprocess, i.e., we
// write to stdin of the child process and read from its stdout.
Private(pid_t pid,
const ChildProcess::Pipe& stderr,
const ChildProcess::Pipe& stdin,
const ChildProcess::Pipe& stdout)
: pipes{stderr, stdin, stdout},
serr(pipes.stderr.read_fd(), io::never_close_handle),
sin(pipes.stdin.write_fd(), io::never_close_handle),
sout(pipes.stdout.read_fd(), io::never_close_handle),
cerr(&serr),
cin(&sin),
cout(&sout),
original_parent_pid(::getpid()),
original_child_pid(pid)
{
}
~Private()
{
// Check if we are in the original parent process.
if (original_parent_pid == getpid())
{
// If so, check if we are considering a valid pid here.
// If so, we kill the original child.
if (original_child_pid != -1)
::kill(original_child_pid, SIGKILL);
}
}
struct
{
ChildProcess::Pipe stdin;
ChildProcess::Pipe stdout;
ChildProcess::Pipe stderr;
} pipes;
io::stream_buffer<io::file_descriptor_source> serr;
io::stream_buffer<io::file_descriptor_sink> sin;
io::stream_buffer<io::file_descriptor_source> sout;
std::istream cerr;
std::ostream cin;
std::istream cout;
// We need to store the original parent pid as we might have been forked
// and with our automatic cleanup in place, it might happen that the d'tor
// is called from the child process.
pid_t original_parent_pid;
pid_t original_child_pid;
};
ChildProcess ChildProcess::invalid()
{
// We take the init process as child.
static const pid_t invalid_pid = 1;
return ChildProcess(invalid_pid, Pipe::invalid(), Pipe::invalid(), Pipe::invalid());
}
ChildProcess::ChildProcess(pid_t pid,
const ChildProcess::Pipe& stdin_pipe,
const ChildProcess::Pipe& stdout_pipe,
const ChildProcess::Pipe& stderr_pipe)
: Process(pid),
d(new Private{pid, stdin_pipe, stdout_pipe, stderr_pipe})
{
}
ChildProcess::~ChildProcess()
{
}
wait::Result ChildProcess::wait_for(const wait::Flags& flags)
{
int status = -1;
pid_t result_pid = ::waitpid(pid(), std::addressof(status), static_cast<int>(flags));
if (result_pid == -1)
throw std::system_error(errno, std::system_category());
wait::Result result;
if (result_pid == 0)
{
result.status = wait::Result::Status::no_state_change;
return result;
}
if (WIFEXITED(status))
{
result.status = wait::Result::Status::exited;
result.detail.if_exited.status = static_cast<exit::Status>(WEXITSTATUS(status));
} else if (WIFSIGNALED(status))
{
result.status = wait::Result::Status::signaled;
result.detail.if_signaled.signal = static_cast<Signal>(WTERMSIG(status));
result.detail.if_signaled.core_dumped = WCOREDUMP(status);
} else if (WIFSTOPPED(status))
{
result.status = wait::Result::Status::stopped;
result.detail.if_stopped.signal = static_cast<Signal>(WSTOPSIG(status));
} else if (WIFCONTINUED(status))
{
result.status = wait::Result::Status::continued;
}
return result;
}
std::istream& ChildProcess::cerr()
{
return d->cerr;
}
std::ostream& ChildProcess::cin()
{
return d->cin;
}
std::istream& ChildProcess::cout()
{
return d->cout;
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/exec.h>
#include <core/posix/fork.h>
#include <core/posix/standard_stream.h>
#include <iostream>
#include <cstring>
#include <unistd.h>
namespace core
{
namespace posix
{
ChildProcess exec(const std::string& fn,
const std::vector<std::string>& argv,
const std::map<std::string, std::string>& env,
const StandardStream& flags)
{
std::function<void()> null_function = [](){};
return exec(fn, argv, env, flags, null_function);
}
ChildProcess exec(const std::string& fn,
const std::vector<std::string>& argv,
const std::map<std::string, std::string>& env,
const StandardStream& flags,
const std::function<void()>& child_setup)
{
return posix::fork([fn, argv, env, child_setup]()
{
char** it; char** pargv; char** penv;
it = pargv = new char*[argv.size()+2];
*it = ::strdup(fn.c_str());
it++;
for (auto element : argv)
{
*it = ::strdup(element.c_str());
it++;
}
*it = nullptr;
it = penv = new char*[env.size()+1];
for (auto pair : env)
{
*it = ::strdup((pair.first + "=" + pair.second).c_str());
it++;
}
*it = nullptr;
child_setup();
return static_cast<posix::exit::Status>(execve(fn.c_str(), pargv, penv));
}, flags);
}
}
}

View file

@ -0,0 +1,180 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/exit.h>
#include <core/posix/fork.h>
#include "backtrace.h"
#include <iomanip>
#include <iostream>
#include <system_error>
#include <unistd.h>
namespace
{
void redirect_stream_to_fd(int fd, int stream)
{
auto rc = ::dup2(fd, stream);
if (rc == -1)
throw std::system_error(errno, std::system_category());
}
void print_backtrace(std::ostream& out, const std::string& line_prefix)
{
core::posix::backtrace::visit_with_handler([&out, line_prefix](const core::posix::backtrace::Frame& frame)
{
out << line_prefix << std::dec << std::setw(2) << frame.depth() << "@" << std::hex << std::setw(14) << frame.frame_pointer() << ": "
<< (frame.symbol().is_cxx() ? frame.symbol().demangled() : frame.symbol().raw()) << std::endl;
return true;
});
}
}
namespace core
{
namespace posix
{
bool is_child(pid_t pid) { return pid == 0; }
ChildProcess fork(const std::function<posix::exit::Status()>& main,
const StandardStream& flags)
{
ChildProcess::Pipe stdin_pipe{ChildProcess::Pipe::invalid()};
ChildProcess::Pipe stdout_pipe{ChildProcess::Pipe::invalid()};
ChildProcess::Pipe stderr_pipe{ChildProcess::Pipe::invalid()};
if ((flags & StandardStream::stdin) != StandardStream::empty)
stdin_pipe = ChildProcess::Pipe();
if ((flags & StandardStream::stdout) != StandardStream::empty)
stdout_pipe = ChildProcess::Pipe();
if ((flags & StandardStream::stderr) != StandardStream::empty)
stderr_pipe = ChildProcess::Pipe();
pid_t pid = ::fork();
if (pid == -1)
throw std::system_error(errno, std::system_category());
if (is_child(pid))
{
posix::exit::Status result = posix::exit::Status::failure;
try
{
stdin_pipe.close_write_fd();
stdout_pipe.close_read_fd();
stderr_pipe.close_read_fd();
// We replace stdin and stdout of the child process first:
if ((flags & StandardStream::stdin) != StandardStream::empty)
redirect_stream_to_fd(stdin_pipe.read_fd(), STDIN_FILENO);
if ((flags & StandardStream::stdout) != StandardStream::empty)
redirect_stream_to_fd(stdout_pipe.write_fd(), STDOUT_FILENO);
if ((flags & StandardStream::stderr) != StandardStream::empty)
redirect_stream_to_fd(stderr_pipe.write_fd(), STDERR_FILENO);
result = main();
} catch(const std::exception& e)
{
std::cerr << "core::posix::fork(): An unhandled std::exception occured in the child process:" << std::endl
<< " what(): " << e.what() << std::endl;
print_backtrace(std::cerr, " ");
} catch(...)
{
std::cerr << "core::posix::fork(): An unhandled exception occured in the child process." << std::endl;
print_backtrace(std::cerr, " ");
}
// We have to ensure that we exit here. Otherwise, we run into
// all sorts of weird issues.
::exit(static_cast<int>(result));
}
// We are in the parent process, and create a process object
// corresponding to the newly forked process.
stdin_pipe.close_read_fd();
stdout_pipe.close_write_fd();
stderr_pipe.close_write_fd();
return ChildProcess(pid,
stdin_pipe,
stdout_pipe,
stderr_pipe);
}
ChildProcess vfork(const std::function<posix::exit::Status()>& main,
const StandardStream& flags)
{
ChildProcess::Pipe stdin_pipe, stdout_pipe, stderr_pipe;
pid_t pid = ::vfork();
if (pid == -1)
throw std::system_error(errno, std::system_category());
if (is_child(pid))
{
posix::exit::Status result = posix::exit::Status::failure;
try
{
// We replace stdin and stdout of the child process first:
stdin_pipe.close_write_fd();
stdout_pipe.close_read_fd();
stderr_pipe.close_read_fd();
// We replace stdin and stdout of the child process first:
if ((flags & StandardStream::stdin) != StandardStream::empty)
redirect_stream_to_fd(stdin_pipe.read_fd(), STDIN_FILENO);
if ((flags & StandardStream::stdout) != StandardStream::empty)
redirect_stream_to_fd(stdout_pipe.write_fd(), STDOUT_FILENO);
if ((flags & StandardStream::stderr) != StandardStream::empty)
redirect_stream_to_fd(stderr_pipe.write_fd(), STDERR_FILENO);
result = main();
} catch(const std::exception& e)
{
std::cerr << "core::posix::fork(): An unhandled std::exception occured in the child process:" << std::endl
<< " what(): " << e.what() << std::endl;
print_backtrace(std::cerr, " ");
} catch(...)
{
std::cerr << "core::posix::fork(): An unhandled exception occured in the child process." << std::endl;
print_backtrace(std::cerr, " ");
}
// We have to ensure that we exit here. Otherwise, we run into
// all sorts of weird issues.
::exit(static_cast<int>(result));
}
// We are in the parent process, and create a process object
// corresponding to the newly forked process.
// Close the parent's pipe end
stdin_pipe.close_read_fd();
stdout_pipe.close_write_fd();
stderr_pipe.close_write_fd();
return ChildProcess(pid,
stdin_pipe,
stdout_pipe,
stderr_pipe);
}
}
}

View file

@ -0,0 +1,28 @@
# Copyright © 2013 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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, see <http://www.gnu.org/licenses/>.
#
# Authored by: Thomas Voss <thomas.voss@canonical.com>
add_library(
linux-process
process.cpp
)
target_link_libraries(
linux-process
posix-process
)

View file

@ -0,0 +1,81 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/linux/proc/process/oom_adj.h>
#include <core/posix/process.h>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <linux/oom.h>
namespace core
{
namespace posix
{
namespace linux
{
namespace proc
{
namespace process
{
int OomAdj::disable_value()
{
return OOM_DISABLE;
}
int OomAdj::min_value()
{
return OOM_ADJUST_MIN;
}
int OomAdj::max_value()
{
return OOM_ADJUST_MAX;
}
const posix::Process& operator>>(const posix::Process& process, OomAdj& adj)
{
std::stringstream ss; ss << "/proc/" << process.pid() << "/oom_adj";
std::ifstream in(ss.str());
in >> adj.value;
return process;
}
const posix::Process& operator<<(const posix::Process& process, const OomAdj& adj)
{
if (!adj.is_valid())
throw std::logic_error("Value for adjusting the oom score is invalid.");
std::stringstream ss; ss << "/proc/" << process.pid() << "/oom_adj";
std::ofstream out(ss.str());
out << adj.value;
return process;
}
}
}
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/linux/proc/process/oom_score.h>
#include <core/posix/process.h>
#include <fstream>
#include <sstream>
namespace core
{
namespace posix
{
namespace linux
{
namespace proc
{
namespace process
{
const posix::Process& operator>>(const posix::Process& process, OomScore& score)
{
std::stringstream ss; ss << "/proc/" << process.pid() << "/oom_score";
std::ifstream in(ss.str());
in >> score.value;
return process;
}
}
}
}
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/linux/proc/process/oom_score_adj.h>
#include <core/posix/process.h>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <linux/oom.h>
namespace core
{
namespace posix
{
namespace linux
{
namespace proc
{
namespace process
{
int OomScoreAdj::min_value()
{
return OOM_SCORE_ADJ_MIN;
}
int OomScoreAdj::max_value()
{
return OOM_SCORE_ADJ_MAX;
}
const posix::Process& operator>>(const posix::Process& process, OomScoreAdj& score_adj)
{
std::stringstream ss; ss << "/proc/" << process.pid() << "/oom_score_adj";
std::ifstream in(ss.str());
in >> score_adj.value;
return process;
}
const posix::Process& operator<<(const posix::Process& process, const OomScoreAdj& score_adj)
{
if (!score_adj.is_valid())
throw std::logic_error("Value for adjusting the oom score is invalid.");
std::stringstream ss; ss << "/proc/" << process.pid() << "/oom_score_adj";
std::ofstream out(ss.str());
out << score_adj.value;
return process;
}
}
}
}
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/linux/proc/process/stat.h>
#include <core/posix/process.h>
#include <fstream>
#include <istream>
#include <sstream>
namespace core
{
namespace posix
{
namespace linux
{
namespace proc
{
namespace process
{
std::istream& operator>>(std::istream& in, State& state)
{
char c; in >> c; state = static_cast<State>(c);
return in;
}
std::istream& operator>>(std::istream& in, Stat& stat)
{
in >> stat.pid
>> stat.executable
>> stat.state
>> stat.parent
>> stat.process_group
>> stat.session_id
>> stat.tty_nr
>> stat.controlling_process_group
>> stat.kernel_flags
>> stat.minor_faults_count
>> stat.minor_faults_count_by_children
>> stat.major_faults_count
>> stat.major_faults_count_by_children
>> stat.time.user
>> stat.time.system
>> stat.time.user_for_children
>> stat.time.system_for_children
>> stat.priority
>> stat.nice
>> stat.thread_count
>> stat.time_before_next_sig_alarm
>> stat.start_time
>> stat.size.virt
>> stat.size.resident_set
>> stat.size.resident_set_limit
>> stat.addresses.start_code
>> stat.addresses.end_code
>> stat.addresses.start_stack
>> stat.addresses.stack_pointer
>> stat.addresses.instruction_pointer
>> stat.signals.pending
>> stat.signals.blocked
>> stat.signals.ignored
>> stat.signals.caught
>> stat.channel
>> stat.swap_count
>> stat.swap_count_children
>> stat.exit_signal
>> stat.cpu_count
>> stat.realtime_priority
>> stat.scheduling_policy
>> stat.aggregated_block_io_delays
>> stat.guest_time
>> stat.guest_time_children;
return in;
}
const posix::Process& operator>>(const posix::Process& process, Stat& stat)
{
std::stringstream ss; ss << "/proc/" << process.pid() << "/stat";
std::ifstream in(ss.str());
in >> stat;
return process;
}
}
}
}
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/linux/proc/process/state.h>
#include <core/posix/process.h>
#include <istream>
namespace core
{
namespace posix
{
namespace linux
{
namespace proc
{
namespace process
{
}
}
}
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/process.h>
#include <core/posix/signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
namespace core
{
namespace posix
{
struct Process::Private
{
pid_t pid;
};
Process Process::invalid()
{
static const pid_t invalid_pid = 0;
Process p(invalid_pid);
p.d->pid = -1;
return p;
}
Process::Process(pid_t pid)
: Signalable(pid),
d(new Private{pid})
{
if (pid < 0)
throw std::runtime_error("Cannot construct instance for invalid pid.");
}
Process::~Process() noexcept
{
}
pid_t Process::pid() const
{
return d->pid;
}
ProcessGroup Process::process_group_or_throw() const
{
pid_t pgid = ::getpgid(pid());
if (pgid == -1)
throw std::system_error(errno, std::system_category());
return ProcessGroup(pgid);
}
ProcessGroup Process::process_group(std::error_code& se) const noexcept(true)
{
pid_t pgid = ::getpgid(pid());
if (pgid == -1)
{
se = std::error_code(errno, std::system_category());
}
return ProcessGroup(pgid);
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/process_group.h>
namespace core
{
namespace posix
{
struct ProcessGroup::Private
{
pid_t id;
};
pid_t ProcessGroup::id() const
{
return d->id;
}
ProcessGroup::ProcessGroup(pid_t id)
: Signalable(-id), // We rely on ::kill to deliver signals, thus negate the id (see man 2 kill).
d(new Private{id})
{
}
ProcessGroup ProcessGroup::invalid()
{
static const pid_t invalid_pid = 1;
return ProcessGroup(invalid_pid);
}
}
}

View file

@ -0,0 +1,221 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/signalable.h>
#include <poll.h>
#include <pthread.h>
#include <sys/eventfd.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include <atomic>
namespace impl
{
void set_thread_signal_mask(::sigset_t* new_mask, ::sigset_t* old_mask)
{
::pthread_sigmask(SIG_BLOCK, new_mask, old_mask);
}
void set_process_signal_mask(::sigset_t* new_mask, ::sigset_t* old_mask)
{
::sigprocmask(SIG_BLOCK, new_mask, old_mask);
}
class SignalTrap : public core::posix::SignalTrap
{
public:
enum class Scope
{
process,
thread
};
enum class State
{
not_running,
running
};
SignalTrap(Scope scope, std::initializer_list<core::posix::Signal> blocked_signals)
: scope(scope),
state(State::not_running),
event_fd(::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK))
{
if (event_fd == -1)
throw std::system_error(errno, std::system_category());
::sigemptyset(&blocked_signals_mask);
for(auto signal : blocked_signals)
::sigaddset(&blocked_signals_mask, static_cast<int>(signal));
switch (scope)
{
case Scope::process:
set_process_signal_mask(&blocked_signals_mask, &old_signals_mask);
break;
case Scope::thread:
set_thread_signal_mask(&blocked_signals_mask, &old_signals_mask);
break;
default:
break;
}
}
~SignalTrap()
{
switch (scope)
{
case Scope::process:
set_process_signal_mask(&old_signals_mask, nullptr);
break;
case Scope::thread:
set_thread_signal_mask(&old_signals_mask, nullptr);
break;
default:
break;
}
::close(event_fd);
}
bool has(core::posix::Signal signal) override
{
return ::sigismember(&blocked_signals_mask, static_cast<int>(signal));
}
void run() override
{
static constexpr int signal_fd_idx = 0;
static constexpr int event_fd_idx = 1;
static constexpr int signal_info_buffer_size = 5;
if (state.load() == State::running)
throw std::runtime_error("SignalTrap::run can only be run once.");
state.store(State::running);
// Make sure we clean up the signal fd whenever
// we leave the scope of run.
struct Scope
{
~Scope()
{
if (signal_fd != -1)
::close(signal_fd);
}
int signal_fd;
} scope{::signalfd(-1, &blocked_signals_mask, SFD_CLOEXEC | SFD_NONBLOCK)};
if (scope.signal_fd == -1)
throw std::system_error(errno, std::system_category());
pollfd fds[2];
signalfd_siginfo signal_info[signal_info_buffer_size];
for (;;)
{
fds[signal_fd_idx] = {scope.signal_fd, POLLIN, 0};
fds[event_fd_idx] = {event_fd, POLLIN, 0};
auto rc = ::poll(fds, 2, -1);
if (rc == -1)
{
if (errno == EINTR)
continue;
break;
}
if (rc == 0)
continue;
if (fds[signal_fd_idx].revents & POLLIN)
{
auto result = ::read(scope.signal_fd, signal_info, sizeof(signal_info));
for (uint i = 0; i < result / sizeof(signalfd_siginfo); i++)
{
if (has(static_cast<core::posix::Signal>(signal_info[i].ssi_signo)))
{
on_signal_raised(
static_cast<core::posix::Signal>(
signal_info[i].ssi_signo));
}
}
}
if (fds[event_fd_idx].revents & POLLIN)
{
std::int64_t value{1};
// Consciously void-ing the return value here.
// Not much we can do about an error.
auto result = ::read(event_fd, &value, sizeof(value));
(void) result;
break;
}
}
state.store(State::not_running);
}
void stop() override
{
static const std::int64_t value = {1};
if (sizeof(value) != ::write(event_fd, &value, sizeof(value)))
throw std::system_error(errno, std::system_category());
}
core::Signal<core::posix::Signal>& signal_raised() override
{
return on_signal_raised;
}
private:
Scope scope;
std::atomic<State> state;
int event_fd;
core::Signal<core::posix::Signal> on_signal_raised;
::sigset_t old_signals_mask;
::sigset_t blocked_signals_mask;
};
}
std::shared_ptr<core::posix::SignalTrap> core::posix::trap_signals_for_process(
std::initializer_list<core::posix::Signal> blocked_signals)
{
return std::make_shared<impl::SignalTrap>(
impl::SignalTrap::Scope::process,
blocked_signals);
}
std::shared_ptr<core::posix::SignalTrap> core::posix::trap_signals_for_all_subsequent_threads(
std::initializer_list<core::posix::Signal> blocked_signals)
{
return std::make_shared<impl::SignalTrap>(
impl::SignalTrap::Scope::thread,
blocked_signals);
}

View file

@ -0,0 +1,52 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/signalable.h>
namespace core
{
namespace posix
{
struct Signalable::Private
{
pid_t pid;
};
Signalable::Signalable(pid_t pid) : d(new Private{pid})
{
}
void Signalable::send_signal_or_throw(Signal signal)
{
auto result = ::kill(d->pid, static_cast<int>(signal));
if (result == -1)
throw std::system_error(errno, std::system_category());
}
void Signalable::send_signal(Signal signal, std::error_code& e) noexcept
{
auto result = ::kill(d->pid, static_cast<int>(signal));
if (result == -1)
{
e = std::error_code(errno, std::system_category());
}
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/standard_stream.h>
namespace core
{
namespace posix
{
StandardStream operator|(StandardStream l, StandardStream r)
{
return static_cast<StandardStream>(static_cast<std::uint8_t>(l) | static_cast<std::uint8_t>(r));
}
StandardStream operator&(StandardStream l, StandardStream r)
{
return static_cast<StandardStream>(static_cast<std::uint8_t>(l) & static_cast<std::uint8_t>(r));
}
}
}

View file

@ -0,0 +1,177 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/this_process.h>
#include <core/posix/process.h>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <mutex>
#include <sstream>
#include <vector>
#include <cerrno>
#include <cstdlib>
#if defined(_GNU_SOURCE)
#include <unistd.h>
#else
extern char** environ;
#endif
namespace core
{
namespace posix
{
namespace this_process
{
namespace env
{
namespace
{
std::mutex& env_guard()
{
static std::mutex m;
return m;
}
}
void for_each(const std::function<void(const std::string&, const std::string&)>& functor) noexcept(true)
{
std::lock_guard<std::mutex> lg(env_guard());
auto it = ::environ;
while (it != nullptr && *it != nullptr)
{
std::string line(*it);
functor(line.substr(0,line.find_first_of('=')),
line.substr(line.find_first_of('=')+1));
++it;
}
}
std::string get_or_throw(const std::string& key)
{
std::lock_guard<std::mutex> lg(env_guard());
auto result = ::getenv(key.c_str());
if (result == nullptr)
{
std::stringstream ss;
ss << "Variable with name " << key << " is not defined in the environment";
throw std::runtime_error(ss.str());
}
return std::string{result};
}
std::string get(const std::string& key,
const std::string& default_value) noexcept(true)
{
std::lock_guard<std::mutex> lg(env_guard());
auto result = ::getenv(key.c_str());
return std::string{result ? result : default_value};
}
void unset_or_throw(const std::string& key)
{
std::lock_guard<std::mutex> lg(env_guard());
auto rc = ::unsetenv(key.c_str());
if (rc == -1)
throw std::system_error(errno, std::system_category());
}
bool unset(const std::string& key,
std::error_code& se) noexcept(true)
{
std::lock_guard<std::mutex> lg(env_guard());
auto rc = ::unsetenv(key.c_str());
if (rc == -1)
{
se = std::error_code(errno, std::system_category());
return false;
}
return true;
}
void set_or_throw(const std::string& key,
const std::string& value)
{
std::lock_guard<std::mutex> lg(env_guard());
static const int overwrite = 0;
auto rc = ::setenv(key.c_str(), value.c_str(), overwrite);
if (rc == -1)
throw std::system_error(errno, std::system_category());
}
bool set(const std::string &key,
const std::string &value,
std::error_code& se) noexcept(true)
{
std::lock_guard<std::mutex> lg(env_guard());
static const int overwrite = 0;
auto rc = ::setenv(key.c_str(), value.c_str(), overwrite);
if (rc == -1)
{
se = std::error_code(errno, std::system_category());
return false;
}
return true;
}
}
Process instance() noexcept(true)
{
static const Process self{getpid()};
return self;
}
Process parent() noexcept(true)
{
return Process(getppid());
}
std::istream& cin() noexcept(true)
{
return std::cin;
}
std::ostream& cout() noexcept(true)
{
return std::cout;
}
std::ostream& cerr() noexcept(true)
{
return std::cerr;
}
}
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/posix/wait.h>
namespace core
{
namespace posix
{
namespace wait
{
Flags operator|(Flags l, Flags r)
{
return static_cast<Flags>(static_cast<std::uint8_t>(l) | static_cast<std::uint8_t>(r));
}
}
}
}

View file

@ -0,0 +1,99 @@
/*
* Copyright © 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voss <thomas.voss@canonical.com>
*/
#include <core/testing/cross_process_sync.h>
#include <system_error>
#include <poll.h>
#include <unistd.h>
namespace
{
const int read_fd = 0;
const int write_fd = 1;
}
core::testing::CrossProcessSync::CrossProcessSync() : counter(0)
{
if (::pipe(fds) < 0)
throw std::system_error(errno, std::system_category());
}
core::testing::CrossProcessSync::CrossProcessSync(const CrossProcessSync& rhs) : counter(rhs.counter)
{
fds[0] = ::dup(rhs.fds[0]);
fds[1] = ::dup(rhs.fds[1]);
}
core::testing::CrossProcessSync::~CrossProcessSync() noexcept
{
::close(fds[0]);
::close(fds[1]);
}
core::testing::CrossProcessSync& core::testing::CrossProcessSync::operator=(const core::testing::CrossProcessSync& rhs)
{
::close(fds[0]);
::close(fds[1]);
fds[0] = ::dup(rhs.fds[0]);
fds[1] = ::dup(rhs.fds[1]);
counter = rhs.counter;
return *this;
}
void core::testing::CrossProcessSync::try_signal_ready_for(const std::chrono::milliseconds& duration)
{
static const short empty_revents = 0;
pollfd poll_fd[1] = { { fds[write_fd], POLLOUT, empty_revents } };
int rc = -1;
if ((rc = ::poll(poll_fd, 1, duration.count())) < 0)
throw std::system_error(errno, std::system_category());
else if (rc == 0)
throw Error::Timeout{};
static const std::uint32_t value = 1;
if (sizeof(value) != write(fds[write_fd], std::addressof(value), sizeof(value)))
throw std::system_error(errno, std::system_category());
}
std::uint32_t core::testing::CrossProcessSync::wait_for_signal_ready_for(const std::chrono::milliseconds& duration)
{
static const short empty_revents = 0;
pollfd poll_fd[1] = { { fds[read_fd], POLLIN, empty_revents } };
int rc = -1;
if ((rc = ::poll(poll_fd, 1, duration.count())) < 0)
throw std::system_error(errno, std::system_category());
else if (rc == 0)
throw Error::Timeout{};
std::uint32_t value = 0;
if (sizeof(value) != read(fds[read_fd], std::addressof(value), sizeof(value)))
throw std::system_error(errno, std::system_category());
if (value != 1)
throw std::system_error(errno, std::system_category());
counter += value;
return counter;
}

View file

@ -0,0 +1,78 @@
/*
* Copyright © 2012-2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3,
* as published by the Free Software Foundation.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Thomas Voß <thomas.voss@canonical.com>
*/
#include <core/testing/fork_and_run.h>
#include <core/posix/exit.h>
#include <core/posix/fork.h>
#include <core/posix/wait.h>
core::testing::ForkAndRunResult core::testing::operator|(
core::testing::ForkAndRunResult lhs,
core::testing::ForkAndRunResult rhs)
{
return static_cast<core::testing::ForkAndRunResult>(
static_cast<unsigned int> (lhs) | static_cast<unsigned int>(rhs));
}
core::testing::ForkAndRunResult core::testing::operator&(
core::testing::ForkAndRunResult lhs,
core::testing::ForkAndRunResult rhs)
{
return static_cast<core::testing::ForkAndRunResult>(
static_cast<unsigned int> (lhs) & static_cast<unsigned int>(rhs));
}
core::testing::ForkAndRunResult core::testing::fork_and_run(
const std::function<core::posix::exit::Status()>& service,
const std::function<core::posix::exit::Status()>& client)
{
core::testing::ForkAndRunResult result = core::testing::ForkAndRunResult::empty;
auto service_process = core::posix::fork(service, core::posix::StandardStream::empty);
auto client_process = core::posix::fork(client, core::posix::StandardStream::empty);
auto client_result = client_process.wait_for(core::posix::wait::Flags::untraced);
switch (client_result.status)
{
case core::posix::wait::Result::Status::exited:
if (client_result.detail.if_exited.status == core::posix::exit::Status::failure)
result = result | core::testing::ForkAndRunResult::client_failed;
break;
default:
result = result | core::testing::ForkAndRunResult::client_failed;
break;
}
service_process.send_signal_or_throw(core::posix::Signal::sig_term);
auto service_result = service_process.wait_for(core::posix::wait::Flags::untraced);
switch (service_result.status)
{
case core::posix::wait::Result::Status::exited:
if (service_result.detail.if_exited.status == core::posix::exit::Status::failure)
result = result | core::testing::ForkAndRunResult::service_failed;
break;
default:
result = result | core::testing::ForkAndRunResult::service_failed;
break;
}
return result;
}