/* 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 // using sigaction, pthread_sigmask #include // using htons #include // using accept, bind, listen, socket #include // using close #include // using std::atomic_signal_fence #include // using std::memset #include // using std::system_error, std::system_category #include // using errno #include // using std::cout #include // using std::array #include // 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 caught_signals; std::array 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 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(&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"; }