Move includes into a separate directory.
authorDavin McCall <davmac@davmac.org>
Fri, 12 Jan 2018 16:45:51 +0000 (16:45 +0000)
committerDavin McCall <davmac@davmac.org>
Fri, 12 Jan 2018 16:45:51 +0000 (16:45 +0000)
26 files changed:
src/Makefile
src/control-cmds.h [deleted file]
src/control.h [deleted file]
src/cpbuffer.h [deleted file]
src/dinit-ll.h [deleted file]
src/dinit-log.h [deleted file]
src/dinit-socket.h [deleted file]
src/dinit-util.h [deleted file]
src/dinit.h [deleted file]
src/includes/control-cmds.h [new file with mode: 0644]
src/includes/control.h [new file with mode: 0644]
src/includes/cpbuffer.h [new file with mode: 0644]
src/includes/dinit-ll.h [new file with mode: 0644]
src/includes/dinit-log.h [new file with mode: 0644]
src/includes/dinit-socket.h [new file with mode: 0644]
src/includes/dinit-util.h [new file with mode: 0644]
src/includes/dinit.h [new file with mode: 0644]
src/includes/proc-service.h [new file with mode: 0644]
src/includes/service-constants.h [new file with mode: 0644]
src/includes/service-listener.h [new file with mode: 0644]
src/includes/service.h [new file with mode: 0644]
src/proc-service.h [deleted file]
src/service-constants.h [deleted file]
src/service-listener.h [deleted file]
src/service.h [deleted file]
src/tests/Makefile

index be9cc4ffd93698593dc8f25b7d4bb2404ab0ccad..b0cf80d88cfea01d3108ba2b137e21d199ebae60 100644 (file)
@@ -4,8 +4,6 @@ ifeq ($(BUILD_SHUTDOWN),yes)
   SHUTDOWN=shutdown
 endif
 
-#objects = dinit.o load_service.o service.o proc-service.o baseproc-service.o control.o dinit-log.o dinit-main.o dinitctl.o shutdown.o
-
 dinit_objects = dinit.o load_service.o service.o proc-service.o baseproc-service.o control.o dinit-log.o dinit-main.o
 
 objects = $(dinit_objects) dinitctl.o shtudown.o
@@ -22,7 +20,7 @@ shutdown: shutdown.o
        $(CXX) -o shutdown shutdown.o $(EXTRA_LIBS)
 
 $(objects): %.o: %.cc
-       $(CXX) $(CXXOPTS) -Idasynq -c $< -o $@
+       $(CXX) $(CXXOPTS) -Iincludes -Idasynq -c $< -o $@
 
 check: $(dinit_objects)
        $(MAKE) -C tests check
@@ -40,6 +38,6 @@ clean:
        $(MAKE) -C tests clean
 
 $(objects:.o=.d): %.d: %.cc
-       $(CXX) $(CXXOPTS) -Idasynq -MM -MG -MF $@ $<
+       $(CXX) $(CXXOPTS) -Iincludes -Idasynq -MM -MG -MF $@ $<
 
 -include $(objects:.o=.d)
