/* Copyright 2015 Florian Philipp
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <signal.h>
// using sigaction, pthread_sigmask
#include <arpa/inet.h>
// using htons
#include <netinet/ip.h>
// using accept, bind, listen, socket
#include <unistd.h>
// using close
#include <atomic>
// using std::atomic_signal_fence
#include <cstring>
// using std::memset
#include <system_error>
// using std::system_error, std::system_category
#include <cerrno>
// using errno
#include <iostream>
// using std::cout
#include <array>
// using std::array
#include <cassert>
// using assert
namespace {
/**
* Throws an std::system_error using errno
*/
void make_system_error(const char* operation)
{
throw std::system_error(errno, std::system_category(), operation);
}
/**
* RAII for an installed signal handler. Dtor restores old behavior
*/
class AlteredSignal
{
int signal;
struct sigaction orig;
public:
typedef void (*signal_handler_t)(int);
AlteredSignal() noexcept : signal(-1) {}
AlteredSignal(int signal, signal_handler_t handler)
: signal(signal)
{
struct sigaction action;
std::memset(&action, 0, sizeof(action));
action.sa_handler = handler;
if(sigaction(signal, &action, &orig))
make_system_error("sigaction");
}
AlteredSignal(const AlteredSignal&) = delete;
AlteredSignal(AlteredSignal&& o) noexcept
{
signal = o.signal;
o.signal = -1;
orig = o.orig;
}
~AlteredSignal()
{
if(signal >= 0)
sigaction(signal, &orig, nullptr);
}
AlteredSignal& operator=(const AlteredSignal&) = delete;
AlteredSignal& operator=(AlteredSignal&& o)
{
if(signal >= 0 && sigaction(signal, &orig, nullptr))
make_system_error("sigaction");
signal = o.signal;
if(o.signal >= 0) {
orig = o.orig;
o.signal = -1;
}
return *this;
}
};
/**
* RAII for blocked signals. Dtor unblocks signals again
*/
class SignalBlock
{
sigset_t orig;
bool active;
public:
SignalBlock() noexcept : orig(), active(false) {}
explicit SignalBlock(const sigset_t& mask)
: orig(), active(true)
{
if(pthread_sigmask(SIG_BLOCK, &mask, &orig))
make_system_error("pthread_sigmask");
}
SignalBlock(const SignalBlock&) = delete;
SignalBlock(SignalBlock&& o) noexcept
: orig(o.orig), active(o.active)
{
o.active = false;
}
~SignalBlock()
{
if(active)
pthread_sigmask(SIG_SETMASK, &orig, nullptr);
}
SignalBlock& operator=(const SignalBlock&) = delete;
SignalBlock& operator=(SignalBlock&& o)
{
if(active && pthread_sigmask(SIG_SETMASK, &orig, nullptr))
make_system_error("pthread_sigmask");
active = o.active;
if(o.active) {
orig = o.orig;
o.active = false;
}
return *this;
}
/**
* Signal mask that can be used to unblock signals in pselect or ppoll
*/
const sigset_t& unmask_set() const noexcept
{ return orig; }
};
/**
* Installs a signal handler that sets a flag on SIGINT and SIGTERM
*/
class TerminationFlag
{
static bool termflag;
static const std::array<int, 2> caught_signals;
std::array<AlteredSignal, 2> altered;
/**
* Signal handler
*/
static void term_action(int) noexcept
{
termflag = true;
std::atomic_signal_fence(std::memory_order_release);
}
public:
TerminationFlag()
{
for(std::size_t i = 0; i < caught_signals.size(); ++i)
altered[i] = AlteredSignal(caught_signals[i], term_action);
}
/**
* Blocks SIGTERM and SIGINT until the returned object is destroyed
*/
SignalBlock block()
{
sigset_t mask;
if(sigemptyset(&mask))
make_system_error("sigemptyset");
for(int sig: caught_signals)
if(sigaddset(&mask, sig))
make_system_error("sigaddset");
return SignalBlock(mask);
}
/**
* True if SIGTERM or SIGINT have been received
*/
bool triggered() const noexcept
{
bool val = termflag;
std::atomic_signal_fence(std::memory_order_acquire);
return val;
}
};
bool TerminationFlag::termflag;
const std::array<int, 2> TerminationFlag::caught_signals = {
SIGTERM, SIGINT
};
/**
* RAII for a socket or any kind of file descriptor
*/
struct Socket
{
int fd;
explicit Socket(int fd = -1) noexcept
: fd(fd) {}
~Socket()
{
if(fd >= 0)
close(fd);
}
Socket(const Socket&) = delete;
Socket(Socket&& o) noexcept
: fd(o.fd)
{
o.fd = -1;
}
explicit operator bool() const noexcept
{ return fd >= 0; }
Socket& operator=(const Socket&) = delete;
Socket& operator=(Socket&& o)
{
using std::swap;
swap(fd, o.fd);
return *this;
}
};
/**
* Listens on 127.0.0.1:1337 for connections
*/
class Listener
{
Socket sock;
public:
Listener()
{
if((sock.fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
make_system_error("socket");
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(1337);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if(bind(sock.fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)))
make_system_error("bind");
if(listen(sock.fd, 128))
make_system_error("listen");
}
/**
* Blocking call to accept a new connection
*/
Socket accept()
{
int fd;
if((fd = ::accept(sock.fd, nullptr, 0)) < 0)
make_system_error("accept");
return Socket(fd);
}
/**
* Blocking, interruptible call to accept a new connection
*
* \param sigmask signal mask for pselect
*/
Socket poll(const sigset_t& sigmask)
{
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(sock.fd, &read_fds);
int n_fds;
if((n_fds = pselect(sock.fd + 1, &read_fds, nullptr /*write*/,
nullptr /*except*/, nullptr /*timeout*/, &sigmask))
< 0)
make_system_error("pselect");
assert(n_fds == 1);
assert(FD_ISSET(sock.fd, &read_fds));
return accept();
}
};
/**
* TODO: Actually process the client.
*/
void handle_client(Socket)
{}
}
int main()
{
TerminationFlag termination;
Listener listener;
while(1) {
Socket sock;
{
SignalBlock block = termination.block();
/* important: Block signals before checking them
* to avoid race condition
*/
if(termination.triggered())
break;
try {
/* Now use pselect or ppoll to wait for a client with unmasked signals.
* A signal will cause an EINTR. For simplicity, we check this in the
* next iteration
*/
sock = listener.poll(block.unmask_set());
} catch(std::system_error& err) {
if(err.code().value() == EINTR)
continue;
throw;
}
}
handle_client(std::move(sock));
}
std::cout << "Clean termination\n";
}
Comments