diff --git a/src/control-cmds.h b/src/control-cmds.h
deleted file mode 100644 (file)
index 8e7967f..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-// Dinit control command packet types
-
-// Requests:
-
-// Query protocol version:
-constexpr static int DINIT_CP_QUERYVERSION = 0;
-
-// Find (but don't load) a service:
-constexpr static int DINIT_CP_FINDSERVICE = 1;
-
-// Find or load a service:
-constexpr static int DINIT_CP_LOADSERVICE = 2;
-
-// Start or stop a service:
-constexpr static int DINIT_CP_STARTSERVICE = 3;
-constexpr static int DINIT_CP_STOPSERVICE  = 4;
-constexpr static int DINIT_CP_WAKESERVICE = 5;
-constexpr static int DINIT_CP_RELEASESERVICE = 6;
-
-constexpr static int DINIT_CP_UNPINSERVICE = 7;
-
-// List services:
-constexpr static int DINIT_CP_LISTSERVICES = 8;
-
-// Shutdown:
-constexpr static int DINIT_CP_SHUTDOWN = 10;
- // followed by 1-byte shutdown type
-
-
-
-// Replies:
-
-// Reply: ACK/NAK to request
-constexpr static int DINIT_RP_ACK = 50;
-constexpr static int DINIT_RP_NAK = 51;
-
-// Request was bad (connection will be closed)
-constexpr static int DINIT_RP_BADREQ = 52;
-
-// Connection being closed due to out-of-memory condition
-constexpr static int DINIT_RP_OOM = 53;
-
-// Start service replies:
-constexpr static int DINIT_RP_SERVICELOADERR = 54;
-constexpr static int DINIT_RP_SERVICEOOM = 55; // couldn't start due to out-of-memory
-
-constexpr static int DINIT_RP_SSISSUED = 56;  // service start/stop was issued (includes 4-byte service handle)
-constexpr static int DINIT_RP_SSREDUNDANT = 57;  // service was already started/stopped (or for stop, not loaded)
-
-// Query version response:
-constexpr static int DINIT_RP_CPVERSION = 58;
-
-// Service record loaded/found
-constexpr static int DINIT_RP_SERVICERECORD = 59;
-//     followed by 4-byte service handle, 1-byte service state
-
-// Couldn't find/load service
-constexpr static int DINIT_RP_NOSERVICE = 60;
-
-// Service is already started/stopped
-constexpr static int DINIT_RP_ALREADYSS = 61;
-
-// Information on a service / list complete:
-constexpr static int DINIT_RP_SVCINFO = 62;
-constexpr static int DINIT_RP_LISTDONE = 63;
-
-// Information:
-
-// Service event occurred (4-byte service handle, 1 byte event code)
-constexpr static int DINIT_IP_SERVICEEVENT = 100;
-
-// rollback completed
-constexpr static int DINIT_ROLLBACK_COMPLETED = 101;
diff --git a/src/control.h b/src/control.h
deleted file mode 100644 (file)
index dd92201..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-#ifndef DINIT_CONTROL_H
-#define DINIT_CONTROL_H
-
-#include <list>
-#include <vector>
-#include <unordered_map>
-#include <limits>
-#include <cstddef>
-
-#include <unistd.h>
-
-#include "dasynq.h"
-
-#include "dinit.h"
-#include "dinit-log.h"
-#include "control-cmds.h"
-#include "service-listener.h"
-#include "cpbuffer.h"
-
-// Control connection for dinit
-
-class control_conn_t;
-class control_conn_watcher;
-
-// forward-declaration of callback:
-static dasynq::rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
-
-// Pointer to the control connection that is listening for rollback completion
-extern control_conn_t * rollback_handler_conn;
-
-extern int active_control_conns;
-
-// "packet" format:
-// (1 byte) packet type
-// (N bytes) additional data (service name, etc)
-//   for LOADSERVICE/FINDSERVICE:
-//      (2 bytes) service name length
-//      (M bytes) service name (without nul terminator)
-
-// Information packet:
-// (1 byte) packet type, >= 100
-// (1 byte) packet length (including all fields)
-//       N bytes: packet data (N = (length - 2))
-
-class service_set;
-class service_record;
-
-class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl<control_conn_watcher>
-{
-    inline rearm receive_event(eventloop_t &loop, int fd, int flags) noexcept;
-
-    eventloop_t * event_loop;
-
-    public:
-    control_conn_watcher(eventloop_t & event_loop_p) : event_loop(&event_loop_p)
-    {
-        // constructor
-    }
-
-    rearm read_ready(eventloop_t &loop, int fd) noexcept
-    {
-        return receive_event(loop, fd, dasynq::IN_EVENTS);
-    }
-    
-    rearm write_ready(eventloop_t &loop, int fd) noexcept
-    {
-        return receive_event(loop, fd, dasynq::OUT_EVENTS);
-    }
-
-    void set_watches(int flags)
-    {
-        eventloop_t::bidi_fd_watcher::set_watches(*event_loop, flags);
-    }
-};
-
-inline dasynq::rearm control_conn_watcher::receive_event(eventloop_t &loop, int fd, int flags) noexcept
-{
-    return control_conn_cb(&loop, this, flags);
-}
-
-
-class control_conn_t : private service_listener
-{
-    friend rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
-    
-    control_conn_watcher iob;
-    eventloop_t &loop;
-    service_set *services;
-    
-    bool bad_conn_close = false; // close when finished output?
-    bool oom_close = false;      // send final 'out of memory' indicator
-
-    // The packet length before we need to re-check if the packet is complete.
-    // process_packet() will not be called until the packet reaches this size.
-    int chklen;
-    
-    // Receive buffer
-    cpbuffer<1024> rbuf;
-    
-    template <typename T> using list = std::list<T>;
-    template <typename T> using vector = std::vector<T>;
-    
-    // A mapping between service records and their associated numerical identifier used
-    // in communction
-    using handle_t = uint32_t;
-    std::unordered_multimap<service_record *, handle_t> serviceKeyMap;
-    std::unordered_map<handle_t, service_record *> keyServiceMap;
-    
-    // Buffer for outgoing packets. Each outgoing back is represented as a vector<char>.
-    list<vector<char>> outbuf;
-    // Current index within the first outgoing packet (all previous bytes have been sent).
-    unsigned outpkt_index = 0;
-    
-    // Queue a packet to be sent
-    //  Returns:  false if the packet could not be queued and a suitable error packet
-    //              could not be sent/queued (the connection should be closed);
-    //            true (with bad_conn_close == false) if the packet was successfully
-    //              queued;
-    //            true (with bad_conn_close == true) if the packet was not successfully
-    //              queued (but a suitable error packate has been queued).
-    // The in/out watch enabled state will also be set appropriately.
-    bool queue_packet(vector<char> &&v) noexcept;
-    bool queue_packet(const char *pkt, unsigned size) noexcept;
-
-    // Process a packet.
-    //  Returns:  true (with bad_conn_close == false) if successful
-    //            true (with bad_conn_close == true) if an error packet was queued
-    //            false if an error occurred but no error packet could be queued
-    //                (connection should be closed).
-    // Throws:
-    //    std::bad_alloc - if an out-of-memory condition prevents processing
-    bool process_packet();
-    
-    // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
-    bool process_start_stop(int pktType);
-    
-    // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
-    bool process_find_load(int pktType);
-
-    // Process an UNPINSERVICE packet. May throw std::bad_alloc.
-    bool process_unpin_service();
-    
-    bool list_services();
-
-    // Notify that data is ready to be read from the socket. Returns true if the connection should
-    // be closed.
-    bool data_ready() noexcept;
-    
-    bool send_data() noexcept;
-    
-    // Allocate a new handle for a service; may throw std::bad_alloc
-    handle_t allocate_service_handle(service_record *record);
-    
-    service_record *find_service_for_key(uint32_t key)
-    {
-        try {
-            return keyServiceMap.at(key);
-        }
-        catch (std::out_of_range &exc) {
-            return nullptr;
-        }
-    }
-    
-    // Close connection due to out-of-memory condition.
-    void do_oom_close()
-    {
-        bad_conn_close = true;
-        oom_close = true;
-        iob.set_watches(dasynq::OUT_EVENTS);
-    }
-    
-    // Process service event broadcast.
-    // Note that this can potentially be called during packet processing (upon issuing
-    // service start/stop orders etc).
-    void service_event(service_record * service, service_event_t event) noexcept final override
-    {
-        // For each service handle corresponding to the event, send an information packet.
-        auto range = serviceKeyMap.equal_range(service);
-        auto & i = range.first;
-        auto & end = range.second;
-        try {
-            while (i != end) {
-                uint32_t key = i->second;
-                std::vector<char> pkt;
-                constexpr int pktsize = 3 + sizeof(key);
-                pkt.reserve(pktsize);
-                pkt.push_back(DINIT_IP_SERVICEEVENT);
-                pkt.push_back(pktsize);
-                char * p = (char *) &key;
-                for (int j = 0; j < (int)sizeof(key); j++) {
-                    pkt.push_back(*p++);
-                }
-                pkt.push_back(static_cast<char>(event));
-                queue_packet(std::move(pkt));
-                ++i;
-            }
-        }
-        catch (std::bad_alloc &exc) {
-            do_oom_close();
-        }
-    }
-    
-    public:
-    control_conn_t(eventloop_t &loop, service_set * services_p, int fd)
-            : iob(loop), loop(loop), services(services_p), chklen(0)
-    {
-        iob.add_watch(loop, fd, dasynq::IN_EVENTS);
-        active_control_conns++;
-    }
-    
-    bool rollback_complete() noexcept;
-        
-    virtual ~control_conn_t() noexcept;
-};
-
-
-static dasynq::rearm control_conn_cb(eventloop_t * loop, control_conn_watcher * watcher, int revents)
-{
-    // Get the address of the containing control_connt_t object:
-    _Pragma ("GCC diagnostic push")
-    _Pragma ("GCC diagnostic ignored \"-Winvalid-offsetof\"")
-    char * cc_addr = (reinterpret_cast<char *>(watcher)) - offsetof(control_conn_t, iob);
-    control_conn_t *conn = reinterpret_cast<control_conn_t *>(cc_addr);
-    _Pragma ("GCC diagnostic pop")
-
-    if (revents & dasynq::IN_EVENTS) {
-        if (conn->data_ready()) {
-            delete conn;
-            return dasynq::rearm::REMOVED;
-        }
-    }
-    if (revents & dasynq::OUT_EVENTS) {
-        if (conn->send_data()) {
-            delete conn;
-            return dasynq::rearm::REMOVED;
-        }
-    }
-    
-    return dasynq::rearm::NOOP;
-}
-
-#endif
diff --git a/src/cpbuffer.h b/src/cpbuffer.h
deleted file mode 100644 (file)
index 9019f4a..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-#ifndef CPBUFFER_H
-#define CPBUFFER_H
-
-#include <cstring>
-
-// control protocol buffer, a circular buffer with 1024-byte capacity.
-template <int SIZE> class cpbuffer
-{
-    char buf[SIZE];
-    int cur_idx = 0;
-    int length = 0;  // number of elements in the buffer
-    
-    public:
-    int get_length() noexcept
-    {
-        return length;
-    }
-    
-    int get_free() noexcept
-    {
-        return SIZE - length;
-    }
-    
-    char * get_ptr(int index)
-    {
-        int pos = cur_idx + index;
-        if (pos >= SIZE) pos -= SIZE;
-    
-        return &buf[pos];
-    }
-    
-    char * get_buf_base()
-    {
-        return buf;
-    }
-    
-    int get_contiguous_length(char *ptr)
-    {
-        int eidx = cur_idx + length;
-        if (eidx >= SIZE) eidx -= SIZE;
-        
-        if (buf + eidx > ptr) {
-            return (buf + eidx) - ptr;
-        }
-        else {
-            return (buf + SIZE) - ptr;
-        }
-    }
-    
-    // fill by reading from the given fd, return positive if some was read or -1 on error.
-    int fill(int fd) noexcept
-    {
-        int pos = cur_idx + length;
-        if (pos >= SIZE) pos -= SIZE;
-        int max_count = std::min(SIZE - pos, SIZE - length);
-        ssize_t r = read(fd, buf + pos, max_count);
-        if (r >= 0) {
-            length += r;
-        }
-        return r;
-    }
-    
-    // fill by reading from the given fd, until at least the specified number of bytes are in
-    // the buffer. Return 0 if end-of-file reached before fill complete, or -1 on error.
-    int fill_to(int fd, int rlength) noexcept
-    {
-        while (length < rlength) {
-            int r = fill(fd);
-            if (r <= 0) return r;
-        }
-        return 1;
-    }
-    
-    // Trim the buffer to the specified length (must be less than current length)
-    void trim_to(int new_length)
-    {
-        length = new_length;
-    }
-    
-    char operator[](int idx) noexcept
-    {
-        int dest_idx = cur_idx + idx;
-        if (dest_idx > SIZE) dest_idx -= SIZE;
-        return buf[dest_idx];
-    }
-    
-    // Remove the given number of bytes from the start of the buffer.
-    void consume(int amount) noexcept
-    {
-        cur_idx += amount;
-        if (cur_idx >= SIZE) cur_idx -= SIZE;
-        length -= amount;
-    }
-    
-    // Extract bytes from the buffer. The bytes remain in the buffer.
-    void extract(char *dest, int index, int length) noexcept
-    {
-        index += cur_idx;
-        if (index >= SIZE) index -= SIZE;
-        if (index + length > SIZE) {
-            // wrap-around copy
-            int half = SIZE - index;
-            std::memcpy(dest, buf + index, half);
-            std::memcpy(dest + half, buf, length - half);
-        }
-        else {
-            std::memcpy(dest, buf + index, length);
-        }
-    }
-    
-    // Extract string of given length from given index
-    // Throws:  std::bad_alloc on allocation failure
-    std::string extract_string(int index, int length)
-    {
-        index += cur_idx;
-        if (index >= SIZE) index -= SIZE;
-        if (index + length > SIZE) {
-            std::string r(buf + index, SIZE - index);
-            r.insert(r.end(), buf, buf + length - (SIZE - index));
-            return r;
-        }
-        else {
-            return std::string(buf + index, length);
-        }
-    }
-    
-    // Append characters to the buffer. Caller must make certain there
-    // is enough space to contain the characters first.
-    void append(const char * s, int len) noexcept
-    {
-        int index = cur_idx + length;
-        if (index >= SIZE) index -= SIZE;
-
-        length += len; // (before we destroy len)
-        
-        int max = SIZE - index;
-        std::memcpy(buf + index, s, std::min(max, len));
-        if (len > max) {
-            // Wrapped around buffer: copy the rest
-            s += max;
-            len -= max;
-            std::memcpy(buf, s, len);
-        }
-    }
-};
-
-#endif
diff --git a/src/dinit-ll.h b/src/dinit-ll.h
deleted file mode 100644 (file)
index 101aa3f..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-#ifndef DINIT_LL_INCLUDED
-#define DINIT_LL_INCLUDED 1
-
-// Simple single- and doubly-linked list implementation, where the contained element includes the
-// list node. This allows a single item to be a member of several different kinds of list, without
-// requiring dynamic allocation of nodes for the different lists.
-//
-// To accomplish this without abstraction penalty, the function to retrieve the list node from the
-// element is specified as the second template parameter.
-
-// Doubly-linked list node:
-template <typename T>
-struct lld_node
-{
-    T * next = nullptr;
-    T * prev = nullptr;
-};
-
-// Singly-linked list node:
-template <typename T>
-struct lls_node
-{
-    T * next = nullptr;
-};
-
-// Doubly-linked list implementation. The list is circular, so 'first->prev' yields the tail of
-// the list, though we still need to special-case the empty list (where first == nullptr).
-// next/prev pointers in a node are set to nullptr when the node is not linked into a list
-// (and are never equal to nullptr when the node is linked into a list).
-template <typename T, lld_node<T> &(*E)(T *)>
-class dlist
-{
-    T * first;
-    // E extractor;
-
-    public:
-    dlist() noexcept : first(nullptr) { }
-
-    bool is_queued(T *e) noexcept
-    {
-        auto &node = E(e);
-        return node.next != nullptr;
-    }
-
-    void append(T *e) noexcept
-    {
-        auto &node = E(e);
-        if (first == nullptr) {
-            first = e;
-            node.next = e;
-            node.prev = e;
-        }
-        else {
-            node.next = first;
-            node.prev = E(first).prev;
-            E(E(first).prev).next = e;
-            E(first).prev = e;
-        }
-    }
-
-    T * tail() noexcept
-    {
-        if (first == nullptr) {
-            return nullptr;
-        }
-        else {
-            return E(first).prev;
-        }
-    }
-
-    bool is_empty() noexcept
-    {
-        return first == nullptr;
-    }
-
-    T * pop_front() noexcept
-    {
-        auto r = first;
-        auto &first_node = E(first);
-        if (first_node.next == first) {
-            // Only one node in the queue:
-            first_node.next = nullptr;
-            first_node.prev = nullptr;
-            first = nullptr;
-        }
-        else {
-            // Unlink first node:
-            auto &node = E(first_node.next);
-            node.prev = first_node.prev;
-            E(node.prev).next = first_node.next;
-            first = first_node.next;
-            // Return original first node:
-            first_node.next = nullptr;
-            first_node.prev = nullptr;
-        }
-        return r;
-    }
-
-    void unlink(T *record) noexcept
-    {
-        auto &node = E(record);
-        if (first == record) {
-            first = node.next;
-            if (first == record) {
-                // unlinking the only node in the list:
-                first = nullptr;
-            }
-        }
-        E(node.next).prev = node.prev;
-        E(node.prev).next = node.next;
-        node.next = nullptr;
-        node.prev = nullptr;
-    }
-};
-
-// Singly-linked list implementation.
-template <typename T, lls_node<T> &(*E)(T *)>
-class slist
-{
-    T * first;
-
-    public:
-    slist() noexcept : first(nullptr) { }
-
-    bool is_queued(T *e) noexcept
-    {
-        auto &node = E(e);
-        return node.next != nullptr || first == e;
-    }
-
-    void insert(T *e) noexcept
-    {
-        auto &node = E(e);
-        node.next = first;
-        first = e;
-    }
-
-    bool is_empty() noexcept
-    {
-        return first == nullptr;
-    }
-
-    T * pop_front() noexcept
-    {
-        T * r = first;
-        auto &node = E(r);
-        first = node.next;
-        node.next = nullptr;
-        return r;
-    }
-};
-
-
-#endif
diff --git a/src/dinit-log.h b/src/dinit-log.h
deleted file mode 100644 (file)
index b94a658..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-#ifndef DINIT_LOG_H
-#define DINIT_LOG_H
-
-// Logging for Dinit
-
-#include <string>
-#include <cstdio>
-#include <climits>
-
-class service_set;
-
-enum class loglevel_t {
-    DEBUG,
-    INFO,
-    WARN,
-    ERROR,
-    ZERO    // log absolutely nothing
-};
-
-extern loglevel_t log_level[2];
-void enable_console_log(bool do_enable) noexcept;
-void init_log(service_set *sset);
-void setup_main_log(int fd);
-bool is_log_flushed() noexcept;
-void discard_console_log_buffer() noexcept;
-
-void log(loglevel_t lvl, const char *msg) noexcept;
-void log_msg_begin(loglevel_t lvl, const char *msg) noexcept;
-void log_msg_part(const char *msg) noexcept;
-void log_msg_end(const char *msg) noexcept;
-void log_service_started(const char *service_name) noexcept;
-void log_service_failed(const char *service_name) noexcept;
-void log_service_stopped(const char *service_name) noexcept;
-
-// Convenience methods which perform type conversion of the argument.
-// There is some duplication here that could possibly be avoided, but
-// it doesn't seem like a big deal.
-static inline void log(loglevel_t lvl, const std::string &str) noexcept
-{
-    log(lvl, str.c_str());
-}
-
-static inline void log_msg_begin(loglevel_t lvl, const std::string &str) noexcept
-{
-    log_msg_begin(lvl, str.c_str());
-}
-
-static inline void log_msg_begin(loglevel_t lvl, int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    log_msg_begin(lvl, nbuf);
-}
-
-static inline void log_msg_part(const std::string &str) noexcept
-{
-    log_msg_part(str.c_str());
-}
-
-static inline void log_msg_part(int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    log_msg_part(nbuf);
-}
-
-static inline void log_msg_end(const std::string &str) noexcept
-{
-    log_msg_end(str.c_str());
-}
-
-static inline void log_msg_end(int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    log_msg_end(nbuf);
-}
-
-static inline void log_service_started(const std::string &str) noexcept
-{
-    log_service_started(str.c_str());
-}
-
-static inline void log_service_failed(const std::string &str) noexcept
-{
-    log_service_failed(str.c_str());
-}
-
-static inline void log_service_stopped(const std::string &str) noexcept
-{
-    log_service_stopped(str.c_str());
-}
-
-// It's not intended that methods in this namespace be called directly:
-namespace dinit_log {
-    template <typename A> static inline void log_parts(A a) noexcept
-    {
-        log_msg_end(a);
-    }
-
-    template <typename A, typename ...B> static inline void log_parts(A a, B... b) noexcept
-    {
-        log_msg_part(a);
-        log_parts(b...);
-    }
-}
-
-// Variadic 'log' method.
-template <typename A, typename ...B> static inline void log(loglevel_t lvl, A a, B ...b) noexcept
-{
-    log_msg_begin(lvl, a);
-    dinit_log::log_parts(b...);
-}
-
-#endif
diff --git a/src/dinit-socket.h b/src/dinit-socket.h
deleted file mode 100644 (file)
index 1e3b3e9..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-#ifndef _DINIT_SOCKET_H_INCLUDED
-#define _DINIT_SOCKET_H_INCLUDED
-
-#include <sys/socket.h>
-#include <fcntl.h>
-
-namespace {
-#if !defined(SOCK_NONBLOCK) && !defined(SOCK_CLOEXEC)
-    // make our own accept4 on systems that don't have it:
-    constexpr int SOCK_NONBLOCK = 1;
-    constexpr int SOCK_CLOEXEC = 2;
-    inline int dinit_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
-    {
-        int fd = accept(sockfd, addr, addrlen);
-        if (fd == -1) {
-            return -1;
-        }
-
-        if (flags & SOCK_CLOEXEC)  fcntl(fd, F_SETFD, FD_CLOEXEC);
-        if (flags & SOCK_NONBLOCK) fcntl(fd, F_SETFL, O_NONBLOCK);
-        return fd;
-    }
-
-    inline int dinit_socket(int domain, int type, int protocol, int flags)
-    {
-        int fd = socket(domain, type, protocol);
-        if (fd == -1) {
-            return -1;
-        }
-
-        if (flags & SOCK_CLOEXEC)  fcntl(fd, F_SETFD, FD_CLOEXEC);
-        if (flags & SOCK_NONBLOCK) fcntl(fd, F_SETFL, O_NONBLOCK);
-        return fd;
-    }
-
-    inline int dinit_socketpair(int domain, int type, int protocol, int socket_vector[2], int flags)
-    {
-        int r = socketpair(domain, type, protocol, socket_vector);
-        if (r == -1) {
-            return -1;
-        }
-
-        if (flags & SOCK_CLOEXEC) {
-            fcntl(socket_vector[0], F_SETFD, FD_CLOEXEC);
-            fcntl(socket_vector[1], F_SETFD, FD_CLOEXEC);
-        }
-        if (flags & SOCK_NONBLOCK) {
-            fcntl(socket_vector[0], F_SETFL, O_NONBLOCK);
-            fcntl(socket_vector[1], F_SETFL, O_NONBLOCK);
-        }
-        return 0;
-    }
-
-#else
-    inline int dinit_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
-    {
-        return accept4(sockfd, addr, addrlen, flags);
-    }
-
-    inline int dinit_socket(int domain, int type, int protocol, int flags)
-    {
-        return socket(domain, type | flags, protocol);
-    }
-
-    inline int dinit_socketpair(int domain, int type, int protocol, int socket_vector[2], int flags)
-    {
-        return socketpair(domain, type | flags, protocol, socket_vector);
-    }
-#endif
-}
-
-#endif
diff --git a/src/dinit-util.h b/src/dinit-util.h
deleted file mode 100644 (file)
index 77170f4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef DINIT_UTIL_H_INCLUDED
-#define DINIT_UTIL_H_INCLUDED 1
-
-#include <cstddef>
-#include <cerrno>
-
-#include <sys/types.h>
-#include <unistd.h>
-
-// Signal-safe read. Read and re-try if interrupted by signal (EINTR).
-// *May* affect errno even on a successful read (when the return is less than n).
-inline ssize_t ss_read(int fd, void * buf, size_t n)
-{
-    char * cbuf = static_cast<char *>(buf);
-    ssize_t r = 0;
-    while ((size_t)r < n) {
-        ssize_t res = read(fd, cbuf + r, n - r);
-        if (res == 0) {
-            return r;
-        }
-        if (res < 0) {
-            if (res == EINTR) {
-                continue;
-            }
-
-            // If any other error, and we have successfully read some, return it:
-            if (r == 0) {
-                return -1;
-            }
-            else {
-                return r;
-            }
-        }
-        r += res;
-    }
-    return n;
-}
-
-#endif
diff --git a/src/dinit.h b/src/dinit.h
deleted file mode 100644 (file)
index ef6b962..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef DINIT_H_INCLUDED
-#define DINIT_H_INCLUDED 1
-
-#include "dasynq.h"
-
-/*
- * General Dinit definitions.
- */
-
-using eventloop_t = dasynq::event_loop<dasynq::null_mutex>;
-
-using clock_type = dasynq::clock_type;
-using rearm = dasynq::rearm;
-using time_val = dasynq::time_val;
-
-void open_control_socket(bool report_ro_failure = true) noexcept;
-void setup_external_log() noexcept;
-
-extern eventloop_t event_loop;
-
-#endif
diff --git a/src/includes/control-cmds.h b/src/includes/control-cmds.h
new file mode 100644 (file)
index 0000000..8e7967f
--- /dev/null
@@ -0,0 +1,73 @@
+// Dinit control command packet types
+
+// Requests:
+
+// Query protocol version:
+constexpr static int DINIT_CP_QUERYVERSION = 0;
+
+// Find (but don't load) a service:
+constexpr static int DINIT_CP_FINDSERVICE = 1;
+
+// Find or load a service:
+constexpr static int DINIT_CP_LOADSERVICE = 2;
+
+// Start or stop a service:
+constexpr static int DINIT_CP_STARTSERVICE = 3;
+constexpr static int DINIT_CP_STOPSERVICE  = 4;
+constexpr static int DINIT_CP_WAKESERVICE = 5;
+constexpr static int DINIT_CP_RELEASESERVICE = 6;
+
+constexpr static int DINIT_CP_UNPINSERVICE = 7;
+
+// List services:
+constexpr static int DINIT_CP_LISTSERVICES = 8;
+
+// Shutdown:
+constexpr static int DINIT_CP_SHUTDOWN = 10;
+ // followed by 1-byte shutdown type
+
+
+
+// Replies:
+
+// Reply: ACK/NAK to request
+constexpr static int DINIT_RP_ACK = 50;
+constexpr static int DINIT_RP_NAK = 51;
+
+// Request was bad (connection will be closed)
+constexpr static int DINIT_RP_BADREQ = 52;
+
+// Connection being closed due to out-of-memory condition
+constexpr static int DINIT_RP_OOM = 53;
+
+// Start service replies:
+constexpr static int DINIT_RP_SERVICELOADERR = 54;
+constexpr static int DINIT_RP_SERVICEOOM = 55; // couldn't start due to out-of-memory
+
+constexpr static int DINIT_RP_SSISSUED = 56;  // service start/stop was issued (includes 4-byte service handle)
+constexpr static int DINIT_RP_SSREDUNDANT = 57;  // service was already started/stopped (or for stop, not loaded)
+
+// Query version response:
+constexpr static int DINIT_RP_CPVERSION = 58;
+
+// Service record loaded/found
+constexpr static int DINIT_RP_SERVICERECORD = 59;
+//     followed by 4-byte service handle, 1-byte service state
+
+// Couldn't find/load service
+constexpr static int DINIT_RP_NOSERVICE = 60;
+
+// Service is already started/stopped
+constexpr static int DINIT_RP_ALREADYSS = 61;
+
+// Information on a service / list complete:
+constexpr static int DINIT_RP_SVCINFO = 62;
+constexpr static int DINIT_RP_LISTDONE = 63;
+
+// Information:
+
+// Service event occurred (4-byte service handle, 1 byte event code)
+constexpr static int DINIT_IP_SERVICEEVENT = 100;
+
+// rollback completed
+constexpr static int DINIT_ROLLBACK_COMPLETED = 101;
diff --git a/src/includes/control.h b/src/includes/control.h
new file mode 100644 (file)
index 0000000..dd92201
--- /dev/null
@@ -0,0 +1,242 @@
+#ifndef DINIT_CONTROL_H
+#define DINIT_CONTROL_H
+
+#include <list>
+#include <vector>
+#include <unordered_map>
+#include <limits>
+#include <cstddef>
+
+#include <unistd.h>
+
+#include "dasynq.h"
+
+#include "dinit.h"
+#include "dinit-log.h"
+#include "control-cmds.h"
+#include "service-listener.h"
+#include "cpbuffer.h"
+
+// Control connection for dinit
+
+class control_conn_t;
+class control_conn_watcher;
+
+// forward-declaration of callback:
+static dasynq::rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
+
+// Pointer to the control connection that is listening for rollback completion
+extern control_conn_t * rollback_handler_conn;
+
+extern int active_control_conns;
+
+// "packet" format:
+// (1 byte) packet type
+// (N bytes) additional data (service name, etc)
+//   for LOADSERVICE/FINDSERVICE:
+//      (2 bytes) service name length
+//      (M bytes) service name (without nul terminator)
+
+// Information packet:
+// (1 byte) packet type, >= 100
+// (1 byte) packet length (including all fields)
+//       N bytes: packet data (N = (length - 2))
+
+class service_set;
+class service_record;
+
+class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl<control_conn_watcher>
+{
+    inline rearm receive_event(eventloop_t &loop, int fd, int flags) noexcept;
+
+    eventloop_t * event_loop;
+
+    public:
+    control_conn_watcher(eventloop_t & event_loop_p) : event_loop(&event_loop_p)
+    {
+        // constructor
+    }
+
+    rearm read_ready(eventloop_t &loop, int fd) noexcept
+    {
+        return receive_event(loop, fd, dasynq::IN_EVENTS);
+    }
+    
+    rearm write_ready(eventloop_t &loop, int fd) noexcept
+    {
+        return receive_event(loop, fd, dasynq::OUT_EVENTS);
+    }
+
+    void set_watches(int flags)
+    {
+        eventloop_t::bidi_fd_watcher::set_watches(*event_loop, flags);
+    }
+};
+
+inline dasynq::rearm control_conn_watcher::receive_event(eventloop_t &loop, int fd, int flags) noexcept
+{
+    return control_conn_cb(&loop, this, flags);
+}
+
+
+class control_conn_t : private service_listener
+{
+    friend rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
+    
+    control_conn_watcher iob;
+    eventloop_t &loop;
+    service_set *services;
+    
+    bool bad_conn_close = false; // close when finished output?
+    bool oom_close = false;      // send final 'out of memory' indicator
+
+    // The packet length before we need to re-check if the packet is complete.
+    // process_packet() will not be called until the packet reaches this size.
+    int chklen;
+    
+    // Receive buffer
+    cpbuffer<1024> rbuf;
+    
+    template <typename T> using list = std::list<T>;
+    template <typename T> using vector = std::vector<T>;
+    
+    // A mapping between service records and their associated numerical identifier used
+    // in communction
+    using handle_t = uint32_t;
+    std::unordered_multimap<service_record *, handle_t> serviceKeyMap;
+    std::unordered_map<handle_t, service_record *> keyServiceMap;
+    
+    // Buffer for outgoing packets. Each outgoing back is represented as a vector<char>.
+    list<vector<char>> outbuf;
+    // Current index within the first outgoing packet (all previous bytes have been sent).
+    unsigned outpkt_index = 0;
+    
+    // Queue a packet to be sent
+    //  Returns:  false if the packet could not be queued and a suitable error packet
+    //              could not be sent/queued (the connection should be closed);
+    //            true (with bad_conn_close == false) if the packet was successfully
+    //              queued;
+    //            true (with bad_conn_close == true) if the packet was not successfully
+    //              queued (but a suitable error packate has been queued).
+    // The in/out watch enabled state will also be set appropriately.
+    bool queue_packet(vector<char> &&v) noexcept;
+    bool queue_packet(const char *pkt, unsigned size) noexcept;
+
+    // Process a packet.
+    //  Returns:  true (with bad_conn_close == false) if successful
+    //            true (with bad_conn_close == true) if an error packet was queued
+    //            false if an error occurred but no error packet could be queued
+    //                (connection should be closed).
+    // Throws:
+    //    std::bad_alloc - if an out-of-memory condition prevents processing
+    bool process_packet();
+    
+    // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
+    bool process_start_stop(int pktType);
+    
+    // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
+    bool process_find_load(int pktType);
+
+    // Process an UNPINSERVICE packet. May throw std::bad_alloc.
+    bool process_unpin_service();
+    
+    bool list_services();
+
+    // Notify that data is ready to be read from the socket. Returns true if the connection should
+    // be closed.
+    bool data_ready() noexcept;
+    
+    bool send_data() noexcept;
+    
+    // Allocate a new handle for a service; may throw std::bad_alloc
+    handle_t allocate_service_handle(service_record *record);
+    
+    service_record *find_service_for_key(uint32_t key)
+    {
+        try {
+            return keyServiceMap.at(key);
+        }
+        catch (std::out_of_range &exc) {
+            return nullptr;
+        }
+    }
+    
+    // Close connection due to out-of-memory condition.
+    void do_oom_close()
+    {
+        bad_conn_close = true;
+        oom_close = true;
+        iob.set_watches(dasynq::OUT_EVENTS);
+    }
+    
+    // Process service event broadcast.
+    // Note that this can potentially be called during packet processing (upon issuing
+    // service start/stop orders etc).
+    void service_event(service_record * service, service_event_t event) noexcept final override
+    {
+        // For each service handle corresponding to the event, send an information packet.
+        auto range = serviceKeyMap.equal_range(service);
+        auto & i = range.first;
+        auto & end = range.second;
+        try {
+            while (i != end) {
+                uint32_t key = i->second;
+                std::vector<char> pkt;
+                constexpr int pktsize = 3 + sizeof(key);
+                pkt.reserve(pktsize);
+                pkt.push_back(DINIT_IP_SERVICEEVENT);
+                pkt.push_back(pktsize);
+                char * p = (char *) &key;
+                for (int j = 0; j < (int)sizeof(key); j++) {
+                    pkt.push_back(*p++);
+                }
+                pkt.push_back(static_cast<char>(event));
+                queue_packet(std::move(pkt));
+                ++i;
+            }
+        }
+        catch (std::bad_alloc &exc) {
+            do_oom_close();
+        }
+    }
+    
+    public:
+    control_conn_t(eventloop_t &loop, service_set * services_p, int fd)
+            : iob(loop), loop(loop), services(services_p), chklen(0)
+    {
+        iob.add_watch(loop, fd, dasynq::IN_EVENTS);
+        active_control_conns++;
+    }
+    
+    bool rollback_complete() noexcept;
+        
+    virtual ~control_conn_t() noexcept;
+};
+
+
+static dasynq::rearm control_conn_cb(eventloop_t * loop, control_conn_watcher * watcher, int revents)
+{
+    // Get the address of the containing control_connt_t object:
+    _Pragma ("GCC diagnostic push")
+    _Pragma ("GCC diagnostic ignored \"-Winvalid-offsetof\"")
+    char * cc_addr = (reinterpret_cast<char *>(watcher)) - offsetof(control_conn_t, iob);
+    control_conn_t *conn = reinterpret_cast<control_conn_t *>(cc_addr);
+    _Pragma ("GCC diagnostic pop")
+
+    if (revents & dasynq::IN_EVENTS) {
+        if (conn->data_ready()) {
+            delete conn;
+            return dasynq::rearm::REMOVED;
+        }
+    }
+    if (revents & dasynq::OUT_EVENTS) {
+        if (conn->send_data()) {
+            delete conn;
+            return dasynq::rearm::REMOVED;
+        }
+    }
+    
+    return dasynq::rearm::NOOP;
+}
+
+#endif
diff --git a/src/includes/cpbuffer.h b/src/includes/cpbuffer.h
new file mode 100644 (file)
index 0000000..9019f4a
--- /dev/null
@@ -0,0 +1,147 @@
+#ifndef CPBUFFER_H
+#define CPBUFFER_H
+
+#include <cstring>
+
+// control protocol buffer, a circular buffer with 1024-byte capacity.
+template <int SIZE> class cpbuffer
+{
+    char buf[SIZE];
+    int cur_idx = 0;
+    int length = 0;  // number of elements in the buffer
+    
+    public:
+    int get_length() noexcept
+    {
+        return length;
+    }
+    
+    int get_free() noexcept
+    {
+        return SIZE - length;
+    }
+    
+    char * get_ptr(int index)
+    {
+        int pos = cur_idx + index;
+        if (pos >= SIZE) pos -= SIZE;
+    
+        return &buf[pos];
+    }
+    
+    char * get_buf_base()
+    {
+        return buf;
+    }
+    
+    int get_contiguous_length(char *ptr)
+    {
+        int eidx = cur_idx + length;
+        if (eidx >= SIZE) eidx -= SIZE;
+        
+        if (buf + eidx > ptr) {
+            return (buf + eidx) - ptr;
+        }
+        else {
+            return (buf + SIZE) - ptr;
+        }
+    }
+    
+    // fill by reading from the given fd, return positive if some was read or -1 on error.
+    int fill(int fd) noexcept
+    {
+        int pos = cur_idx + length;
+        if (pos >= SIZE) pos -= SIZE;
+        int max_count = std::min(SIZE - pos, SIZE - length);
+        ssize_t r = read(fd, buf + pos, max_count);
+        if (r >= 0) {
+            length += r;
+        }
+        return r;
+    }
+    
+    // fill by reading from the given fd, until at least the specified number of bytes are in
+    // the buffer. Return 0 if end-of-file reached before fill complete, or -1 on error.
+    int fill_to(int fd, int rlength) noexcept
+    {
+        while (length < rlength) {
+            int r = fill(fd);
+            if (r <= 0) return r;
+        }
+        return 1;
+    }
+    
+    // Trim the buffer to the specified length (must be less than current length)
+    void trim_to(int new_length)
+    {
+        length = new_length;
+    }
+    
+    char operator[](int idx) noexcept
+    {
+        int dest_idx = cur_idx + idx;
+        if (dest_idx > SIZE) dest_idx -= SIZE;
+        return buf[dest_idx];
+    }
+    
+    // Remove the given number of bytes from the start of the buffer.
+    void consume(int amount) noexcept
+    {
+        cur_idx += amount;
+        if (cur_idx >= SIZE) cur_idx -= SIZE;
+        length -= amount;
+    }
+    
+    // Extract bytes from the buffer. The bytes remain in the buffer.
+    void extract(char *dest, int index, int length) noexcept
+    {
+        index += cur_idx;
+        if (index >= SIZE) index -= SIZE;
+        if (index + length > SIZE) {
+            // wrap-around copy
+            int half = SIZE - index;
+            std::memcpy(dest, buf + index, half);
+            std::memcpy(dest + half, buf, length - half);
+        }
+        else {
+            std::memcpy(dest, buf + index, length);
+        }
+    }
+    
+    // Extract string of given length from given index
+    // Throws:  std::bad_alloc on allocation failure
+    std::string extract_string(int index, int length)
+    {
+        index += cur_idx;
+        if (index >= SIZE) index -= SIZE;
+        if (index + length > SIZE) {
+            std::string r(buf + index, SIZE - index);
+            r.insert(r.end(), buf, buf + length - (SIZE - index));
+            return r;
+        }
+        else {
+            return std::string(buf + index, length);
+        }
+    }
+    
+    // Append characters to the buffer. Caller must make certain there
+    // is enough space to contain the characters first.
+    void append(const char * s, int len) noexcept
+    {
+        int index = cur_idx + length;
+        if (index >= SIZE) index -= SIZE;
+
+        length += len; // (before we destroy len)
+        
+        int max = SIZE - index;
+        std::memcpy(buf + index, s, std::min(max, len));
+        if (len > max) {
+            // Wrapped around buffer: copy the rest
+            s += max;
+            len -= max;
+            std::memcpy(buf, s, len);
+        }
+    }
+};
+
+#endif
diff --git a/src/includes/dinit-ll.h b/src/includes/dinit-ll.h
new file mode 100644 (file)
index 0000000..101aa3f
--- /dev/null
@@ -0,0 +1,154 @@
+#ifndef DINIT_LL_INCLUDED
+#define DINIT_LL_INCLUDED 1
+
+// Simple single- and doubly-linked list implementation, where the contained element includes the
+// list node. This allows a single item to be a member of several different kinds of list, without
+// requiring dynamic allocation of nodes for the different lists.
+//
+// To accomplish this without abstraction penalty, the function to retrieve the list node from the
+// element is specified as the second template parameter.
+
+// Doubly-linked list node:
+template <typename T>
+struct lld_node
+{
+    T * next = nullptr;
+    T * prev = nullptr;
+};
+
+// Singly-linked list node:
+template <typename T>
+struct lls_node
+{
+    T * next = nullptr;
+};
+
+// Doubly-linked list implementation. The list is circular, so 'first->prev' yields the tail of
+// the list, though we still need to special-case the empty list (where first == nullptr).
+// next/prev pointers in a node are set to nullptr when the node is not linked into a list
+// (and are never equal to nullptr when the node is linked into a list).
+template <typename T, lld_node<T> &(*E)(T *)>
+class dlist
+{
+    T * first;
+    // E extractor;
+
+    public:
+    dlist() noexcept : first(nullptr) { }
+
+    bool is_queued(T *e) noexcept
+    {
+        auto &node = E(e);
+        return node.next != nullptr;
+    }
+
+    void append(T *e) noexcept
+    {
+        auto &node = E(e);
+        if (first == nullptr) {
+            first = e;
+            node.next = e;
+            node.prev = e;
+        }
+        else {
+            node.next = first;
+            node.prev = E(first).prev;
+            E(E(first).prev).next = e;
+            E(first).prev = e;
+        }
+    }
+
+    T * tail() noexcept
+    {
+        if (first == nullptr) {
+            return nullptr;
+        }
+        else {
+            return E(first).prev;
+        }
+    }
+
+    bool is_empty() noexcept
+    {
+        return first == nullptr;
+    }
+
+    T * pop_front() noexcept
+    {
+        auto r = first;
+        auto &first_node = E(first);
+        if (first_node.next == first) {
+            // Only one node in the queue:
+            first_node.next = nullptr;
+            first_node.prev = nullptr;
+            first = nullptr;
+        }
+        else {
+            // Unlink first node:
+            auto &node = E(first_node.next);
+            node.prev = first_node.prev;
+            E(node.prev).next = first_node.next;
+            first = first_node.next;
+            // Return original first node:
+            first_node.next = nullptr;
+            first_node.prev = nullptr;
+        }
+        return r;
+    }
+
+    void unlink(T *record) noexcept
+    {
+        auto &node = E(record);
+        if (first == record) {
+            first = node.next;
+            if (first == record) {
+                // unlinking the only node in the list:
+                first = nullptr;
+            }
+        }
+        E(node.next).prev = node.prev;
+        E(node.prev).next = node.next;
+        node.next = nullptr;
+        node.prev = nullptr;
+    }
+};
+
+// Singly-linked list implementation.
+template <typename T, lls_node<T> &(*E)(T *)>
+class slist
+{
+    T * first;
+
+    public:
+    slist() noexcept : first(nullptr) { }
+
+    bool is_queued(T *e) noexcept
+    {
+        auto &node = E(e);
+        return node.next != nullptr || first == e;
+    }
+
+    void insert(T *e) noexcept
+    {
+        auto &node = E(e);
+        node.next = first;
+        first = e;
+    }
+
+    bool is_empty() noexcept
+    {
+        return first == nullptr;
+    }
+
+    T * pop_front() noexcept
+    {
+        T * r = first;
+        auto &node = E(r);
+        first = node.next;
+        node.next = nullptr;
+        return r;
+    }
+};
+
+
+#endif
diff --git a/src/includes/dinit-log.h b/src/includes/dinit-log.h
new file mode 100644 (file)
index 0000000..b94a658
--- /dev/null
@@ -0,0 +1,118 @@
+#ifndef DINIT_LOG_H
+#define DINIT_LOG_H
+
+// Logging for Dinit
+
+#include <string>
+#include <cstdio>
+#include <climits>
+
+class service_set;
+
+enum class loglevel_t {
+    DEBUG,
+    INFO,
+    WARN,
+    ERROR,
+    ZERO    // log absolutely nothing
+};
+
+extern loglevel_t log_level[2];
+void enable_console_log(bool do_enable) noexcept;
+void init_log(service_set *sset);
+void setup_main_log(int fd);
+bool is_log_flushed() noexcept;
+void discard_console_log_buffer() noexcept;
+
+void log(loglevel_t lvl, const char *msg) noexcept;
+void log_msg_begin(loglevel_t lvl, const char *msg) noexcept;
+void log_msg_part(const char *msg) noexcept;
+void log_msg_end(const char *msg) noexcept;
+void log_service_started(const char *service_name) noexcept;
+void log_service_failed(const char *service_name) noexcept;
+void log_service_stopped(const char *service_name) noexcept;
+
+// Convenience methods which perform type conversion of the argument.
+// There is some duplication here that could possibly be avoided, but
+// it doesn't seem like a big deal.
+static inline void log(loglevel_t lvl, const std::string &str) noexcept
+{
+    log(lvl, str.c_str());
+}
+
+static inline void log_msg_begin(loglevel_t lvl, const std::string &str) noexcept
+{
+    log_msg_begin(lvl, str.c_str());
+}
+
+static inline void log_msg_begin(loglevel_t lvl, int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    log_msg_begin(lvl, nbuf);
+}
+
+static inline void log_msg_part(const std::string &str) noexcept
+{
+    log_msg_part(str.c_str());
+}
+
+static inline void log_msg_part(int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    log_msg_part(nbuf);
+}
+
+static inline void log_msg_end(const std::string &str) noexcept
+{
+    log_msg_end(str.c_str());
+}
+
+static inline void log_msg_end(int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    log_msg_end(nbuf);
+}
+
+static inline void log_service_started(const std::string &str) noexcept
+{
+    log_service_started(str.c_str());
+}
+
+static inline void log_service_failed(const std::string &str) noexcept
+{
+    log_service_failed(str.c_str());
+}
+
+static inline void log_service_stopped(const std::string &str) noexcept
+{
+    log_service_stopped(str.c_str());
+}
+
+// It's not intended that methods in this namespace be called directly:
+namespace dinit_log {
+    template <typename A> static inline void log_parts(A a) noexcept
+    {
+        log_msg_end(a);
+    }
+
+    template <typename A, typename ...B> static inline void log_parts(A a, B... b) noexcept
+    {
+        log_msg_part(a);
+        log_parts(b...);
+    }
+}
+
+// Variadic 'log' method.
+template <typename A, typename ...B> static inline void log(loglevel_t lvl, A a, B ...b) noexcept
+{
+    log_msg_begin(lvl, a);
+    dinit_log::log_parts(b...);
+}
+
+#endif
diff --git a/src/includes/dinit-socket.h b/src/includes/dinit-socket.h
new file mode 100644 (file)
index 0000000..1e3b3e9
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef _DINIT_SOCKET_H_INCLUDED
+#define _DINIT_SOCKET_H_INCLUDED
+
+#include <sys/socket.h>
+#include <fcntl.h>
+
+namespace {
+#if !defined(SOCK_NONBLOCK) && !defined(SOCK_CLOEXEC)
+    // make our own accept4 on systems that don't have it:
+    constexpr int SOCK_NONBLOCK = 1;
+    constexpr int SOCK_CLOEXEC = 2;
+    inline int dinit_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
+    {
+        int fd = accept(sockfd, addr, addrlen);
+        if (fd == -1) {
+            return -1;
+        }
+
+        if (flags & SOCK_CLOEXEC)  fcntl(fd, F_SETFD, FD_CLOEXEC);
+        if (flags & SOCK_NONBLOCK) fcntl(fd, F_SETFL, O_NONBLOCK);
+        return fd;
+    }
+
+    inline int dinit_socket(int domain, int type, int protocol, int flags)
+    {
+        int fd = socket(domain, type, protocol);
+        if (fd == -1) {
+            return -1;
+        }
+
+        if (flags & SOCK_CLOEXEC)  fcntl(fd, F_SETFD, FD_CLOEXEC);
+        if (flags & SOCK_NONBLOCK) fcntl(fd, F_SETFL, O_NONBLOCK);
+        return fd;
+    }
+
+    inline int dinit_socketpair(int domain, int type, int protocol, int socket_vector[2], int flags)
+    {
+        int r = socketpair(domain, type, protocol, socket_vector);
+        if (r == -1) {
+            return -1;
+        }
+
+        if (flags & SOCK_CLOEXEC) {
+            fcntl(socket_vector[0], F_SETFD, FD_CLOEXEC);
+            fcntl(socket_vector[1], F_SETFD, FD_CLOEXEC);
+        }
+        if (flags & SOCK_NONBLOCK) {
+            fcntl(socket_vector[0], F_SETFL, O_NONBLOCK);
+            fcntl(socket_vector[1], F_SETFL, O_NONBLOCK);
+        }
+        return 0;
+    }
+
+#else
+    inline int dinit_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
+    {
+        return accept4(sockfd, addr, addrlen, flags);
+    }
+
+    inline int dinit_socket(int domain, int type, int protocol, int flags)
+    {
+        return socket(domain, type | flags, protocol);
+    }
+
+    inline int dinit_socketpair(int domain, int type, int protocol, int socket_vector[2], int flags)
+    {
+        return socketpair(domain, type | flags, protocol, socket_vector);
+    }
+#endif
+}
+
+#endif
diff --git a/src/includes/dinit-util.h b/src/includes/dinit-util.h
new file mode 100644 (file)
index 0000000..77170f4
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef DINIT_UTIL_H_INCLUDED
+#define DINIT_UTIL_H_INCLUDED 1
+
+#include <cstddef>
+#include <cerrno>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+// Signal-safe read. Read and re-try if interrupted by signal (EINTR).
+// *May* affect errno even on a successful read (when the return is less than n).
+inline ssize_t ss_read(int fd, void * buf, size_t n)
+{
+    char * cbuf = static_cast<char *>(buf);
+    ssize_t r = 0;
+    while ((size_t)r < n) {
+        ssize_t res = read(fd, cbuf + r, n - r);
+        if (res == 0) {
+            return r;
+        }
+        if (res < 0) {
+            if (res == EINTR) {
+                continue;
+            }
+
+            // If any other error, and we have successfully read some, return it:
+            if (r == 0) {
+                return -1;
+            }
+            else {
+                return r;
+            }
+        }
+        r += res;
+    }
+    return n;
+}
+
+#endif
diff --git a/src/includes/dinit.h b/src/includes/dinit.h
new file mode 100644 (file)
index 0000000..ef6b962
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef DINIT_H_INCLUDED
+#define DINIT_H_INCLUDED 1
+
+#include "dasynq.h"
+
+/*
+ * General Dinit definitions.
+ */
+
+using eventloop_t = dasynq::event_loop<dasynq::null_mutex>;
+
+using clock_type = dasynq::clock_type;
+using rearm = dasynq::rearm;
+using time_val = dasynq::time_val;
+
+void open_control_socket(bool report_ro_failure = true) noexcept;
+void setup_external_log() noexcept;
+
+extern eventloop_t event_loop;
+
+#endif
diff --git a/src/includes/proc-service.h b/src/includes/proc-service.h
new file mode 100644 (file)
index 0000000..d32938f
--- /dev/null
@@ -0,0 +1,232 @@
+#include "service.h"
+
+// Given a string and a list of pairs of (start,end) indices for each argument in that string,
+// store a null terminator for the argument. Return a `char *` vector containing the beginning
+// of each argument and a trailing nullptr. (The returned array is invalidated if the string is later modified).
+std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices);
+
+class base_process_service;
+
+// A timer for process restarting. Used to ensure a minimum delay between process restarts (and
+// also for timing service stop before the SIGKILL hammer is used).
+class process_restart_timer : public eventloop_t::timer_impl<process_restart_timer>
+{
+    public:
+    base_process_service * service;
+
+    process_restart_timer(base_process_service *service_p)
+        : service(service_p)
+    {
+    }
+
+    dasynq::rearm timer_expiry(eventloop_t &, int expiry_count);
+};
+
+class base_process_service : public service_record
+{
+    friend class service_child_watcher;
+    friend class exec_status_pipe_watcher;
+    friend class process_restart_timer;
+
+    private:
+    // Re-launch process
+    void do_restart() noexcept;
+
+    protected:
+    string program_name;          // storage for program/script and arguments
+    std::vector<const char *> exec_arg_parts; // pointer to each argument/part of the program_name, and nullptr
+
+    string stop_command;          // storage for stop program/script and arguments
+    std::vector<const char *> stop_arg_parts; // pointer to each argument/part of the stop_command, and nullptr
+
+    service_child_watcher child_listener;
+    exec_status_pipe_watcher child_status_listener;
+    process_restart_timer restart_timer;
+    time_val last_start_time;
+
+    // Restart interval time and restart count are used to track the number of automatic restarts
+    // over an interval. Too many restarts over an interval will inhibit further restarts.
+    time_val restart_interval_time;  // current restart interval
+    int restart_interval_count;      // count of restarts within current interval
+
+    time_val restart_interval;       // maximum restart interval
+    int max_restart_interval_count;  // number of restarts allowed over maximum interval
+    time_val restart_delay;          // delay between restarts
+
+    // Time allowed for service stop, after which SIGKILL is sent. 0 to disable.
+    time_val stop_timeout = {10, 0}; // default of 10 seconds
+
+    // Time allowed for service start, after which SIGINT is sent (and then SIGKILL after
+    // <stop_timeout>). 0 to disable.
+    time_val start_timeout = {60, 0}; // default of 1 minute
+
+    bool waiting_restart_timer : 1;
+    bool stop_timer_armed : 1;
+    bool reserved_child_watch : 1;
+    bool tracking_child : 1;  // whether we expect to see child process status
+    bool start_is_interruptible : 1;  // whether we can interrupt start
+
+    // Launch the process with the given arguments, return true on success
+    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
+
+    // Restart the process (due to start failure or unexpected termination). Restarts will be
+    // rate-limited.
+    bool restart_ps_process() noexcept;
+
+    // Perform smooth recovery process
+    void do_smooth_recovery() noexcept;
+
+    // Start the process, return true on success
+    virtual bool bring_up() noexcept override;
+
+    virtual void bring_down() noexcept override;
+
+    // Called when the process exits. The exit_status is the status value yielded by
+    // the "wait" system call.
+    virtual void handle_exit_status(int exit_status) noexcept = 0;
+
+    // Called if an exec fails.
+    virtual void exec_failed(int errcode) noexcept = 0;
+
+    // Called if exec succeeds.
+    virtual void exec_succeeded() noexcept { };
+
+    virtual bool can_interrupt_start() noexcept override
+    {
+        return waiting_restart_timer || start_is_interruptible || service_record::can_interrupt_start();
+    }
+
+    virtual bool can_proceed_to_start() noexcept override
+    {
+        return ! waiting_restart_timer;
+    }
+
+    virtual bool interrupt_start() noexcept override;
+
+    // Kill with SIGKILL
+    void kill_with_fire() noexcept;
+
+    // Signal the process group of the service process
+    void kill_pg(int signo) noexcept;
+
+    public:
+    base_process_service(service_set *sset, string name, service_type_t record_type_p, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            const std::list<prelim_dep> &deplist_p);
+
+    ~base_process_service() noexcept
+    {
+    }
+
+    // Set the stop command and arguments (may throw std::bad_alloc)
+    void set_stop_command(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
+    {
+        stop_command = command;
+        stop_arg_parts = separate_args(stop_command, stop_command_offsets);
+    }
+
+    void set_restart_interval(timespec interval, int max_restarts) noexcept
+    {
+        restart_interval = interval;
+        max_restart_interval_count = max_restarts;
+    }
+
+    void set_restart_delay(timespec delay) noexcept
+    {
+        restart_delay = delay;
+    }
+
+    void set_stop_timeout(timespec timeout) noexcept
+    {
+        stop_timeout = timeout;
+    }
+
+    void set_start_timeout(timespec timeout) noexcept
+    {
+        start_timeout = timeout;
+    }
+
+    void set_start_interruptible(bool value) noexcept
+    {
+        start_is_interruptible = value;
+    }
+};
+
+class process_service : public base_process_service
+{
+    virtual void handle_exit_status(int exit_status) noexcept override;
+    virtual void exec_failed(int errcode) noexcept override;
+    virtual void exec_succeeded() noexcept override;
+    virtual void bring_down() noexcept override;
+
+    public:
+    process_service(service_set *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            std::list<prelim_dep> depends_p)
+         : base_process_service(sset, name, service_type_t::PROCESS, std::move(command), command_offsets,
+             depends_p)
+    {
+    }
+
+    ~process_service() noexcept
+    {
+    }
+};
+
+class bgproc_service : public base_process_service
+{
+    virtual void handle_exit_status(int exit_status) noexcept override;
+    virtual void exec_failed(int errcode) noexcept override;
+
+    enum class pid_result_t {
+        OK,
+        FAILED,      // failed to read pid or read invalid pid
+        TERMINATED   // read pid successfully, but the process already terminated
+    };
+
+    // Read the pid-file, return false on failure
+    pid_result_t read_pid_file(int *exit_status) noexcept;
+
+    public:
+    bgproc_service(service_set *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            std::list<prelim_dep> depends_p)
+         : base_process_service(sset, name, service_type_t::BGPROCESS, std::move(command), command_offsets,
+             depends_p)
+    {
+    }
+
+    ~bgproc_service() noexcept
+    {
+    }
+};
+
+class scripted_service : public base_process_service
+{
+    virtual void handle_exit_status(int exit_status) noexcept override;
+    virtual void exec_failed(int errcode) noexcept override;
+    virtual void bring_down() noexcept override;
+
+    virtual bool interrupt_start() noexcept override
+    {
+        // if base::interrupt_start() returns false, then start hasn't been fully interrupted, but an
+        // interrupt has been issued:
+        interrupting_start = ! base_process_service::interrupt_start();
+        return ! interrupting_start;
+    }
+
+    bool interrupting_start : 1;  // running start script (true) or stop script (false)
+
+    public:
+    scripted_service(service_set *sset, string name, string &&command,
+            std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            std::list<prelim_dep> depends_p)
+         : base_process_service(sset, name, service_type_t::SCRIPTED, std::move(command), command_offsets,
+             depends_p), interrupting_start(false)
+    {
+    }
+
+    ~scripted_service() noexcept
+    {
+    }
+};
diff --git a/src/includes/service-constants.h b/src/includes/service-constants.h
new file mode 100644 (file)
index 0000000..9a5e477
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef SERVICE_CONSTANTS_H
+#define SERVICE_CONSTANTS_H
+
+/* Service states */
+enum class service_state_t {
+    STOPPED,    // service is not running.
+    STARTING,   // service is starting, and will start (or fail to start) in time.
+    STARTED,    // service is running,
+    STOPPING    // service script is stopping and will stop.
+};
+
+/* Service types */
+enum class service_type_t {
+    DUMMY,      // Dummy service, used to detect cyclice dependencies
+    PROCESS,    // Service runs as a process, and can be stopped by
+                // sending the process a signal (usually SIGTERM)
+    BGPROCESS,  // Service runs as a process which "daemonizes" to run in the
+                // "background".
+    SCRIPTED,   // Service requires an external command to start,
+                // and a second command to stop
+    INTERNAL    // Internal service, runs no external process
+};
+
+/* Service events */
+enum class service_event_t {
+    STARTED,           // Service was started (reached STARTED state)
+    STOPPED,           // Service was stopped (reached STOPPED state)
+    FAILEDSTART,       // Service failed to start (possibly due to dependency failing)
+    STARTCANCELLED,    // Service was set to be started but a stop was requested
+    STOPCANCELLED      // Service was set to be stopped but a start was requested
+};
+
+/* Shutdown types */
+enum class shutdown_type_t {
+    CONTINUE,          // Continue normal boot sequence (used after single-user shell)
+    HALT,              // Halt system without powering down
+    POWEROFF,          // Power off system
+    REBOOT             // Reboot system
+};
+
+#endif
diff --git a/src/includes/service-listener.h b/src/includes/service-listener.h
new file mode 100644 (file)
index 0000000..34b64ed
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef SERVICE_LISTENER_H
+#define SERVICE_LISTENER_H
+
+#include "service-constants.h"
+
+class service_record;
+
+// Interface for listening to services
+class service_listener
+{
+    public:
+    
+    // An event occurred on the service being observed.
+    // Listeners must not be added or removed during event notification.
+    virtual void service_event(service_record * service, service_event_t event) noexcept = 0;
+};
+
+#endif
diff --git a/src/includes/service.h b/src/includes/service.h
new file mode 100644 (file)
index 0000000..de20521
--- /dev/null
@@ -0,0 +1,824 @@
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include <string>
+#include <list>
+#include <vector>
+#include <csignal>
+#include <unordered_set>
+#include <algorithm>
+
+#include "dasynq.h"
+
+#include "control.h"
+#include "service-listener.h"
+#include "service-constants.h"
+#include "dinit-ll.h"
+
+/*
+ * This header defines service_record, a data record maintaining information about a service,
+ * and service_set, a set of interdependent service records. It also defines some associated
+ * types and exceptions.
+ *
+ * Service states
+ * --------------
+ * Services have both a current state and a desired state. The desired state can be
+ * either STARTED or STOPPED. The current state can also be STARTING or STOPPING.
+ * A service can be "pinned" in either the STARTED or STOPPED states to prevent it
+ * from leaving that state until it is unpinned.
+ *
+ * The total state is a combination of the two, current and desired:
+ *      STOPPED/STOPPED  : stopped and will remain stopped
+ *      STOPPED/STARTED  : stopped (pinned), must be unpinned to start
+ *      STARTING/STARTED : starting, but not yet started. Dependencies may also be starting.
+ *      STARTING/STOPPED : as above, but the service will be stopped again as soon as it has
+ *                         completed startup.
+ *      STARTED/STARTED  : running and will continue running.
+ *      STARTED/STOPPED  : started (pinned), must be unpinned to stop
+ *      STOPPING/STOPPED : stopping and will stop. Dependents may be stopping.
+ *      STOPPING/STARTED : as above, but the service will be re-started again once it stops.
+ *
+ * A scripted service is in the STARTING/STOPPING states during the script execution.
+ * A process service is in the STOPPING state when it has been signalled to stop, and is
+ * in the STARTING state when waiting for dependencies to start or for the exec() call in
+ * the forked child to complete and return a status.
+ *
+ * Acquisition/release:
+ * ------------------
+ * Each service has a dependent-count ("required_by"). This starts at 0, adds 1 if the
+ * service has explicitly been started (i.e. "start_explicit" is true), and adds 1 for
+ * each dependent service which is not STOPPED (including dependents with a soft dependency).
+ * When required_by transitions to 0, the service is stopped (unless it is pinned). When
+ * require_by transitions from 0, the service is started (unless pinned).
+ *
+ * So, in general, the dependent-count determines the desired state (STARTED if the count
+ * is greater than 0, otherwise STOPPED). However, a service can be issued a stop-and-take
+ * down order (via `stop(true)'); this will first stop dependent services, which may restart
+ * and cancel the stop of the former service. Finally, a service can be force-stopped, which
+ * means that its stop process cannot be cancelled (though it may still be put in a desired
+ * state of STARTED, meaning it will start immediately upon stopping).
+ *
+ * Pinning
+ * -------
+ * A service may be "pinned" in either STARTED or STOPPED states (or even both). Once it
+ * reaches a pinned state, a service will not leave that state, though its desired state
+ * may still be set. (Note that pinning prevents, but never causes, state transition).
+ *
+ * The priority of the different state deciders is:
+ *  - pins
+ *  - force stop flag
+ *  - desired state (which is manipulated by require/release operations)
+ *
+ * So a forced stop cannot occur until the service is not pinned started, for instance.
+ *
+ * Two-phase transition
+ * --------------------
+ * Transition between states occurs in two phases: propagation and execution. In both phases
+ * a linked-list queue is used to keep track of which services need processing; this avoids
+ * recursion (which would be of unknown depth and therefore liable to stack overflow).
+ *
+ * In the propagation phase, acquisition/release messages are processed, and desired state may be
+ * altered accordingly. Start and stop requests are also propagated in this phase. The state may
+ * be set to STARTING or STOPPING to reflect the desired state, but will never be set to STARTED
+ * or STOPPED (that happens in the execution phase).
+ *
+ * Propagation variables:
+ *   prop_acquire:  the service has transitioned to an acquired state and must issue an acquire
+ *                  on its dependencies
+ *   prop_release:  the service has transitioned to a released state and must issue a release on
+ *                  its dependencies.
+ *
+ *   prop_start:    the service should start
+ *   prop_stop:     the service should stop
+ *
+ * Note that "prop_acquire"/"prop_release" form a pair which cannot both be set at the same time
+ * which is enforced via explicit checks. For "prop_start"/"prop_stop" this occurs implicitly.
+ *
+ * In the execution phase, actions are taken to achieve the desired state. Actual state may
+ * transition according to the current and desired states. Processes can be sent signals, etc
+ * in order to stop them. A process can restart if it stops, but it does so by raising prop_start
+ * which needs to be processed in a second transition phase. Seeing as starting never causes
+ * another process to stop, the transition-execute-transition cycle always ends at the 2nd
+ * transition stage, at the latest.
+ */
+
+struct onstart_flags_t {
+    bool rw_ready : 1;  // file system should be writable once this service starts
+    bool log_ready : 1; // syslog should be available once this service starts
+    
+    // Not actually "onstart" commands:
+    bool no_sigterm : 1;  // do not send SIGTERM
+    bool runs_on_console : 1;  // run "in the foreground"
+    bool starts_on_console : 1; // starts in the foreground
+    bool pass_cs_fd : 1;  // pass this service a control socket connection via fd
+    
+    onstart_flags_t() noexcept : rw_ready(false), log_ready(false),
+            no_sigterm(false), runs_on_console(false), starts_on_console(false), pass_cs_fd(false)
+    {
+    }
+};
+
+// Exception while loading a service
+class service_load_exc
+{
+    public:
+    std::string serviceName;
+    std::string excDescription;
+    
+    protected:
+    service_load_exc(std::string serviceName, std::string &&desc) noexcept
+        : serviceName(serviceName), excDescription(std::move(desc))
+    {
+    }
+};
+
+class service_not_found : public service_load_exc
+{
+    public:
+    service_not_found(std::string serviceName) noexcept
+        : service_load_exc(serviceName, "Service description not found.")
+    {
+    }
+};
+
+class service_cyclic_dependency : public service_load_exc
+{
+    public:
+    service_cyclic_dependency(std::string serviceName) noexcept
+        : service_load_exc(serviceName, "Has cyclic dependency.")
+    {
+    }
+};
+
+class service_description_exc : public service_load_exc
+{
+    public:
+    service_description_exc(std::string serviceName, std::string &&extraInfo) noexcept
+        : service_load_exc(serviceName, std::move(extraInfo))
+    {
+    }    
+};
+
+class service_record;
+class service_set;
+class base_process_service;
+
+enum class dependency_type
+{
+    REGULAR,
+    SOFT,       // dependency starts in parallel, failure/stop does not affect dependent
+    WAITS_FOR,  // as for SOFT, but dependent waits until dependency starts/fails before starting
+    MILESTONE   // dependency must start successfully, but once started the dependency becomes soft
+};
+
+/* Service dependency record */
+class service_dep
+{
+    service_record * from;
+    service_record * to;
+
+    public:
+    /* Whether the 'from' service is waiting for the 'to' service to start */
+    bool waiting_on;
+    /* Whether the 'from' service is holding an acquire on the 'to' service */
+    bool holding_acq;
+
+    const dependency_type dep_type;
+
+    service_dep(service_record * from, service_record * to, dependency_type dep_type_p) noexcept
+            : from(from), to(to), waiting_on(false), holding_acq(false), dep_type(dep_type_p)
+    {  }
+
+    service_record * get_from() noexcept
+    {
+        return from;
+    }
+
+    service_record * get_to() noexcept
+    {
+        return to;
+    }
+};
+
+/* preliminary service dependency information */
+class prelim_dep
+{
+    public:
+    service_record * const to;
+    dependency_type const dep_type;
+
+    prelim_dep(service_record *to_p, dependency_type dep_type_p) : to(to_p), dep_type(dep_type_p)
+    {
+        //
+    }
+};
+
+class service_child_watcher : public eventloop_t::child_proc_watcher_impl<service_child_watcher>
+{
+    public:
+    base_process_service * service;
+    dasynq::rearm status_change(eventloop_t &eloop, pid_t child, int status) noexcept;
+    
+    service_child_watcher(base_process_service * sr) noexcept : service(sr) { }
+};
+
+// Watcher for the pipe used to receive exec() failure status errno
+class exec_status_pipe_watcher : public eventloop_t::fd_watcher_impl<exec_status_pipe_watcher>
+{
+    public:
+    base_process_service * service;
+    dasynq::rearm fd_event(eventloop_t &eloop, int fd, int flags) noexcept;
+    
+    exec_status_pipe_watcher(base_process_service * sr) noexcept : service(sr) { }
+};
+
+// service_record: base class for service record containing static information
+// and current state of each service.
+//
+// This abstract base class defines the dependency behaviour of services. The actions to actually bring a
+// service up or down are specified by subclasses in the virtual methods (see especially bring_up() and
+// bring_down()).
+//
+class service_record
+{
+    protected:
+    using string = std::string;
+    using time_val = dasynq::time_val;
+    
+    private:
+    string service_name;
+    service_type_t record_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
+    service_state_t service_state = service_state_t::STOPPED; /* service_state_t::STOPPED, STARTING, STARTED, STOPPING */
+    service_state_t desired_state = service_state_t::STOPPED; /* service_state_t::STOPPED / STARTED */
+
+    protected:
+    string pid_file;
+    
+    onstart_flags_t onstart_flags;
+
+    string logfile;           // log file name, empty string specifies /dev/null
+    
+    bool auto_restart : 1;    // whether to restart this (process) if it dies unexpectedly
+    bool smooth_recovery : 1; // whether the service process can restart without bringing down service
+    
+    bool pinned_stopped : 1;
+    bool pinned_started : 1;
+    bool waiting_for_deps : 1;  // if STARTING, whether we are waiting for dependencies (inc console) to start
+                                // if STOPPING, whether we are waiting for dependents to stop
+    bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
+    bool start_explicit : 1;    // whether we are are explicitly required to be started
+
+    bool prop_require : 1;      // require must be propagated
+    bool prop_release : 1;      // release must be propagated
+    bool prop_failure : 1;      // failure to start must be propagated
+    bool prop_start   : 1;
+    bool prop_stop    : 1;
+
+    bool restarting   : 1;      // re-starting after unexpected termination
+    
+    int required_by = 0;        // number of dependents wanting this service to be started
+
+    // list of dependencies
+    typedef std::list<service_dep> dep_list;
+    
+    // list of dependents
+    typedef std::list<service_dep *> dpt_list;
+    
+    dep_list depends_on;  // services this one depends on
+    dpt_list dependents;  // services depending on this one
+    
+    service_set *services; // the set this service belongs to
+    
+    std::unordered_set<service_listener *> listeners;
+    
+    // Process services:
+    bool force_stop; // true if the service must actually stop. This is the
+                     // case if for example the process dies; the service,
+                     // and all its dependencies, MUST be stopped.
+    
+    int term_signal = -1;  // signal to use for process termination
+    
+    string socket_path; // path to the socket for socket-activation service
+    int socket_perms;   // socket permissions ("mode")
+    uid_t socket_uid = -1;  // socket user id or -1
+    gid_t socket_gid = -1;  // socket group id or -1
+
+    // Implementation details
+    
+    pid_t pid = -1;  // PID of the process. If state is STARTING or STOPPING,
+                     //   this is PID of the service script; otherwise it is the
+                     //   PID of the process itself (process service).
+    int exit_status; // Exit status, if the process has exited (pid == -1).
+    int socket_fd = -1;  // For socket-activation services, this is the file
+                         // descriptor for the socket.
+    
+    // Data for use by service_set
+    public:
+    
+    // Console queue.
+    lld_node<service_record> console_queue_node;
+    
+    // Propagation and start/stop queues
+    lls_node<service_record> prop_queue_node;
+    lls_node<service_record> stop_queue_node;
+    
+    protected:
+    
+    // stop immediately
+    void emergency_stop() noexcept;
+    
+    // Service has actually stopped (includes having all dependents
+    // reaching STOPPED state).
+    void stopped() noexcept;
+    
+    // Service has successfully started
+    void started() noexcept;
+    
+    // Service failed to start (only called when in STARTING state).
+    //   dep_failed: whether failure is recorded due to a dependency failing
+    void failed_to_start(bool dep_failed = false) noexcept;
+
+    void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd,
+            int csfd) noexcept;
+    
+    // A dependency has reached STARTED state
+    void dependency_started() noexcept;
+    
+    void all_deps_started(bool haveConsole = false) noexcept;
+
+    // Open the activation socket, return false on failure
+    bool open_socket() noexcept;
+
+    // Start all dependencies, return true if all have started
+    bool start_check_dependencies() noexcept;
+
+    // Check whether all dependencies have started (i.e. whether we can start now)
+    bool check_deps_started() noexcept;
+
+    // Whether a STOPPING service can immediately transition to STARTED.
+    bool can_interrupt_stop() noexcept
+    {
+        return waiting_for_deps && ! force_stop;
+    }
+
+    // A dependent has reached STOPPED state
+    void dependent_stopped() noexcept;
+
+    // check if all dependents have stopped
+    bool stop_check_dependents() noexcept;
+    
+    // issue a stop to all dependents, return true if they are all already stopped
+    bool stop_dependents() noexcept;
+    
+    void require() noexcept;
+    void release() noexcept;
+    void release_dependencies() noexcept;
+    
+    // Check if service is, fundamentally, stopped.
+    bool is_stopped() noexcept
+    {
+        return service_state == service_state_t::STOPPED
+            || (service_state == service_state_t::STARTING && waiting_for_deps);
+    }
+    
+    void notify_listeners(service_event_t event) noexcept
+    {
+        for (auto l : listeners) {
+            l->service_event(this, event);
+        }
+    }
+    
+    // Queue to run on the console. 'acquired_console()' will be called when the console is available.
+    // Has no effect if the service has already queued for console.
+    void queue_for_console() noexcept;
+    
+    // Release console (console must be currently held by this service)
+    void release_console() noexcept;
+    
+    bool do_auto_restart() noexcept;
+
+    // Started state reached
+    bool process_started() noexcept;
+
+    // Called on transition of desired state from stopped to started (or unpinned stop)
+    void do_start() noexcept;
+
+    // Called on transition of desired state from started to stopped (or unpinned start)
+    void do_stop() noexcept;
+
+    // Set the service state
+    void set_state(service_state_t new_state) noexcept
+    {
+        service_state = new_state;
+    }
+
+    // Virtual functions, to be implemented by service implementations:
+
+    // Do any post-dependency startup; return false on failure
+    virtual bool bring_up() noexcept;
+
+    // All dependents have stopped, and this service should proceed to stop.
+    virtual void bring_down() noexcept;
+
+    // Whether a STARTING service can immediately transition to STOPPED (as opposed to
+    // having to wait for it reach STARTED and then go through STOPPING).
+    virtual bool can_interrupt_start() noexcept
+    {
+        return waiting_for_deps;
+    }
+
+    // Whether a STARTING service can transition to its STARTED state, once all
+    // dependencies have started.
+    virtual bool can_proceed_to_start() noexcept
+    {
+        return true;
+    }
+
+    // Interrupt startup. Returns true if service start is fully cancelled; returns false if cancel order
+    // issued but service has not yet responded (state will be set to STOPPING).
+    virtual bool interrupt_start() noexcept;
+
+    public:
+
+    service_record(service_set *set, string name)
+        : service_state(service_state_t::STOPPED), desired_state(service_state_t::STOPPED),
+            auto_restart(false), smooth_recovery(false),
+            pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
+            waiting_for_execstat(false), start_explicit(false),
+            prop_require(false), prop_release(false), prop_failure(false),
+            prop_start(false), prop_stop(false), restarting(false), force_stop(false)
+    {
+        services = set;
+        service_name = name;
+        record_type = service_type_t::DUMMY;
+        socket_perms = 0;
+        exit_status = 0;
+    }
+
+    service_record(service_set *set, string name, service_type_t record_type_p,
+            const std::list<prelim_dep> &deplist_p)
+        : service_record(set, name)
+    {
+        services = set;
+        service_name = name;
+        this->record_type = record_type_p;
+
+        for (auto & pdep : deplist_p) {
+            auto b = depends_on.emplace(depends_on.end(), this, pdep.to, pdep.dep_type);
+            pdep.to->dependents.push_back(&(*b));
+        }
+    }
+
+    virtual ~service_record() noexcept
+    {
+    }
+    
+    // Get the type of this service record
+    service_type_t get_type() noexcept
+    {
+        return record_type;
+    }
+
+    // begin transition from stopped to started state or vice versa depending on current and desired state
+    void execute_transition() noexcept;
+    
+    void do_propagation() noexcept;
+
+    // Console is available.
+    void acquired_console() noexcept;
+    
+    // Get the target (aka desired) state.
+    service_state_t get_target_state() noexcept
+    {
+        return desired_state;
+    }
+
+    // Set logfile, should be done before service is started
+    void set_log_file(string logfile)
+    {
+        this->logfile = logfile;
+    }
+    
+    // Set whether this service should automatically restart when it dies
+    void set_auto_restart(bool auto_restart) noexcept
+    {
+        this->auto_restart = auto_restart;
+    }
+    
+    void set_smooth_recovery(bool smooth_recovery) noexcept
+    {
+        this->smooth_recovery = smooth_recovery;
+    }
+    
+    // Set "on start" flags (commands)
+    void set_flags(onstart_flags_t flags) noexcept
+    {
+        this->onstart_flags = flags;
+    }
+    
+    // Set an additional signal (other than SIGTERM) to be used to terminate the process
+    void set_extra_termination_signal(int signo) noexcept
+    {
+        this->term_signal = signo;
+    }
+    
+    void set_pid_file(string &&pid_file) noexcept
+    {
+        this->pid_file = std::move(pid_file);
+    }
+    
+    void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
+    {
+        this->socket_path = std::move(socket_path);
+        this->socket_perms = socket_perms;
+        this->socket_uid = socket_uid;
+        this->socket_gid = socket_gid;
+    }
+
+    const std::string &get_name() const noexcept { return service_name; }
+    service_state_t get_state() const noexcept { return service_state; }
+    
+    void start(bool activate = true) noexcept;  // start the service
+    void stop(bool bring_down = true) noexcept;   // stop the service
+    
+    void forced_stop() noexcept; // force-stop this service and all dependents
+    
+    // Pin the service in "started" state (when it reaches the state)
+    void pin_start() noexcept
+    {
+        pinned_started = true;
+    }
+    
+    // Pin the service in "stopped" state (when it reaches the state)
+    void pin_stop() noexcept
+    {
+        pinned_stopped = true;
+    }
+    
+    // Remove both "started" and "stopped" pins. If the service is currently pinned
+    // in either state but would naturally be in the opposite state, it will immediately
+    // commence starting/stopping.
+    void unpin() noexcept;
+    
+    bool isDummy() noexcept
+    {
+        return record_type == service_type_t::DUMMY;
+    }
+    
+    // Add a listener. A listener must only be added once. May throw std::bad_alloc.
+    void addListener(service_listener * listener)
+    {
+        listeners.insert(listener);
+    }
+    
+    // Remove a listener.    
+    void removeListener(service_listener * listener) noexcept
+    {
+        listeners.erase(listener);
+    }
+};
+
+inline auto extract_prop_queue(service_record *sr) -> decltype(sr->prop_queue_node) &
+{
+    return sr->prop_queue_node;
+}
+
+inline auto extract_stop_queue(service_record *sr) -> decltype(sr->stop_queue_node) &
+{
+    return sr->stop_queue_node;
+}
+
+inline auto extract_console_queue(service_record *sr) -> decltype(sr->console_queue_node) &
+{
+    return sr->console_queue_node;
+}
+
+/*
+ * A service_set, as the name suggests, manages a set of services.
+ *
+ * Other than the ability to find services by name, the service set manages various queues.
+ * One is the queue for processes wishing to acquire the console. There is also a set of
+ * processes that want to start, and another set of those that want to stop. These latter
+ * two "queues" (not really queues since their order is not important) are used to prevent too
+ * much recursion and to prevent service states from "bouncing" too rapidly.
+ * 
+ * A service that wishes to start or stop puts itself on the start/stop queue; a service that
+ * needs to propagate changes to dependent services or dependencies puts itself on the
+ * propagation queue. Any operation that potentially manipulates the queues must be followed
+ * by a "process queues" order (processQueues() method).
+ *
+ * Note that processQueues always repeatedly processes both queues until they are empty. The
+ * process is finite because starting a service can never cause services to stop, unless they
+ * fail to start, which should cause them to stop semi-permanently.
+ */
+class service_set
+{
+    protected:
+    int active_services;
+    std::list<service_record *> records;
+    bool restart_enabled; // whether automatic restart is enabled (allowed)
+    
+    shutdown_type_t shutdown_type = shutdown_type_t::CONTINUE;  // Shutdown type, if stopping
+    
+    // Services waiting for exclusive access to the console
+    dlist<service_record, extract_console_queue> console_queue;
+
+    // Propagation and start/stop "queues" - list of services waiting for processing
+    slist<service_record, extract_prop_queue> prop_queue;
+    slist<service_record, extract_stop_queue> stop_queue;
+    
+    public:
+    service_set()
+    {
+        active_services = 0;
+        restart_enabled = true;
+    }
+    
+    virtual ~service_set()
+    {
+        for (auto * s : records) {
+            delete s;
+        }
+    }
+
+    // Start the specified service. The service will be marked active.
+    void start_service(service_record *svc)
+    {
+        svc->start();
+        process_queues();
+    }
+
+    // Stop the specified service. Its active mark will be cleared.
+    void stop_service(service_record *svc)
+    {
+        svc->stop(true);
+        process_queues();
+    }
+
+    // Locate an existing service record.
+    service_record *find_service(const std::string &name) noexcept;
+
+    // Load a service description, and dependencies, if there is no existing
+    // record for the given name.
+    // Throws:
+    //   ServiceLoadException (or subclass) on problem with service description
+    //   std::bad_alloc on out-of-memory condition
+    virtual service_record *load_service(const char *name)
+    {
+        auto r = find_service(name);
+        if (r == nullptr) {
+            throw service_not_found(name);
+        }
+        return r;
+    }
+
+    // Start the service with the given name. The named service will begin
+    // transition to the 'started' state.
+    //
+    // Throws a ServiceLoadException (or subclass) if the service description
+    // cannot be loaded or is invalid;
+    // Throws std::bad_alloc if out of memory.
+    void start_service(const char *name)
+    {
+        using namespace std;
+        service_record *record = load_service(name);
+        service_set::start_service(record);
+    }
+    
+    void add_service(service_record *svc)
+    {
+        records.push_back(svc);
+    }
+    
+    void remove_service(service_record *svc)
+    {
+        std::remove(records.begin(), records.end(), svc);
+    }
+
+    // Get the list of all loaded services.
+    const std::list<service_record *> &list_services() noexcept
+    {
+        return records;
+    }
+    
+    // Stop the service with the given name. The named service will begin
+    // transition to the 'stopped' state.
+    void stop_service(const std::string &name) noexcept;
+    
+    // Add a service record to the state propagation queue. The service record will have its
+    // do_propagation() method called when the queue is processed.
+    void add_prop_queue(service_record *service) noexcept
+    {
+        if (! prop_queue.is_queued(service)) {
+            prop_queue.insert(service);
+        }
+    }
+    
+    // Add a service record to the stop queue. The service record will have its
+    // execute_transition() method called when the queue is processed.
+    void add_transition_queue(service_record *service) noexcept
+    {
+        if (! stop_queue.is_queued(service)) {
+            stop_queue.insert(service);
+        }
+    }
+    
+    // Process state propagation and start/stop queues, until they are empty.
+    void process_queues() noexcept
+    {
+        while (! stop_queue.is_empty() || ! prop_queue.is_empty()) {
+            while (! prop_queue.is_empty()) {
+                auto next = prop_queue.pop_front();
+                next->do_propagation();
+            }
+            while (! stop_queue.is_empty()) {
+                auto next = stop_queue.pop_front();
+                next->execute_transition();
+            }
+        }
+    }
+    
+    // Set the console queue tail (returns previous tail)
+    void append_console_queue(service_record * newTail) noexcept
+    {
+        bool was_empty = console_queue.is_empty();
+        console_queue.append(newTail);
+        if (was_empty) {
+            enable_console_log(false);
+        }
+    }
+    
+    // Pull and dispatch a waiter from the console queue
+    void pull_console_queue() noexcept
+    {
+        if (console_queue.is_empty()) {
+            enable_console_log(true);
+        }
+        else {
+            service_record * front = console_queue.pop_front();
+            front->acquired_console();
+        }
+    }
+    
+    void unqueue_console(service_record * service) noexcept
+    {
+        if (console_queue.is_queued(service)) {
+            console_queue.unlink(service);
+        }
+    }
+
+    // Notification from service that it is active (state != STOPPED)
+    // Only to be called on the transition from inactive to active.
+    void service_active(service_record *) noexcept;
+    
+    // Notification from service that it is inactive (STOPPED)
+    // Only to be called on the transition from active to inactive.
+    void service_inactive(service_record *) noexcept;
+    
+    // Find out how many services are active (starting, running or stopping,
+    // but not stopped).
+    int count_active_services() noexcept
+    {
+        return active_services;
+    }
+    
+    void stop_all_services(shutdown_type_t type = shutdown_type_t::HALT) noexcept
+    {
+        restart_enabled = false;
+        shutdown_type = type;
+        for (std::list<service_record *>::iterator i = records.begin(); i != records.end(); ++i) {
+            (*i)->stop(false);
+            (*i)->unpin();
+        }
+        process_queues();
+    }
+    
+    void set_auto_restart(bool restart) noexcept
+    {
+        restart_enabled = restart;
+    }
+    
+    bool get_auto_restart() noexcept
+    {
+        return restart_enabled;
+    }
+    
+    shutdown_type_t getShutdownType() noexcept
+    {
+        return shutdown_type;
+    }
+};
+
+class dirload_service_set : public service_set
+{
+    const char *service_dir;  // directory containing service descriptions
+
+    public:
+    dirload_service_set(const char *service_dir_p) : service_set(), service_dir(service_dir_p)
+    {
+    }
+
+    service_record *load_service(const char *name) override;
+};
+
+#endif
diff --git a/src/proc-service.h b/src/proc-service.h
deleted file mode 100644 (file)
index d32938f..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-#include "service.h"
-
-// Given a string and a list of pairs of (start,end) indices for each argument in that string,
-// store a null terminator for the argument. Return a `char *` vector containing the beginning
-// of each argument and a trailing nullptr. (The returned array is invalidated if the string is later modified).
-std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices);
-
-class base_process_service;
-
-// A timer for process restarting. Used to ensure a minimum delay between process restarts (and
-// also for timing service stop before the SIGKILL hammer is used).
-class process_restart_timer : public eventloop_t::timer_impl<process_restart_timer>
-{
-    public:
-    base_process_service * service;
-
-    process_restart_timer(base_process_service *service_p)
-        : service(service_p)
-    {
-    }
-
-    dasynq::rearm timer_expiry(eventloop_t &, int expiry_count);
-};
-
-class base_process_service : public service_record
-{
-    friend class service_child_watcher;
-    friend class exec_status_pipe_watcher;
-    friend class process_restart_timer;
-
-    private:
-    // Re-launch process
-    void do_restart() noexcept;
-
-    protected:
-    string program_name;          // storage for program/script and arguments
-    std::vector<const char *> exec_arg_parts; // pointer to each argument/part of the program_name, and nullptr
-
-    string stop_command;          // storage for stop program/script and arguments
-    std::vector<const char *> stop_arg_parts; // pointer to each argument/part of the stop_command, and nullptr
-
-    service_child_watcher child_listener;
-    exec_status_pipe_watcher child_status_listener;
-    process_restart_timer restart_timer;
-    time_val last_start_time;
-
-    // Restart interval time and restart count are used to track the number of automatic restarts
-    // over an interval. Too many restarts over an interval will inhibit further restarts.
-    time_val restart_interval_time;  // current restart interval
-    int restart_interval_count;      // count of restarts within current interval
-
-    time_val restart_interval;       // maximum restart interval
-    int max_restart_interval_count;  // number of restarts allowed over maximum interval
-    time_val restart_delay;          // delay between restarts
-
-    // Time allowed for service stop, after which SIGKILL is sent. 0 to disable.
-    time_val stop_timeout = {10, 0}; // default of 10 seconds
-
-    // Time allowed for service start, after which SIGINT is sent (and then SIGKILL after
-    // <stop_timeout>). 0 to disable.
-    time_val start_timeout = {60, 0}; // default of 1 minute
-
-    bool waiting_restart_timer : 1;
-    bool stop_timer_armed : 1;
-    bool reserved_child_watch : 1;
-    bool tracking_child : 1;  // whether we expect to see child process status
-    bool start_is_interruptible : 1;  // whether we can interrupt start
-
-    // Launch the process with the given arguments, return true on success
-    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
-
-    // Restart the process (due to start failure or unexpected termination). Restarts will be
-    // rate-limited.
-    bool restart_ps_process() noexcept;
-
-    // Perform smooth recovery process
-    void do_smooth_recovery() noexcept;
-
-    // Start the process, return true on success
-    virtual bool bring_up() noexcept override;
-
-    virtual void bring_down() noexcept override;
-
-    // Called when the process exits. The exit_status is the status value yielded by
-    // the "wait" system call.
-    virtual void handle_exit_status(int exit_status) noexcept = 0;
-
-    // Called if an exec fails.
-    virtual void exec_failed(int errcode) noexcept = 0;
-
-    // Called if exec succeeds.
-    virtual void exec_succeeded() noexcept { };
-
-    virtual bool can_interrupt_start() noexcept override
-    {
-        return waiting_restart_timer || start_is_interruptible || service_record::can_interrupt_start();
-    }
-
-    virtual bool can_proceed_to_start() noexcept override
-    {
-        return ! waiting_restart_timer;
-    }
-
-    virtual bool interrupt_start() noexcept override;
-
-    // Kill with SIGKILL
-    void kill_with_fire() noexcept;
-
-    // Signal the process group of the service process
-    void kill_pg(int signo) noexcept;
-
-    public:
-    base_process_service(service_set *sset, string name, service_type_t record_type_p, string &&command,
-            std::list<std::pair<unsigned,unsigned>> &command_offsets,
-            const std::list<prelim_dep> &deplist_p);
-
-    ~base_process_service() noexcept
-    {
-    }
-
-    // Set the stop command and arguments (may throw std::bad_alloc)
-    void set_stop_command(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
-    {
-        stop_command = command;
-        stop_arg_parts = separate_args(stop_command, stop_command_offsets);
-    }
-
-    void set_restart_interval(timespec interval, int max_restarts) noexcept
-    {
-        restart_interval = interval;
-        max_restart_interval_count = max_restarts;
-    }
-
-    void set_restart_delay(timespec delay) noexcept
-    {
-        restart_delay = delay;
-    }
-
-    void set_stop_timeout(timespec timeout) noexcept
-    {
-        stop_timeout = timeout;
-    }
-
-    void set_start_timeout(timespec timeout) noexcept
-    {
-        start_timeout = timeout;
-    }
-
-    void set_start_interruptible(bool value) noexcept
-    {
-        start_is_interruptible = value;
-    }
-};
-
-class process_service : public base_process_service
-{
-    virtual void handle_exit_status(int exit_status) noexcept override;
-    virtual void exec_failed(int errcode) noexcept override;
-    virtual void exec_succeeded() noexcept override;
-    virtual void bring_down() noexcept override;
-
-    public:
-    process_service(service_set *sset, string name, string &&command,
-            std::list<std::pair<unsigned,unsigned>> &command_offsets,
-            std::list<prelim_dep> depends_p)
-         : base_process_service(sset, name, service_type_t::PROCESS, std::move(command), command_offsets,
-             depends_p)
-    {
-    }
-
-    ~process_service() noexcept
-    {
-    }
-};
-
-class bgproc_service : public base_process_service
-{
-    virtual void handle_exit_status(int exit_status) noexcept override;
-    virtual void exec_failed(int errcode) noexcept override;
-
-    enum class pid_result_t {
-        OK,
-        FAILED,      // failed to read pid or read invalid pid
-        TERMINATED   // read pid successfully, but the process already terminated
-    };
-
-    // Read the pid-file, return false on failure
-    pid_result_t read_pid_file(int *exit_status) noexcept;
-
-    public:
-    bgproc_service(service_set *sset, string name, string &&command,
-            std::list<std::pair<unsigned,unsigned>> &command_offsets,
-            std::list<prelim_dep> depends_p)
-         : base_process_service(sset, name, service_type_t::BGPROCESS, std::move(command), command_offsets,
-             depends_p)
-    {
-    }
-
-    ~bgproc_service() noexcept
-    {
-    }
-};
-
-class scripted_service : public base_process_service
-{
-    virtual void handle_exit_status(int exit_status) noexcept override;
-    virtual void exec_failed(int errcode) noexcept override;
-    virtual void bring_down() noexcept override;
-
-    virtual bool interrupt_start() noexcept override
-    {
-        // if base::interrupt_start() returns false, then start hasn't been fully interrupted, but an
-        // interrupt has been issued:
-        interrupting_start = ! base_process_service::interrupt_start();
-        return ! interrupting_start;
-    }
-
-    bool interrupting_start : 1;  // running start script (true) or stop script (false)
-
-    public:
-    scripted_service(service_set *sset, string name, string &&command,
-            std::list<std::pair<unsigned,unsigned>> &command_offsets,
-            std::list<prelim_dep> depends_p)
-         : base_process_service(sset, name, service_type_t::SCRIPTED, std::move(command), command_offsets,
-             depends_p), interrupting_start(false)
-    {
-    }
-
-    ~scripted_service() noexcept
-    {
-    }
-};
diff --git a/src/service-constants.h b/src/service-constants.h
deleted file mode 100644 (file)
index 9a5e477..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef SERVICE_CONSTANTS_H
-#define SERVICE_CONSTANTS_H
-
-/* Service states */
-enum class service_state_t {
-    STOPPED,    // service is not running.
-    STARTING,   // service is starting, and will start (or fail to start) in time.
-    STARTED,    // service is running,
-    STOPPING    // service script is stopping and will stop.
-};
-
-/* Service types */
-enum class service_type_t {
-    DUMMY,      // Dummy service, used to detect cyclice dependencies
-    PROCESS,    // Service runs as a process, and can be stopped by
-                // sending the process a signal (usually SIGTERM)
-    BGPROCESS,  // Service runs as a process which "daemonizes" to run in the
-                // "background".
-    SCRIPTED,   // Service requires an external command to start,
-                // and a second command to stop
-    INTERNAL    // Internal service, runs no external process
-};
-
-/* Service events */
-enum class service_event_t {
-    STARTED,           // Service was started (reached STARTED state)
-    STOPPED,           // Service was stopped (reached STOPPED state)
-    FAILEDSTART,       // Service failed to start (possibly due to dependency failing)
-    STARTCANCELLED,    // Service was set to be started but a stop was requested
-    STOPCANCELLED      // Service was set to be stopped but a start was requested
-};
-
-/* Shutdown types */
-enum class shutdown_type_t {
-    CONTINUE,          // Continue normal boot sequence (used after single-user shell)
-    HALT,              // Halt system without powering down
-    POWEROFF,          // Power off system
-    REBOOT             // Reboot system
-};
-
-#endif
diff --git a/src/service-listener.h b/src/service-listener.h
deleted file mode 100644 (file)
index 34b64ed..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef SERVICE_LISTENER_H
-#define SERVICE_LISTENER_H
-
-#include "service-constants.h"
-
-class service_record;
-
-// Interface for listening to services
-class service_listener
-{
-    public:
-    
-    // An event occurred on the service being observed.
-    // Listeners must not be added or removed during event notification.
-    virtual void service_event(service_record * service, service_event_t event) noexcept = 0;
-};
-
-#endif
diff --git a/src/service.h b/src/service.h
deleted file mode 100644 (file)
index de20521..0000000
+++ /dev/null
@@ -1,824 +0,0 @@
-#ifndef SERVICE_H
-#define SERVICE_H
-
-#include <string>
-#include <list>
-#include <vector>
-#include <csignal>
-#include <unordered_set>
-#include <algorithm>
-
-#include "dasynq.h"
-
-#include "control.h"
-#include "service-listener.h"
-#include "service-constants.h"
-#include "dinit-ll.h"
-
-/*
- * This header defines service_record, a data record maintaining information about a service,
- * and service_set, a set of interdependent service records. It also defines some associated
- * types and exceptions.
- *
- * Service states
- * --------------
- * Services have both a current state and a desired state. The desired state can be
- * either STARTED or STOPPED. The current state can also be STARTING or STOPPING.
- * A service can be "pinned" in either the STARTED or STOPPED states to prevent it
- * from leaving that state until it is unpinned.
- *
- * The total state is a combination of the two, current and desired:
- *      STOPPED/STOPPED  : stopped and will remain stopped
- *      STOPPED/STARTED  : stopped (pinned), must be unpinned to start
- *      STARTING/STARTED : starting, but not yet started. Dependencies may also be starting.
- *      STARTING/STOPPED : as above, but the service will be stopped again as soon as it has
- *                         completed startup.
- *      STARTED/STARTED  : running and will continue running.
- *      STARTED/STOPPED  : started (pinned), must be unpinned to stop
- *      STOPPING/STOPPED : stopping and will stop. Dependents may be stopping.
- *      STOPPING/STARTED : as above, but the service will be re-started again once it stops.
- *
- * A scripted service is in the STARTING/STOPPING states during the script execution.
- * A process service is in the STOPPING state when it has been signalled to stop, and is
- * in the STARTING state when waiting for dependencies to start or for the exec() call in
- * the forked child to complete and return a status.
- *
- * Acquisition/release:
- * ------------------
- * Each service has a dependent-count ("required_by"). This starts at 0, adds 1 if the
- * service has explicitly been started (i.e. "start_explicit" is true), and adds 1 for
- * each dependent service which is not STOPPED (including dependents with a soft dependency).
- * When required_by transitions to 0, the service is stopped (unless it is pinned). When
- * require_by transitions from 0, the service is started (unless pinned).
- *
- * So, in general, the dependent-count determines the desired state (STARTED if the count
- * is greater than 0, otherwise STOPPED). However, a service can be issued a stop-and-take
- * down order (via `stop(true)'); this will first stop dependent services, which may restart
- * and cancel the stop of the former service. Finally, a service can be force-stopped, which
- * means that its stop process cannot be cancelled (though it may still be put in a desired
- * state of STARTED, meaning it will start immediately upon stopping).
- *
- * Pinning
- * -------
- * A service may be "pinned" in either STARTED or STOPPED states (or even both). Once it
- * reaches a pinned state, a service will not leave that state, though its desired state
- * may still be set. (Note that pinning prevents, but never causes, state transition).
- *
- * The priority of the different state deciders is:
- *  - pins
- *  - force stop flag
- *  - desired state (which is manipulated by require/release operations)
- *
- * So a forced stop cannot occur until the service is not pinned started, for instance.
- *
- * Two-phase transition
- * --------------------
- * Transition between states occurs in two phases: propagation and execution. In both phases
- * a linked-list queue is used to keep track of which services need processing; this avoids
- * recursion (which would be of unknown depth and therefore liable to stack overflow).
- *
- * In the propagation phase, acquisition/release messages are processed, and desired state may be
- * altered accordingly. Start and stop requests are also propagated in this phase. The state may
- * be set to STARTING or STOPPING to reflect the desired state, but will never be set to STARTED
- * or STOPPED (that happens in the execution phase).
- *
- * Propagation variables:
- *   prop_acquire:  the service has transitioned to an acquired state and must issue an acquire
- *                  on its dependencies
- *   prop_release:  the service has transitioned to a released state and must issue a release on
- *                  its dependencies.
- *
- *   prop_start:    the service should start
- *   prop_stop:     the service should stop
- *
- * Note that "prop_acquire"/"prop_release" form a pair which cannot both be set at the same time
- * which is enforced via explicit checks. For "prop_start"/"prop_stop" this occurs implicitly.
- *
- * In the execution phase, actions are taken to achieve the desired state. Actual state may
- * transition according to the current and desired states. Processes can be sent signals, etc
- * in order to stop them. A process can restart if it stops, but it does so by raising prop_start
- * which needs to be processed in a second transition phase. Seeing as starting never causes
- * another process to stop, the transition-execute-transition cycle always ends at the 2nd
- * transition stage, at the latest.
- */
-
-struct onstart_flags_t {
-    bool rw_ready : 1;  // file system should be writable once this service starts
-    bool log_ready : 1; // syslog should be available once this service starts
-    
-    // Not actually "onstart" commands:
-    bool no_sigterm : 1;  // do not send SIGTERM
-    bool runs_on_console : 1;  // run "in the foreground"
-    bool starts_on_console : 1; // starts in the foreground
-    bool pass_cs_fd : 1;  // pass this service a control socket connection via fd
-    
-    onstart_flags_t() noexcept : rw_ready(false), log_ready(false),
-            no_sigterm(false), runs_on_console(false), starts_on_console(false), pass_cs_fd(false)
-    {
-    }
-};
-
-// Exception while loading a service
-class service_load_exc
-{
-    public:
-    std::string serviceName;
-    std::string excDescription;
-    
-    protected:
-    service_load_exc(std::string serviceName, std::string &&desc) noexcept
-        : serviceName(serviceName), excDescription(std::move(desc))
-    {
-    }
-};
-
-class service_not_found : public service_load_exc
-{
-    public:
-    service_not_found(std::string serviceName) noexcept
-        : service_load_exc(serviceName, "Service description not found.")
-    {
-    }
-};
-
-class service_cyclic_dependency : public service_load_exc
-{
-    public:
-    service_cyclic_dependency(std::string serviceName) noexcept
-        : service_load_exc(serviceName, "Has cyclic dependency.")
-    {
-    }
-};
-
-class service_description_exc : public service_load_exc
-{
-    public:
-    service_description_exc(std::string serviceName, std::string &&extraInfo) noexcept
-        : service_load_exc(serviceName, std::move(extraInfo))
-    {
-    }    
-};
-
-class service_record;
-class service_set;
-class base_process_service;
-
-enum class dependency_type
-{
-    REGULAR,
-    SOFT,       // dependency starts in parallel, failure/stop does not affect dependent
-    WAITS_FOR,  // as for SOFT, but dependent waits until dependency starts/fails before starting
-    MILESTONE   // dependency must start successfully, but once started the dependency becomes soft
-};
-
-/* Service dependency record */
-class service_dep
-{
-    service_record * from;
-    service_record * to;
-
-    public:
-    /* Whether the 'from' service is waiting for the 'to' service to start */
-    bool waiting_on;
-    /* Whether the 'from' service is holding an acquire on the 'to' service */
-    bool holding_acq;
-
-    const dependency_type dep_type;
-
-    service_dep(service_record * from, service_record * to, dependency_type dep_type_p) noexcept
-            : from(from), to(to), waiting_on(false), holding_acq(false), dep_type(dep_type_p)
-    {  }
-
-    service_record * get_from() noexcept
-    {
-        return from;
-    }
-
-    service_record * get_to() noexcept
-    {
-        return to;
-    }
-};
-
-/* preliminary service dependency information */
-class prelim_dep
-{
-    public:
-    service_record * const to;
-    dependency_type const dep_type;
-
-    prelim_dep(service_record *to_p, dependency_type dep_type_p) : to(to_p), dep_type(dep_type_p)
-    {
-        //
-    }
-};
-
-class service_child_watcher : public eventloop_t::child_proc_watcher_impl<service_child_watcher>
-{
-    public:
-    base_process_service * service;
-    dasynq::rearm status_change(eventloop_t &eloop, pid_t child, int status) noexcept;
-    
-    service_child_watcher(base_process_service * sr) noexcept : service(sr) { }
-};
-
-// Watcher for the pipe used to receive exec() failure status errno
-class exec_status_pipe_watcher : public eventloop_t::fd_watcher_impl<exec_status_pipe_watcher>
-{
-    public:
-    base_process_service * service;
-    dasynq::rearm fd_event(eventloop_t &eloop, int fd, int flags) noexcept;
-    
-    exec_status_pipe_watcher(base_process_service * sr) noexcept : service(sr) { }
-};
-
-// service_record: base class for service record containing static information
-// and current state of each service.
-//
-// This abstract base class defines the dependency behaviour of services. The actions to actually bring a
-// service up or down are specified by subclasses in the virtual methods (see especially bring_up() and
-// bring_down()).
-//
-class service_record
-{
-    protected:
-    using string = std::string;
-    using time_val = dasynq::time_val;
-    
-    private:
-    string service_name;
-    service_type_t record_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
-    service_state_t service_state = service_state_t::STOPPED; /* service_state_t::STOPPED, STARTING, STARTED, STOPPING */
-    service_state_t desired_state = service_state_t::STOPPED; /* service_state_t::STOPPED / STARTED */
-
-    protected:
-    string pid_file;
-    
-    onstart_flags_t onstart_flags;
-
-    string logfile;           // log file name, empty string specifies /dev/null
-    
-    bool auto_restart : 1;    // whether to restart this (process) if it dies unexpectedly
-    bool smooth_recovery : 1; // whether the service process can restart without bringing down service
-    
-    bool pinned_stopped : 1;
-    bool pinned_started : 1;
-    bool waiting_for_deps : 1;  // if STARTING, whether we are waiting for dependencies (inc console) to start
-                                // if STOPPING, whether we are waiting for dependents to stop
-    bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
-    bool start_explicit : 1;    // whether we are are explicitly required to be started
-
-    bool prop_require : 1;      // require must be propagated
-    bool prop_release : 1;      // release must be propagated
-    bool prop_failure : 1;      // failure to start must be propagated
-    bool prop_start   : 1;
-    bool prop_stop    : 1;
-
-    bool restarting   : 1;      // re-starting after unexpected termination
-    
-    int required_by = 0;        // number of dependents wanting this service to be started
-
-    // list of dependencies
-    typedef std::list<service_dep> dep_list;
-    
-    // list of dependents
-    typedef std::list<service_dep *> dpt_list;
-    
-    dep_list depends_on;  // services this one depends on
-    dpt_list dependents;  // services depending on this one
-    
-    service_set *services; // the set this service belongs to
-    
-    std::unordered_set<service_listener *> listeners;
-    
-    // Process services:
-    bool force_stop; // true if the service must actually stop. This is the
-                     // case if for example the process dies; the service,
-                     // and all its dependencies, MUST be stopped.
-    
-    int term_signal = -1;  // signal to use for process termination
-    
-    string socket_path; // path to the socket for socket-activation service
-    int socket_perms;   // socket permissions ("mode")
-    uid_t socket_uid = -1;  // socket user id or -1
-    gid_t socket_gid = -1;  // socket group id or -1
-
-    // Implementation details
-    
-    pid_t pid = -1;  // PID of the process. If state is STARTING or STOPPING,
-                     //   this is PID of the service script; otherwise it is the
-                     //   PID of the process itself (process service).
-    int exit_status; // Exit status, if the process has exited (pid == -1).
-    int socket_fd = -1;  // For socket-activation services, this is the file
-                         // descriptor for the socket.
-    
-    // Data for use by service_set
-    public:
-    
-    // Console queue.
-    lld_node<service_record> console_queue_node;
-    
-    // Propagation and start/stop queues
-    lls_node<service_record> prop_queue_node;
-    lls_node<service_record> stop_queue_node;
-    
-    protected:
-    
-    // stop immediately
-    void emergency_stop() noexcept;
-    
-    // Service has actually stopped (includes having all dependents
-    // reaching STOPPED state).
-    void stopped() noexcept;
-    
-    // Service has successfully started
-    void started() noexcept;
-    
-    // Service failed to start (only called when in STARTING state).
-    //   dep_failed: whether failure is recorded due to a dependency failing
-    void failed_to_start(bool dep_failed = false) noexcept;
-
-    void run_child_proc(const char * const *args, const char *logfile, bool on_console, int wpipefd,
-            int csfd) noexcept;
-    
-    // A dependency has reached STARTED state
-    void dependency_started() noexcept;
-    
-    void all_deps_started(bool haveConsole = false) noexcept;
-
-    // Open the activation socket, return false on failure
-    bool open_socket() noexcept;
-
-    // Start all dependencies, return true if all have started
-    bool start_check_dependencies() noexcept;
-
-    // Check whether all dependencies have started (i.e. whether we can start now)
-    bool check_deps_started() noexcept;
-
-    // Whether a STOPPING service can immediately transition to STARTED.
-    bool can_interrupt_stop() noexcept
-    {
-        return waiting_for_deps && ! force_stop;
-    }
-
-    // A dependent has reached STOPPED state
-    void dependent_stopped() noexcept;
-
-    // check if all dependents have stopped
-    bool stop_check_dependents() noexcept;
-    
-    // issue a stop to all dependents, return true if they are all already stopped
-    bool stop_dependents() noexcept;
-    
-    void require() noexcept;
-    void release() noexcept;
-    void release_dependencies() noexcept;
-    
-    // Check if service is, fundamentally, stopped.
-    bool is_stopped() noexcept
-    {
-        return service_state == service_state_t::STOPPED
-            || (service_state == service_state_t::STARTING && waiting_for_deps);
-    }
-    
-    void notify_listeners(service_event_t event) noexcept
-    {
-        for (auto l : listeners) {
-            l->service_event(this, event);
-        }
-    }
-    
-    // Queue to run on the console. 'acquired_console()' will be called when the console is available.
-    // Has no effect if the service has already queued for console.
-    void queue_for_console() noexcept;
-    
-    // Release console (console must be currently held by this service)
-    void release_console() noexcept;
-    
-    bool do_auto_restart() noexcept;
-
-    // Started state reached
-    bool process_started() noexcept;
-
-    // Called on transition of desired state from stopped to started (or unpinned stop)
-    void do_start() noexcept;
-
-    // Called on transition of desired state from started to stopped (or unpinned start)
-    void do_stop() noexcept;
-
-    // Set the service state
-    void set_state(service_state_t new_state) noexcept
-    {
-        service_state = new_state;
-    }
-
-    // Virtual functions, to be implemented by service implementations:
-
-    // Do any post-dependency startup; return false on failure
-    virtual bool bring_up() noexcept;
-
-    // All dependents have stopped, and this service should proceed to stop.
-    virtual void bring_down() noexcept;
-
-    // Whether a STARTING service can immediately transition to STOPPED (as opposed to
-    // having to wait for it reach STARTED and then go through STOPPING).
-    virtual bool can_interrupt_start() noexcept
-    {
-        return waiting_for_deps;
-    }
-
-    // Whether a STARTING service can transition to its STARTED state, once all
-    // dependencies have started.
-    virtual bool can_proceed_to_start() noexcept
-    {
-        return true;
-    }
-
-    // Interrupt startup. Returns true if service start is fully cancelled; returns false if cancel order
-    // issued but service has not yet responded (state will be set to STOPPING).
-    virtual bool interrupt_start() noexcept;
-
-    public:
-
-    service_record(service_set *set, string name)
-        : service_state(service_state_t::STOPPED), desired_state(service_state_t::STOPPED),
-            auto_restart(false), smooth_recovery(false),
-            pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
-            waiting_for_execstat(false), start_explicit(false),
-            prop_require(false), prop_release(false), prop_failure(false),
-            prop_start(false), prop_stop(false), restarting(false), force_stop(false)
-    {
-        services = set;
-        service_name = name;
-        record_type = service_type_t::DUMMY;
-        socket_perms = 0;
-        exit_status = 0;
-    }
-
-    service_record(service_set *set, string name, service_type_t record_type_p,
-            const std::list<prelim_dep> &deplist_p)
-        : service_record(set, name)
-    {
-        services = set;
-        service_name = name;
-        this->record_type = record_type_p;
-
-        for (auto & pdep : deplist_p) {
-            auto b = depends_on.emplace(depends_on.end(), this, pdep.to, pdep.dep_type);
-            pdep.to->dependents.push_back(&(*b));
-        }
-    }
-
-    virtual ~service_record() noexcept
-    {
-    }
-    
-    // Get the type of this service record
-    service_type_t get_type() noexcept
-    {
-        return record_type;
-    }
-
-    // begin transition from stopped to started state or vice versa depending on current and desired state
-    void execute_transition() noexcept;
-    
-    void do_propagation() noexcept;
-
-    // Console is available.
-    void acquired_console() noexcept;
-    
-    // Get the target (aka desired) state.
-    service_state_t get_target_state() noexcept
-    {
-        return desired_state;
-    }
-
-    // Set logfile, should be done before service is started
-    void set_log_file(string logfile)
-    {
-        this->logfile = logfile;
-    }
-    
-    // Set whether this service should automatically restart when it dies
-    void set_auto_restart(bool auto_restart) noexcept
-    {
-        this->auto_restart = auto_restart;
-    }
-    
-    void set_smooth_recovery(bool smooth_recovery) noexcept
-    {
-        this->smooth_recovery = smooth_recovery;
-    }
-    
-    // Set "on start" flags (commands)
-    void set_flags(onstart_flags_t flags) noexcept
-    {
-        this->onstart_flags = flags;
-    }
-    
-    // Set an additional signal (other than SIGTERM) to be used to terminate the process
-    void set_extra_termination_signal(int signo) noexcept
-    {
-        this->term_signal = signo;
-    }
-    
-    void set_pid_file(string &&pid_file) noexcept
-    {
-        this->pid_file = std::move(pid_file);
-    }
-    
-    void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
-    {
-        this->socket_path = std::move(socket_path);
-        this->socket_perms = socket_perms;
-        this->socket_uid = socket_uid;
-        this->socket_gid = socket_gid;
-    }
-
-    const std::string &get_name() const noexcept { return service_name; }
-    service_state_t get_state() const noexcept { return service_state; }
-    
-    void start(bool activate = true) noexcept;  // start the service
-    void stop(bool bring_down = true) noexcept;   // stop the service
-    
-    void forced_stop() noexcept; // force-stop this service and all dependents
-    
-    // Pin the service in "started" state (when it reaches the state)
-    void pin_start() noexcept
-    {
-        pinned_started = true;
-    }
-    
-    // Pin the service in "stopped" state (when it reaches the state)
-    void pin_stop() noexcept
-    {
-        pinned_stopped = true;
-    }
-    
-    // Remove both "started" and "stopped" pins. If the service is currently pinned
-    // in either state but would naturally be in the opposite state, it will immediately
-    // commence starting/stopping.
-    void unpin() noexcept;
-    
-    bool isDummy() noexcept
-    {
-        return record_type == service_type_t::DUMMY;
-    }
-    
-    // Add a listener. A listener must only be added once. May throw std::bad_alloc.
-    void addListener(service_listener * listener)
-    {
-        listeners.insert(listener);
-    }
-    
-    // Remove a listener.    
-    void removeListener(service_listener * listener) noexcept
-    {
-        listeners.erase(listener);
-    }
-};
-
-inline auto extract_prop_queue(service_record *sr) -> decltype(sr->prop_queue_node) &
-{
-    return sr->prop_queue_node;
-}
-
-inline auto extract_stop_queue(service_record *sr) -> decltype(sr->stop_queue_node) &
-{
-    return sr->stop_queue_node;
-}
-
-inline auto extract_console_queue(service_record *sr) -> decltype(sr->console_queue_node) &
-{
-    return sr->console_queue_node;
-}
-
-/*
- * A service_set, as the name suggests, manages a set of services.
- *
- * Other than the ability to find services by name, the service set manages various queues.
- * One is the queue for processes wishing to acquire the console. There is also a set of
- * processes that want to start, and another set of those that want to stop. These latter
- * two "queues" (not really queues since their order is not important) are used to prevent too
- * much recursion and to prevent service states from "bouncing" too rapidly.
- * 
- * A service that wishes to start or stop puts itself on the start/stop queue; a service that
- * needs to propagate changes to dependent services or dependencies puts itself on the
- * propagation queue. Any operation that potentially manipulates the queues must be followed
- * by a "process queues" order (processQueues() method).
- *
- * Note that processQueues always repeatedly processes both queues until they are empty. The
- * process is finite because starting a service can never cause services to stop, unless they
- * fail to start, which should cause them to stop semi-permanently.
- */
-class service_set
-{
-    protected:
-    int active_services;
-    std::list<service_record *> records;
-    bool restart_enabled; // whether automatic restart is enabled (allowed)
-    
-    shutdown_type_t shutdown_type = shutdown_type_t::CONTINUE;  // Shutdown type, if stopping
-    
-    // Services waiting for exclusive access to the console
-    dlist<service_record, extract_console_queue> console_queue;
-
-    // Propagation and start/stop "queues" - list of services waiting for processing
-    slist<service_record, extract_prop_queue> prop_queue;
-    slist<service_record, extract_stop_queue> stop_queue;
-    
-    public:
-    service_set()
-    {
-        active_services = 0;
-        restart_enabled = true;
-    }
-    
-    virtual ~service_set()
-    {
-        for (auto * s : records) {
-            delete s;
-        }
-    }
-
-    // Start the specified service. The service will be marked active.
-    void start_service(service_record *svc)
-    {
-        svc->start();
-        process_queues();
-    }
-
-    // Stop the specified service. Its active mark will be cleared.
-    void stop_service(service_record *svc)
-    {
-        svc->stop(true);
-        process_queues();
-    }
-
-    // Locate an existing service record.
-    service_record *find_service(const std::string &name) noexcept;
-
-    // Load a service description, and dependencies, if there is no existing
-    // record for the given name.
-    // Throws:
-    //   ServiceLoadException (or subclass) on problem with service description
-    //   std::bad_alloc on out-of-memory condition
-    virtual service_record *load_service(const char *name)
-    {
-        auto r = find_service(name);
-        if (r == nullptr) {
-            throw service_not_found(name);
-        }
-        return r;
-    }
-
-    // Start the service with the given name. The named service will begin
-    // transition to the 'started' state.
-    //
-    // Throws a ServiceLoadException (or subclass) if the service description
-    // cannot be loaded or is invalid;
-    // Throws std::bad_alloc if out of memory.
-    void start_service(const char *name)
-    {
-        using namespace std;
-        service_record *record = load_service(name);
-        service_set::start_service(record);
-    }
-    
-    void add_service(service_record *svc)
-    {
-        records.push_back(svc);
-    }
-    
-    void remove_service(service_record *svc)
-    {
-        std::remove(records.begin(), records.end(), svc);
-    }
-
-    // Get the list of all loaded services.
-    const std::list<service_record *> &list_services() noexcept
-    {
-        return records;
-    }
-    
-    // Stop the service with the given name. The named service will begin
-    // transition to the 'stopped' state.
-    void stop_service(const std::string &name) noexcept;
-    
-    // Add a service record to the state propagation queue. The service record will have its
-    // do_propagation() method called when the queue is processed.
-    void add_prop_queue(service_record *service) noexcept
-    {
-        if (! prop_queue.is_queued(service)) {
-            prop_queue.insert(service);
-        }
-    }
-    
-    // Add a service record to the stop queue. The service record will have its
-    // execute_transition() method called when the queue is processed.
-    void add_transition_queue(service_record *service) noexcept
-    {
-        if (! stop_queue.is_queued(service)) {
-            stop_queue.insert(service);
-        }
-    }
-    
-    // Process state propagation and start/stop queues, until they are empty.
-    void process_queues() noexcept
-    {
-        while (! stop_queue.is_empty() || ! prop_queue.is_empty()) {
-            while (! prop_queue.is_empty()) {
-                auto next = prop_queue.pop_front();
-                next->do_propagation();
-            }
-            while (! stop_queue.is_empty()) {
-                auto next = stop_queue.pop_front();
-                next->execute_transition();
-            }
-        }
-    }
-    
-    // Set the console queue tail (returns previous tail)
-    void append_console_queue(service_record * newTail) noexcept
-    {
-        bool was_empty = console_queue.is_empty();
-        console_queue.append(newTail);
-        if (was_empty) {
-            enable_console_log(false);
-        }
-    }
-    
-    // Pull and dispatch a waiter from the console queue
-    void pull_console_queue() noexcept
-    {
-        if (console_queue.is_empty()) {
-            enable_console_log(true);
-        }
-        else {
-            service_record * front = console_queue.pop_front();
-            front->acquired_console();
-        }
-    }
-    
-    void unqueue_console(service_record * service) noexcept
-    {
-        if (console_queue.is_queued(service)) {
-            console_queue.unlink(service);
-        }
-    }
-
-    // Notification from service that it is active (state != STOPPED)
-    // Only to be called on the transition from inactive to active.
-    void service_active(service_record *) noexcept;
-    
-    // Notification from service that it is inactive (STOPPED)
-    // Only to be called on the transition from active to inactive.
-    void service_inactive(service_record *) noexcept;
-    
-    // Find out how many services are active (starting, running or stopping,
-    // but not stopped).
-    int count_active_services() noexcept
-    {
-        return active_services;
-    }
-    
-    void stop_all_services(shutdown_type_t type = shutdown_type_t::HALT) noexcept
-    {
-        restart_enabled = false;
-        shutdown_type = type;
-        for (std::list<service_record *>::iterator i = records.begin(); i != records.end(); ++i) {
-            (*i)->stop(false);
-            (*i)->unpin();
-        }
-        process_queues();
-    }
-    
-    void set_auto_restart(bool restart) noexcept
-    {
-        restart_enabled = restart;
-    }
-    
-    bool get_auto_restart() noexcept
-    {
-        return restart_enabled;
-    }
-    
-    shutdown_type_t getShutdownType() noexcept
-    {
-        return shutdown_type;
-    }
-};
-
-class dirload_service_set : public service_set
-{
-    const char *service_dir;  // directory containing service descriptions
-
-    public:
-    dirload_service_set(const char *service_dir_p) : service_set(), service_dir(service_dir_p)
-    {
-    }
-
-    service_record *load_service(const char *name) override;
-};
-
-#endif
index 26a9eef08e03b3e174f8cae0dfc4ec2cb9e198aa..376955770817c1cda346b214da7f76fa995bc532 100644 (file)
@@ -12,15 +12,15 @@ tests: $(objects) $(parent_objs)
        $(CXX) $(SANITIZEOPTS) -o tests $(objects) $(parent_objs) $(EXTRA_LIBS)
 
 $(objects): %.o: %.cc
-       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -I.. -I../dasynq -c $< -o $@
+       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -I../includes -I../dasynq -c $< -o $@
 
 $(parent_objs): %.o: ../%.cc
-       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -I.. -I../dasynq -c $< -o $@
+       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -I../includes -I../dasynq -c $< -o $@
 
 clean:
        rm -f *.o *.d
 
 $(objects:.o=.d): %.d: %.cc
-       $(CXX) $(CXXOPTS) -I.. -I../dasynq -MM -MG -MF $@ $<
+       $(CXX) $(CXXOPTS) -I../includes -I../dasynq -MM -MG -MF $@ $<
 
 include $(objects:.o=.d)