Move source files int src directory
authorDavin McCall <davmac@davmac.org>
Tue, 5 Jan 2016 23:55:05 +0000 (23:55 +0000)
committerDavin McCall <davmac@davmac.org>
Tue, 5 Jan 2016 23:55:05 +0000 (23:55 +0000)
34 files changed:
Makefile [deleted file]
control-cmds.h [deleted file]
control.cc [deleted file]
control.h [deleted file]
cpbuffer.h [deleted file]
dinit-log.cc [deleted file]
dinit-log.h [deleted file]
dinit-start.cc [deleted file]
dinit.cc [deleted file]
halt [deleted file]
load_service.cc [deleted file]
reboot [deleted file]
service-constants.h [deleted file]
service-listener.h [deleted file]
service.cc [deleted file]
service.h [deleted file]
shutdown.cc [deleted file]
src/Makefile [new file with mode: 0644]
src/control-cmds.h [new file with mode: 0644]
src/control.cc [new file with mode: 0644]
src/control.h [new file with mode: 0644]
src/cpbuffer.h [new file with mode: 0644]
src/dinit-log.cc [new file with mode: 0644]
src/dinit-log.h [new file with mode: 0644]
src/dinit-start.cc [new file with mode: 0644]
src/dinit.cc [new file with mode: 0644]
src/halt [new file with mode: 0755]
src/load_service.cc [new file with mode: 0644]
src/reboot [new file with mode: 0755]
src/service-constants.h [new file with mode: 0644]
src/service-listener.h [new file with mode: 0644]
src/service.cc [new file with mode: 0644]
src/service.h [new file with mode: 0644]
src/shutdown.cc [new file with mode: 0644]

diff --git a/Makefile b/Makefile
deleted file mode 100644 (file)
index fe89883..0000000
--- a/Makefile
+++ /dev/null
@@ -1,32 +0,0 @@
--include mconfig
-
-objects = dinit.o load_service.o service.o control.o dinit-log.o dinit-start.o shutdown.o dinit-reboot.o
-
-dinit_objects = dinit.o load_service.o service.o control.o dinit-log.o
-
-all: dinit dinit-start
-
-shutdown-utils: shutdown
-
-dinit: $(dinit_objects)
-       $(CXX) -o dinit $(dinit_objects) -lev $(EXTRA_LIBS)
-
-dinit-start: dinit-start.o
-       $(CXX) -o dinit-start dinit-start.o $(EXTRA_LIBS)
-       
-shutdown: shutdown.o
-       $(CXX) -o shutdown shutdown.o
-
-dinit-reboot: dinit-reboot.o
-       $(CXX) -o dinit-reboot dinit-reboot.o   
-
-$(objects): %.o: %.cc service.h dinit-log.h control.h control-cmds.h
-       $(CXX) $(CXXOPTS) -c $< -o $@
-
-#install: all
-
-#install.man:
-
-clean:
-       rm *.o
-       rm dinit
diff --git a/control-cmds.h b/control-cmds.h
deleted file mode 100644 (file)
index 263f61a..0000000
+++ /dev/null
@@ -1,61 +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;
-
-// Shutdown:
-constexpr static int DINIT_CP_SHUTDOWN = 5;
- // 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;
-
-
-
-// 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/control.cc b/control.cc
deleted file mode 100644 (file)
index 22f4378..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-#include "control.h"
-#include "service.h"
-
-void ControlConn::processPacket()
-{
-    using std::string;
-    
-    // Note that where we call queuePacket, we must generally check the return value. If it
-    // returns false it has either deleted the connection or marked it for deletion; we
-    // shouldn't touch instance members after that point.
-
-    int pktType = rbuf[0];
-    if (pktType == DINIT_CP_QUERYVERSION) {
-        // Responds with:
-        // DINIT_RP_CVERSION, (2 byte) minimum compatible version, (2 byte) maximum compatible version
-        char replyBuf[] = { DINIT_RP_CPVERSION, 0, 0, 0, 0 };
-        if (! queuePacket(replyBuf, 1)) return;
-        rbuf.consume(1);
-        return;
-    }
-    if (pktType == DINIT_CP_FINDSERVICE || pktType == DINIT_CP_LOADSERVICE) {
-        processFindLoad(pktType);
-        return;
-    }
-    if (pktType == DINIT_CP_STARTSERVICE || pktType == DINIT_CP_STOPSERVICE) {
-        processStartStop(pktType);
-        return;
-    }
-    else if (pktType == DINIT_CP_SHUTDOWN) {
-        // Shutdown/reboot
-        if (rbuf.get_length() < 2) {
-            chklen = 2;
-            return;
-        }
-        
-        auto sd_type = static_cast<ShutdownType>(rbuf[1]);
-        
-        service_set->stop_all_services(sd_type);
-        log_to_console = true;
-        char ackBuf[] = { DINIT_RP_ACK };
-        if (! queuePacket(ackBuf, 1)) return;
-        
-        // Clear the packet from the buffer
-        rbuf.consume(2);
-        chklen = 0;
-        return;
-    }
-    else {
-        // Unrecognized: give error response
-        char outbuf[] = { DINIT_RP_BADREQ };
-        if (! queuePacket(outbuf, 1)) return;
-        bad_conn_close = true;
-        ev_io_set(&iob, iob.fd, EV_WRITE);
-    }
-    return;
-}
-
-void ControlConn::processFindLoad(int pktType)
-{
-    using std::string;
-    
-    constexpr int pkt_size = 4;
-    
-    if (rbuf.get_length() < pkt_size) {
-        chklen = pkt_size;
-        return;
-    }
-    
-    uint16_t svcSize;
-    rbuf.extract((char *)&svcSize, 1, 2);
-    chklen = svcSize + 3; // packet type + (2 byte) length + service name
-    if (svcSize <= 0 || chklen > 1024) {
-        // Queue error response / mark connection bad
-        char badreqRep[] = { DINIT_RP_BADREQ };
-        if (! queuePacket(badreqRep, 1)) return;
-        bad_conn_close = true;
-        ev_io_set(&iob, iob.fd, EV_WRITE);
-        return;
-    }
-    
-    if (rbuf.get_length() < chklen) {
-        // packet not complete yet; read more
-        return;
-    }
-    
-    ServiceRecord * record = nullptr;
-    
-    string serviceName = std::move(rbuf.extract_string(3, svcSize));
-    
-    if (pktType == DINIT_CP_LOADSERVICE) {
-        // LOADSERVICE
-        try {
-            record = service_set->loadService(serviceName);
-        }
-        catch (ServiceLoadExc &slexc) {
-            log(LogLevel::ERROR, "Could not load service ", slexc.serviceName, ": ", slexc.excDescription);
-        }
-    }
-    else {
-        // FINDSERVICE
-        record = service_set->findService(serviceName.c_str());
-    }
-    
-    if (record != nullptr) {
-        // Allocate a service handle
-        handle_t handle = allocateServiceHandle(record);
-        std::vector<char> rp_buf;
-        rp_buf.reserve(7);
-        rp_buf.push_back(DINIT_RP_SERVICERECORD);
-        rp_buf.push_back(static_cast<char>(record->getState()));
-        for (int i = 0; i < (int) sizeof(handle); i++) {
-            rp_buf.push_back(*(((char *) &handle) + i));
-        }
-        rp_buf.push_back(static_cast<char>(record->getTargetState()));
-        if (! queuePacket(std::move(rp_buf))) return;
-    }
-    else {
-        std::vector<char> rp_buf = { DINIT_RP_NOSERVICE };
-        if (! queuePacket(std::move(rp_buf))) return;
-    }
-    
-    // Clear the packet from the buffer
-    rbuf.consume(chklen);
-    chklen = 0;
-    return;
-}
-
-void ControlConn::processStartStop(int pktType)
-{
-    using std::string;
-    
-    constexpr int pkt_size = 2 + sizeof(handle_t);
-    
-    if (rbuf.get_length() < pkt_size) {
-        chklen = pkt_size;
-        return;
-    }
-    
-    // 1 byte: packet type
-    // 1 byte: pin in requested state (0 = no pin, 1 = pin)
-    // 4 bytes: service handle
-    
-    bool do_pin = (rbuf[1] == 1);
-    handle_t handle;
-    rbuf.extract((char *) &handle, 2, sizeof(handle));
-    
-    ServiceRecord *service = findServiceForKey(handle);
-    if (service == nullptr) {
-        // Service handle is bad
-        char badreqRep[] = { DINIT_RP_BADREQ };
-        if (! queuePacket(badreqRep, 1)) return;
-        bad_conn_close = true;
-        ev_io_set(&iob, iob.fd, EV_WRITE);
-        return;
-    }
-    else {
-        if (pktType == DINIT_CP_STARTSERVICE) {
-            if (do_pin) {
-                service->pinStart();
-            }
-            else {
-                service->start();
-            }
-        }
-        else {
-            if (do_pin) {
-                service->pinStop();
-            }
-            else {
-                service->stop();
-            }
-        }
-        
-        char ack_buf[] = { DINIT_RP_ACK };
-        if (! queuePacket(ack_buf, 1)) return;
-    }
-    
-    // Clear the packet from the buffer
-    rbuf.consume(pkt_size);
-    chklen = 0;
-    return;
-}
-
-ControlConn::handle_t ControlConn::allocateServiceHandle(ServiceRecord *record)
-{
-    bool is_unique = true;
-    handle_t largest_seen = 0;
-    handle_t candidate = 0;
-    for (auto p : keyServiceMap) {
-        if (p.first > largest_seen) largest_seen = p.first;
-        if (p.first == candidate) {
-            if (largest_seen == std::numeric_limits<handle_t>::max()) throw std::bad_alloc();
-            candidate = largest_seen + 1;
-        }
-        is_unique &= (p.second != record);
-    }
-    
-    keyServiceMap[candidate] = record;
-    serviceKeyMap.insert(std::make_pair(record, candidate));
-    
-    if (is_unique) {
-        record->addListener(this);
-    }
-    
-    return candidate;
-}
-
-
-bool ControlConn::queuePacket(const char *pkt, unsigned size) noexcept
-{
-    if (bad_conn_close) return false;
-
-    bool was_empty = outbuf.empty();
-
-    if (was_empty) {
-        int wr = write(iob.fd, pkt, size);
-        if (wr == -1) {
-            if (errno == EPIPE) {
-                delete this;
-                return false;
-            }
-            if (errno != EAGAIN && errno != EWOULDBLOCK) {
-                // TODO log error
-                delete this;
-                return false;
-            }
-        }
-        else {
-            if ((unsigned)wr == size) {
-                // Ok, all written.
-                return true;
-            }
-            pkt += wr;
-            size -= wr;
-        }
-        ev_io_set(&iob, iob.fd, EV_READ | EV_WRITE);
-    }
-    
-    // Create a vector out of the (remaining part of the) packet:
-    try {
-        outbuf.emplace_back(pkt, pkt + size);
-        return true;
-    }
-    catch (std::bad_alloc &baexc) {
-        // Mark the connection bad, and stop reading further requests
-        bad_conn_close = true;
-        oom_close = true;
-        if (was_empty) {
-            // We can't send out-of-memory response as we already wrote as much as we
-            // could above. Neither can we later send the response since we have currently
-            // sent an incomplete packet. All we can do is close the connection.
-            delete this;
-        }
-        else {
-            ev_io_set(&iob, iob.fd, EV_WRITE);
-        }
-        return false;    
-    }
-}
-
-
-bool ControlConn::queuePacket(std::vector<char> &&pkt) noexcept
-{
-    if (bad_conn_close) return false;
-
-    bool was_empty = outbuf.empty();
-    
-    if (was_empty) {
-        outpkt_index = 0;
-        // We can try sending the packet immediately:
-        int wr = write(iob.fd, pkt.data(), pkt.size());
-        if (wr == -1) {
-            if (errno == EPIPE) {
-                delete this;
-                return false;
-            }
-            if (errno != EAGAIN && errno != EWOULDBLOCK) {
-                // TODO log error
-                delete this;
-                return false;
-            }
-        }
-        else {
-            if ((unsigned)wr == pkt.size()) {
-                // Ok, all written.
-                return true;
-            }
-            outpkt_index = wr;
-        }
-        ev_io_set(&iob, iob.fd, EV_READ | EV_WRITE);
-    }
-    
-    try {
-        outbuf.emplace_back(pkt);
-        return true;
-    }
-    catch (std::bad_alloc &baexc) {
-        // Mark the connection bad, and stop reading further requests
-        bad_conn_close = true;
-        oom_close = true;
-        if (was_empty) {
-            // We can't send out-of-memory response as we already wrote as much as we
-            // could above. Neither can we later send the response since we have currently
-            // sent an incomplete packet. All we can do is close the connection.
-            delete this;
-        }
-        else {
-            ev_io_set(&iob, iob.fd, EV_WRITE);
-        }
-        return false;
-    }
-}
-
-bool ControlConn::rollbackComplete() noexcept
-{
-    char ackBuf[2] = { DINIT_ROLLBACK_COMPLETED, 2 };
-    return queuePacket(ackBuf, 2);
-}
-
-bool ControlConn::dataReady() noexcept
-{
-    int fd = iob.fd;
-    
-    int r = rbuf.fill(fd);
-    
-    // Note file descriptor is non-blocking
-    if (r == -1) {
-        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
-            // TODO log error
-            delete this;
-            return true;
-        }
-        return false;
-    }
-    
-    if (r == 0) {
-        delete this;
-        return true;
-    }
-    
-    // complete packet?
-    if (rbuf.get_length() >= chklen) {
-        try {
-            processPacket();
-        }
-        catch (std::bad_alloc &baexc) {
-            doOomClose();
-        }
-    }
-    
-    if (rbuf.get_length() == 1024) {
-        // Too big packet
-        // TODO log error?
-        // TODO error response?
-        bad_conn_close = true;
-        ev_io_set(&iob, iob.fd, EV_WRITE);
-    }
-    
-    return false;
-}
-
-void ControlConn::sendData() noexcept
-{
-    if (outbuf.empty() && bad_conn_close) {
-        if (oom_close) {
-            // Send oom response
-            char oomBuf[] = { DINIT_RP_OOM };
-            write(iob.fd, oomBuf, 1);
-        }
-        delete this;
-        return;
-    }
-    
-    vector<char> & pkt = outbuf.front();
-    char *data = pkt.data();
-    int written = write(iob.fd, data + outpkt_index, pkt.size() - outpkt_index);
-    if (written == -1) {
-        if (errno == EPIPE) {
-            // read end closed
-            delete this;
-        }
-        else if (errno == EAGAIN || errno == EWOULDBLOCK) {
-            // spurious readiness notification?
-        }
-        else {
-            log(LogLevel::ERROR, "Error writing to control connection: ", strerror(errno));
-            delete this;
-        }
-        return;
-    }
-
-    outpkt_index += written;
-    if (outpkt_index == pkt.size()) {
-        // We've finished this packet, move on to the next:
-        outbuf.pop_front();
-        outpkt_index = 0;
-        if (outbuf.empty() && ! oom_close) {
-            if (! bad_conn_close) {
-                ev_io_set(&iob, iob.fd, EV_READ);
-            }
-            else {
-                delete this;
-            }
-        }
-    }
-}
-
-ControlConn::~ControlConn() noexcept
-{
-    close(iob.fd);
-    ev_io_stop(loop, &iob);
-    
-    // Clear service listeners
-    for (auto p : serviceKeyMap) {
-        p.first->removeListener(this);
-    }
-    
-    active_control_conns--;
-}
diff --git a/control.h b/control.h
deleted file mode 100644 (file)
index 0bce51c..0000000
--- a/control.h
+++ /dev/null
@@ -1,184 +0,0 @@
-#ifndef DINIT_CONTROL_H
-#define DINIT_CONTROL_H
-
-#include <list>
-#include <vector>
-#include <unordered_map>
-#include <limits>
-
-#include <unistd.h>
-#include <ev++.h>
-
-#include "dinit-log.h"
-#include "control-cmds.h"
-#include "service-listener.h"
-#include "cpbuffer.h"
-
-// Control connection for dinit
-
-// TODO: Use the input buffer as a circular buffer, instead of chomping data from
-// the front using a data move.
-
-// forward-declaration of callback:
-static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents);
-
-class ControlConn;
-
-// Pointer to the control connection that is listening for rollback completion
-extern ControlConn * 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 ServiceSet;
-class ServiceRecord;
-
-class ControlConn : private ServiceListener
-{
-    friend void control_conn_cb(struct ev_loop *, ev_io *, int);
-    
-    struct ev_io iob;
-    struct ev_loop *loop;
-    ServiceSet *service_set;
-    
-    bool bad_conn_close; // close when finished output?
-    bool oom_close;      // send final 'out of memory' indicator
-
-    // The packet length before we need to re-check if the packet is complete.
-    // processPacket() will not be called until the packet reaches this size.
-    int chklen;
-    
-    // Receive buffer
-    CPBuffer 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<ServiceRecord *, handle_t> serviceKeyMap;
-    std::unordered_map<handle_t, ServiceRecord *> 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:  true if the packet was successfully queued, false if otherwise
-    //            (eg if out of memory); in the latter case the connection might
-    //            no longer be valid (iff there are no outgoing packets queued).
-    bool queuePacket(vector<char> &&v) noexcept;
-    bool queuePacket(const char *pkt, unsigned size) noexcept;
-
-    // Process a packet. Can cause the ControlConn to be deleted iff there are no
-    // outgoing packets queued.
-    // Throws:
-    //    std::bad_alloc - if an out-of-memory condition prevents processing
-    void processPacket();
-    
-    // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
-    void processStartStop(int pktType);
-    
-    // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
-    void processFindLoad(int pktType);
-
-    // Notify that data is ready to be read from the socket. Returns true in cases where the
-    // connection was deleted with potentially pending outgoing packets.
-    bool dataReady() noexcept;
-    
-    void sendData() noexcept;
-    
-    // Allocate a new handle for a service; may throw std::bad_alloc
-    handle_t allocateServiceHandle(ServiceRecord *record);
-    
-    ServiceRecord *findServiceForKey(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 doOomClose()
-    {
-        bad_conn_close = true;
-        oom_close = true;
-        ev_io_set(&iob, iob.fd, EV_WRITE);
-    }
-    
-    // Process service event broadcast.
-    void serviceEvent(ServiceRecord * service, ServiceEvent 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));
-                queuePacket(std::move(pkt));
-                ++i;
-            }
-        }
-        catch (std::bad_alloc &exc) {
-            doOomClose();
-        }
-    }
-    
-    public:
-    ControlConn(struct ev_loop * loop, ServiceSet * service_set, int fd) : loop(loop), service_set(service_set), chklen(0)
-    {
-        ev_io_init(&iob, control_conn_cb, fd, EV_READ);
-        iob.data = this;
-        ev_io_start(loop, &iob);
-        
-        active_control_conns++;
-    }
-    
-    bool rollbackComplete() noexcept;
-        
-    virtual ~ControlConn() noexcept;
-};
-
-
-static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents)
-{
-    ControlConn *conn = (ControlConn *) w->data;
-    if (revents & EV_READ) {
-        if (conn->dataReady()) {
-            // ControlConn was deleted
-            return;
-        }
-    }
-    if (revents & EV_WRITE) {
-        conn->sendData();
-    }    
-}
-
-#endif
diff --git a/cpbuffer.h b/cpbuffer.h
deleted file mode 100644 (file)
index 251ef6a..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-#ifndef CPBUFFER_H
-#define CPBUFFER_H
-
-#include <cstring>
-
-// control protocol buffer, a circular buffer with 1024-byte capacity.
-class CPBuffer
-{
-    char buf[1024];
-    int cur_idx = 0;
-    int length = 0;  // number of elements in the buffer
-    
-    public:
-    int get_length() noexcept
-    {
-        return length;
-    }
-    
-    // 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 >= 1024) pos -= 1024;
-        int max_count = std::min(1024 - pos, 1024 - length);
-        ssize_t r = read(fd, buf + cur_idx, max_count);
-        if (r >= 0) {
-            length += r;
-        }
-        return r;
-    }
-    
-    // fill by readin 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 fillTo(int fd, int rlength) noexcept
-    {
-        while (length < rlength) {
-            int r = fill(fd);
-            if (r <= 0) return r;
-        }
-        return 1;
-    }
-    
-    int operator[](int idx) noexcept
-    {
-        int dest_idx = cur_idx + idx;
-        if (dest_idx > 1024) dest_idx -= 1024;
-        return buf[dest_idx];
-    }
-    
-    void consume(int amount) noexcept
-    {
-        cur_idx += amount;
-        if (cur_idx >= 1024) cur_idx -= 1024;
-        length -= amount;
-    }
-    
-    void extract(char *dest, int index, int length) noexcept
-    {
-        index += cur_idx;
-        if (index >= 1024) index -= 1024;
-        if (index + length > 1024) {
-            // wrap-around copy
-            int half = 1024 - index;
-            std::memcpy(dest, buf + index, half);
-            std::memcpy(dest + half, buf, length - half);
-        }
-        else {
-            std::memcpy(dest, buf + index, length);
-        }
-    }
-    
-    // Extract string of give length from given index
-    // Throws:  std::bad_alloc on allocation failure
-    std::string extract_string(int index, int length)
-    {
-        index += cur_idx;
-        if (index >= 1024) index -= 1024;
-        if (index + length > 1024) {
-            std::string r(buf + index, 1024 - index);
-            r.insert(r.end(), buf, buf + length - (1024 - index));
-            return r;
-        }
-        else {
-            return std::string(buf + index, length);
-        }
-    }
-};
-
-#endif
diff --git a/dinit-log.cc b/dinit-log.cc
deleted file mode 100644 (file)
index 8e031e7..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <iostream>
-#include "dinit-log.h"
-
-LogLevel log_level = LogLevel::WARN;
-bool log_to_console = true;    // whether we should output log messages to console
-bool log_current_line;
-
-// Log a message
-void log(LogLevel lvl, const char *msg) noexcept
-{
-    if (lvl >= log_level) {
-        if (log_to_console) {
-            std::cout << "dinit: " << msg << std::endl;
-        }
-    }
-}
-
-// Log a multi-part message beginning
-void logMsgBegin(LogLevel lvl, const char *msg) noexcept
-{
-    log_current_line = lvl >= log_level;
-    if (log_current_line) {
-        if (log_to_console) {
-            std::cout << "dinit: " << msg;
-        }
-    }
-}
-
-// Continue a multi-part log message
-void logMsgPart(const char *msg) noexcept
-{
-    if (log_current_line) {
-        if (log_to_console) {
-            std::cout << msg;
-        }
-    }
-}
-
-// Complete a multi-part log message
-void logMsgEnd(const char *msg) noexcept
-{
-    if (log_current_line) {
-        if (log_to_console) {
-            std::cout << msg << std::endl;
-        }
-    }
-}
-
-void logServiceStarted(const char *service_name) noexcept
-{
-    if (log_to_console) {
-        std::cout << "[  OK  ] " << service_name << std::endl;
-    }
-}
-
-void logServiceFailed(const char *service_name) noexcept
-{
-    if (log_to_console) {
-        std::cout << "[FAILED] " << service_name << std::endl;
-    }
-}
-
-void logServiceStopped(const char *service_name) noexcept
-{
-    if (log_to_console) {
-        std::cout << "[STOPPED] " << service_name << std::endl;
-    }
-}
diff --git a/dinit-log.h b/dinit-log.h
deleted file mode 100644 (file)
index b54664e..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-#ifndef DINIT_LOG_H
-#define DINIT_LOG_H
-
-// Logging for Dinit
-
-#include <string>
-#include <cstdio>
-#include <climits>
-
-enum class LogLevel {
-    DEBUG,
-    INFO,
-    WARN,
-    ERROR,
-    ZERO    // log absolutely nothing
-};
-
-extern LogLevel log_level;
-extern bool log_to_console;
-
-void log(LogLevel lvl, const char *msg) noexcept;
-void logMsgBegin(LogLevel lvl, const char *msg) noexcept;
-void logMsgPart(const char *msg) noexcept;
-void logMsgEnd(const char *msg) noexcept;
-void logServiceStarted(const char *service_name) noexcept;
-void logServiceFailed(const char *service_name) noexcept;
-void logServiceStopped(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 lvl, const std::string &str) noexcept
-{
-    log(lvl, str.c_str());
-}
-
-static inline void logMsgBegin(LogLevel lvl, const std::string &str) noexcept
-{
-    logMsgBegin(lvl, str.c_str());
-}
-
-static inline void logMsgBegin(LogLevel lvl, int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    logMsgBegin(lvl, nbuf);
-}
-
-static inline void logMsgPart(const std::string &str) noexcept
-{
-    logMsgPart(str.c_str());
-}
-
-static inline void logMsgPart(int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    logMsgPart(nbuf);
-}
-
-static inline void logMsgEnd(const std::string &str) noexcept
-{
-    logMsgEnd(str.c_str());
-}
-
-static inline void logMsgEnd(int a) noexcept
-{
-    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
-    char nbuf[bufsz];
-    snprintf(nbuf, bufsz, "%d", a);
-    logMsgEnd(nbuf);
-}
-
-static inline void logServiceStarted(const std::string &str) noexcept
-{
-    logServiceStarted(str.c_str());
-}
-
-static inline void logServiceFailed(const std::string &str) noexcept
-{
-    logServiceFailed(str.c_str());
-}
-
-static inline void logServiceStopped(const std::string &str) noexcept
-{
-    logServiceStopped(str.c_str());
-}
-
-// It's not intended that methods in this namespace be called directly:
-namespace dinit_log {
-    template <typename A> static inline void logParts(A a) noexcept
-    {
-        logMsgEnd(a);
-    }
-
-    template <typename A, typename ...B> static inline void logParts(A a, B... b) noexcept
-    {
-        logMsgPart(a);
-        logParts(b...);
-    }
-}
-
-// Variadic 'log' method.
-template <typename A, typename ...B> static inline void log(LogLevel lvl, A a, B ...b) noexcept
-{
-    logMsgBegin(lvl, a);
-    dinit_log::logParts(b...);
-}
-
-#endif
diff --git a/dinit-start.cc b/dinit-start.cc
deleted file mode 100644 (file)
index 3e6a9e7..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-#include <cstdio>
-#include <cstddef>
-#include <cstring>
-#include <string>
-#include <iostream>
-#include <system_error>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <pwd.h>
-
-#include "control-cmds.h"
-#include "service-constants.h"
-#include "cpbuffer.h"
-
-// dinit-start:  utility to start a dinit service
-
-// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
-
-using handle_t = uint32_t;
-
-
-class ReadCPException
-{
-    public:
-    int errcode;
-    ReadCPException(int err) : errcode(err) { }
-};
-
-static void fillBufferTo(CPBuffer *buf, int fd, int rlength)
-{
-    int r = buf->fillTo(fd, rlength);
-    if (r == -1) {
-        throw ReadCPException(errno);
-    }
-    else if (r == 0) {
-        throw ReadCPException(0);
-    }
-}
-
-
-int main(int argc, char **argv)
-{
-    using namespace std;
-    
-    bool show_help = argc < 2;
-    char *service_name = nullptr;
-    
-    std::string control_socket_str;
-    const char * control_socket_path = nullptr;
-    
-    bool verbose = true;
-    bool sys_dinit = false;  // communicate with system daemon
-    bool wait_for_service = true;
-        
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-') {
-            if (strcmp(argv[i], "--help") == 0) {
-                show_help = true;
-                break;
-            }
-            else if (strcmp(argv[i], "--no-wait") == 0) {
-                wait_for_service = false;
-            }
-            else if (strcmp(argv[i], "--quiet") == 0) {
-                verbose = false;
-            }
-            else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
-                sys_dinit = true;
-            }
-            else {
-                cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
-                return 1;
-            }
-        }
-        else {
-            // service name
-            service_name = argv[i];
-            // TODO support multiple services (or at least give error if multiple
-            //      services supplied)
-        }
-    }
-
-    if (show_help) {
-        cout << "dinit-start:   start a dinit service" << endl;
-        cout << "  --help           : show this help" << endl;
-        cout << "  --no-wait        : don't wait for service startup/shutdown to complete" << endl;
-        cout << "  --quiet          : suppress output (except errors)" << endl;
-        cout << "  -s, --system     : control system daemon instead of user daemon" << endl;
-        cout << "  <service-name>   : start the named service" << endl;
-        return 1;
-    }
-    
-    
-    control_socket_path = "/dev/dinitctl";
-    
-    if (! sys_dinit) {
-        char * userhome = getenv("HOME");
-        if (userhome == nullptr) {
-            struct passwd * pwuid_p = getpwuid(getuid());
-            if (pwuid_p != nullptr) {
-                userhome = pwuid_p->pw_dir;
-            }
-        }
-        
-        if (userhome != nullptr) {
-            control_socket_str = userhome;
-            control_socket_str += "/.dinitctl";
-            control_socket_path = control_socket_str.c_str();
-        }
-        else {
-            cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
-            return 1;
-        }
-    }
-    
-    int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (socknum == -1) {
-        perror("socket");
-        return 1;
-    }
-
-    struct sockaddr_un * name;
-    uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
-    name = (struct sockaddr_un *) malloc(sockaddr_size);
-    if (name == nullptr) {
-        cerr << "dinit-start: out of memory" << endl;
-        return 1;
-    }
-    
-    name->sun_family = AF_UNIX;
-    strcpy(name->sun_path, control_socket_path);
-    
-    int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
-    if (connr == -1) {
-        perror("connect");
-        return 1;
-    }
-    
-    // TODO should start by querying protocol version
-    
-    // Build buffer;
-    uint16_t sname_len = strlen(service_name);
-    int bufsize = 3 + sname_len;
-    char * buf = new char[bufsize];
-    
-    buf[0] = DINIT_CP_LOADSERVICE;
-    memcpy(buf + 1, &sname_len, 2);
-    memcpy(buf + 3, service_name, sname_len);
-    
-    int r = write(socknum, buf, bufsize);
-    // TODO make sure we write it all
-    delete [] buf;
-    if (r == -1) {
-        perror("write");
-        return 1;
-    }
-    
-    // Now we expect a reply:
-    // NOTE: should skip over information packets.
-    
-    try {
-        CPBuffer rbuffer;
-        fillBufferTo(&rbuffer, socknum, 1);
-        
-        ServiceState state;
-        ServiceState target_state;
-        handle_t handle;
-        
-        if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
-            fillBufferTo(&rbuffer, socknum, 2 + sizeof(handle));
-            rbuffer.extract((char *) &handle, 2, sizeof(handle));
-            state = static_cast<ServiceState>(rbuffer[1]);
-            target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
-            rbuffer.consume(3 + sizeof(handle));
-        }
-        else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
-            cerr << "Failed to find/load service." << endl;
-            return 1;
-        }
-        else {
-            cerr << "Protocol error." << endl;
-            return 1;
-        }
-        
-        // Need to issue STARTSERVICE:
-        if (target_state != ServiceState::STARTED) {
-            buf = new char[2 + sizeof(handle)];
-            buf[0] = DINIT_CP_STARTSERVICE;
-            buf[1] = 0;  // don't pin
-            memcpy(buf + 2, &handle, sizeof(handle));
-            r = write(socknum, buf, 2 + sizeof(handle));
-            delete buf;
-        }
-        
-        if (state == ServiceState::STARTED) {
-            if (verbose) {
-                cout << "Service already started." << endl;
-            }
-            return 0; // success!
-        }
-        
-        if (! wait_for_service) {
-            return 0;
-        }
-        
-        // Wait until service started:
-        r = rbuffer.fillTo(socknum, 2);
-        while (r > 0) {
-            if (rbuffer[0] >= 100) {
-                int pktlen = (unsigned char) rbuffer[1];
-                fillBufferTo(&rbuffer, socknum, pktlen);
-                
-                if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
-                    handle_t ev_handle;
-                    rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
-                    ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
-                    if (ev_handle == handle && event == ServiceEvent::STARTED) {
-                        if (verbose) {
-                            cout << "Service started." << endl;
-                        }
-                        return 0;
-                    }
-                }
-            }
-            else {
-                // Not an information packet?
-                cerr << "protocol error" << endl;
-                return 1;
-            }
-        }
-        
-        if (r == -1) {
-            perror("read");
-        }
-        else {
-            cerr << "protocol error (connection closed by server)" << endl;
-        }
-        return 1;
-    }
-    catch (ReadCPException &exc) {
-        cerr << "control socket read failure or protocol error" << endl;
-        return 1;
-    }
-    catch (std::bad_alloc &exc) {
-        cerr << "out of memory" << endl;
-        return 1;
-    }
-    
-    return 0;
-}
diff --git a/dinit.cc b/dinit.cc
deleted file mode 100644 (file)
index 9b26b3a..0000000
--- a/dinit.cc
+++ /dev/null
@@ -1,429 +0,0 @@
-#include <iostream>
-#include <list>
-#include <cstring>
-#include <csignal>
-#include <cstddef>
-#include <cstdlib>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <pwd.h>
-
-#include "service.h"
-#include "ev++.h"
-#include "control.h"
-#include "dinit-log.h"
-
-#ifdef __linux__
-#include <sys/klog.h>
-#include <sys/reboot.h>
-#endif
-
-/*
- * "simpleinit" from util-linux-ng package handles signals as follows:
- * SIGTSTP - spawn no more gettys (in preparation for shutdown etc).
- *           In dinit terms this should probably mean "no more auto restarts"
- *           (for any service). (Actually the signal acts as a toggle, if
- *           respawn is disabled it will be re-enabled and init will
- *           act as if SIGHUP had also been sent)
- * SIGTERM - kill spawned gettys (which are still alive)
- *           Interestingly, simpleinit just sends a SIGTERM to the gettys,
- *           which will not normall kill shells (eg bash ignores SIGTERM).
- * "/sbin/initctl -r" - rollback services (ran by "shutdown"/halt etc);
- *           shouldn't return until all services have been stopped.
- *           shutdown calls this after sending SIGTERM to processes running
- *           with uid >= 100 ("mortals").
- * SIGQUIT - init will exec() shutdown. shutdown will detect that it is
- *           running as pid 1 and will just loop and reap child processes.
- *           This is used by shutdown so that init will not hang on to its
- *           inode, allowing the filesystem to be re-mounted readonly
- *           (this is only an issue if the init binary has been unlinked,
- *           since it's then holding an inode which can't be maintained
- *           when the filesystem is unmounted).
- *
- * Not sent by shutdown:
- * SIGHUP -  re-read inittab and spawn any new getty entries
- * SIGINT - (ctrl+alt+del handler) - fork & exec "reboot"
- * 
- * On the contrary dinit currently uses:
- * SIGTERM - roll back services and then fork/exec /sbin/halt
- * SIGINT - roll back services and then fork/exec /sbin/reboot
- * SIGQUIT - exec() /sbin/shutdown as per above.
- *
- * It's an open question about whether dinit should roll back services *before*
- * running halt/reboot, since those commands should prompt rollback of services
- * anyway. But it seems safe to do so.
- */
-
-
-static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents);
-static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents);
-static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents);
-void open_control_socket(struct ev_loop *loop) noexcept;
-void close_control_socket(struct ev_loop *loop) noexcept;
-
-struct ev_io control_socket_io;
-
-
-// Variables
-
-static ServiceSet *service_set;
-
-static bool am_system_init = false; // true if we are the system init process
-
-static bool control_socket_open = false;
-int active_control_conns = 0;
-
-static const char *control_socket_path = "/dev/dinitctl";
-static std::string control_socket_str;
-
-
-int main(int argc, char **argv)
-{
-    using namespace std;
-    
-    am_system_init = (getpid() == 1);
-    
-    if (am_system_init) {
-        // setup STDIN, STDOUT, STDERR so that we can use them
-        int onefd = open("/dev/console", O_RDONLY, 0);
-        dup2(onefd, 0);
-        int twofd = open("/dev/console", O_RDWR, 0);
-        dup2(twofd, 1);
-        dup2(twofd, 2);
-    }
-    
-    /* Set up signal handlers etc */
-    /* SIG_CHILD is ignored by default: good */
-    /* sigemptyset(&sigwait_set); */
-    /* sigaddset(&sigwait_set, SIGCHLD); */
-    /* sigaddset(&sigwait_set, SIGINT); */
-    /* sigaddset(&sigwait_set, SIGTERM); */
-    /* sigprocmask(SIG_BLOCK, &sigwait_set, NULL); */
-    
-    // Terminal access control signals - we block these so that dinit can't be
-    // suspended if it writes to the terminal after some other process has claimed
-    // ownership of it.
-    signal(SIGTSTP, SIG_IGN);
-    signal(SIGTTIN, SIG_IGN);
-    signal(SIGTTOU, SIG_IGN);
-    
-    /* list of services to start */
-    list<const char *> services_to_start;
-    
-    if (! am_system_init) {
-        char * userhome = getenv("HOME");
-        if (userhome == nullptr) {
-            struct passwd * pwuid_p = getpwuid(getuid());
-            if (pwuid_p != nullptr) {
-                userhome = pwuid_p->pw_dir;
-            }
-        }
-        
-        if (userhome != nullptr) {
-            control_socket_str = userhome;
-            control_socket_str += "/.dinitctl";
-            control_socket_path = control_socket_str.c_str();
-        }
-    }
-    
-    /* service directory name */
-    const char * service_dir = "/etc/dinit.d";
-    
-    // Arguments, if given, specify a list of services to start.
-    // If we are running as init (PID=1), the kernel gives us any command line
-    // arguments it was given but didn't recognize, including "single" (usually
-    // for "boot to single user mode" aka just start the shell). We can treat
-    // them as service names. In the worst case we can't find any of the named
-    // services, and so we'll start the "boot" service by default.
-    if (argc > 1) {
-      for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-') {
-            // An option...
-            if (strcmp(argv[i], "--services-dir") == 0 ||
-                    strcmp(argv[i], "-d") == 0) {
-                ++i;
-                if (i < argc) {
-                    service_dir = argv[i];
-                }
-                else {
-                    cerr << "dinit: '--services-dir' (-d) requires an argument" << endl;
-                    return 1;
-                }
-            }
-            else if (strcmp(argv[i], "--help") == 0) {
-                cout << "dinit, an init with dependency management" << endl;
-                cout << " --help                         : display help" << endl;
-                cout << " --services-dir <dir>, -d <dir> : set base directory for service description files (-d <dir>)" << endl;
-                cout << " <service-name>                 : start service with name <service-name>" << endl;
-                return 0;
-            }
-            else {
-                // unrecognized
-                if (! am_system_init) {
-                    cerr << "dinit: Unrecognized option: " << argv[i] << endl;
-                    return 1;
-                }
-            }
-        }
-        else {
-            // LILO puts "auto" on the kernel command line for unattended boots; we'll filter it.
-            if (! am_system_init || strcmp(argv[i], "auto") != 0) {
-                services_to_start.push_back(argv[i]);
-            }
-        }
-      }
-    }
-    
-    if (services_to_start.empty()) {
-        services_to_start.push_back("boot");
-    }
-
-    // Set up signal handlers
-    ev_signal sigint_ev_signal;
-    if (am_system_init) {
-      ev_signal_init(&sigint_ev_signal, sigint_reboot_cb, SIGINT);
-    }
-    else {
-      ev_signal_init(&sigint_ev_signal, sigterm_cb, SIGINT);
-    }
-    
-    ev_signal sigquit_ev_signal;
-    if (am_system_init) {
-        // PID 1: SIGQUIT exec's shutdown
-        ev_signal_init(&sigquit_ev_signal, sigquit_cb, SIGQUIT);
-    }
-    else {
-        // Otherwise: SIGQUIT terminates dinit
-        ev_signal_init(&sigquit_ev_signal, sigterm_cb, SIGQUIT);
-    }
-    
-    ev_signal sigterm_ev_signal;
-    ev_signal_init(&sigterm_ev_signal, sigterm_cb, SIGTERM);
-    
-    /* Set up libev */
-    struct ev_loop *loop = ev_default_loop(EVFLAG_AUTO /* | EVFLAG_SIGNALFD */);
-    ev_signal_start(loop, &sigint_ev_signal);
-    ev_signal_start(loop, &sigquit_ev_signal);
-    ev_signal_start(loop, &sigterm_ev_signal);
-
-    // Try to open control socket (may fail due to readonly filesystem)
-    open_control_socket(loop);
-    
-#ifdef __linux__
-    if (am_system_init) {
-        // Disable non-critical kernel output to console
-        klogctl(6 /* SYSLOG_ACTION_CONSOLE_OFF */, nullptr, 0);
-        // Make ctrl+alt+del combination send SIGINT to PID 1 (this process)
-        reboot(RB_DISABLE_CAD);
-    }
-#endif
-    
-    /* start requested services */
-    service_set = new ServiceSet(service_dir);
-    for (list<const char *>::iterator i = services_to_start.begin();
-            i != services_to_start.end();
-            ++i) {
-        try {
-            service_set->startService(*i);
-        }
-        catch (ServiceNotFound &snf) {
-            log(LogLevel::ERROR, snf.serviceName, ": Could not find service description.");
-        }
-        catch (ServiceLoadExc &sle) {
-            log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription);
-        }
-        catch (std::bad_alloc &badalloce) {
-            log(LogLevel::ERROR, "Out of memory when trying to start service: ", *i, ".");
-        }
-    }
-    
-    event_loop:
-    
-    // Process events until all services have terminated.
-    while (service_set->count_active_services() != 0) {
-        ev_loop(loop, EVLOOP_ONESHOT);
-    }
-
-    ShutdownType shutdown_type = service_set->getShutdownType();
-    
-    if (am_system_init) {
-        logMsgBegin(LogLevel::INFO, "No more active services.");
-        
-        if (shutdown_type == ShutdownType::REBOOT) {
-            logMsgEnd(" Will reboot.");
-        }
-        else if (shutdown_type == ShutdownType::HALT) {
-            logMsgEnd(" Will halt.");
-        }
-        else if (shutdown_type == ShutdownType::POWEROFF) {
-            logMsgEnd(" Will power down.");
-        }
-        else {
-            logMsgEnd(" Re-initiating boot sequence.");
-        }
-    }
-    
-    close_control_socket(ev_default_loop(EVFLAG_AUTO));
-    
-    if (am_system_init) {
-        if (shutdown_type == ShutdownType::CONTINUE) {
-            // It could be that we started in single user mode, and the
-            // user has now exited the shell. We'll try and re-start the
-            // boot process...
-            try {
-                service_set->startService("boot");
-                goto event_loop; // yes, the "evil" goto
-            }
-            catch (...) {
-                // Now WTF do we do? try to reboot
-                log(LogLevel::ERROR, "Could not start 'boot' service; rebooting.");
-                shutdown_type = ShutdownType::REBOOT;
-            }
-        }
-        
-        const char * cmd_arg;
-        if (shutdown_type == ShutdownType::HALT) {
-            cmd_arg = "-h";
-        }
-        else if (shutdown_type == ShutdownType::REBOOT) {
-            cmd_arg = "-r";
-        }
-        else {
-            // power off.
-            cmd_arg = "-p";
-        }
-        
-        // Fork and execute dinit-reboot.
-        execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr);
-        log(LogLevel::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
-        
-        // PID 1 must not actually exit, although we should never reach this point:
-        while (true) {
-            ev_loop(loop, EVLOOP_ONESHOT);
-        }
-    }
-    
-    return 0;
-}
-
-// Callback for control socket
-static void control_socket_cb(struct ev_loop *loop, ev_io *w, int revents)
-{
-    // TODO limit the number of active connections. Keep a tally, and disable the
-    // control connection listening socket watcher if it gets high, and re-enable
-    // it once it falls below the maximum.
-
-    // Accept a connection
-    int sockfd = w->fd;
-
-    int newfd = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
-
-    if (newfd != -1) {
-        try {
-            new ControlConn(loop, service_set, newfd);  // will delete itself when it's finished
-        }
-        catch (std::bad_alloc &bad_alloc_exc) {
-            log(LogLevel::ERROR, "Accepting control connection: Out of memory");
-            close(newfd);
-        }
-    }
-}
-
-void open_control_socket(struct ev_loop *loop) noexcept
-{
-    if (! control_socket_open) {
-        const char * saddrname = control_socket_path;
-        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1;
-        
-        struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
-        if (name == nullptr) {
-            log(LogLevel::ERROR, "Opening control socket: out of memory");
-            return;
-        }
-
-        if (am_system_init) {
-            // Unlink any stale control socket file, but only if we are system init, since otherwise
-            // the 'stale' file may not be stale at all:
-            unlink(saddrname);
-        }
-
-        name->sun_family = AF_UNIX;
-        strcpy(name->sun_path, saddrname);
-
-        int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
-        if (sockfd == -1) {
-            log(LogLevel::ERROR, "Error creating control socket: ", strerror(errno));
-            free(name);
-            return;
-        }
-
-        if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
-            log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
-            close(sockfd);
-            free(name);
-            return;
-        }
-        
-        free(name);
-
-        // No connections can be made until we listen, so it is fine to change the permissions now
-        // (and anyway there is no way to atomically create the socket and set permissions):
-        if (chmod(saddrname, S_IRUSR | S_IWUSR) == -1) {
-            log(LogLevel::ERROR, "Error setting control socket permissions: ", strerror(errno));
-            close(sockfd);
-            return;
-        }
-
-        if (listen(sockfd, 10) == -1) {
-            log(LogLevel::ERROR, "Error listening on control socket: ", strerror(errno));
-            close(sockfd);
-            return;
-        }
-
-        control_socket_open = true;
-        ev_io_init(&control_socket_io, control_socket_cb, sockfd, EV_READ);
-        ev_io_start(loop, &control_socket_io);
-    }
-}
-
-void close_control_socket(struct ev_loop *loop) noexcept
-{
-    if (control_socket_open) {
-        int fd = control_socket_io.fd;
-        ev_io_stop(loop, &control_socket_io);
-        close(fd);
-        
-        // Unlink the socket:
-        unlink(control_socket_path);
-    }
-}
-
-/* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
-static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents)
-{
-    log_to_console = true;
-    service_set->stop_all_services(ShutdownType::REBOOT);
-}
-
-/* handle SIGQUIT (if we are system init) */
-static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents)
-{
-    // This allows remounting the filesystem read-only if the dinit binary has been
-    // unlinked. In that case the kernel holds the binary open, so that it can't be
-    // properly removed.
-    close_control_socket(ev_default_loop(EVFLAG_AUTO));
-    execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
-    log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
-}
-
-/* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */
-static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents)
-{
-    log_to_console = true;
-    service_set->stop_all_services();
-}
diff --git a/halt b/halt
deleted file mode 100755 (executable)
index 4987aff..0000000
--- a/halt
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-# "halt" command actually executes the more useful "power off".
-shutdown -p "$@"
diff --git a/load_service.cc b/load_service.cc
deleted file mode 100644 (file)
index 79972ff..0000000
+++ /dev/null
@@ -1,507 +0,0 @@
-#include <algorithm>
-#include <string>
-#include <fstream>
-#include <locale>
-#include <iostream>
-#include <limits>
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <pwd.h>
-#include <grp.h>
-
-#include "service.h"
-
-typedef std::string string;
-typedef std::string::iterator string_iterator;
-
-// Utility function to skip white space. Returns an iterator at the
-// first non-white-space position (or at end).
-static string_iterator skipws(string_iterator i, string_iterator end)
-{
-    using std::locale;
-    using std::isspace;
-    
-    while (i != end) {
-      if (! isspace(*i, locale::classic())) {
-        break;
-      }
-      ++i;
-    }
-    return i;
-}
-
-// Read a setting name.
-static string read_setting_name(string_iterator & i, string_iterator end)
-{
-    using std::locale;
-    using std::ctype;
-    using std::use_facet;
-    
-    const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
-
-    string rval;
-    // Allow alphabetical characters, and dash (-) in setting name
-    while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
-        rval += *i;
-        ++i;
-    }
-    return rval;
-}
-
-// Read a setting value
-//
-// In general a setting value is a single-line string. It may contain multiple parts
-// separated by white space (which is normally collapsed). A hash mark - # - denotes
-// the end of the value and the beginning of a comment (it should be preceded by
-// whitespace).
-//
-// Part of a value may be quoted using double quote marks, which prevents collapse
-// of whitespace and interpretation of most special characters (the quote marks will
-// not be considered part of the value). A backslash can precede a character (such
-// as '#' or '"' or another backslash) to remove its special meaning. Newline
-// characters are not allowed in values and cannot be quoted.
-//
-// This function expects the string to be in an ASCII-compatible, single byte
-// encoding (the "classic" locale).
-//
-// Params:
-//    i  -  reference to string iterator through the line
-//    end -   iterator at end of line
-//    part_positions -  list of <int,int> to which the position of each setting value
-//                      part will be added as [start,end). May be null.
-static string read_setting_value(string_iterator & i, string_iterator end,
-        std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
-{
-    using std::locale;
-    using std::isspace;
-
-    i = skipws(i, end);
-    
-    string rval;
-    bool new_part = true;
-    int part_start;
-    
-    while (i != end) {
-        char c = *i;
-        if (c == '\"') {
-            if (new_part) {
-                part_start = rval.length();
-                new_part = false;
-            }
-            // quoted string
-            ++i;
-            while (i != end) {
-                c = *i;
-                if (c == '\"') break;
-                if (c == '\n') {
-                    // TODO error here.
-                }
-                else if (c == '\\') {
-                    // A backslash escapes the following character.
-                    ++i;
-                    if (i != end) {
-                        c = *i;
-                        if (c == '\n') {
-                            // TODO error here.
-                        }
-                        rval += c;
-                    }
-                }
-                else {
-                    rval += c;
-                }
-                ++i;
-            }
-            if (i == end) {
-                // String wasn't terminated
-                // TODO error here
-                break;
-            }
-        }
-        else if (c == '\\') {
-            if (new_part) {
-                part_start = rval.length();
-                new_part = false;
-            }
-            // A backslash escapes the next character
-            ++i;
-            if (i != end) {
-                rval += *i;
-            }
-            else {
-                // TODO error here
-            }
-        }
-        else if (isspace(c, locale::classic())) {
-            if (! new_part && part_positions != nullptr) {
-                part_positions->emplace_back(part_start, rval.length());
-                new_part = true;
-            }
-            i = skipws(i, end);
-            if (i == end) break;
-            if (*i == '#') break; // comment
-            rval += ' ';  // collapse ws to a single space
-            continue;
-        }
-        else if (c == '#') {
-            // hmm... comment? Probably, though they should have put a space
-            // before it really. TODO throw an exception, and document
-            // that '#' for comments must be preceded by space, and in values
-            // must be quoted.
-            break;
-        }
-        else {
-            if (new_part) {
-                part_start = rval.length();
-                new_part = false;
-            }
-            rval += c;
-        }
-        ++i;
-    }
-
-    // Got to end:
-    if (part_positions != nullptr) {
-        part_positions->emplace_back(part_start, rval.length());
-    }
-
-    return rval;
-}
-
-static int signalNameToNumber(std::string &signame)
-{
-    if (signame == "HUP") return SIGHUP;
-    if (signame == "INT") return SIGINT;
-    if (signame == "QUIT") return SIGQUIT;
-    if (signame == "USR1") return SIGUSR1;
-    if (signame == "USR2") return SIGUSR2;
-    return -1;
-}
-
-static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
-
-// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
-// userid is looked up via the system user database (getpwnam() function). In this case,
-// the associated group is stored in the location specified by the group_p parameter iff
-// it is not null and iff it contains the value -1.
-static uid_t parse_uid_param(const std::string &param, const std::string &service_name, gid_t *group_p)
-{
-    // Could be a name or a numeric id. But we should assume numeric first, just in case
-    // a user manages to give themselves a username that parses as a number.
-    std::size_t ind = 0;
-    try {
-        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
-        // is is probably safe to assume that valid values are positive. We'll also assume
-        // that the value range fits with "unsigned long long" since it seems unlikely
-        // that would ever not be the case.
-        //
-        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
-        //      functions accept a leading minus sign...
-        static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
-        unsigned long long v = std::stoull(param, &ind, 0);
-        if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
-            throw ServiceDescriptionExc(service_name, uid_err_msg);
-        }
-        return v;
-    }
-    catch (std::out_of_range &exc) {
-        throw ServiceDescriptionExc(service_name, uid_err_msg);
-    }
-    catch (std::invalid_argument &exc) {
-        // Ok, so it doesn't look like a number: proceed...
-    }
-
-    errno = 0;
-    struct passwd * pwent = getpwnam(param.c_str());
-    if (pwent == nullptr) {
-        // Maybe an error, maybe just no entry.
-        if (errno == 0) {
-            throw new ServiceDescriptionExc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
-        }
-        else {
-            throw new ServiceDescriptionExc(service_name, std::string("Error accessing user database: ") + strerror(errno));
-        }
-    }
-    
-    if (group_p && *group_p != (gid_t)-1) {
-        *group_p = pwent->pw_gid;
-    }
-    
-    return pwent->pw_uid;
-}
-
-static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
-
-static gid_t parse_gid_param(const std::string &param, const std::string &service_name)
-{
-    // Could be a name or a numeric id. But we should assume numeric first, just in case
-    // a user manages to give themselves a username that parses as a number.
-    std::size_t ind = 0;
-    try {
-        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
-        // is is probably safe to assume that valid values are positive. We'll also assume
-        // that the value range fits with "unsigned long long" since it seems unlikely
-        // that would ever not be the case.
-        //
-        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
-        //      functions accept a leading minus sign...
-        unsigned long long v = std::stoull(param, &ind, 0);
-        if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
-            throw ServiceDescriptionExc(service_name, gid_err_msg);
-        }
-        return v;
-    }
-    catch (std::out_of_range &exc) {
-        throw ServiceDescriptionExc(service_name, gid_err_msg);
-    }
-    catch (std::invalid_argument &exc) {
-        // Ok, so it doesn't look like a number: proceed...
-    }
-
-    errno = 0;
-    struct group * grent = getgrnam(param.c_str());
-    if (grent == nullptr) {
-        // Maybe an error, maybe just no entry.
-        if (errno == 0) {
-            throw new ServiceDescriptionExc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
-        }
-        else {
-            throw new ServiceDescriptionExc(service_name, std::string("Error accessing group database: ") + strerror(errno));
-        }
-    }
-    
-    return grent->gr_gid;
-}
-
-// Find a service record, or load it from file. If the service has
-// dependencies, load those also.
-//
-// Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
-// problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
-// if a memory allocation failure occurs.
-ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
-{
-    using std::string;
-    using std::ifstream;
-    using std::ios;
-    using std::ios_base;
-    using std::locale;
-    using std::isspace;
-    
-    using std::list;
-    using std::pair;
-    
-    // First try and find an existing record...
-    ServiceRecord * rval = findService(string(name));
-    if (rval != 0) {
-        if (rval->isDummy()) {
-            throw ServiceCyclicDependency(name);
-        }
-        return rval;
-    }
-
-    // Couldn't find one. Have to load it.    
-    string service_filename = service_dir;
-    if (*(service_filename.rbegin()) != '/') {
-        service_filename += '/';
-    }
-    service_filename += name;
-    
-    string command;
-    list<pair<unsigned,unsigned>> command_offsets;
-    string stop_command;
-    list<pair<unsigned,unsigned>> stop_command_offsets;
-    string pid_file;
-
-    ServiceType service_type = ServiceType::PROCESS;
-    std::list<ServiceRecord *> depends_on;
-    std::list<ServiceRecord *> depends_soft;
-    string logfile;
-    OnstartFlags onstart_flags;
-    int term_signal = -1;  // additional termination signal
-    bool auto_restart = false;
-    bool smooth_recovery = false;
-    string socket_path;
-    int socket_perms = 0666;
-    // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
-    // invalid value, so it's safe to assume that we can do the same:
-    uid_t socket_uid = -1;
-    gid_t socket_gid = -1;
-    
-    string line;
-    ifstream service_file;
-    service_file.exceptions(ios::badbit | ios::failbit);
-    
-    try {
-        service_file.open(service_filename.c_str(), ios::in);
-    }
-    catch (std::ios_base::failure &exc) {
-        throw ServiceNotFound(name);
-    }
-    
-    // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
-    rval = new ServiceRecord(this, string(name));
-    records.push_back(rval);
-    
-    try {
-        // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
-        service_file.exceptions(ios::badbit);
-        
-        while (! (service_file.rdstate() & ios::eofbit)) {
-            getline(service_file, line);
-            string::iterator i = line.begin();
-            string::iterator end = line.end();
-          
-            i = skipws(i, end);
-            if (i != end) {
-                if (*i == '#') {
-                    continue;  // comment line
-                }
-                string setting = read_setting_name(i, end);
-                i = skipws(i, end);
-                if (i == end || (*i != '=' && *i != ':')) {
-                    throw ServiceDescriptionExc(name, "Badly formed line.");
-                }
-                i = skipws(++i, end);
-                
-                if (setting == "command") {
-                    command = read_setting_value(i, end, &command_offsets);
-                }
-                else if (setting == "socket-listen") {
-                    socket_path = read_setting_value(i, end, nullptr);
-                }
-                else if (setting == "socket-permissions") {
-                    string sock_perm_str = read_setting_value(i, end, nullptr);
-                    std::size_t ind = 0;
-                    try {
-                        socket_perms = std::stoi(sock_perm_str, &ind, 8);
-                        if (ind != sock_perm_str.length()) {
-                            throw std::logic_error("");
-                        }
-                    }
-                    catch (std::logic_error &exc) {
-                        throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
-                    }
-                }
-                else if (setting == "socket-uid") {
-                    string sock_uid_s = read_setting_value(i, end, nullptr);
-                    socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
-                }
-                else if (setting == "socket-gid") {
-                    string sock_gid_s = read_setting_value(i, end, nullptr);
-                    socket_gid = parse_gid_param(sock_gid_s, name);
-                }
-                else if (setting == "stop-command") {
-                    stop_command = read_setting_value(i, end, &stop_command_offsets);
-                }
-                else if (setting == "pid-file") {
-                    pid_file = read_setting_value(i, end);
-                }
-                else if (setting == "depends-on") {
-                    string dependency_name = read_setting_value(i, end);
-                    depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
-                }
-                else if (setting == "waits-for") {
-                    string dependency_name = read_setting_value(i, end);
-                    depends_soft.push_back(loadServiceRecord(dependency_name.c_str()));
-                }
-                else if (setting == "logfile") {
-                    logfile = read_setting_value(i, end);
-                }
-                else if (setting == "restart") {
-                    string restart = read_setting_value(i, end);
-                    auto_restart = (restart == "yes" || restart == "true");
-                }
-                else if (setting == "smooth-recovery") {
-                    string recovery = read_setting_value(i, end);
-                    smooth_recovery = (recovery == "yes" || recovery == "true");
-                }
-                else if (setting == "type") {
-                    string type_str = read_setting_value(i, end);
-                    if (type_str == "scripted") {
-                        service_type = ServiceType::SCRIPTED;
-                    }
-                    else if (type_str == "process") {
-                        service_type = ServiceType::PROCESS;
-                    }
-                    else if (type_str == "bgprocess") {
-                        service_type = ServiceType::BGPROCESS;
-                    }
-                    else if (type_str == "internal") {
-                        service_type = ServiceType::INTERNAL;
-                    }
-                    else {
-                        throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
-                            " \"process\", \"bgprocess\" or \"internal\"");
-                    }
-                }
-                else if (setting == "onstart") {
-                    std::list<std::pair<unsigned,unsigned>> indices;
-                    string onstart_cmds = read_setting_value(i, end, &indices);
-                    for (auto indexpair : indices) {
-                        string onstart_cmd = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
-                        if (onstart_cmd == "rw_ready") {
-                            onstart_flags.rw_ready = true;
-                        }
-                        else {
-                            throw new ServiceDescriptionExc(name, "Unknown onstart command: " + onstart_cmd);
-                        }
-                    }
-                }
-                else if (setting == "termsignal") {
-                    string signame = read_setting_value(i, end, nullptr);
-                    int signo = signalNameToNumber(signame);
-                    if (signo == -1) {
-                        throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
-                    }
-                    else {
-                        term_signal = signo;
-                    }
-                }
-                else if (setting == "nosigterm") {
-                    string sigtermsetting = read_setting_value(i, end);
-                    onstart_flags.no_sigterm = (sigtermsetting == "yes" || sigtermsetting == "true");
-                }
-                else if (setting == "runs-on-console") {
-                    string runconsolesetting = read_setting_value(i, end);
-                    onstart_flags.runs_on_console = (runconsolesetting == "yes" || runconsolesetting == "true");
-                }
-                else {
-                    throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
-                }
-            }
-        }
-        
-        service_file.close();
-        // TODO check we actually have all the settings - type, command
-        
-        // Now replace the dummy service record with a real record:
-        for (auto iter = records.begin(); iter != records.end(); iter++) {
-            if (*iter == rval) {
-                // We've found the dummy record
-                delete rval;
-                rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
-                        & depends_on, & depends_soft);
-                rval->setStopCommand(stop_command, stop_command_offsets);
-                rval->setLogfile(logfile);
-                rval->setAutoRestart(auto_restart);
-                rval->setSmoothRecovery(smooth_recovery);
-                rval->setOnstartFlags(onstart_flags);
-                rval->setExtraTerminationSignal(term_signal);
-                rval->set_pid_file(std::move(pid_file));
-                rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
-                *iter = rval;
-                break;
-            }
-        }
-        
-        return rval;
-    }
-    catch (...) {
-        // Must remove the dummy service record.
-        std::remove(records.begin(), records.end(), rval);
-        delete rval;
-        throw;
-    }
-}
diff --git a/reboot b/reboot
deleted file mode 100755 (executable)
index c607879..0000000
--- a/reboot
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-shutdown -r "$@"
diff --git a/service-constants.h b/service-constants.h
deleted file mode 100644 (file)
index 8084066..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef SERVICE_CONSTANTS_H
-#define SERVICE_CONSTANTS_H
-
-/* Service states */
-enum class ServiceState {
-    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 ServiceType {
-    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 ServiceEvent {
-    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 ShutdownType {
-    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/service-listener.h b/service-listener.h
deleted file mode 100644 (file)
index c45c2a9..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef SERVICE_LISTENER_H
-#define SERVICE_LISTENER_H
-
-#include "service-constants.h"
-
-class ServiceRecord;
-
-// Interface for listening to services
-class ServiceListener
-{
-    public:
-    
-    // An event occurred on the service being observed.
-    // Listeners must not be added or removed during event notification.
-    virtual void serviceEvent(ServiceRecord * service, ServiceEvent event) noexcept = 0;
-};
-
-#endif
diff --git a/service.cc b/service.cc
deleted file mode 100644 (file)
index 4fec13b..0000000
+++ /dev/null
@@ -1,870 +0,0 @@
-#include <cstring>
-#include <cerrno>
-#include <sstream>
-#include <iterator>
-#include <memory>
-#include <cstddef>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <sys/un.h>
-#include <sys/socket.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <termios.h>
-
-#include "service.h"
-#include "dinit-log.h"
-
-// from dinit.cc:
-void open_control_socket(struct ev_loop *loop) noexcept;
-
-
-// Find the requested service by name
-static ServiceRecord * findService(const std::list<ServiceRecord *> & records,
-                                    const char *name) noexcept
-{
-    using std::list;
-    list<ServiceRecord *>::const_iterator i = records.begin();
-    for ( ; i != records.end(); i++ ) {
-        if (strcmp((*i)->getServiceName(), name) == 0) {
-            return *i;
-        }
-    }
-    return (ServiceRecord *)0;
-}
-
-ServiceRecord * ServiceSet::findService(const std::string &name) noexcept
-{
-    return ::findService(records, name.c_str());
-}
-
-void ServiceSet::startService(const char *name)
-{
-    using namespace std;
-    ServiceRecord *record = loadServiceRecord(name);
-    
-    record->start();
-}
-
-void ServiceSet::stopService(const std::string & name) noexcept
-{
-    ServiceRecord *record = findService(name);
-    if (record != nullptr) {
-        record->stop();
-    }
-}
-
-// Called when a service has actually stopped.
-void ServiceRecord::stopped() noexcept
-{
-    if (service_type != ServiceType::SCRIPTED && service_type != ServiceType::BGPROCESS && onstart_flags.runs_on_console) {
-        tcsetpgrp(0, getpgrp());
-        releaseConsole();
-    }
-
-    logServiceStopped(service_name);
-    service_state = ServiceState::STOPPED;
-    force_stop = false;
-    
-    // Stop any dependencies whose desired state is STOPPED:
-    for (sr_iter i = depends_on.begin(); i != depends_on.end(); i++) {
-        (*i)->dependentStopped();
-    }
-
-    service_set->service_inactive(this);
-    notifyListeners(ServiceEvent::STOPPED);
-    
-    if (desired_state == ServiceState::STARTED) {
-        // Desired state is "started".
-        start();
-    }
-    else if (socket_fd != -1) {
-        close(socket_fd);
-        socket_fd = -1;
-    }
-}
-
-void ServiceRecord::process_child_callback(struct ev_loop *loop, ev_child *w, int revents) noexcept
-{    
-    ServiceRecord *sr = (ServiceRecord *) w->data;
-
-    sr->pid = -1;
-    sr->exit_status = w->rstatus;
-    ev_child_stop(loop, w);
-    
-    // Ok, for a process service, any process death which we didn't rig
-    // ourselves is a bit... unexpected. Probably, the child died because
-    // we asked it to (sr->service_state == STOPPING). But even if
-    // we didn't, there's not much we can do.
-    
-    if (sr->waiting_for_execstat) {
-        // We still don't have an exec() status from the forked child, wait for that
-        // before doing any further processing.
-        return;
-    }
-    
-    sr->handle_exit_status();
-}
-
-void ServiceRecord::handle_exit_status() noexcept
-{
-    if (exit_status != 0 && service_state != ServiceState::STOPPING) {
-        log(LogLevel::ERROR, "Service ", service_name, " process terminated with exit code ", exit_status);
-    }
-
-    if (doing_recovery) {
-        // (BGPROCESS only)
-        doing_recovery = false;
-        bool do_stop = false;
-        if (exit_status != 0) {
-            do_stop = true;
-        }
-        else {
-            // We need to re-read the PID, since it has now changed.
-            if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) {
-                if (! read_pid_file()) {
-                    do_stop = true;
-                }
-            }
-        }
-        
-        if (do_stop) {
-            stop();
-            if (auto_restart && service_set->get_auto_restart()) {
-                start();
-            }
-        }
-        
-        return;
-    }
-    
-    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
-        if (service_state == ServiceState::STARTING) {
-            // (only applies to BGPROCESS)
-            if (exit_status == 0) {
-                started();
-            }
-            else {
-                failed_to_start();
-            }
-        }
-        else if (service_state == ServiceState::STOPPING) {
-            // TODO log non-zero rstatus?
-            stopped();
-        }
-        else if (smooth_recovery && service_state == ServiceState::STARTED) {
-            // TODO ensure a minimum time between restarts
-            // TODO if we are pinned-started then we should probably check
-            //      that dependencies have started before trying to re-start the
-            //      service process.
-            doing_recovery = (service_type == ServiceType::BGPROCESS);
-            start_ps_process();
-            return;
-        }
-        else {
-            forceStop();
-        }
-        
-        if (auto_restart && service_set->get_auto_restart()) {
-            start();
-        }
-    }
-    else {  // SCRIPTED
-        if (service_state == ServiceState::STOPPING) {
-            if (exit_status == 0) {
-                stopped();
-            }
-            else {
-                // ??? failed to stop! Let's log it as info:
-                log(LogLevel::INFO, "service ", service_name, " stop command failed with exit code ", exit_status);
-                // Just assume that we stopped, so that any dependencies
-                // can be stopped:
-                stopped();
-            }
-        }
-        else { // STARTING
-            if (exit_status == 0) {
-                started();
-            }
-            else {
-                // failed to start
-                log(LogLevel::ERROR, "service ", service_name, " command failed with exit code ", exit_status);
-                failed_to_start();
-            }
-        }
-    }
-}
-
-void ServiceRecord::process_child_status(struct ev_loop *loop, ev_io * stat_io, int revents) noexcept
-{
-    ServiceRecord *sr = (ServiceRecord *) stat_io->data;
-    sr->waiting_for_execstat = false;
-    
-    int exec_status;
-    int r = read(stat_io->fd, &exec_status, sizeof(int));
-    close(stat_io->fd);
-    ev_io_stop(loop, stat_io);
-    
-    if (r != 0) {
-        // We read an errno code; exec() failed, and the service startup failed.
-        sr->pid = -1;
-        log(LogLevel::ERROR, sr->service_name, ": execution failed: ", strerror(exec_status));
-        if (sr->service_state == ServiceState::STARTING) {
-            sr->failed_to_start();
-        }
-        else if (sr->service_state == ServiceState::STOPPING) {
-            // Must be a scripted servce. We've logged the failure, but it's probably better
-            // not to leave the service in STARTED state:
-            sr->stopped();
-        }
-    }
-    else {
-        // exec() succeeded.
-        if (sr->service_type == ServiceType::PROCESS) {
-            if (sr->service_state != ServiceState::STARTED) {
-                sr->started();
-            }
-        }
-        
-        if (sr->pid == -1) {
-            // Somehow the process managed to complete before we even saw the status.
-            sr->handle_exit_status();
-        }
-    }
-}
-
-void ServiceRecord::start() noexcept
-{
-    if ((service_state == ServiceState::STARTING || service_state == ServiceState::STARTED)
-            && desired_state == ServiceState::STOPPED) {
-        // This service was starting, or started, but was set to be stopped.
-        // Cancel the stop (and continue starting/running).
-        notifyListeners(ServiceEvent::STOPCANCELLED);
-    }
-
-    if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return;
-
-    desired_state = ServiceState::STARTED;
-    
-    if (pinned_stopped) return;
-    
-    if (service_state != ServiceState::STOPPED) {
-        // We're already starting/started, or we are stopping and need to wait for
-        // that the complete.
-        if (service_state != ServiceState::STOPPING || ! can_interrupt_stop()) {
-            return;
-        }
-        // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING,
-        // but if so they are waiting (for us), so they too can be instantly returned to
-        // STARTING state.
-    }
-    
-    service_state = ServiceState::STARTING;
-    service_set->service_active(this);
-
-    waiting_for_deps = true;
-
-    // Ask dependencies to start, mark them as being waited on.
-    if (! startCheckDependencies(true)) {
-        return;
-    }
-
-    // Actually start this service.
-    allDepsStarted();
-}
-
-void ServiceRecord::dependencyStarted() noexcept
-{
-    if (service_state != ServiceState::STARTING || ! waiting_for_deps) {
-        return;
-    }
-
-    if (startCheckDependencies(false)) {
-        allDepsStarted();
-    }
-}
-
-bool ServiceRecord::startCheckDependencies(bool start_deps) noexcept
-{
-    bool all_deps_started = true;
-
-    for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
-        if ((*i)->service_state != ServiceState::STARTED) {
-            if (start_deps) {
-                all_deps_started = false;
-                (*i)->start();
-            }
-            else {
-                return false;
-            }
-        }
-    }
-
-    for (auto i = soft_deps.begin(); i != soft_deps.end(); ++i) {
-        ServiceRecord * to = i->getTo();
-        if (start_deps) {
-            if (to->service_state != ServiceState::STARTED) {
-                to->start();
-                i->waiting_on = true;
-                all_deps_started = false;
-            }
-            else {
-                i->waiting_on = false;
-            }
-        }
-        else if (i->waiting_on) {
-            if (to->service_state != ServiceState::STARTING) {
-                // Service has either started or is no longer starting
-                i->waiting_on = false;
-            }
-            else {
-                // We are still waiting on this service
-                return false;
-            }
-        }
-    }
-    
-    return all_deps_started;
-}
-
-bool ServiceRecord::open_socket() noexcept
-{
-    if (socket_path.empty() || socket_fd != -1) {
-        // No socket, or already open
-        return true;
-    }
-    
-    const char * saddrname = socket_path.c_str();
-    uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1;
-
-    struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
-    if (name == nullptr) {
-        log(LogLevel::ERROR, service_name, ": Opening activation socket: out of memory");
-        return false;
-    }
-    
-    // Un-link any stale socket. TODO: safety check? should at least confirm the path is a socket.
-    unlink(saddrname);
-
-    name->sun_family = AF_UNIX;
-    strcpy(name->sun_path, saddrname);
-
-    int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
-    if (sockfd == -1) {
-        log(LogLevel::ERROR, service_name, ": Error creating activation socket: ", strerror(errno));
-        free(name);
-        return false;
-    }
-
-    if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
-        log(LogLevel::ERROR, service_name, ": Error binding activation socket: ", strerror(errno));
-        close(sockfd);
-        free(name);
-        return false;
-    }
-    
-    free(name);
-    
-    // POSIX (1003.1, 2013) says that fchown and fchmod don't necesarily work on sockets. We have to
-    // use chown and chmod instead.
-    if (chown(saddrname, socket_uid, socket_gid)) {
-        log(LogLevel::ERROR, service_name, ": Error setting activation socket owner/group: ", strerror(errno));
-        close(sockfd);
-        return false;
-    }
-    
-    if (chmod(saddrname, socket_perms) == -1) {
-        log(LogLevel::ERROR, service_name, ": Error setting activation socket permissions: ", strerror(errno));
-        close(sockfd);
-        return false;
-    }
-
-    if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
-        log(LogLevel::ERROR, ": Error listening on activation socket: ", strerror(errno));
-        close(sockfd);
-        return false;
-    }
-    
-    socket_fd = sockfd;
-    return true;
-}
-
-void ServiceRecord::allDepsStarted(bool has_console) noexcept
-{
-    if (onstart_flags.runs_on_console && ! has_console) {
-        waiting_for_deps = true;
-        queueForConsole();
-        return;
-    }
-    
-    waiting_for_deps = false;
-
-    if (! open_socket()) {
-        failed_to_start();
-    }
-
-    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS
-            || service_type == ServiceType::SCRIPTED) {
-        bool start_success = start_ps_process();
-        if (! start_success) {
-            failed_to_start();
-        }
-    }
-    else {
-        // "internal" service
-        started();
-    }
-}
-
-void ServiceRecord::acquiredConsole() noexcept
-{
-    if (service_state != ServiceState::STARTING) {
-        // We got the console but no longer want it.
-        releaseConsole();
-    }
-    else if (startCheckDependencies(false)) {
-        log_to_console = false;
-        allDepsStarted(true);
-    }
-    else {
-        // We got the console but can't use it yet.
-        releaseConsole();
-    }
-}
-
-bool ServiceRecord::read_pid_file() noexcept
-{
-    const char *pid_file_c = pid_file.c_str();
-    int fd = open(pid_file_c, O_CLOEXEC);
-    if (fd != -1) {
-        char pidbuf[21]; // just enought to hold any 64-bit integer
-        int r = read(fd, pidbuf, 20);
-        if (r > 0) {
-            pidbuf[r] = 0; // store nul terminator
-            pid = std::atoi(pidbuf);
-            if (kill(pid, 0) == 0) {
-                ev_child_init(&child_listener, process_child_callback, pid, 0);
-                child_listener.data = this;
-                ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
-            }
-            else {
-                log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
-                pid = -1;
-                close(fd);
-                return false;
-            }
-        }
-        close(fd);
-        return true;
-    }
-    else {
-        log(LogLevel::ERROR, service_name, ": read pid file: ", strerror(errno));
-        return false;
-    }
-}
-
-void ServiceRecord::started() noexcept
-{
-    if (onstart_flags.runs_on_console && (service_type == ServiceType::SCRIPTED || service_type == ServiceType::BGPROCESS)) {
-        tcsetpgrp(0, getpgrp());
-        releaseConsole();
-    }
-    
-    if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) {
-        if (! read_pid_file()) {
-            failed_to_start();
-            return;
-        }
-    }
-    
-    logServiceStarted(service_name);
-    service_state = ServiceState::STARTED;
-    notifyListeners(ServiceEvent::STARTED);
-
-    if (onstart_flags.rw_ready) {
-        open_control_socket(ev_default_loop(EVFLAG_AUTO));
-    }
-
-    if (force_stop || desired_state == ServiceState::STOPPED) {
-        // We must now stop.
-        bool do_restart = (desired_state != ServiceState::STOPPED);
-        stop();
-        if (do_restart) {
-            start();
-        }
-        return;
-    }
-
-    // Notify any dependents whose desired state is STARTED:
-    for (auto i = dependents.begin(); i != dependents.end(); i++) {
-        (*i)->dependencyStarted();
-    }
-    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
-        (*i)->getFrom()->dependencyStarted();
-    }
-}
-
-void ServiceRecord::failed_to_start()
-{
-    if (onstart_flags.runs_on_console) {
-        tcsetpgrp(0, getpgrp());
-        releaseConsole();
-    }
-    
-    logServiceFailed(service_name);
-    service_state = ServiceState::STOPPED;
-    desired_state = ServiceState::STOPPED;
-    service_set->service_inactive(this);
-    notifyListeners(ServiceEvent::FAILEDSTART);
-    
-    // failure to start
-    // Cancel start of dependents:
-    for (sr_iter i = dependents.begin(); i != dependents.end(); i++) {
-        if ((*i)->service_state == ServiceState::STARTING) {
-            (*i)->failed_dependency();
-        }
-    }    
-    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
-        // We can send 'start', because this is only a soft dependency.
-        // Our startup failure means that they don't have to wait for us.
-        (*i)->getFrom()->dependencyStarted();
-    }
-}
-
-bool ServiceRecord::start_ps_process() noexcept
-{
-    return start_ps_process(exec_arg_parts, onstart_flags.runs_on_console);
-}
-
-bool ServiceRecord::start_ps_process(const std::vector<const char *> &cmd, bool on_console) noexcept
-{
-    // In general, you can't tell whether fork/exec is successful. We use a pipe to communicate
-    // success/failure from the child to the parent. The pipe is set CLOEXEC so a successful
-    // exec closes the pipe, and the parent sees EOF. If the exec is unsuccessful, the errno
-    // is written to the pipe, and the parent can read it.
-
-    int pipefd[2];
-    if (pipe2(pipefd, O_CLOEXEC)) {
-        // TODO log error
-        return false;
-    }
-    
-    // Set up the argument array and other data now (before fork), in case memory allocation fails.
-    
-    auto args = cmd.data();
-    
-    const char * logfile = this->logfile.c_str();
-    if (*logfile == 0) {
-        logfile = "/dev/null";
-    }
-
-    // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
-    // until they are not.
-
-    pid_t forkpid = fork();
-    if (forkpid == -1) {
-        // TODO log error
-        close(pipefd[0]);
-        close(pipefd[1]);
-        return false;
-    }
-
-    // If the console already has a session leader, presumably it is us. On the other hand
-    // if it has no session leader, and we don't create one, then control inputs such as
-    // ^C will have no effect.
-    bool do_set_ctty = (tcgetsid(0) == -1);
-
-    if (forkpid == 0) {
-        // Child process. Must not allocate memory (or otherwise risk throwing any exception)
-        // from here until exit().
-        ev_default_destroy(); // won't need that on this side, free up fds.
-
-        constexpr int bufsz = ((CHAR_BIT * sizeof(pid_t) - 1) / 3 + 2) + 11;
-        // "LISTEN_PID=" - 11 characters
-        char nbuf[bufsz];
-
-        if (socket_fd != -1) {
-            dup2(socket_fd, 3);
-            if (socket_fd != 3) {
-                close(socket_fd);
-            }
-            
-            if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
-            
-            snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
-            
-            if (putenv(nbuf)) goto failure_out;
-        }
-
-        if (! on_console) {
-            // Re-set stdin, stdout, stderr
-            close(0); close(1); close(2);
-
-            // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
-            if (open("/dev/null", O_RDONLY) == 0) {
-              // stdin = 0. That's what we should have; proceed with opening
-              // stdout and stderr.
-              open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
-              dup2(1, 2);
-            }
-        }
-        else {
-            // "run on console" - run as a foreground job on the terminal/console device
-            if (do_set_ctty) {
-                setsid();
-                ioctl(0, TIOCSCTTY, 0);
-            }
-            setpgid(0,0);
-            tcsetpgrp(0, getpgrp());
-            
-            // TODO disable suspend (^Z)? (via tcsetattr)
-            //      (should be done before TIOCSCTTY)
-        }
-
-        execvp(exec_arg_parts[0], const_cast<char **>(args));
-
-        // If we got here, the exec failed:
-        failure_out:
-        int exec_status = errno;
-        write(pipefd[1], &exec_status, sizeof(int));
-        exit(0);
-    }
-    else {
-        // Parent process
-        close(pipefd[1]); // close the 'other end' fd
-
-        pid = forkpid;
-
-        // Listen for status
-        ev_io_init(&child_status_listener, process_child_status, pipefd[0], EV_READ);
-        child_status_listener.data = this;
-        ev_io_start(ev_default_loop(EVFLAG_AUTO), &child_status_listener);
-
-        // Add a process listener so we can detect when the
-        // service stops
-        ev_child_init(&child_listener, process_child_callback, pid, 0);
-        child_listener.data = this;
-        ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
-        waiting_for_execstat = true;
-        return true;
-    }
-}
-
-// Mark this and all dependent services as force-stopped.
-void ServiceRecord::forceStop() noexcept
-{
-    if (service_state != ServiceState::STOPPED) {
-        force_stop = true;
-        for (sr_iter i = dependents.begin(); i != dependents.end(); i++) {
-            (*i)->forceStop();
-        }
-        stop();
-        
-        // We don't want to force stop soft dependencies, however.
-    }
-}
-
-// A dependency of this service failed to start.
-// Only called when state == STARTING.
-void ServiceRecord::failed_dependency()
-{
-    desired_state = ServiceState::STOPPED;
-    
-    // Presumably, we were starting. So now we're not.
-    service_state = ServiceState::STOPPED;
-    service_set->service_inactive(this);
-    logServiceFailed(service_name);
-    
-    // Notify dependents of this service also
-    for (auto i = dependents.begin(); i != dependents.end(); i++) {
-        if ((*i)->service_state == ServiceState::STARTING) {
-            (*i)->failed_dependency();
-        }
-    }
-    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
-        // It's a soft dependency, so send them 'started' rather than
-        // 'failed dep'.
-        (*i)->getFrom()->dependencyStarted();
-    }    
-}
-
-void ServiceRecord::dependentStopped() noexcept
-{
-    if (service_state == ServiceState::STOPPING) {
-        // Check the other dependents before we stop.
-        if (stopCheckDependents()) {
-            allDepsStopped();
-        }
-    }
-}
-
-void ServiceRecord::stop() noexcept
-{
-    if ((service_state == ServiceState::STOPPING || service_state == ServiceState::STOPPED)
-            && desired_state == ServiceState::STARTED) {
-        // The service *was* stopped/stopping, but it was going to restart.
-        // Now, we'll cancel the restart.
-        notifyListeners(ServiceEvent::STARTCANCELLED);
-    }
-    
-    if (desired_state == ServiceState::STOPPED && service_state != ServiceState::STARTED) return;
-    
-    desired_state = ServiceState::STOPPED;
-    
-    if (pinned_started) return;
-
-    if (service_state != ServiceState::STARTED) {
-        if (service_state == ServiceState::STARTING) {
-            if (! can_interrupt_start()) {
-                // Well this is awkward: we're going to have to continue
-                // starting, but we don't want any dependents to think that
-                // they are still waiting to start.
-                // Make sure they remain stopped:
-                stopDependents();
-                return;
-            }
-            
-            // Reaching this point, we have can_interrupt_start() == true. So,
-            // we can stop. Dependents might be starting, but they must be
-            // waiting on us, so they should also be immediately stoppable.
-            // Fall through to below.
-        }
-        else {
-            // If we're starting we need to wait for that to complete.
-            // If we're already stopping/stopped there's nothing to do.
-            return;
-        }
-    }
-    
-    service_state = ServiceState::STOPPING;
-    waiting_for_deps = true;
-    
-    // If we get here, we are in STARTED state; stop all dependents.
-    if (stopDependents()) {
-        allDepsStopped();
-    }
-}
-
-bool ServiceRecord::stopCheckDependents() noexcept
-{
-    bool all_deps_stopped = true;
-    for (sr_iter i = dependents.begin(); i != dependents.end(); ++i) {
-        if ((*i)->service_state != ServiceState::STOPPED) {
-            all_deps_stopped = false;
-            break;
-        }
-    }
-    
-    return all_deps_stopped;
-}
-
-bool ServiceRecord::stopDependents() noexcept
-{
-    bool all_deps_stopped = true;
-    for (sr_iter i = dependents.begin(); i != dependents.end(); ++i) {
-        if ((*i)->service_state != ServiceState::STOPPED) {
-            all_deps_stopped = false;
-            (*i)->stop();
-        }
-    }
-    
-    return all_deps_stopped;
-}
-
-// Dependency stopped or is stopping; we must stop too.
-void ServiceRecord::allDepsStopped()
-{
-    waiting_for_deps = false;
-    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
-        if (pid != -1) {
-            // The process is still kicking on - must actually kill it.
-            if (! onstart_flags.no_sigterm) {
-                kill(pid, SIGTERM);
-            }
-            if (term_signal != -1) {
-                kill(pid, term_signal);
-            }
-            // Now we wait; the rest is done in process_child_callback
-        }
-        else {
-            // The process is already dead.
-            stopped();
-        }
-    }
-    else if (service_type == ServiceType::SCRIPTED) {
-        // Scripted service.
-        if (stop_command.length() == 0) {
-            stopped();
-        }
-        else if (! start_ps_process(stop_arg_parts, false)) {
-            // Couldn't execute stop script, but there's not much we can do:
-            stopped();
-        }
-    }
-    else {
-        stopped();
-    }
-}
-
-void ServiceRecord::pinStart() noexcept
-{
-    start();
-    pinned_started = true;
-}
-
-void ServiceRecord::pinStop() noexcept
-{
-    stop();
-    pinned_stopped = true;
-}
-
-void ServiceRecord::unpin() noexcept
-{
-    if (pinned_started) {
-        pinned_started = false;
-        if (desired_state == ServiceState::STOPPED) {
-            stop();
-        }
-    }
-    if (pinned_stopped) {
-        pinned_stopped = false;
-        if (desired_state == ServiceState::STARTED) {
-            start();
-        }
-    }
-}
-
-void ServiceRecord::queueForConsole() noexcept
-{
-    next_for_console = nullptr;
-    auto tail = service_set->consoleQueueTail(this);
-    if (tail == nullptr) {
-        acquiredConsole();
-    }
-    else {
-        tail->next_for_console = this;
-    }
-}
-
-void ServiceRecord::releaseConsole() noexcept
-{
-    log_to_console = true;
-    if (next_for_console != nullptr) {
-        next_for_console->acquiredConsole();
-    }
-    else {
-        service_set->consoleQueueTail(nullptr);
-    }
-}
-
-void ServiceSet::service_active(ServiceRecord *sr) noexcept
-{
-    active_services++;
-}
-
-void ServiceSet::service_inactive(ServiceRecord *sr) noexcept
-{
-    active_services--;
-}
diff --git a/service.h b/service.h
deleted file mode 100644 (file)
index cbeab72..0000000
--- a/service.h
+++ /dev/null
@@ -1,547 +0,0 @@
-#ifndef SERVICE_H
-#define SERVICE_H
-
-#include <string>
-#include <list>
-#include <vector>
-#include <csignal>
-#include <unordered_set>
-#include "ev.h"
-#include "control.h"
-#include "service-listener.h"
-#include "service-constants.h"
-
-/*
- * Possible 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.
- */
-
-struct OnstartFlags {
-    bool rw_ready : 1;
-    
-    // Not actually "onstart" commands:
-    bool no_sigterm : 1;  // do not send SIGTERM
-    bool runs_on_console : 1;  // run "in the foreground"
-    
-    OnstartFlags() noexcept : rw_ready(false),
-            no_sigterm(false), runs_on_console(false)
-    {
-    }
-};
-
-// Exception while loading a service
-class ServiceLoadExc
-{
-    public:
-    std::string serviceName;
-    const char *excDescription;
-    
-    protected:
-    ServiceLoadExc(std::string serviceName) noexcept
-        : serviceName(serviceName)
-    {
-    }
-};
-
-class ServiceNotFound : public ServiceLoadExc
-{
-    public:
-    ServiceNotFound(std::string serviceName) noexcept
-        : ServiceLoadExc(serviceName)
-    {
-        excDescription = "Service description not found.";
-    }
-};
-
-class ServiceCyclicDependency : public ServiceLoadExc
-{
-    public:
-    ServiceCyclicDependency(std::string serviceName) noexcept
-        : ServiceLoadExc(serviceName)
-    {
-        excDescription = "Has cyclic dependency.";
-    }
-};
-
-class ServiceDescriptionExc : public ServiceLoadExc
-{
-    public:
-    std::string extraInfo;
-    
-    ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
-        : ServiceLoadExc(serviceName), extraInfo(extraInfo)
-    {
-        excDescription = extraInfo.c_str();
-    }    
-};
-
-class ServiceRecord; // forward declaration
-class ServiceSet; // forward declaration
-
-/* Service dependency record */
-class ServiceDep
-{
-    ServiceRecord * from;
-    ServiceRecord * to;
-
-    public:
-    /* Whether the 'from' service is waiting for the 'to' service to start */
-    bool waiting_on;
-
-    ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false)
-    {  }
-
-    ServiceRecord * getFrom() noexcept
-    {
-        return from;
-    }
-
-    ServiceRecord * getTo() noexcept
-    {
-        return to;
-    }
-};
-
-// 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).
-static std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
-{
-    std::vector<const char *> r;
-    r.reserve(arg_indices.size() + 1);
-
-    // First store nul terminator for each part:
-    for (auto index_pair : arg_indices) {
-        if (index_pair.second < s.length()) {
-            s[index_pair.second] = 0;
-        }
-    }
-
-    // Now we can get the C string (c_str) and store offsets into it:
-    const char * cstr = s.c_str();
-    for (auto index_pair : arg_indices) {
-        r.push_back(cstr + index_pair.first);
-    }
-    r.push_back(nullptr);
-    return r;
-}
-
-
-class ServiceRecord
-{
-    typedef std::string string;
-    
-    string service_name;
-    ServiceType service_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
-    ServiceState service_state = ServiceState::STOPPED; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
-    ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
-
-    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 */
-    
-    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 */
-    
-    string pid_file;
-    
-    OnstartFlags 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
-    bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
-    bool doing_recovery : 1;    // if we are currently recovering a BGPROCESS (restarting process, while
-                                //   holding STARTED service state)
-
-    typedef std::list<ServiceRecord *> sr_list;
-    typedef sr_list::iterator sr_iter;
-    
-    // list of soft dependencies
-    typedef std::list<ServiceDep> softdep_list;
-    
-    // list of soft dependents
-    typedef std::list<ServiceDep *> softdpt_list;
-    
-    sr_list depends_on; // services this one depends on
-    sr_list dependents; // services depending on this one
-    softdep_list soft_deps;  // services this one depends on via a soft dependency
-    softdpt_list soft_dpts;  // services depending on this one via a soft dependency
-    
-    // unsigned wait_count;  /* if we are waiting for dependents/dependencies to
-    //                         start/stop, this is how many we're waiting for */
-    
-    ServiceSet *service_set; // the set this service belongs to
-    
-    // Next service (after this one) in the queue for the console:
-    ServiceRecord *next_for_console;
-
-    std::unordered_set<ServiceListener *> 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;  // sockget 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.
-
-    ev_child child_listener;
-    ev_io child_status_listener;
-    
-    // All dependents have stopped.
-    void allDepsStopped();
-    
-    // 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
-    void failed_to_start();
-    
-    // A dependency of this service failed to start.
-    void failed_dependency();
-    
-    // For process services, start the process, return true on success
-    bool start_ps_process() noexcept;
-    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
-    
-    // Callback from libev when a child process dies
-    static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
-            int revents) noexcept;
-    
-    static void process_child_status(struct ev_loop *loop, ev_io * stat_io,
-            int revents) noexcept;
-    
-    void handle_exit_status() noexcept;
-    
-    // A dependency has reached STARTED state
-    void dependencyStarted() noexcept;
-    
-    void allDepsStarted(bool haveConsole = false) noexcept;
-    
-    // Read the pid-file, return false on failure
-    bool read_pid_file() noexcept;
-    
-    // Open the activation socket, return false on failure
-    bool open_socket() noexcept;
-    
-    // Check whether dependencies have started, and optionally ask them to start
-    bool startCheckDependencies(bool do_start) 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).
-    bool can_interrupt_start() noexcept
-    {
-        return waiting_for_deps;
-    }
-    
-    // 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 dependentStopped() noexcept;
-
-    // check if all dependents have stopped
-    bool stopCheckDependents() noexcept;
-    
-    // issue a stop to all dependents, return true if they are all already stopped
-    bool stopDependents() noexcept;
-    
-    void forceStop() noexcept; // force-stop this service and all dependents
-    
-    void notifyListeners(ServiceEvent event) noexcept
-    {
-        for (auto l : listeners) {
-            l->serviceEvent(this, event);
-        }
-    }
-    
-    // Queue to run on the console. 'acquiredConsole()' will be called when the console is available.
-    void queueForConsole() noexcept;
-    
-    // Console is available.
-    void acquiredConsole() noexcept;
-    
-    // Release console (console must be currently held by this service)
-    void releaseConsole() noexcept;
-    
-    public:
-
-    ServiceRecord(ServiceSet *set, string name)
-        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false),
-            pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
-            waiting_for_execstat(false), doing_recovery(false), force_stop(false)
-    {
-        service_set = set;
-        service_name = name;
-        service_type = ServiceType::DUMMY;
-    }
-    
-    ServiceRecord(ServiceSet *set, string name, ServiceType service_type, string &&command, std::list<std::pair<unsigned,unsigned>> &command_offsets,
-            sr_list * pdepends_on, sr_list * pdepends_soft)
-        : ServiceRecord(set, name)
-    {
-        service_set = set;
-        service_name = name;
-        this->service_type = service_type;
-        this->depends_on = std::move(*pdepends_on);
-
-        program_name = command;
-        exec_arg_parts = separate_args(program_name, command_offsets);
-
-        for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
-            (*i)->dependents.push_back(this);
-        }
-
-        // Soft dependencies
-        auto b_iter = soft_deps.end();
-        for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) {
-            b_iter = soft_deps.emplace(b_iter, this, *i);
-            (*i)->soft_dpts.push_back(&(*b_iter));
-            ++b_iter;
-        }
-    }
-    
-    // TODO write a destructor
-    
-    // Set the stop command and arguments (may throw std::bad_alloc)
-    void setStopCommand(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);
-    }
-    
-    // Get the current service state.
-    ServiceState getState() noexcept
-    {
-        return service_state;
-    }
-    
-    // Get the target (aka desired) state.
-    ServiceState getTargetState() noexcept
-    {
-        return desired_state;
-    }
-
-    // Set logfile, should be done before service is started
-    void setLogfile(string logfile)
-    {
-        this->logfile = logfile;
-    }
-    
-    // Set whether this service should automatically restart when it dies
-    void setAutoRestart(bool auto_restart) noexcept
-    {
-        this->auto_restart = auto_restart;
-    }
-    
-    void setSmoothRecovery(bool smooth_recovery) noexcept
-    {
-        this->smooth_recovery = smooth_recovery;
-    }
-    
-    // Set "on start" flags (commands)
-    void setOnstartFlags(OnstartFlags flags) noexcept
-    {
-        this->onstart_flags = flags;
-    }
-    
-    // Set an additional signal (other than SIGTERM) to be used to terminate the process
-    void setExtraTerminationSignal(int signo) noexcept
-    {
-        this->term_signal = signo;
-    }
-    
-    void set_pid_file(string &&pid_file) noexcept
-    {
-        this->pid_file = pid_file;
-    }
-    
-    void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
-    {
-        this->socket_path = socket_path;
-        this->socket_perms = socket_perms;
-        this->socket_uid = socket_uid;
-        this->socket_gid = socket_gid;
-    }
-
-    const char *getServiceName() const noexcept { return service_name.c_str(); }
-    ServiceState getState() const noexcept { return service_state; }
-    
-    void start() noexcept;  // start the service
-    void stop() noexcept;   // stop the service
-    
-    void pinStart() noexcept;  // start the service and pin it
-    void pinStop() noexcept;   // stop the service and pin it
-    void unpin() noexcept;     // unpin the service
-    
-    bool isDummy() noexcept
-    {
-        return service_type == ServiceType::DUMMY;
-    }
-    
-    // Add a listener. A listener must only be added once. May throw std::bad_alloc.
-    void addListener(ServiceListener * listener)
-    {
-        listeners.insert(listener);
-    }
-    
-    // Remove a listener.    
-    void removeListener(ServiceListener * listener) noexcept
-    {
-        listeners.erase(listener);
-    }
-};
-
-
-class ServiceSet
-{
-    int active_services;
-    std::list<ServiceRecord *> records;
-    const char *service_dir;  // directory containing service descriptions
-    bool restart_enabled; // whether automatic restart is enabled (allowed)
-    
-    ShutdownType shutdown_type = ShutdownType::CONTINUE;  // Shutdown type, if stopping
-    
-    ServiceRecord * console_queue_tail = nullptr; // last record in console queue
-    
-    // Private methods
-        
-    // 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
-    ServiceRecord *loadServiceRecord(const char *name);
-
-    // Public
-    
-    public:
-    ServiceSet(const char *service_dir)
-    {
-        this->service_dir = service_dir;
-        active_services = 0;
-        restart_enabled = true;
-    }
-    
-    // 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 startService(const char *name);
-    
-    // Locate an existing service record.
-    ServiceRecord *findService(const std::string &name) noexcept;
-    
-    // Find a loaded service record, or load it if it is not loaded.
-    // Throws:
-    //   ServiceLoadException (or subclass) on problem with service description
-    //   std::bad_alloc on out-of-memory condition 
-    ServiceRecord *loadService(const std::string &name)
-    {
-        ServiceRecord *record = findService(name);
-        if (record == nullptr) {
-            record = loadServiceRecord(name.c_str());
-        }
-        return record;
-    }
-    
-    // Stop the service with the given name. The named service will begin
-    // transition to the 'stopped' state.
-    void stopService(const std::string &name) noexcept;
-    
-    // Set the console queue tail (returns previous tail)
-    ServiceRecord * consoleQueueTail(ServiceRecord * newTail) noexcept
-    {
-        auto prev_tail = console_queue_tail;
-        console_queue_tail = newTail;
-        return prev_tail;
-    }
-    
-    // Notification from service that it is active (state != STOPPED)
-    // Only to be called on the transition from inactive to active.
-    void service_active(ServiceRecord *) noexcept;
-    
-    // Notification from service that it is inactive (STOPPED)
-    // Only to be called on the transition from active to inactive.
-    void service_inactive(ServiceRecord *) 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(ShutdownType type = ShutdownType::HALT) noexcept
-    {
-        restart_enabled = false;
-        shutdown_type = type;
-        for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
-            (*i)->stop();
-            (*i)->unpin();
-        }
-    }
-    
-    void set_auto_restart(bool restart) noexcept
-    {
-        restart_enabled = restart;
-    }
-    
-    bool get_auto_restart() noexcept
-    {
-        return restart_enabled;
-    }
-    
-    ShutdownType getShutdownType() noexcept
-    {
-        return shutdown_type;
-    }
-};
-
-#endif
diff --git a/shutdown.cc b/shutdown.cc
deleted file mode 100644 (file)
index 1d46ab2..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-// #include <netinet/in.h>
-#include <cstddef>
-#include <cstdio>
-#include <csignal>
-#include <unistd.h>
-#include <cstring>
-#include <string>
-#include <iostream>
-
-#include <sys/reboot.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "control-cmds.h"
-#include "service-constants.h"
-
-// shutdown:  shut down the system
-// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
-
-void do_system_shutdown(ShutdownType shutdown_type);
-static void unmount_disks();
-static void swap_off();
-
-int main(int argc, char **argv)
-{
-    using namespace std;
-    
-    bool show_help = false;
-    bool sys_shutdown = false;
-    
-    auto shutdown_type = ShutdownType::POWEROFF;
-        
-    for (int i = 1; i < argc; i++) {
-        if (argv[i][0] == '-') {
-            if (strcmp(argv[i], "--help") == 0) {
-                show_help = true;
-                break;
-            }
-            
-            if (strcmp(argv[i], "--system") == 0) {
-                sys_shutdown = true;
-            }
-            else if (strcmp(argv[i], "-r") == 0) {
-                shutdown_type = ShutdownType::REBOOT;
-            }
-            else if (strcmp(argv[i], "-h") == 0) {
-                shutdown_type = ShutdownType::HALT;
-            }
-            else if (strcmp(argv[i], "-p") == 0) {
-                shutdown_type = ShutdownType::POWEROFF;
-            }
-            else {
-                cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
-                return 1;
-            }
-        }
-        else {
-            // time argument? TODO
-            show_help = true;
-        }
-    }
-
-    if (show_help) {
-        cout << "dinit-shutdown :   shutdown the system" << endl;
-        cout << "  --help           : show this help" << endl;
-        cout << "  -r               : reboot" << endl;
-        cout << "  -h               : halt system" << endl;
-        cout << "  -p               : power down (default)" << endl;
-        cout << "  --system         : perform shutdown immediately, instead of issuing shutdown" << endl;
-        cout << "                     command to the init program. Not recommended for use" << endl;
-        cout << "                     by users." << endl;
-        return 1;
-    }
-    
-    if (sys_shutdown) {
-        do_system_shutdown(shutdown_type);
-        return 0;
-    }
-    
-    int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
-    if (socknum == -1) {
-        perror("socket");
-        return 1;
-    }
-
-    const char *naddr = "/dev/dinitctl";
-    
-    struct sockaddr_un name;
-    name.sun_family = AF_UNIX;
-    strcpy(name.sun_path, naddr);
-    int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
-    
-    int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
-    if (connr == -1) {
-        perror("connect");
-        return 1;
-    }
-    
-    // Build buffer;
-    //uint16_t sname_len = strlen(service_name);
-    int bufsize = 2;
-    char * buf = new char[bufsize];
-    
-    buf[0] = DINIT_CP_SHUTDOWN;
-    buf[1] = static_cast<char>(shutdown_type);
-    
-    cout << "Issuing shutdown command..." << endl; // DAV
-    
-    // TODO make sure to write the whole buffer
-    int r = write(socknum, buf, bufsize);
-    if (r == -1) {
-        perror("write");
-    }
-    
-    // Wait for ACK/NACK
-    r = read(socknum, buf, 1);
-    // TODO: check result
-    
-    return 0;
-}
-
-void do_system_shutdown(ShutdownType shutdown_type)
-{
-    using namespace std;
-    
-    int reboot_type = 0;
-    if (shutdown_type == ShutdownType::REBOOT) reboot_type = RB_AUTOBOOT;
-    else if (shutdown_type == ShutdownType::POWEROFF) reboot_type = RB_POWER_OFF;
-    else reboot_type = RB_HALT_SYSTEM;
-    
-    // Write to console rather than any terminal, since we lose the terminal it seems:
-    close(STDOUT_FILENO);
-    int consfd = open("/dev/console", O_WRONLY);
-    if (consfd != STDOUT_FILENO) {
-        dup2(consfd, STDOUT_FILENO);
-    }
-    
-    cout << "Sending TERM/KILL to all processes..." << endl; // DAV
-    
-    // Send TERM/KILL to all (remaining) processes
-    kill(-1, SIGTERM);
-    sleep(1);
-    kill(-1, SIGKILL);
-    
-    // cout << "Sending QUIT to init..." << endl; // DAV
-    
-    // Tell init to exec reboot:
-    // TODO what if it's not PID=1? probably should have dinit pass us its PID
-    // kill(1, SIGQUIT);
-    
-    // TODO can we wait somehow for above to work?
-    // maybe have a pipe/socket and we read from our end...
-    
-    // TODO: close all ancillary file descriptors.
-    
-    // perform shutdown
-    cout << "Turning off swap..." << endl;
-    swap_off();
-    cout << "Unmounting disks..." << endl;
-    unmount_disks();
-    sync();
-    
-    cout << "Issuing shutdown via kernel..." << endl;
-    reboot(reboot_type);
-}
-
-static void unmount_disks()
-{
-    pid_t chpid = fork();
-    if (chpid == 0) {
-        // umount -a -r
-        //  -a : all filesystems (except proc)
-        //  -r : mount readonly if can't unmount
-        execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
-    }
-    else if (chpid > 0) {
-        int status;
-        waitpid(chpid, &status, 0);
-    }
-}
-
-static void swap_off()
-{
-    pid_t chpid = fork();
-    if (chpid == 0) {
-        // swapoff -a
-        execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
-    }
-    else if (chpid > 0) {
-        int status;
-        waitpid(chpid, &status, 0);
-    }
-}
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..24bb78d
--- /dev/null
@@ -0,0 +1,32 @@
+-include ../mconfig
+
+objects = dinit.o load_service.o service.o control.o dinit-log.o dinit-start.o shutdown.o dinit-reboot.o
+
+dinit_objects = dinit.o load_service.o service.o control.o dinit-log.o
+
+all: dinit dinit-start
+
+shutdown-utils: shutdown
+
+dinit: $(dinit_objects)
+       $(CXX) -o dinit $(dinit_objects) -lev $(EXTRA_LIBS)
+
+dinit-start: dinit-start.o
+       $(CXX) -o dinit-start dinit-start.o $(EXTRA_LIBS)
+       
+shutdown: shutdown.o
+       $(CXX) -o shutdown shutdown.o
+
+dinit-reboot: dinit-reboot.o
+       $(CXX) -o dinit-reboot dinit-reboot.o   
+
+$(objects): %.o: %.cc service.h dinit-log.h control.h control-cmds.h
+       $(CXX) $(CXXOPTS) -c $< -o $@
+
+#install: all
+
+#install.man:
+
+clean:
+       rm *.o
+       rm dinit
diff --git a/src/control-cmds.h b/src/control-cmds.h
new file mode 100644 (file)
index 0000000..263f61a
--- /dev/null
@@ -0,0 +1,61 @@
+// 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;
+
+// Shutdown:
+constexpr static int DINIT_CP_SHUTDOWN = 5;
+ // 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;
+
+
+
+// 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.cc b/src/control.cc
new file mode 100644 (file)
index 0000000..22f4378
--- /dev/null
@@ -0,0 +1,419 @@
+#include "control.h"
+#include "service.h"
+
+void ControlConn::processPacket()
+{
+    using std::string;
+    
+    // Note that where we call queuePacket, we must generally check the return value. If it
+    // returns false it has either deleted the connection or marked it for deletion; we
+    // shouldn't touch instance members after that point.
+
+    int pktType = rbuf[0];
+    if (pktType == DINIT_CP_QUERYVERSION) {
+        // Responds with:
+        // DINIT_RP_CVERSION, (2 byte) minimum compatible version, (2 byte) maximum compatible version
+        char replyBuf[] = { DINIT_RP_CPVERSION, 0, 0, 0, 0 };
+        if (! queuePacket(replyBuf, 1)) return;
+        rbuf.consume(1);
+        return;
+    }
+    if (pktType == DINIT_CP_FINDSERVICE || pktType == DINIT_CP_LOADSERVICE) {
+        processFindLoad(pktType);
+        return;
+    }
+    if (pktType == DINIT_CP_STARTSERVICE || pktType == DINIT_CP_STOPSERVICE) {
+        processStartStop(pktType);
+        return;
+    }
+    else if (pktType == DINIT_CP_SHUTDOWN) {
+        // Shutdown/reboot
+        if (rbuf.get_length() < 2) {
+            chklen = 2;
+            return;
+        }
+        
+        auto sd_type = static_cast<ShutdownType>(rbuf[1]);
+        
+        service_set->stop_all_services(sd_type);
+        log_to_console = true;
+        char ackBuf[] = { DINIT_RP_ACK };
+        if (! queuePacket(ackBuf, 1)) return;
+        
+        // Clear the packet from the buffer
+        rbuf.consume(2);
+        chklen = 0;
+        return;
+    }
+    else {
+        // Unrecognized: give error response
+        char outbuf[] = { DINIT_RP_BADREQ };
+        if (! queuePacket(outbuf, 1)) return;
+        bad_conn_close = true;
+        ev_io_set(&iob, iob.fd, EV_WRITE);
+    }
+    return;
+}
+
+void ControlConn::processFindLoad(int pktType)
+{
+    using std::string;
+    
+    constexpr int pkt_size = 4;
+    
+    if (rbuf.get_length() < pkt_size) {
+        chklen = pkt_size;
+        return;
+    }
+    
+    uint16_t svcSize;
+    rbuf.extract((char *)&svcSize, 1, 2);
+    chklen = svcSize + 3; // packet type + (2 byte) length + service name
+    if (svcSize <= 0 || chklen > 1024) {
+        // Queue error response / mark connection bad
+        char badreqRep[] = { DINIT_RP_BADREQ };
+        if (! queuePacket(badreqRep, 1)) return;
+        bad_conn_close = true;
+        ev_io_set(&iob, iob.fd, EV_WRITE);
+        return;
+    }
+    
+    if (rbuf.get_length() < chklen) {
+        // packet not complete yet; read more
+        return;
+    }
+    
+    ServiceRecord * record = nullptr;
+    
+    string serviceName = std::move(rbuf.extract_string(3, svcSize));
+    
+    if (pktType == DINIT_CP_LOADSERVICE) {
+        // LOADSERVICE
+        try {
+            record = service_set->loadService(serviceName);
+        }
+        catch (ServiceLoadExc &slexc) {
+            log(LogLevel::ERROR, "Could not load service ", slexc.serviceName, ": ", slexc.excDescription);
+        }
+    }
+    else {
+        // FINDSERVICE
+        record = service_set->findService(serviceName.c_str());
+    }
+    
+    if (record != nullptr) {
+        // Allocate a service handle
+        handle_t handle = allocateServiceHandle(record);
+        std::vector<char> rp_buf;
+        rp_buf.reserve(7);
+        rp_buf.push_back(DINIT_RP_SERVICERECORD);
+        rp_buf.push_back(static_cast<char>(record->getState()));
+        for (int i = 0; i < (int) sizeof(handle); i++) {
+            rp_buf.push_back(*(((char *) &handle) + i));
+        }
+        rp_buf.push_back(static_cast<char>(record->getTargetState()));
+        if (! queuePacket(std::move(rp_buf))) return;
+    }
+    else {
+        std::vector<char> rp_buf = { DINIT_RP_NOSERVICE };
+        if (! queuePacket(std::move(rp_buf))) return;
+    }
+    
+    // Clear the packet from the buffer
+    rbuf.consume(chklen);
+    chklen = 0;
+    return;
+}
+
+void ControlConn::processStartStop(int pktType)
+{
+    using std::string;
+    
+    constexpr int pkt_size = 2 + sizeof(handle_t);
+    
+    if (rbuf.get_length() < pkt_size) {
+        chklen = pkt_size;
+        return;
+    }
+    
+    // 1 byte: packet type
+    // 1 byte: pin in requested state (0 = no pin, 1 = pin)
+    // 4 bytes: service handle
+    
+    bool do_pin = (rbuf[1] == 1);
+    handle_t handle;
+    rbuf.extract((char *) &handle, 2, sizeof(handle));
+    
+    ServiceRecord *service = findServiceForKey(handle);
+    if (service == nullptr) {
+        // Service handle is bad
+        char badreqRep[] = { DINIT_RP_BADREQ };
+        if (! queuePacket(badreqRep, 1)) return;
+        bad_conn_close = true;
+        ev_io_set(&iob, iob.fd, EV_WRITE);
+        return;
+    }
+    else {
+        if (pktType == DINIT_CP_STARTSERVICE) {
+            if (do_pin) {
+                service->pinStart();
+            }
+            else {
+                service->start();
+            }
+        }
+        else {
+            if (do_pin) {
+                service->pinStop();
+            }
+            else {
+                service->stop();
+            }
+        }
+        
+        char ack_buf[] = { DINIT_RP_ACK };
+        if (! queuePacket(ack_buf, 1)) return;
+    }
+    
+    // Clear the packet from the buffer
+    rbuf.consume(pkt_size);
+    chklen = 0;
+    return;
+}
+
+ControlConn::handle_t ControlConn::allocateServiceHandle(ServiceRecord *record)
+{
+    bool is_unique = true;
+    handle_t largest_seen = 0;
+    handle_t candidate = 0;
+    for (auto p : keyServiceMap) {
+        if (p.first > largest_seen) largest_seen = p.first;
+        if (p.first == candidate) {
+            if (largest_seen == std::numeric_limits<handle_t>::max()) throw std::bad_alloc();
+            candidate = largest_seen + 1;
+        }
+        is_unique &= (p.second != record);
+    }
+    
+    keyServiceMap[candidate] = record;
+    serviceKeyMap.insert(std::make_pair(record, candidate));
+    
+    if (is_unique) {
+        record->addListener(this);
+    }
+    
+    return candidate;
+}
+
+
+bool ControlConn::queuePacket(const char *pkt, unsigned size) noexcept
+{
+    if (bad_conn_close) return false;
+
+    bool was_empty = outbuf.empty();
+
+    if (was_empty) {
+        int wr = write(iob.fd, pkt, size);
+        if (wr == -1) {
+            if (errno == EPIPE) {
+                delete this;
+                return false;
+            }
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                // TODO log error
+                delete this;
+                return false;
+            }
+        }
+        else {
+            if ((unsigned)wr == size) {
+                // Ok, all written.
+                return true;
+            }
+            pkt += wr;
+            size -= wr;
+        }
+        ev_io_set(&iob, iob.fd, EV_READ | EV_WRITE);
+    }
+    
+    // Create a vector out of the (remaining part of the) packet:
+    try {
+        outbuf.emplace_back(pkt, pkt + size);
+        return true;
+    }
+    catch (std::bad_alloc &baexc) {
+        // Mark the connection bad, and stop reading further requests
+        bad_conn_close = true;
+        oom_close = true;
+        if (was_empty) {
+            // We can't send out-of-memory response as we already wrote as much as we
+            // could above. Neither can we later send the response since we have currently
+            // sent an incomplete packet. All we can do is close the connection.
+            delete this;
+        }
+        else {
+            ev_io_set(&iob, iob.fd, EV_WRITE);
+        }
+        return false;    
+    }
+}
+
+
+bool ControlConn::queuePacket(std::vector<char> &&pkt) noexcept
+{
+    if (bad_conn_close) return false;
+
+    bool was_empty = outbuf.empty();
+    
+    if (was_empty) {
+        outpkt_index = 0;
+        // We can try sending the packet immediately:
+        int wr = write(iob.fd, pkt.data(), pkt.size());
+        if (wr == -1) {
+            if (errno == EPIPE) {
+                delete this;
+                return false;
+            }
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                // TODO log error
+                delete this;
+                return false;
+            }
+        }
+        else {
+            if ((unsigned)wr == pkt.size()) {
+                // Ok, all written.
+                return true;
+            }
+            outpkt_index = wr;
+        }
+        ev_io_set(&iob, iob.fd, EV_READ | EV_WRITE);
+    }
+    
+    try {
+        outbuf.emplace_back(pkt);
+        return true;
+    }
+    catch (std::bad_alloc &baexc) {
+        // Mark the connection bad, and stop reading further requests
+        bad_conn_close = true;
+        oom_close = true;
+        if (was_empty) {
+            // We can't send out-of-memory response as we already wrote as much as we
+            // could above. Neither can we later send the response since we have currently
+            // sent an incomplete packet. All we can do is close the connection.
+            delete this;
+        }
+        else {
+            ev_io_set(&iob, iob.fd, EV_WRITE);
+        }
+        return false;
+    }
+}
+
+bool ControlConn::rollbackComplete() noexcept
+{
+    char ackBuf[2] = { DINIT_ROLLBACK_COMPLETED, 2 };
+    return queuePacket(ackBuf, 2);
+}
+
+bool ControlConn::dataReady() noexcept
+{
+    int fd = iob.fd;
+    
+    int r = rbuf.fill(fd);
+    
+    // Note file descriptor is non-blocking
+    if (r == -1) {
+        if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
+            // TODO log error
+            delete this;
+            return true;
+        }
+        return false;
+    }
+    
+    if (r == 0) {
+        delete this;
+        return true;
+    }
+    
+    // complete packet?
+    if (rbuf.get_length() >= chklen) {
+        try {
+            processPacket();
+        }
+        catch (std::bad_alloc &baexc) {
+            doOomClose();
+        }
+    }
+    
+    if (rbuf.get_length() == 1024) {
+        // Too big packet
+        // TODO log error?
+        // TODO error response?
+        bad_conn_close = true;
+        ev_io_set(&iob, iob.fd, EV_WRITE);
+    }
+    
+    return false;
+}
+
+void ControlConn::sendData() noexcept
+{
+    if (outbuf.empty() && bad_conn_close) {
+        if (oom_close) {
+            // Send oom response
+            char oomBuf[] = { DINIT_RP_OOM };
+            write(iob.fd, oomBuf, 1);
+        }
+        delete this;
+        return;
+    }
+    
+    vector<char> & pkt = outbuf.front();
+    char *data = pkt.data();
+    int written = write(iob.fd, data + outpkt_index, pkt.size() - outpkt_index);
+    if (written == -1) {
+        if (errno == EPIPE) {
+            // read end closed
+            delete this;
+        }
+        else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // spurious readiness notification?
+        }
+        else {
+            log(LogLevel::ERROR, "Error writing to control connection: ", strerror(errno));
+            delete this;
+        }
+        return;
+    }
+
+    outpkt_index += written;
+    if (outpkt_index == pkt.size()) {
+        // We've finished this packet, move on to the next:
+        outbuf.pop_front();
+        outpkt_index = 0;
+        if (outbuf.empty() && ! oom_close) {
+            if (! bad_conn_close) {
+                ev_io_set(&iob, iob.fd, EV_READ);
+            }
+            else {
+                delete this;
+            }
+        }
+    }
+}
+
+ControlConn::~ControlConn() noexcept
+{
+    close(iob.fd);
+    ev_io_stop(loop, &iob);
+    
+    // Clear service listeners
+    for (auto p : serviceKeyMap) {
+        p.first->removeListener(this);
+    }
+    
+    active_control_conns--;
+}
diff --git a/src/control.h b/src/control.h
new file mode 100644 (file)
index 0000000..0bce51c
--- /dev/null
@@ -0,0 +1,184 @@
+#ifndef DINIT_CONTROL_H
+#define DINIT_CONTROL_H
+
+#include <list>
+#include <vector>
+#include <unordered_map>
+#include <limits>
+
+#include <unistd.h>
+#include <ev++.h>
+
+#include "dinit-log.h"
+#include "control-cmds.h"
+#include "service-listener.h"
+#include "cpbuffer.h"
+
+// Control connection for dinit
+
+// TODO: Use the input buffer as a circular buffer, instead of chomping data from
+// the front using a data move.
+
+// forward-declaration of callback:
+static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents);
+
+class ControlConn;
+
+// Pointer to the control connection that is listening for rollback completion
+extern ControlConn * 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 ServiceSet;
+class ServiceRecord;
+
+class ControlConn : private ServiceListener
+{
+    friend void control_conn_cb(struct ev_loop *, ev_io *, int);
+    
+    struct ev_io iob;
+    struct ev_loop *loop;
+    ServiceSet *service_set;
+    
+    bool bad_conn_close; // close when finished output?
+    bool oom_close;      // send final 'out of memory' indicator
+
+    // The packet length before we need to re-check if the packet is complete.
+    // processPacket() will not be called until the packet reaches this size.
+    int chklen;
+    
+    // Receive buffer
+    CPBuffer 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<ServiceRecord *, handle_t> serviceKeyMap;
+    std::unordered_map<handle_t, ServiceRecord *> 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:  true if the packet was successfully queued, false if otherwise
+    //            (eg if out of memory); in the latter case the connection might
+    //            no longer be valid (iff there are no outgoing packets queued).
+    bool queuePacket(vector<char> &&v) noexcept;
+    bool queuePacket(const char *pkt, unsigned size) noexcept;
+
+    // Process a packet. Can cause the ControlConn to be deleted iff there are no
+    // outgoing packets queued.
+    // Throws:
+    //    std::bad_alloc - if an out-of-memory condition prevents processing
+    void processPacket();
+    
+    // Process a STARTSERVICE/STOPSERVICE packet. May throw std::bad_alloc.
+    void processStartStop(int pktType);
+    
+    // Process a FINDSERVICE/LOADSERVICE packet. May throw std::bad_alloc.
+    void processFindLoad(int pktType);
+
+    // Notify that data is ready to be read from the socket. Returns true in cases where the
+    // connection was deleted with potentially pending outgoing packets.
+    bool dataReady() noexcept;
+    
+    void sendData() noexcept;
+    
+    // Allocate a new handle for a service; may throw std::bad_alloc
+    handle_t allocateServiceHandle(ServiceRecord *record);
+    
+    ServiceRecord *findServiceForKey(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 doOomClose()
+    {
+        bad_conn_close = true;
+        oom_close = true;
+        ev_io_set(&iob, iob.fd, EV_WRITE);
+    }
+    
+    // Process service event broadcast.
+    void serviceEvent(ServiceRecord * service, ServiceEvent 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));
+                queuePacket(std::move(pkt));
+                ++i;
+            }
+        }
+        catch (std::bad_alloc &exc) {
+            doOomClose();
+        }
+    }
+    
+    public:
+    ControlConn(struct ev_loop * loop, ServiceSet * service_set, int fd) : loop(loop), service_set(service_set), chklen(0)
+    {
+        ev_io_init(&iob, control_conn_cb, fd, EV_READ);
+        iob.data = this;
+        ev_io_start(loop, &iob);
+        
+        active_control_conns++;
+    }
+    
+    bool rollbackComplete() noexcept;
+        
+    virtual ~ControlConn() noexcept;
+};
+
+
+static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents)
+{
+    ControlConn *conn = (ControlConn *) w->data;
+    if (revents & EV_READ) {
+        if (conn->dataReady()) {
+            // ControlConn was deleted
+            return;
+        }
+    }
+    if (revents & EV_WRITE) {
+        conn->sendData();
+    }    
+}
+
+#endif
diff --git a/src/cpbuffer.h b/src/cpbuffer.h
new file mode 100644 (file)
index 0000000..251ef6a
--- /dev/null
@@ -0,0 +1,89 @@
+#ifndef CPBUFFER_H
+#define CPBUFFER_H
+
+#include <cstring>
+
+// control protocol buffer, a circular buffer with 1024-byte capacity.
+class CPBuffer
+{
+    char buf[1024];
+    int cur_idx = 0;
+    int length = 0;  // number of elements in the buffer
+    
+    public:
+    int get_length() noexcept
+    {
+        return length;
+    }
+    
+    // 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 >= 1024) pos -= 1024;
+        int max_count = std::min(1024 - pos, 1024 - length);
+        ssize_t r = read(fd, buf + cur_idx, max_count);
+        if (r >= 0) {
+            length += r;
+        }
+        return r;
+    }
+    
+    // fill by readin 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 fillTo(int fd, int rlength) noexcept
+    {
+        while (length < rlength) {
+            int r = fill(fd);
+            if (r <= 0) return r;
+        }
+        return 1;
+    }
+    
+    int operator[](int idx) noexcept
+    {
+        int dest_idx = cur_idx + idx;
+        if (dest_idx > 1024) dest_idx -= 1024;
+        return buf[dest_idx];
+    }
+    
+    void consume(int amount) noexcept
+    {
+        cur_idx += amount;
+        if (cur_idx >= 1024) cur_idx -= 1024;
+        length -= amount;
+    }
+    
+    void extract(char *dest, int index, int length) noexcept
+    {
+        index += cur_idx;
+        if (index >= 1024) index -= 1024;
+        if (index + length > 1024) {
+            // wrap-around copy
+            int half = 1024 - index;
+            std::memcpy(dest, buf + index, half);
+            std::memcpy(dest + half, buf, length - half);
+        }
+        else {
+            std::memcpy(dest, buf + index, length);
+        }
+    }
+    
+    // Extract string of give length from given index
+    // Throws:  std::bad_alloc on allocation failure
+    std::string extract_string(int index, int length)
+    {
+        index += cur_idx;
+        if (index >= 1024) index -= 1024;
+        if (index + length > 1024) {
+            std::string r(buf + index, 1024 - index);
+            r.insert(r.end(), buf, buf + length - (1024 - index));
+            return r;
+        }
+        else {
+            return std::string(buf + index, length);
+        }
+    }
+};
+
+#endif
diff --git a/src/dinit-log.cc b/src/dinit-log.cc
new file mode 100644 (file)
index 0000000..8e031e7
--- /dev/null
@@ -0,0 +1,68 @@
+#include <iostream>
+#include "dinit-log.h"
+
+LogLevel log_level = LogLevel::WARN;
+bool log_to_console = true;    // whether we should output log messages to console
+bool log_current_line;
+
+// Log a message
+void log(LogLevel lvl, const char *msg) noexcept
+{
+    if (lvl >= log_level) {
+        if (log_to_console) {
+            std::cout << "dinit: " << msg << std::endl;
+        }
+    }
+}
+
+// Log a multi-part message beginning
+void logMsgBegin(LogLevel lvl, const char *msg) noexcept
+{
+    log_current_line = lvl >= log_level;
+    if (log_current_line) {
+        if (log_to_console) {
+            std::cout << "dinit: " << msg;
+        }
+    }
+}
+
+// Continue a multi-part log message
+void logMsgPart(const char *msg) noexcept
+{
+    if (log_current_line) {
+        if (log_to_console) {
+            std::cout << msg;
+        }
+    }
+}
+
+// Complete a multi-part log message
+void logMsgEnd(const char *msg) noexcept
+{
+    if (log_current_line) {
+        if (log_to_console) {
+            std::cout << msg << std::endl;
+        }
+    }
+}
+
+void logServiceStarted(const char *service_name) noexcept
+{
+    if (log_to_console) {
+        std::cout << "[  OK  ] " << service_name << std::endl;
+    }
+}
+
+void logServiceFailed(const char *service_name) noexcept
+{
+    if (log_to_console) {
+        std::cout << "[FAILED] " << service_name << std::endl;
+    }
+}
+
+void logServiceStopped(const char *service_name) noexcept
+{
+    if (log_to_console) {
+        std::cout << "[STOPPED] " << service_name << std::endl;
+    }
+}
diff --git a/src/dinit-log.h b/src/dinit-log.h
new file mode 100644 (file)
index 0000000..b54664e
--- /dev/null
@@ -0,0 +1,112 @@
+#ifndef DINIT_LOG_H
+#define DINIT_LOG_H
+
+// Logging for Dinit
+
+#include <string>
+#include <cstdio>
+#include <climits>
+
+enum class LogLevel {
+    DEBUG,
+    INFO,
+    WARN,
+    ERROR,
+    ZERO    // log absolutely nothing
+};
+
+extern LogLevel log_level;
+extern bool log_to_console;
+
+void log(LogLevel lvl, const char *msg) noexcept;
+void logMsgBegin(LogLevel lvl, const char *msg) noexcept;
+void logMsgPart(const char *msg) noexcept;
+void logMsgEnd(const char *msg) noexcept;
+void logServiceStarted(const char *service_name) noexcept;
+void logServiceFailed(const char *service_name) noexcept;
+void logServiceStopped(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 lvl, const std::string &str) noexcept
+{
+    log(lvl, str.c_str());
+}
+
+static inline void logMsgBegin(LogLevel lvl, const std::string &str) noexcept
+{
+    logMsgBegin(lvl, str.c_str());
+}
+
+static inline void logMsgBegin(LogLevel lvl, int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    logMsgBegin(lvl, nbuf);
+}
+
+static inline void logMsgPart(const std::string &str) noexcept
+{
+    logMsgPart(str.c_str());
+}
+
+static inline void logMsgPart(int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    logMsgPart(nbuf);
+}
+
+static inline void logMsgEnd(const std::string &str) noexcept
+{
+    logMsgEnd(str.c_str());
+}
+
+static inline void logMsgEnd(int a) noexcept
+{
+    constexpr int bufsz = (CHAR_BIT * sizeof(int) - 1) / 3 + 2;
+    char nbuf[bufsz];
+    snprintf(nbuf, bufsz, "%d", a);
+    logMsgEnd(nbuf);
+}
+
+static inline void logServiceStarted(const std::string &str) noexcept
+{
+    logServiceStarted(str.c_str());
+}
+
+static inline void logServiceFailed(const std::string &str) noexcept
+{
+    logServiceFailed(str.c_str());
+}
+
+static inline void logServiceStopped(const std::string &str) noexcept
+{
+    logServiceStopped(str.c_str());
+}
+
+// It's not intended that methods in this namespace be called directly:
+namespace dinit_log {
+    template <typename A> static inline void logParts(A a) noexcept
+    {
+        logMsgEnd(a);
+    }
+
+    template <typename A, typename ...B> static inline void logParts(A a, B... b) noexcept
+    {
+        logMsgPart(a);
+        logParts(b...);
+    }
+}
+
+// Variadic 'log' method.
+template <typename A, typename ...B> static inline void log(LogLevel lvl, A a, B ...b) noexcept
+{
+    logMsgBegin(lvl, a);
+    dinit_log::logParts(b...);
+}
+
+#endif
diff --git a/src/dinit-start.cc b/src/dinit-start.cc
new file mode 100644 (file)
index 0000000..3e6a9e7
--- /dev/null
@@ -0,0 +1,253 @@
+#include <cstdio>
+#include <cstddef>
+#include <cstring>
+#include <string>
+#include <iostream>
+#include <system_error>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <pwd.h>
+
+#include "control-cmds.h"
+#include "service-constants.h"
+#include "cpbuffer.h"
+
+// dinit-start:  utility to start a dinit service
+
+// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
+
+using handle_t = uint32_t;
+
+
+class ReadCPException
+{
+    public:
+    int errcode;
+    ReadCPException(int err) : errcode(err) { }
+};
+
+static void fillBufferTo(CPBuffer *buf, int fd, int rlength)
+{
+    int r = buf->fillTo(fd, rlength);
+    if (r == -1) {
+        throw ReadCPException(errno);
+    }
+    else if (r == 0) {
+        throw ReadCPException(0);
+    }
+}
+
+
+int main(int argc, char **argv)
+{
+    using namespace std;
+    
+    bool show_help = argc < 2;
+    char *service_name = nullptr;
+    
+    std::string control_socket_str;
+    const char * control_socket_path = nullptr;
+    
+    bool verbose = true;
+    bool sys_dinit = false;  // communicate with system daemon
+    bool wait_for_service = true;
+        
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            if (strcmp(argv[i], "--help") == 0) {
+                show_help = true;
+                break;
+            }
+            else if (strcmp(argv[i], "--no-wait") == 0) {
+                wait_for_service = false;
+            }
+            else if (strcmp(argv[i], "--quiet") == 0) {
+                verbose = false;
+            }
+            else if (strcmp(argv[i], "--system") == 0 || strcmp(argv[i], "-s") == 0) {
+                sys_dinit = true;
+            }
+            else {
+                cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
+                return 1;
+            }
+        }
+        else {
+            // service name
+            service_name = argv[i];
+            // TODO support multiple services (or at least give error if multiple
+            //      services supplied)
+        }
+    }
+
+    if (show_help) {
+        cout << "dinit-start:   start a dinit service" << endl;
+        cout << "  --help           : show this help" << endl;
+        cout << "  --no-wait        : don't wait for service startup/shutdown to complete" << endl;
+        cout << "  --quiet          : suppress output (except errors)" << endl;
+        cout << "  -s, --system     : control system daemon instead of user daemon" << endl;
+        cout << "  <service-name>   : start the named service" << endl;
+        return 1;
+    }
+    
+    
+    control_socket_path = "/dev/dinitctl";
+    
+    if (! sys_dinit) {
+        char * userhome = getenv("HOME");
+        if (userhome == nullptr) {
+            struct passwd * pwuid_p = getpwuid(getuid());
+            if (pwuid_p != nullptr) {
+                userhome = pwuid_p->pw_dir;
+            }
+        }
+        
+        if (userhome != nullptr) {
+            control_socket_str = userhome;
+            control_socket_str += "/.dinitctl";
+            control_socket_path = control_socket_str.c_str();
+        }
+        else {
+            cerr << "Cannot locate user home directory (set HOME or check /etc/passwd file)" << endl;
+            return 1;
+        }
+    }
+    
+    int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (socknum == -1) {
+        perror("socket");
+        return 1;
+    }
+
+    struct sockaddr_un * name;
+    uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(control_socket_path) + 1;
+    name = (struct sockaddr_un *) malloc(sockaddr_size);
+    if (name == nullptr) {
+        cerr << "dinit-start: out of memory" << endl;
+        return 1;
+    }
+    
+    name->sun_family = AF_UNIX;
+    strcpy(name->sun_path, control_socket_path);
+    
+    int connr = connect(socknum, (struct sockaddr *) name, sockaddr_size);
+    if (connr == -1) {
+        perror("connect");
+        return 1;
+    }
+    
+    // TODO should start by querying protocol version
+    
+    // Build buffer;
+    uint16_t sname_len = strlen(service_name);
+    int bufsize = 3 + sname_len;
+    char * buf = new char[bufsize];
+    
+    buf[0] = DINIT_CP_LOADSERVICE;
+    memcpy(buf + 1, &sname_len, 2);
+    memcpy(buf + 3, service_name, sname_len);
+    
+    int r = write(socknum, buf, bufsize);
+    // TODO make sure we write it all
+    delete [] buf;
+    if (r == -1) {
+        perror("write");
+        return 1;
+    }
+    
+    // Now we expect a reply:
+    // NOTE: should skip over information packets.
+    
+    try {
+        CPBuffer rbuffer;
+        fillBufferTo(&rbuffer, socknum, 1);
+        
+        ServiceState state;
+        ServiceState target_state;
+        handle_t handle;
+        
+        if (rbuffer[0] == DINIT_RP_SERVICERECORD) {
+            fillBufferTo(&rbuffer, socknum, 2 + sizeof(handle));
+            rbuffer.extract((char *) &handle, 2, sizeof(handle));
+            state = static_cast<ServiceState>(rbuffer[1]);
+            target_state = static_cast<ServiceState>(rbuffer[2 + sizeof(handle)]);
+            rbuffer.consume(3 + sizeof(handle));
+        }
+        else if (rbuffer[0] == DINIT_RP_NOSERVICE) {
+            cerr << "Failed to find/load service." << endl;
+            return 1;
+        }
+        else {
+            cerr << "Protocol error." << endl;
+            return 1;
+        }
+        
+        // Need to issue STARTSERVICE:
+        if (target_state != ServiceState::STARTED) {
+            buf = new char[2 + sizeof(handle)];
+            buf[0] = DINIT_CP_STARTSERVICE;
+            buf[1] = 0;  // don't pin
+            memcpy(buf + 2, &handle, sizeof(handle));
+            r = write(socknum, buf, 2 + sizeof(handle));
+            delete buf;
+        }
+        
+        if (state == ServiceState::STARTED) {
+            if (verbose) {
+                cout << "Service already started." << endl;
+            }
+            return 0; // success!
+        }
+        
+        if (! wait_for_service) {
+            return 0;
+        }
+        
+        // Wait until service started:
+        r = rbuffer.fillTo(socknum, 2);
+        while (r > 0) {
+            if (rbuffer[0] >= 100) {
+                int pktlen = (unsigned char) rbuffer[1];
+                fillBufferTo(&rbuffer, socknum, pktlen);
+                
+                if (rbuffer[0] == DINIT_IP_SERVICEEVENT) {
+                    handle_t ev_handle;
+                    rbuffer.extract((char *) &ev_handle, 2, sizeof(ev_handle));
+                    ServiceEvent event = static_cast<ServiceEvent>(rbuffer[2 + sizeof(ev_handle)]);
+                    if (ev_handle == handle && event == ServiceEvent::STARTED) {
+                        if (verbose) {
+                            cout << "Service started." << endl;
+                        }
+                        return 0;
+                    }
+                }
+            }
+            else {
+                // Not an information packet?
+                cerr << "protocol error" << endl;
+                return 1;
+            }
+        }
+        
+        if (r == -1) {
+            perror("read");
+        }
+        else {
+            cerr << "protocol error (connection closed by server)" << endl;
+        }
+        return 1;
+    }
+    catch (ReadCPException &exc) {
+        cerr << "control socket read failure or protocol error" << endl;
+        return 1;
+    }
+    catch (std::bad_alloc &exc) {
+        cerr << "out of memory" << endl;
+        return 1;
+    }
+    
+    return 0;
+}
diff --git a/src/dinit.cc b/src/dinit.cc
new file mode 100644 (file)
index 0000000..9b26b3a
--- /dev/null
@@ -0,0 +1,429 @@
+#include <iostream>
+#include <list>
+#include <cstring>
+#include <csignal>
+#include <cstddef>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pwd.h>
+
+#include "service.h"
+#include "ev++.h"
+#include "control.h"
+#include "dinit-log.h"
+
+#ifdef __linux__
+#include <sys/klog.h>
+#include <sys/reboot.h>
+#endif
+
+/*
+ * "simpleinit" from util-linux-ng package handles signals as follows:
+ * SIGTSTP - spawn no more gettys (in preparation for shutdown etc).
+ *           In dinit terms this should probably mean "no more auto restarts"
+ *           (for any service). (Actually the signal acts as a toggle, if
+ *           respawn is disabled it will be re-enabled and init will
+ *           act as if SIGHUP had also been sent)
+ * SIGTERM - kill spawned gettys (which are still alive)
+ *           Interestingly, simpleinit just sends a SIGTERM to the gettys,
+ *           which will not normall kill shells (eg bash ignores SIGTERM).
+ * "/sbin/initctl -r" - rollback services (ran by "shutdown"/halt etc);
+ *           shouldn't return until all services have been stopped.
+ *           shutdown calls this after sending SIGTERM to processes running
+ *           with uid >= 100 ("mortals").
+ * SIGQUIT - init will exec() shutdown. shutdown will detect that it is
+ *           running as pid 1 and will just loop and reap child processes.
+ *           This is used by shutdown so that init will not hang on to its
+ *           inode, allowing the filesystem to be re-mounted readonly
+ *           (this is only an issue if the init binary has been unlinked,
+ *           since it's then holding an inode which can't be maintained
+ *           when the filesystem is unmounted).
+ *
+ * Not sent by shutdown:
+ * SIGHUP -  re-read inittab and spawn any new getty entries
+ * SIGINT - (ctrl+alt+del handler) - fork & exec "reboot"
+ * 
+ * On the contrary dinit currently uses:
+ * SIGTERM - roll back services and then fork/exec /sbin/halt
+ * SIGINT - roll back services and then fork/exec /sbin/reboot
+ * SIGQUIT - exec() /sbin/shutdown as per above.
+ *
+ * It's an open question about whether dinit should roll back services *before*
+ * running halt/reboot, since those commands should prompt rollback of services
+ * anyway. But it seems safe to do so.
+ */
+
+
+static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents);
+static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents);
+static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents);
+void open_control_socket(struct ev_loop *loop) noexcept;
+void close_control_socket(struct ev_loop *loop) noexcept;
+
+struct ev_io control_socket_io;
+
+
+// Variables
+
+static ServiceSet *service_set;
+
+static bool am_system_init = false; // true if we are the system init process
+
+static bool control_socket_open = false;
+int active_control_conns = 0;
+
+static const char *control_socket_path = "/dev/dinitctl";
+static std::string control_socket_str;
+
+
+int main(int argc, char **argv)
+{
+    using namespace std;
+    
+    am_system_init = (getpid() == 1);
+    
+    if (am_system_init) {
+        // setup STDIN, STDOUT, STDERR so that we can use them
+        int onefd = open("/dev/console", O_RDONLY, 0);
+        dup2(onefd, 0);
+        int twofd = open("/dev/console", O_RDWR, 0);
+        dup2(twofd, 1);
+        dup2(twofd, 2);
+    }
+    
+    /* Set up signal handlers etc */
+    /* SIG_CHILD is ignored by default: good */
+    /* sigemptyset(&sigwait_set); */
+    /* sigaddset(&sigwait_set, SIGCHLD); */
+    /* sigaddset(&sigwait_set, SIGINT); */
+    /* sigaddset(&sigwait_set, SIGTERM); */
+    /* sigprocmask(SIG_BLOCK, &sigwait_set, NULL); */
+    
+    // Terminal access control signals - we block these so that dinit can't be
+    // suspended if it writes to the terminal after some other process has claimed
+    // ownership of it.
+    signal(SIGTSTP, SIG_IGN);
+    signal(SIGTTIN, SIG_IGN);
+    signal(SIGTTOU, SIG_IGN);
+    
+    /* list of services to start */
+    list<const char *> services_to_start;
+    
+    if (! am_system_init) {
+        char * userhome = getenv("HOME");
+        if (userhome == nullptr) {
+            struct passwd * pwuid_p = getpwuid(getuid());
+            if (pwuid_p != nullptr) {
+                userhome = pwuid_p->pw_dir;
+            }
+        }
+        
+        if (userhome != nullptr) {
+            control_socket_str = userhome;
+            control_socket_str += "/.dinitctl";
+            control_socket_path = control_socket_str.c_str();
+        }
+    }
+    
+    /* service directory name */
+    const char * service_dir = "/etc/dinit.d";
+    
+    // Arguments, if given, specify a list of services to start.
+    // If we are running as init (PID=1), the kernel gives us any command line
+    // arguments it was given but didn't recognize, including "single" (usually
+    // for "boot to single user mode" aka just start the shell). We can treat
+    // them as service names. In the worst case we can't find any of the named
+    // services, and so we'll start the "boot" service by default.
+    if (argc > 1) {
+      for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            // An option...
+            if (strcmp(argv[i], "--services-dir") == 0 ||
+                    strcmp(argv[i], "-d") == 0) {
+                ++i;
+                if (i < argc) {
+                    service_dir = argv[i];
+                }
+                else {
+                    cerr << "dinit: '--services-dir' (-d) requires an argument" << endl;
+                    return 1;
+                }
+            }
+            else if (strcmp(argv[i], "--help") == 0) {
+                cout << "dinit, an init with dependency management" << endl;
+                cout << " --help                         : display help" << endl;
+                cout << " --services-dir <dir>, -d <dir> : set base directory for service description files (-d <dir>)" << endl;
+                cout << " <service-name>                 : start service with name <service-name>" << endl;
+                return 0;
+            }
+            else {
+                // unrecognized
+                if (! am_system_init) {
+                    cerr << "dinit: Unrecognized option: " << argv[i] << endl;
+                    return 1;
+                }
+            }
+        }
+        else {
+            // LILO puts "auto" on the kernel command line for unattended boots; we'll filter it.
+            if (! am_system_init || strcmp(argv[i], "auto") != 0) {
+                services_to_start.push_back(argv[i]);
+            }
+        }
+      }
+    }
+    
+    if (services_to_start.empty()) {
+        services_to_start.push_back("boot");
+    }
+
+    // Set up signal handlers
+    ev_signal sigint_ev_signal;
+    if (am_system_init) {
+      ev_signal_init(&sigint_ev_signal, sigint_reboot_cb, SIGINT);
+    }
+    else {
+      ev_signal_init(&sigint_ev_signal, sigterm_cb, SIGINT);
+    }
+    
+    ev_signal sigquit_ev_signal;
+    if (am_system_init) {
+        // PID 1: SIGQUIT exec's shutdown
+        ev_signal_init(&sigquit_ev_signal, sigquit_cb, SIGQUIT);
+    }
+    else {
+        // Otherwise: SIGQUIT terminates dinit
+        ev_signal_init(&sigquit_ev_signal, sigterm_cb, SIGQUIT);
+    }
+    
+    ev_signal sigterm_ev_signal;
+    ev_signal_init(&sigterm_ev_signal, sigterm_cb, SIGTERM);
+    
+    /* Set up libev */
+    struct ev_loop *loop = ev_default_loop(EVFLAG_AUTO /* | EVFLAG_SIGNALFD */);
+    ev_signal_start(loop, &sigint_ev_signal);
+    ev_signal_start(loop, &sigquit_ev_signal);
+    ev_signal_start(loop, &sigterm_ev_signal);
+
+    // Try to open control socket (may fail due to readonly filesystem)
+    open_control_socket(loop);
+    
+#ifdef __linux__
+    if (am_system_init) {
+        // Disable non-critical kernel output to console
+        klogctl(6 /* SYSLOG_ACTION_CONSOLE_OFF */, nullptr, 0);
+        // Make ctrl+alt+del combination send SIGINT to PID 1 (this process)
+        reboot(RB_DISABLE_CAD);
+    }
+#endif
+    
+    /* start requested services */
+    service_set = new ServiceSet(service_dir);
+    for (list<const char *>::iterator i = services_to_start.begin();
+            i != services_to_start.end();
+            ++i) {
+        try {
+            service_set->startService(*i);
+        }
+        catch (ServiceNotFound &snf) {
+            log(LogLevel::ERROR, snf.serviceName, ": Could not find service description.");
+        }
+        catch (ServiceLoadExc &sle) {
+            log(LogLevel::ERROR, sle.serviceName, ": ", sle.excDescription);
+        }
+        catch (std::bad_alloc &badalloce) {
+            log(LogLevel::ERROR, "Out of memory when trying to start service: ", *i, ".");
+        }
+    }
+    
+    event_loop:
+    
+    // Process events until all services have terminated.
+    while (service_set->count_active_services() != 0) {
+        ev_loop(loop, EVLOOP_ONESHOT);
+    }
+
+    ShutdownType shutdown_type = service_set->getShutdownType();
+    
+    if (am_system_init) {
+        logMsgBegin(LogLevel::INFO, "No more active services.");
+        
+        if (shutdown_type == ShutdownType::REBOOT) {
+            logMsgEnd(" Will reboot.");
+        }
+        else if (shutdown_type == ShutdownType::HALT) {
+            logMsgEnd(" Will halt.");
+        }
+        else if (shutdown_type == ShutdownType::POWEROFF) {
+            logMsgEnd(" Will power down.");
+        }
+        else {
+            logMsgEnd(" Re-initiating boot sequence.");
+        }
+    }
+    
+    close_control_socket(ev_default_loop(EVFLAG_AUTO));
+    
+    if (am_system_init) {
+        if (shutdown_type == ShutdownType::CONTINUE) {
+            // It could be that we started in single user mode, and the
+            // user has now exited the shell. We'll try and re-start the
+            // boot process...
+            try {
+                service_set->startService("boot");
+                goto event_loop; // yes, the "evil" goto
+            }
+            catch (...) {
+                // Now WTF do we do? try to reboot
+                log(LogLevel::ERROR, "Could not start 'boot' service; rebooting.");
+                shutdown_type = ShutdownType::REBOOT;
+            }
+        }
+        
+        const char * cmd_arg;
+        if (shutdown_type == ShutdownType::HALT) {
+            cmd_arg = "-h";
+        }
+        else if (shutdown_type == ShutdownType::REBOOT) {
+            cmd_arg = "-r";
+        }
+        else {
+            // power off.
+            cmd_arg = "-p";
+        }
+        
+        // Fork and execute dinit-reboot.
+        execl("/sbin/shutdown", "/sbin/shutdown", "--system", cmd_arg, nullptr);
+        log(LogLevel::ERROR, "Could not execute /sbin/shutdown: ", strerror(errno));
+        
+        // PID 1 must not actually exit, although we should never reach this point:
+        while (true) {
+            ev_loop(loop, EVLOOP_ONESHOT);
+        }
+    }
+    
+    return 0;
+}
+
+// Callback for control socket
+static void control_socket_cb(struct ev_loop *loop, ev_io *w, int revents)
+{
+    // TODO limit the number of active connections. Keep a tally, and disable the
+    // control connection listening socket watcher if it gets high, and re-enable
+    // it once it falls below the maximum.
+
+    // Accept a connection
+    int sockfd = w->fd;
+
+    int newfd = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
+
+    if (newfd != -1) {
+        try {
+            new ControlConn(loop, service_set, newfd);  // will delete itself when it's finished
+        }
+        catch (std::bad_alloc &bad_alloc_exc) {
+            log(LogLevel::ERROR, "Accepting control connection: Out of memory");
+            close(newfd);
+        }
+    }
+}
+
+void open_control_socket(struct ev_loop *loop) noexcept
+{
+    if (! control_socket_open) {
+        const char * saddrname = control_socket_path;
+        uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(saddrname) + 1;
+        
+        struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+        if (name == nullptr) {
+            log(LogLevel::ERROR, "Opening control socket: out of memory");
+            return;
+        }
+
+        if (am_system_init) {
+            // Unlink any stale control socket file, but only if we are system init, since otherwise
+            // the 'stale' file may not be stale at all:
+            unlink(saddrname);
+        }
+
+        name->sun_family = AF_UNIX;
+        strcpy(name->sun_path, saddrname);
+
+        int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+        if (sockfd == -1) {
+            log(LogLevel::ERROR, "Error creating control socket: ", strerror(errno));
+            free(name);
+            return;
+        }
+
+        if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
+            log(LogLevel::ERROR, "Error binding control socket: ", strerror(errno));
+            close(sockfd);
+            free(name);
+            return;
+        }
+        
+        free(name);
+
+        // No connections can be made until we listen, so it is fine to change the permissions now
+        // (and anyway there is no way to atomically create the socket and set permissions):
+        if (chmod(saddrname, S_IRUSR | S_IWUSR) == -1) {
+            log(LogLevel::ERROR, "Error setting control socket permissions: ", strerror(errno));
+            close(sockfd);
+            return;
+        }
+
+        if (listen(sockfd, 10) == -1) {
+            log(LogLevel::ERROR, "Error listening on control socket: ", strerror(errno));
+            close(sockfd);
+            return;
+        }
+
+        control_socket_open = true;
+        ev_io_init(&control_socket_io, control_socket_cb, sockfd, EV_READ);
+        ev_io_start(loop, &control_socket_io);
+    }
+}
+
+void close_control_socket(struct ev_loop *loop) noexcept
+{
+    if (control_socket_open) {
+        int fd = control_socket_io.fd;
+        ev_io_stop(loop, &control_socket_io);
+        close(fd);
+        
+        // Unlink the socket:
+        unlink(control_socket_path);
+    }
+}
+
+/* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
+static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents)
+{
+    log_to_console = true;
+    service_set->stop_all_services(ShutdownType::REBOOT);
+}
+
+/* handle SIGQUIT (if we are system init) */
+static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents)
+{
+    // This allows remounting the filesystem read-only if the dinit binary has been
+    // unlinked. In that case the kernel holds the binary open, so that it can't be
+    // properly removed.
+    close_control_socket(ev_default_loop(EVFLAG_AUTO));
+    execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
+    log(LogLevel::ERROR, "Error executing /sbin/shutdown: ", strerror(errno));
+}
+
+/* handle SIGTERM/SIGQUIT - stop all services (not used for system daemon) */
+static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents)
+{
+    log_to_console = true;
+    service_set->stop_all_services();
+}
diff --git a/src/halt b/src/halt
new file mode 100755 (executable)
index 0000000..4987aff
--- /dev/null
+++ b/src/halt
@@ -0,0 +1,3 @@
+#!/bin/sh
+# "halt" command actually executes the more useful "power off".
+shutdown -p "$@"
diff --git a/src/load_service.cc b/src/load_service.cc
new file mode 100644 (file)
index 0000000..79972ff
--- /dev/null
@@ -0,0 +1,507 @@
+#include <algorithm>
+#include <string>
+#include <fstream>
+#include <locale>
+#include <iostream>
+#include <limits>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "service.h"
+
+typedef std::string string;
+typedef std::string::iterator string_iterator;
+
+// Utility function to skip white space. Returns an iterator at the
+// first non-white-space position (or at end).
+static string_iterator skipws(string_iterator i, string_iterator end)
+{
+    using std::locale;
+    using std::isspace;
+    
+    while (i != end) {
+      if (! isspace(*i, locale::classic())) {
+        break;
+      }
+      ++i;
+    }
+    return i;
+}
+
+// Read a setting name.
+static string read_setting_name(string_iterator & i, string_iterator end)
+{
+    using std::locale;
+    using std::ctype;
+    using std::use_facet;
+    
+    const ctype<char> & facet = use_facet<ctype<char> >(locale::classic());
+
+    string rval;
+    // Allow alphabetical characters, and dash (-) in setting name
+    while (i != end && (*i == '-' || facet.is(ctype<char>::alpha, *i))) {
+        rval += *i;
+        ++i;
+    }
+    return rval;
+}
+
+// Read a setting value
+//
+// In general a setting value is a single-line string. It may contain multiple parts
+// separated by white space (which is normally collapsed). A hash mark - # - denotes
+// the end of the value and the beginning of a comment (it should be preceded by
+// whitespace).
+//
+// Part of a value may be quoted using double quote marks, which prevents collapse
+// of whitespace and interpretation of most special characters (the quote marks will
+// not be considered part of the value). A backslash can precede a character (such
+// as '#' or '"' or another backslash) to remove its special meaning. Newline
+// characters are not allowed in values and cannot be quoted.
+//
+// This function expects the string to be in an ASCII-compatible, single byte
+// encoding (the "classic" locale).
+//
+// Params:
+//    i  -  reference to string iterator through the line
+//    end -   iterator at end of line
+//    part_positions -  list of <int,int> to which the position of each setting value
+//                      part will be added as [start,end). May be null.
+static string read_setting_value(string_iterator & i, string_iterator end,
+        std::list<std::pair<unsigned,unsigned>> * part_positions = nullptr)
+{
+    using std::locale;
+    using std::isspace;
+
+    i = skipws(i, end);
+    
+    string rval;
+    bool new_part = true;
+    int part_start;
+    
+    while (i != end) {
+        char c = *i;
+        if (c == '\"') {
+            if (new_part) {
+                part_start = rval.length();
+                new_part = false;
+            }
+            // quoted string
+            ++i;
+            while (i != end) {
+                c = *i;
+                if (c == '\"') break;
+                if (c == '\n') {
+                    // TODO error here.
+                }
+                else if (c == '\\') {
+                    // A backslash escapes the following character.
+                    ++i;
+                    if (i != end) {
+                        c = *i;
+                        if (c == '\n') {
+                            // TODO error here.
+                        }
+                        rval += c;
+                    }
+                }
+                else {
+                    rval += c;
+                }
+                ++i;
+            }
+            if (i == end) {
+                // String wasn't terminated
+                // TODO error here
+                break;
+            }
+        }
+        else if (c == '\\') {
+            if (new_part) {
+                part_start = rval.length();
+                new_part = false;
+            }
+            // A backslash escapes the next character
+            ++i;
+            if (i != end) {
+                rval += *i;
+            }
+            else {
+                // TODO error here
+            }
+        }
+        else if (isspace(c, locale::classic())) {
+            if (! new_part && part_positions != nullptr) {
+                part_positions->emplace_back(part_start, rval.length());
+                new_part = true;
+            }
+            i = skipws(i, end);
+            if (i == end) break;
+            if (*i == '#') break; // comment
+            rval += ' ';  // collapse ws to a single space
+            continue;
+        }
+        else if (c == '#') {
+            // hmm... comment? Probably, though they should have put a space
+            // before it really. TODO throw an exception, and document
+            // that '#' for comments must be preceded by space, and in values
+            // must be quoted.
+            break;
+        }
+        else {
+            if (new_part) {
+                part_start = rval.length();
+                new_part = false;
+            }
+            rval += c;
+        }
+        ++i;
+    }
+
+    // Got to end:
+    if (part_positions != nullptr) {
+        part_positions->emplace_back(part_start, rval.length());
+    }
+
+    return rval;
+}
+
+static int signalNameToNumber(std::string &signame)
+{
+    if (signame == "HUP") return SIGHUP;
+    if (signame == "INT") return SIGINT;
+    if (signame == "QUIT") return SIGQUIT;
+    if (signame == "USR1") return SIGUSR1;
+    if (signame == "USR2") return SIGUSR2;
+    return -1;
+}
+
+static const char * uid_err_msg = "Specified user id contains invalid numeric characters or is outside allowed range.";
+
+// Parse a userid parameter which may be a numeric user ID or a username. If a name, the
+// userid is looked up via the system user database (getpwnam() function). In this case,
+// the associated group is stored in the location specified by the group_p parameter iff
+// it is not null and iff it contains the value -1.
+static uid_t parse_uid_param(const std::string &param, const std::string &service_name, gid_t *group_p)
+{
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assume
+        // that the value range fits with "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        //
+        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
+        //      functions accept a leading minus sign...
+        static_assert((uintmax_t)std::numeric_limits<uid_t>::max() <= (uintmax_t)std::numeric_limits<unsigned long long>::max(), "uid_t is too large");
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<uid_t>::max()) || ind != param.length()) {
+            throw ServiceDescriptionExc(service_name, uid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw ServiceDescriptionExc(service_name, uid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct passwd * pwent = getpwnam(param.c_str());
+    if (pwent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw new ServiceDescriptionExc(service_name, "Specified user \"" + param + "\" does not exist in system database.");
+        }
+        else {
+            throw new ServiceDescriptionExc(service_name, std::string("Error accessing user database: ") + strerror(errno));
+        }
+    }
+    
+    if (group_p && *group_p != (gid_t)-1) {
+        *group_p = pwent->pw_gid;
+    }
+    
+    return pwent->pw_uid;
+}
+
+static const char * gid_err_msg = "Specified group id contains invalid numeric characters or is outside allowed range.";
+
+static gid_t parse_gid_param(const std::string &param, const std::string &service_name)
+{
+    // Could be a name or a numeric id. But we should assume numeric first, just in case
+    // a user manages to give themselves a username that parses as a number.
+    std::size_t ind = 0;
+    try {
+        // POSIX does not specify whether uid_t is an signed or unsigned, but regardless
+        // is is probably safe to assume that valid values are positive. We'll also assume
+        // that the value range fits with "unsigned long long" since it seems unlikely
+        // that would ever not be the case.
+        //
+        // TODO perhaps write a number parser, since even the unsigned variants of the C/C++
+        //      functions accept a leading minus sign...
+        unsigned long long v = std::stoull(param, &ind, 0);
+        if (v > static_cast<unsigned long long>(std::numeric_limits<gid_t>::max()) || ind != param.length()) {
+            throw ServiceDescriptionExc(service_name, gid_err_msg);
+        }
+        return v;
+    }
+    catch (std::out_of_range &exc) {
+        throw ServiceDescriptionExc(service_name, gid_err_msg);
+    }
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
+    }
+
+    errno = 0;
+    struct group * grent = getgrnam(param.c_str());
+    if (grent == nullptr) {
+        // Maybe an error, maybe just no entry.
+        if (errno == 0) {
+            throw new ServiceDescriptionExc(service_name, "Specified group \"" + param + "\" does not exist in system database.");
+        }
+        else {
+            throw new ServiceDescriptionExc(service_name, std::string("Error accessing group database: ") + strerror(errno));
+        }
+    }
+    
+    return grent->gr_gid;
+}
+
+// Find a service record, or load it from file. If the service has
+// dependencies, load those also.
+//
+// Might throw a ServiceLoadExc exception if a dependency cycle is found or if another
+// problem occurs (I/O error, service description not found etc). Throws std::bad_alloc
+// if a memory allocation failure occurs.
+ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
+{
+    using std::string;
+    using std::ifstream;
+    using std::ios;
+    using std::ios_base;
+    using std::locale;
+    using std::isspace;
+    
+    using std::list;
+    using std::pair;
+    
+    // First try and find an existing record...
+    ServiceRecord * rval = findService(string(name));
+    if (rval != 0) {
+        if (rval->isDummy()) {
+            throw ServiceCyclicDependency(name);
+        }
+        return rval;
+    }
+
+    // Couldn't find one. Have to load it.    
+    string service_filename = service_dir;
+    if (*(service_filename.rbegin()) != '/') {
+        service_filename += '/';
+    }
+    service_filename += name;
+    
+    string command;
+    list<pair<unsigned,unsigned>> command_offsets;
+    string stop_command;
+    list<pair<unsigned,unsigned>> stop_command_offsets;
+    string pid_file;
+
+    ServiceType service_type = ServiceType::PROCESS;
+    std::list<ServiceRecord *> depends_on;
+    std::list<ServiceRecord *> depends_soft;
+    string logfile;
+    OnstartFlags onstart_flags;
+    int term_signal = -1;  // additional termination signal
+    bool auto_restart = false;
+    bool smooth_recovery = false;
+    string socket_path;
+    int socket_perms = 0666;
+    // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
+    // invalid value, so it's safe to assume that we can do the same:
+    uid_t socket_uid = -1;
+    gid_t socket_gid = -1;
+    
+    string line;
+    ifstream service_file;
+    service_file.exceptions(ios::badbit | ios::failbit);
+    
+    try {
+        service_file.open(service_filename.c_str(), ios::in);
+    }
+    catch (std::ios_base::failure &exc) {
+        throw ServiceNotFound(name);
+    }
+    
+    // Add a dummy service record now to prevent infinite recursion in case of cyclic dependency
+    rval = new ServiceRecord(this, string(name));
+    records.push_back(rval);
+    
+    try {
+        // getline can set failbit if it reaches end-of-file, we don't want an exception in that case:
+        service_file.exceptions(ios::badbit);
+        
+        while (! (service_file.rdstate() & ios::eofbit)) {
+            getline(service_file, line);
+            string::iterator i = line.begin();
+            string::iterator end = line.end();
+          
+            i = skipws(i, end);
+            if (i != end) {
+                if (*i == '#') {
+                    continue;  // comment line
+                }
+                string setting = read_setting_name(i, end);
+                i = skipws(i, end);
+                if (i == end || (*i != '=' && *i != ':')) {
+                    throw ServiceDescriptionExc(name, "Badly formed line.");
+                }
+                i = skipws(++i, end);
+                
+                if (setting == "command") {
+                    command = read_setting_value(i, end, &command_offsets);
+                }
+                else if (setting == "socket-listen") {
+                    socket_path = read_setting_value(i, end, nullptr);
+                }
+                else if (setting == "socket-permissions") {
+                    string sock_perm_str = read_setting_value(i, end, nullptr);
+                    std::size_t ind = 0;
+                    try {
+                        socket_perms = std::stoi(sock_perm_str, &ind, 8);
+                        if (ind != sock_perm_str.length()) {
+                            throw std::logic_error("");
+                        }
+                    }
+                    catch (std::logic_error &exc) {
+                        throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
+                    }
+                }
+                else if (setting == "socket-uid") {
+                    string sock_uid_s = read_setting_value(i, end, nullptr);
+                    socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
+                }
+                else if (setting == "socket-gid") {
+                    string sock_gid_s = read_setting_value(i, end, nullptr);
+                    socket_gid = parse_gid_param(sock_gid_s, name);
+                }
+                else if (setting == "stop-command") {
+                    stop_command = read_setting_value(i, end, &stop_command_offsets);
+                }
+                else if (setting == "pid-file") {
+                    pid_file = read_setting_value(i, end);
+                }
+                else if (setting == "depends-on") {
+                    string dependency_name = read_setting_value(i, end);
+                    depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
+                }
+                else if (setting == "waits-for") {
+                    string dependency_name = read_setting_value(i, end);
+                    depends_soft.push_back(loadServiceRecord(dependency_name.c_str()));
+                }
+                else if (setting == "logfile") {
+                    logfile = read_setting_value(i, end);
+                }
+                else if (setting == "restart") {
+                    string restart = read_setting_value(i, end);
+                    auto_restart = (restart == "yes" || restart == "true");
+                }
+                else if (setting == "smooth-recovery") {
+                    string recovery = read_setting_value(i, end);
+                    smooth_recovery = (recovery == "yes" || recovery == "true");
+                }
+                else if (setting == "type") {
+                    string type_str = read_setting_value(i, end);
+                    if (type_str == "scripted") {
+                        service_type = ServiceType::SCRIPTED;
+                    }
+                    else if (type_str == "process") {
+                        service_type = ServiceType::PROCESS;
+                    }
+                    else if (type_str == "bgprocess") {
+                        service_type = ServiceType::BGPROCESS;
+                    }
+                    else if (type_str == "internal") {
+                        service_type = ServiceType::INTERNAL;
+                    }
+                    else {
+                        throw ServiceDescriptionExc(name, "Service type must be one of: \"scripted\","
+                            " \"process\", \"bgprocess\" or \"internal\"");
+                    }
+                }
+                else if (setting == "onstart") {
+                    std::list<std::pair<unsigned,unsigned>> indices;
+                    string onstart_cmds = read_setting_value(i, end, &indices);
+                    for (auto indexpair : indices) {
+                        string onstart_cmd = onstart_cmds.substr(indexpair.first, indexpair.second - indexpair.first);
+                        if (onstart_cmd == "rw_ready") {
+                            onstart_flags.rw_ready = true;
+                        }
+                        else {
+                            throw new ServiceDescriptionExc(name, "Unknown onstart command: " + onstart_cmd);
+                        }
+                    }
+                }
+                else if (setting == "termsignal") {
+                    string signame = read_setting_value(i, end, nullptr);
+                    int signo = signalNameToNumber(signame);
+                    if (signo == -1) {
+                        throw new ServiceDescriptionExc(name, "Unknown/unsupported termination signal: " + signame);
+                    }
+                    else {
+                        term_signal = signo;
+                    }
+                }
+                else if (setting == "nosigterm") {
+                    string sigtermsetting = read_setting_value(i, end);
+                    onstart_flags.no_sigterm = (sigtermsetting == "yes" || sigtermsetting == "true");
+                }
+                else if (setting == "runs-on-console") {
+                    string runconsolesetting = read_setting_value(i, end);
+                    onstart_flags.runs_on_console = (runconsolesetting == "yes" || runconsolesetting == "true");
+                }
+                else {
+                    throw ServiceDescriptionExc(name, "Unknown setting: " + setting);
+                }
+            }
+        }
+        
+        service_file.close();
+        // TODO check we actually have all the settings - type, command
+        
+        // Now replace the dummy service record with a real record:
+        for (auto iter = records.begin(); iter != records.end(); iter++) {
+            if (*iter == rval) {
+                // We've found the dummy record
+                delete rval;
+                rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
+                        & depends_on, & depends_soft);
+                rval->setStopCommand(stop_command, stop_command_offsets);
+                rval->setLogfile(logfile);
+                rval->setAutoRestart(auto_restart);
+                rval->setSmoothRecovery(smooth_recovery);
+                rval->setOnstartFlags(onstart_flags);
+                rval->setExtraTerminationSignal(term_signal);
+                rval->set_pid_file(std::move(pid_file));
+                rval->set_socket_details(std::move(socket_path), socket_perms, socket_uid, socket_gid);
+                *iter = rval;
+                break;
+            }
+        }
+        
+        return rval;
+    }
+    catch (...) {
+        // Must remove the dummy service record.
+        std::remove(records.begin(), records.end(), rval);
+        delete rval;
+        throw;
+    }
+}
diff --git a/src/reboot b/src/reboot
new file mode 100755 (executable)
index 0000000..c607879
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+shutdown -r "$@"
diff --git a/src/service-constants.h b/src/service-constants.h
new file mode 100644 (file)
index 0000000..8084066
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef SERVICE_CONSTANTS_H
+#define SERVICE_CONSTANTS_H
+
+/* Service states */
+enum class ServiceState {
+    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 ServiceType {
+    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 ServiceEvent {
+    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 ShutdownType {
+    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
new file mode 100644 (file)
index 0000000..c45c2a9
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef SERVICE_LISTENER_H
+#define SERVICE_LISTENER_H
+
+#include "service-constants.h"
+
+class ServiceRecord;
+
+// Interface for listening to services
+class ServiceListener
+{
+    public:
+    
+    // An event occurred on the service being observed.
+    // Listeners must not be added or removed during event notification.
+    virtual void serviceEvent(ServiceRecord * service, ServiceEvent event) noexcept = 0;
+};
+
+#endif
diff --git a/src/service.cc b/src/service.cc
new file mode 100644 (file)
index 0000000..4fec13b
--- /dev/null
@@ -0,0 +1,870 @@
+#include <cstring>
+#include <cerrno>
+#include <sstream>
+#include <iterator>
+#include <memory>
+#include <cstddef>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+
+#include "service.h"
+#include "dinit-log.h"
+
+// from dinit.cc:
+void open_control_socket(struct ev_loop *loop) noexcept;
+
+
+// Find the requested service by name
+static ServiceRecord * findService(const std::list<ServiceRecord *> & records,
+                                    const char *name) noexcept
+{
+    using std::list;
+    list<ServiceRecord *>::const_iterator i = records.begin();
+    for ( ; i != records.end(); i++ ) {
+        if (strcmp((*i)->getServiceName(), name) == 0) {
+            return *i;
+        }
+    }
+    return (ServiceRecord *)0;
+}
+
+ServiceRecord * ServiceSet::findService(const std::string &name) noexcept
+{
+    return ::findService(records, name.c_str());
+}
+
+void ServiceSet::startService(const char *name)
+{
+    using namespace std;
+    ServiceRecord *record = loadServiceRecord(name);
+    
+    record->start();
+}
+
+void ServiceSet::stopService(const std::string & name) noexcept
+{
+    ServiceRecord *record = findService(name);
+    if (record != nullptr) {
+        record->stop();
+    }
+}
+
+// Called when a service has actually stopped.
+void ServiceRecord::stopped() noexcept
+{
+    if (service_type != ServiceType::SCRIPTED && service_type != ServiceType::BGPROCESS && onstart_flags.runs_on_console) {
+        tcsetpgrp(0, getpgrp());
+        releaseConsole();
+    }
+
+    logServiceStopped(service_name);
+    service_state = ServiceState::STOPPED;
+    force_stop = false;
+    
+    // Stop any dependencies whose desired state is STOPPED:
+    for (sr_iter i = depends_on.begin(); i != depends_on.end(); i++) {
+        (*i)->dependentStopped();
+    }
+
+    service_set->service_inactive(this);
+    notifyListeners(ServiceEvent::STOPPED);
+    
+    if (desired_state == ServiceState::STARTED) {
+        // Desired state is "started".
+        start();
+    }
+    else if (socket_fd != -1) {
+        close(socket_fd);
+        socket_fd = -1;
+    }
+}
+
+void ServiceRecord::process_child_callback(struct ev_loop *loop, ev_child *w, int revents) noexcept
+{    
+    ServiceRecord *sr = (ServiceRecord *) w->data;
+
+    sr->pid = -1;
+    sr->exit_status = w->rstatus;
+    ev_child_stop(loop, w);
+    
+    // Ok, for a process service, any process death which we didn't rig
+    // ourselves is a bit... unexpected. Probably, the child died because
+    // we asked it to (sr->service_state == STOPPING). But even if
+    // we didn't, there's not much we can do.
+    
+    if (sr->waiting_for_execstat) {
+        // We still don't have an exec() status from the forked child, wait for that
+        // before doing any further processing.
+        return;
+    }
+    
+    sr->handle_exit_status();
+}
+
+void ServiceRecord::handle_exit_status() noexcept
+{
+    if (exit_status != 0 && service_state != ServiceState::STOPPING) {
+        log(LogLevel::ERROR, "Service ", service_name, " process terminated with exit code ", exit_status);
+    }
+
+    if (doing_recovery) {
+        // (BGPROCESS only)
+        doing_recovery = false;
+        bool do_stop = false;
+        if (exit_status != 0) {
+            do_stop = true;
+        }
+        else {
+            // We need to re-read the PID, since it has now changed.
+            if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) {
+                if (! read_pid_file()) {
+                    do_stop = true;
+                }
+            }
+        }
+        
+        if (do_stop) {
+            stop();
+            if (auto_restart && service_set->get_auto_restart()) {
+                start();
+            }
+        }
+        
+        return;
+    }
+    
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
+        if (service_state == ServiceState::STARTING) {
+            // (only applies to BGPROCESS)
+            if (exit_status == 0) {
+                started();
+            }
+            else {
+                failed_to_start();
+            }
+        }
+        else if (service_state == ServiceState::STOPPING) {
+            // TODO log non-zero rstatus?
+            stopped();
+        }
+        else if (smooth_recovery && service_state == ServiceState::STARTED) {
+            // TODO ensure a minimum time between restarts
+            // TODO if we are pinned-started then we should probably check
+            //      that dependencies have started before trying to re-start the
+            //      service process.
+            doing_recovery = (service_type == ServiceType::BGPROCESS);
+            start_ps_process();
+            return;
+        }
+        else {
+            forceStop();
+        }
+        
+        if (auto_restart && service_set->get_auto_restart()) {
+            start();
+        }
+    }
+    else {  // SCRIPTED
+        if (service_state == ServiceState::STOPPING) {
+            if (exit_status == 0) {
+                stopped();
+            }
+            else {
+                // ??? failed to stop! Let's log it as info:
+                log(LogLevel::INFO, "service ", service_name, " stop command failed with exit code ", exit_status);
+                // Just assume that we stopped, so that any dependencies
+                // can be stopped:
+                stopped();
+            }
+        }
+        else { // STARTING
+            if (exit_status == 0) {
+                started();
+            }
+            else {
+                // failed to start
+                log(LogLevel::ERROR, "service ", service_name, " command failed with exit code ", exit_status);
+                failed_to_start();
+            }
+        }
+    }
+}
+
+void ServiceRecord::process_child_status(struct ev_loop *loop, ev_io * stat_io, int revents) noexcept
+{
+    ServiceRecord *sr = (ServiceRecord *) stat_io->data;
+    sr->waiting_for_execstat = false;
+    
+    int exec_status;
+    int r = read(stat_io->fd, &exec_status, sizeof(int));
+    close(stat_io->fd);
+    ev_io_stop(loop, stat_io);
+    
+    if (r != 0) {
+        // We read an errno code; exec() failed, and the service startup failed.
+        sr->pid = -1;
+        log(LogLevel::ERROR, sr->service_name, ": execution failed: ", strerror(exec_status));
+        if (sr->service_state == ServiceState::STARTING) {
+            sr->failed_to_start();
+        }
+        else if (sr->service_state == ServiceState::STOPPING) {
+            // Must be a scripted servce. We've logged the failure, but it's probably better
+            // not to leave the service in STARTED state:
+            sr->stopped();
+        }
+    }
+    else {
+        // exec() succeeded.
+        if (sr->service_type == ServiceType::PROCESS) {
+            if (sr->service_state != ServiceState::STARTED) {
+                sr->started();
+            }
+        }
+        
+        if (sr->pid == -1) {
+            // Somehow the process managed to complete before we even saw the status.
+            sr->handle_exit_status();
+        }
+    }
+}
+
+void ServiceRecord::start() noexcept
+{
+    if ((service_state == ServiceState::STARTING || service_state == ServiceState::STARTED)
+            && desired_state == ServiceState::STOPPED) {
+        // This service was starting, or started, but was set to be stopped.
+        // Cancel the stop (and continue starting/running).
+        notifyListeners(ServiceEvent::STOPCANCELLED);
+    }
+
+    if (desired_state == ServiceState::STARTED && service_state != ServiceState::STOPPED) return;
+
+    desired_state = ServiceState::STARTED;
+    
+    if (pinned_stopped) return;
+    
+    if (service_state != ServiceState::STOPPED) {
+        // We're already starting/started, or we are stopping and need to wait for
+        // that the complete.
+        if (service_state != ServiceState::STOPPING || ! can_interrupt_stop()) {
+            return;
+        }
+        // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING,
+        // but if so they are waiting (for us), so they too can be instantly returned to
+        // STARTING state.
+    }
+    
+    service_state = ServiceState::STARTING;
+    service_set->service_active(this);
+
+    waiting_for_deps = true;
+
+    // Ask dependencies to start, mark them as being waited on.
+    if (! startCheckDependencies(true)) {
+        return;
+    }
+
+    // Actually start this service.
+    allDepsStarted();
+}
+
+void ServiceRecord::dependencyStarted() noexcept
+{
+    if (service_state != ServiceState::STARTING || ! waiting_for_deps) {
+        return;
+    }
+
+    if (startCheckDependencies(false)) {
+        allDepsStarted();
+    }
+}
+
+bool ServiceRecord::startCheckDependencies(bool start_deps) noexcept
+{
+    bool all_deps_started = true;
+
+    for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
+        if ((*i)->service_state != ServiceState::STARTED) {
+            if (start_deps) {
+                all_deps_started = false;
+                (*i)->start();
+            }
+            else {
+                return false;
+            }
+        }
+    }
+
+    for (auto i = soft_deps.begin(); i != soft_deps.end(); ++i) {
+        ServiceRecord * to = i->getTo();
+        if (start_deps) {
+            if (to->service_state != ServiceState::STARTED) {
+                to->start();
+                i->waiting_on = true;
+                all_deps_started = false;
+            }
+            else {
+                i->waiting_on = false;
+            }
+        }
+        else if (i->waiting_on) {
+            if (to->service_state != ServiceState::STARTING) {
+                // Service has either started or is no longer starting
+                i->waiting_on = false;
+            }
+            else {
+                // We are still waiting on this service
+                return false;
+            }
+        }
+    }
+    
+    return all_deps_started;
+}
+
+bool ServiceRecord::open_socket() noexcept
+{
+    if (socket_path.empty() || socket_fd != -1) {
+        // No socket, or already open
+        return true;
+    }
+    
+    const char * saddrname = socket_path.c_str();
+    uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1;
+
+    struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+    if (name == nullptr) {
+        log(LogLevel::ERROR, service_name, ": Opening activation socket: out of memory");
+        return false;
+    }
+    
+    // Un-link any stale socket. TODO: safety check? should at least confirm the path is a socket.
+    unlink(saddrname);
+
+    name->sun_family = AF_UNIX;
+    strcpy(name->sun_path, saddrname);
+
+    int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+    if (sockfd == -1) {
+        log(LogLevel::ERROR, service_name, ": Error creating activation socket: ", strerror(errno));
+        free(name);
+        return false;
+    }
+
+    if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
+        log(LogLevel::ERROR, service_name, ": Error binding activation socket: ", strerror(errno));
+        close(sockfd);
+        free(name);
+        return false;
+    }
+    
+    free(name);
+    
+    // POSIX (1003.1, 2013) says that fchown and fchmod don't necesarily work on sockets. We have to
+    // use chown and chmod instead.
+    if (chown(saddrname, socket_uid, socket_gid)) {
+        log(LogLevel::ERROR, service_name, ": Error setting activation socket owner/group: ", strerror(errno));
+        close(sockfd);
+        return false;
+    }
+    
+    if (chmod(saddrname, socket_perms) == -1) {
+        log(LogLevel::ERROR, service_name, ": Error setting activation socket permissions: ", strerror(errno));
+        close(sockfd);
+        return false;
+    }
+
+    if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
+        log(LogLevel::ERROR, ": Error listening on activation socket: ", strerror(errno));
+        close(sockfd);
+        return false;
+    }
+    
+    socket_fd = sockfd;
+    return true;
+}
+
+void ServiceRecord::allDepsStarted(bool has_console) noexcept
+{
+    if (onstart_flags.runs_on_console && ! has_console) {
+        waiting_for_deps = true;
+        queueForConsole();
+        return;
+    }
+    
+    waiting_for_deps = false;
+
+    if (! open_socket()) {
+        failed_to_start();
+    }
+
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS
+            || service_type == ServiceType::SCRIPTED) {
+        bool start_success = start_ps_process();
+        if (! start_success) {
+            failed_to_start();
+        }
+    }
+    else {
+        // "internal" service
+        started();
+    }
+}
+
+void ServiceRecord::acquiredConsole() noexcept
+{
+    if (service_state != ServiceState::STARTING) {
+        // We got the console but no longer want it.
+        releaseConsole();
+    }
+    else if (startCheckDependencies(false)) {
+        log_to_console = false;
+        allDepsStarted(true);
+    }
+    else {
+        // We got the console but can't use it yet.
+        releaseConsole();
+    }
+}
+
+bool ServiceRecord::read_pid_file() noexcept
+{
+    const char *pid_file_c = pid_file.c_str();
+    int fd = open(pid_file_c, O_CLOEXEC);
+    if (fd != -1) {
+        char pidbuf[21]; // just enought to hold any 64-bit integer
+        int r = read(fd, pidbuf, 20);
+        if (r > 0) {
+            pidbuf[r] = 0; // store nul terminator
+            pid = std::atoi(pidbuf);
+            if (kill(pid, 0) == 0) {
+                ev_child_init(&child_listener, process_child_callback, pid, 0);
+                child_listener.data = this;
+                ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
+            }
+            else {
+                log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
+                pid = -1;
+                close(fd);
+                return false;
+            }
+        }
+        close(fd);
+        return true;
+    }
+    else {
+        log(LogLevel::ERROR, service_name, ": read pid file: ", strerror(errno));
+        return false;
+    }
+}
+
+void ServiceRecord::started() noexcept
+{
+    if (onstart_flags.runs_on_console && (service_type == ServiceType::SCRIPTED || service_type == ServiceType::BGPROCESS)) {
+        tcsetpgrp(0, getpgrp());
+        releaseConsole();
+    }
+    
+    if (service_type == ServiceType::BGPROCESS && pid_file.length() != 0) {
+        if (! read_pid_file()) {
+            failed_to_start();
+            return;
+        }
+    }
+    
+    logServiceStarted(service_name);
+    service_state = ServiceState::STARTED;
+    notifyListeners(ServiceEvent::STARTED);
+
+    if (onstart_flags.rw_ready) {
+        open_control_socket(ev_default_loop(EVFLAG_AUTO));
+    }
+
+    if (force_stop || desired_state == ServiceState::STOPPED) {
+        // We must now stop.
+        bool do_restart = (desired_state != ServiceState::STOPPED);
+        stop();
+        if (do_restart) {
+            start();
+        }
+        return;
+    }
+
+    // Notify any dependents whose desired state is STARTED:
+    for (auto i = dependents.begin(); i != dependents.end(); i++) {
+        (*i)->dependencyStarted();
+    }
+    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
+        (*i)->getFrom()->dependencyStarted();
+    }
+}
+
+void ServiceRecord::failed_to_start()
+{
+    if (onstart_flags.runs_on_console) {
+        tcsetpgrp(0, getpgrp());
+        releaseConsole();
+    }
+    
+    logServiceFailed(service_name);
+    service_state = ServiceState::STOPPED;
+    desired_state = ServiceState::STOPPED;
+    service_set->service_inactive(this);
+    notifyListeners(ServiceEvent::FAILEDSTART);
+    
+    // failure to start
+    // Cancel start of dependents:
+    for (sr_iter i = dependents.begin(); i != dependents.end(); i++) {
+        if ((*i)->service_state == ServiceState::STARTING) {
+            (*i)->failed_dependency();
+        }
+    }    
+    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
+        // We can send 'start', because this is only a soft dependency.
+        // Our startup failure means that they don't have to wait for us.
+        (*i)->getFrom()->dependencyStarted();
+    }
+}
+
+bool ServiceRecord::start_ps_process() noexcept
+{
+    return start_ps_process(exec_arg_parts, onstart_flags.runs_on_console);
+}
+
+bool ServiceRecord::start_ps_process(const std::vector<const char *> &cmd, bool on_console) noexcept
+{
+    // In general, you can't tell whether fork/exec is successful. We use a pipe to communicate
+    // success/failure from the child to the parent. The pipe is set CLOEXEC so a successful
+    // exec closes the pipe, and the parent sees EOF. If the exec is unsuccessful, the errno
+    // is written to the pipe, and the parent can read it.
+
+    int pipefd[2];
+    if (pipe2(pipefd, O_CLOEXEC)) {
+        // TODO log error
+        return false;
+    }
+    
+    // Set up the argument array and other data now (before fork), in case memory allocation fails.
+    
+    auto args = cmd.data();
+    
+    const char * logfile = this->logfile.c_str();
+    if (*logfile == 0) {
+        logfile = "/dev/null";
+    }
+
+    // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
+    // until they are not.
+
+    pid_t forkpid = fork();
+    if (forkpid == -1) {
+        // TODO log error
+        close(pipefd[0]);
+        close(pipefd[1]);
+        return false;
+    }
+
+    // If the console already has a session leader, presumably it is us. On the other hand
+    // if it has no session leader, and we don't create one, then control inputs such as
+    // ^C will have no effect.
+    bool do_set_ctty = (tcgetsid(0) == -1);
+
+    if (forkpid == 0) {
+        // Child process. Must not allocate memory (or otherwise risk throwing any exception)
+        // from here until exit().
+        ev_default_destroy(); // won't need that on this side, free up fds.
+
+        constexpr int bufsz = ((CHAR_BIT * sizeof(pid_t) - 1) / 3 + 2) + 11;
+        // "LISTEN_PID=" - 11 characters
+        char nbuf[bufsz];
+
+        if (socket_fd != -1) {
+            dup2(socket_fd, 3);
+            if (socket_fd != 3) {
+                close(socket_fd);
+            }
+            
+            if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
+            
+            snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
+            
+            if (putenv(nbuf)) goto failure_out;
+        }
+
+        if (! on_console) {
+            // Re-set stdin, stdout, stderr
+            close(0); close(1); close(2);
+
+            // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
+            if (open("/dev/null", O_RDONLY) == 0) {
+              // stdin = 0. That's what we should have; proceed with opening
+              // stdout and stderr.
+              open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+              dup2(1, 2);
+            }
+        }
+        else {
+            // "run on console" - run as a foreground job on the terminal/console device
+            if (do_set_ctty) {
+                setsid();
+                ioctl(0, TIOCSCTTY, 0);
+            }
+            setpgid(0,0);
+            tcsetpgrp(0, getpgrp());
+            
+            // TODO disable suspend (^Z)? (via tcsetattr)
+            //      (should be done before TIOCSCTTY)
+        }
+
+        execvp(exec_arg_parts[0], const_cast<char **>(args));
+
+        // If we got here, the exec failed:
+        failure_out:
+        int exec_status = errno;
+        write(pipefd[1], &exec_status, sizeof(int));
+        exit(0);
+    }
+    else {
+        // Parent process
+        close(pipefd[1]); // close the 'other end' fd
+
+        pid = forkpid;
+
+        // Listen for status
+        ev_io_init(&child_status_listener, process_child_status, pipefd[0], EV_READ);
+        child_status_listener.data = this;
+        ev_io_start(ev_default_loop(EVFLAG_AUTO), &child_status_listener);
+
+        // Add a process listener so we can detect when the
+        // service stops
+        ev_child_init(&child_listener, process_child_callback, pid, 0);
+        child_listener.data = this;
+        ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
+        waiting_for_execstat = true;
+        return true;
+    }
+}
+
+// Mark this and all dependent services as force-stopped.
+void ServiceRecord::forceStop() noexcept
+{
+    if (service_state != ServiceState::STOPPED) {
+        force_stop = true;
+        for (sr_iter i = dependents.begin(); i != dependents.end(); i++) {
+            (*i)->forceStop();
+        }
+        stop();
+        
+        // We don't want to force stop soft dependencies, however.
+    }
+}
+
+// A dependency of this service failed to start.
+// Only called when state == STARTING.
+void ServiceRecord::failed_dependency()
+{
+    desired_state = ServiceState::STOPPED;
+    
+    // Presumably, we were starting. So now we're not.
+    service_state = ServiceState::STOPPED;
+    service_set->service_inactive(this);
+    logServiceFailed(service_name);
+    
+    // Notify dependents of this service also
+    for (auto i = dependents.begin(); i != dependents.end(); i++) {
+        if ((*i)->service_state == ServiceState::STARTING) {
+            (*i)->failed_dependency();
+        }
+    }
+    for (auto i = soft_dpts.begin(); i != soft_dpts.end(); i++) {
+        // It's a soft dependency, so send them 'started' rather than
+        // 'failed dep'.
+        (*i)->getFrom()->dependencyStarted();
+    }    
+}
+
+void ServiceRecord::dependentStopped() noexcept
+{
+    if (service_state == ServiceState::STOPPING) {
+        // Check the other dependents before we stop.
+        if (stopCheckDependents()) {
+            allDepsStopped();
+        }
+    }
+}
+
+void ServiceRecord::stop() noexcept
+{
+    if ((service_state == ServiceState::STOPPING || service_state == ServiceState::STOPPED)
+            && desired_state == ServiceState::STARTED) {
+        // The service *was* stopped/stopping, but it was going to restart.
+        // Now, we'll cancel the restart.
+        notifyListeners(ServiceEvent::STARTCANCELLED);
+    }
+    
+    if (desired_state == ServiceState::STOPPED && service_state != ServiceState::STARTED) return;
+    
+    desired_state = ServiceState::STOPPED;
+    
+    if (pinned_started) return;
+
+    if (service_state != ServiceState::STARTED) {
+        if (service_state == ServiceState::STARTING) {
+            if (! can_interrupt_start()) {
+                // Well this is awkward: we're going to have to continue
+                // starting, but we don't want any dependents to think that
+                // they are still waiting to start.
+                // Make sure they remain stopped:
+                stopDependents();
+                return;
+            }
+            
+            // Reaching this point, we have can_interrupt_start() == true. So,
+            // we can stop. Dependents might be starting, but they must be
+            // waiting on us, so they should also be immediately stoppable.
+            // Fall through to below.
+        }
+        else {
+            // If we're starting we need to wait for that to complete.
+            // If we're already stopping/stopped there's nothing to do.
+            return;
+        }
+    }
+    
+    service_state = ServiceState::STOPPING;
+    waiting_for_deps = true;
+    
+    // If we get here, we are in STARTED state; stop all dependents.
+    if (stopDependents()) {
+        allDepsStopped();
+    }
+}
+
+bool ServiceRecord::stopCheckDependents() noexcept
+{
+    bool all_deps_stopped = true;
+    for (sr_iter i = dependents.begin(); i != dependents.end(); ++i) {
+        if ((*i)->service_state != ServiceState::STOPPED) {
+            all_deps_stopped = false;
+            break;
+        }
+    }
+    
+    return all_deps_stopped;
+}
+
+bool ServiceRecord::stopDependents() noexcept
+{
+    bool all_deps_stopped = true;
+    for (sr_iter i = dependents.begin(); i != dependents.end(); ++i) {
+        if ((*i)->service_state != ServiceState::STOPPED) {
+            all_deps_stopped = false;
+            (*i)->stop();
+        }
+    }
+    
+    return all_deps_stopped;
+}
+
+// Dependency stopped or is stopping; we must stop too.
+void ServiceRecord::allDepsStopped()
+{
+    waiting_for_deps = false;
+    if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS) {
+        if (pid != -1) {
+            // The process is still kicking on - must actually kill it.
+            if (! onstart_flags.no_sigterm) {
+                kill(pid, SIGTERM);
+            }
+            if (term_signal != -1) {
+                kill(pid, term_signal);
+            }
+            // Now we wait; the rest is done in process_child_callback
+        }
+        else {
+            // The process is already dead.
+            stopped();
+        }
+    }
+    else if (service_type == ServiceType::SCRIPTED) {
+        // Scripted service.
+        if (stop_command.length() == 0) {
+            stopped();
+        }
+        else if (! start_ps_process(stop_arg_parts, false)) {
+            // Couldn't execute stop script, but there's not much we can do:
+            stopped();
+        }
+    }
+    else {
+        stopped();
+    }
+}
+
+void ServiceRecord::pinStart() noexcept
+{
+    start();
+    pinned_started = true;
+}
+
+void ServiceRecord::pinStop() noexcept
+{
+    stop();
+    pinned_stopped = true;
+}
+
+void ServiceRecord::unpin() noexcept
+{
+    if (pinned_started) {
+        pinned_started = false;
+        if (desired_state == ServiceState::STOPPED) {
+            stop();
+        }
+    }
+    if (pinned_stopped) {
+        pinned_stopped = false;
+        if (desired_state == ServiceState::STARTED) {
+            start();
+        }
+    }
+}
+
+void ServiceRecord::queueForConsole() noexcept
+{
+    next_for_console = nullptr;
+    auto tail = service_set->consoleQueueTail(this);
+    if (tail == nullptr) {
+        acquiredConsole();
+    }
+    else {
+        tail->next_for_console = this;
+    }
+}
+
+void ServiceRecord::releaseConsole() noexcept
+{
+    log_to_console = true;
+    if (next_for_console != nullptr) {
+        next_for_console->acquiredConsole();
+    }
+    else {
+        service_set->consoleQueueTail(nullptr);
+    }
+}
+
+void ServiceSet::service_active(ServiceRecord *sr) noexcept
+{
+    active_services++;
+}
+
+void ServiceSet::service_inactive(ServiceRecord *sr) noexcept
+{
+    active_services--;
+}
diff --git a/src/service.h b/src/service.h
new file mode 100644 (file)
index 0000000..cbeab72
--- /dev/null
@@ -0,0 +1,547 @@
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include <string>
+#include <list>
+#include <vector>
+#include <csignal>
+#include <unordered_set>
+#include "ev.h"
+#include "control.h"
+#include "service-listener.h"
+#include "service-constants.h"
+
+/*
+ * Possible 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.
+ */
+
+struct OnstartFlags {
+    bool rw_ready : 1;
+    
+    // Not actually "onstart" commands:
+    bool no_sigterm : 1;  // do not send SIGTERM
+    bool runs_on_console : 1;  // run "in the foreground"
+    
+    OnstartFlags() noexcept : rw_ready(false),
+            no_sigterm(false), runs_on_console(false)
+    {
+    }
+};
+
+// Exception while loading a service
+class ServiceLoadExc
+{
+    public:
+    std::string serviceName;
+    const char *excDescription;
+    
+    protected:
+    ServiceLoadExc(std::string serviceName) noexcept
+        : serviceName(serviceName)
+    {
+    }
+};
+
+class ServiceNotFound : public ServiceLoadExc
+{
+    public:
+    ServiceNotFound(std::string serviceName) noexcept
+        : ServiceLoadExc(serviceName)
+    {
+        excDescription = "Service description not found.";
+    }
+};
+
+class ServiceCyclicDependency : public ServiceLoadExc
+{
+    public:
+    ServiceCyclicDependency(std::string serviceName) noexcept
+        : ServiceLoadExc(serviceName)
+    {
+        excDescription = "Has cyclic dependency.";
+    }
+};
+
+class ServiceDescriptionExc : public ServiceLoadExc
+{
+    public:
+    std::string extraInfo;
+    
+    ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
+        : ServiceLoadExc(serviceName), extraInfo(extraInfo)
+    {
+        excDescription = extraInfo.c_str();
+    }    
+};
+
+class ServiceRecord; // forward declaration
+class ServiceSet; // forward declaration
+
+/* Service dependency record */
+class ServiceDep
+{
+    ServiceRecord * from;
+    ServiceRecord * to;
+
+    public:
+    /* Whether the 'from' service is waiting for the 'to' service to start */
+    bool waiting_on;
+
+    ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false)
+    {  }
+
+    ServiceRecord * getFrom() noexcept
+    {
+        return from;
+    }
+
+    ServiceRecord * getTo() noexcept
+    {
+        return to;
+    }
+};
+
+// 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).
+static std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
+{
+    std::vector<const char *> r;
+    r.reserve(arg_indices.size() + 1);
+
+    // First store nul terminator for each part:
+    for (auto index_pair : arg_indices) {
+        if (index_pair.second < s.length()) {
+            s[index_pair.second] = 0;
+        }
+    }
+
+    // Now we can get the C string (c_str) and store offsets into it:
+    const char * cstr = s.c_str();
+    for (auto index_pair : arg_indices) {
+        r.push_back(cstr + index_pair.first);
+    }
+    r.push_back(nullptr);
+    return r;
+}
+
+
+class ServiceRecord
+{
+    typedef std::string string;
+    
+    string service_name;
+    ServiceType service_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
+    ServiceState service_state = ServiceState::STOPPED; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
+    ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
+
+    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 */
+    
+    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 */
+    
+    string pid_file;
+    
+    OnstartFlags 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
+    bool waiting_for_execstat : 1;  // if we are waiting for exec status after fork()
+    bool doing_recovery : 1;    // if we are currently recovering a BGPROCESS (restarting process, while
+                                //   holding STARTED service state)
+
+    typedef std::list<ServiceRecord *> sr_list;
+    typedef sr_list::iterator sr_iter;
+    
+    // list of soft dependencies
+    typedef std::list<ServiceDep> softdep_list;
+    
+    // list of soft dependents
+    typedef std::list<ServiceDep *> softdpt_list;
+    
+    sr_list depends_on; // services this one depends on
+    sr_list dependents; // services depending on this one
+    softdep_list soft_deps;  // services this one depends on via a soft dependency
+    softdpt_list soft_dpts;  // services depending on this one via a soft dependency
+    
+    // unsigned wait_count;  /* if we are waiting for dependents/dependencies to
+    //                         start/stop, this is how many we're waiting for */
+    
+    ServiceSet *service_set; // the set this service belongs to
+    
+    // Next service (after this one) in the queue for the console:
+    ServiceRecord *next_for_console;
+
+    std::unordered_set<ServiceListener *> 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;  // sockget 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.
+
+    ev_child child_listener;
+    ev_io child_status_listener;
+    
+    // All dependents have stopped.
+    void allDepsStopped();
+    
+    // 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
+    void failed_to_start();
+    
+    // A dependency of this service failed to start.
+    void failed_dependency();
+    
+    // For process services, start the process, return true on success
+    bool start_ps_process() noexcept;
+    bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
+    
+    // Callback from libev when a child process dies
+    static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
+            int revents) noexcept;
+    
+    static void process_child_status(struct ev_loop *loop, ev_io * stat_io,
+            int revents) noexcept;
+    
+    void handle_exit_status() noexcept;
+    
+    // A dependency has reached STARTED state
+    void dependencyStarted() noexcept;
+    
+    void allDepsStarted(bool haveConsole = false) noexcept;
+    
+    // Read the pid-file, return false on failure
+    bool read_pid_file() noexcept;
+    
+    // Open the activation socket, return false on failure
+    bool open_socket() noexcept;
+    
+    // Check whether dependencies have started, and optionally ask them to start
+    bool startCheckDependencies(bool do_start) 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).
+    bool can_interrupt_start() noexcept
+    {
+        return waiting_for_deps;
+    }
+    
+    // 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 dependentStopped() noexcept;
+
+    // check if all dependents have stopped
+    bool stopCheckDependents() noexcept;
+    
+    // issue a stop to all dependents, return true if they are all already stopped
+    bool stopDependents() noexcept;
+    
+    void forceStop() noexcept; // force-stop this service and all dependents
+    
+    void notifyListeners(ServiceEvent event) noexcept
+    {
+        for (auto l : listeners) {
+            l->serviceEvent(this, event);
+        }
+    }
+    
+    // Queue to run on the console. 'acquiredConsole()' will be called when the console is available.
+    void queueForConsole() noexcept;
+    
+    // Console is available.
+    void acquiredConsole() noexcept;
+    
+    // Release console (console must be currently held by this service)
+    void releaseConsole() noexcept;
+    
+    public:
+
+    ServiceRecord(ServiceSet *set, string name)
+        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false),
+            pinned_stopped(false), pinned_started(false), waiting_for_deps(false),
+            waiting_for_execstat(false), doing_recovery(false), force_stop(false)
+    {
+        service_set = set;
+        service_name = name;
+        service_type = ServiceType::DUMMY;
+    }
+    
+    ServiceRecord(ServiceSet *set, string name, ServiceType service_type, string &&command, std::list<std::pair<unsigned,unsigned>> &command_offsets,
+            sr_list * pdepends_on, sr_list * pdepends_soft)
+        : ServiceRecord(set, name)
+    {
+        service_set = set;
+        service_name = name;
+        this->service_type = service_type;
+        this->depends_on = std::move(*pdepends_on);
+
+        program_name = command;
+        exec_arg_parts = separate_args(program_name, command_offsets);
+
+        for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
+            (*i)->dependents.push_back(this);
+        }
+
+        // Soft dependencies
+        auto b_iter = soft_deps.end();
+        for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) {
+            b_iter = soft_deps.emplace(b_iter, this, *i);
+            (*i)->soft_dpts.push_back(&(*b_iter));
+            ++b_iter;
+        }
+    }
+    
+    // TODO write a destructor
+    
+    // Set the stop command and arguments (may throw std::bad_alloc)
+    void setStopCommand(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);
+    }
+    
+    // Get the current service state.
+    ServiceState getState() noexcept
+    {
+        return service_state;
+    }
+    
+    // Get the target (aka desired) state.
+    ServiceState getTargetState() noexcept
+    {
+        return desired_state;
+    }
+
+    // Set logfile, should be done before service is started
+    void setLogfile(string logfile)
+    {
+        this->logfile = logfile;
+    }
+    
+    // Set whether this service should automatically restart when it dies
+    void setAutoRestart(bool auto_restart) noexcept
+    {
+        this->auto_restart = auto_restart;
+    }
+    
+    void setSmoothRecovery(bool smooth_recovery) noexcept
+    {
+        this->smooth_recovery = smooth_recovery;
+    }
+    
+    // Set "on start" flags (commands)
+    void setOnstartFlags(OnstartFlags flags) noexcept
+    {
+        this->onstart_flags = flags;
+    }
+    
+    // Set an additional signal (other than SIGTERM) to be used to terminate the process
+    void setExtraTerminationSignal(int signo) noexcept
+    {
+        this->term_signal = signo;
+    }
+    
+    void set_pid_file(string &&pid_file) noexcept
+    {
+        this->pid_file = pid_file;
+    }
+    
+    void set_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) noexcept
+    {
+        this->socket_path = socket_path;
+        this->socket_perms = socket_perms;
+        this->socket_uid = socket_uid;
+        this->socket_gid = socket_gid;
+    }
+
+    const char *getServiceName() const noexcept { return service_name.c_str(); }
+    ServiceState getState() const noexcept { return service_state; }
+    
+    void start() noexcept;  // start the service
+    void stop() noexcept;   // stop the service
+    
+    void pinStart() noexcept;  // start the service and pin it
+    void pinStop() noexcept;   // stop the service and pin it
+    void unpin() noexcept;     // unpin the service
+    
+    bool isDummy() noexcept
+    {
+        return service_type == ServiceType::DUMMY;
+    }
+    
+    // Add a listener. A listener must only be added once. May throw std::bad_alloc.
+    void addListener(ServiceListener * listener)
+    {
+        listeners.insert(listener);
+    }
+    
+    // Remove a listener.    
+    void removeListener(ServiceListener * listener) noexcept
+    {
+        listeners.erase(listener);
+    }
+};
+
+
+class ServiceSet
+{
+    int active_services;
+    std::list<ServiceRecord *> records;
+    const char *service_dir;  // directory containing service descriptions
+    bool restart_enabled; // whether automatic restart is enabled (allowed)
+    
+    ShutdownType shutdown_type = ShutdownType::CONTINUE;  // Shutdown type, if stopping
+    
+    ServiceRecord * console_queue_tail = nullptr; // last record in console queue
+    
+    // Private methods
+        
+    // 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
+    ServiceRecord *loadServiceRecord(const char *name);
+
+    // Public
+    
+    public:
+    ServiceSet(const char *service_dir)
+    {
+        this->service_dir = service_dir;
+        active_services = 0;
+        restart_enabled = true;
+    }
+    
+    // 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 startService(const char *name);
+    
+    // Locate an existing service record.
+    ServiceRecord *findService(const std::string &name) noexcept;
+    
+    // Find a loaded service record, or load it if it is not loaded.
+    // Throws:
+    //   ServiceLoadException (or subclass) on problem with service description
+    //   std::bad_alloc on out-of-memory condition 
+    ServiceRecord *loadService(const std::string &name)
+    {
+        ServiceRecord *record = findService(name);
+        if (record == nullptr) {
+            record = loadServiceRecord(name.c_str());
+        }
+        return record;
+    }
+    
+    // Stop the service with the given name. The named service will begin
+    // transition to the 'stopped' state.
+    void stopService(const std::string &name) noexcept;
+    
+    // Set the console queue tail (returns previous tail)
+    ServiceRecord * consoleQueueTail(ServiceRecord * newTail) noexcept
+    {
+        auto prev_tail = console_queue_tail;
+        console_queue_tail = newTail;
+        return prev_tail;
+    }
+    
+    // Notification from service that it is active (state != STOPPED)
+    // Only to be called on the transition from inactive to active.
+    void service_active(ServiceRecord *) noexcept;
+    
+    // Notification from service that it is inactive (STOPPED)
+    // Only to be called on the transition from active to inactive.
+    void service_inactive(ServiceRecord *) 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(ShutdownType type = ShutdownType::HALT) noexcept
+    {
+        restart_enabled = false;
+        shutdown_type = type;
+        for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
+            (*i)->stop();
+            (*i)->unpin();
+        }
+    }
+    
+    void set_auto_restart(bool restart) noexcept
+    {
+        restart_enabled = restart;
+    }
+    
+    bool get_auto_restart() noexcept
+    {
+        return restart_enabled;
+    }
+    
+    ShutdownType getShutdownType() noexcept
+    {
+        return shutdown_type;
+    }
+};
+
+#endif
diff --git a/src/shutdown.cc b/src/shutdown.cc
new file mode 100644 (file)
index 0000000..1d46ab2
--- /dev/null
@@ -0,0 +1,197 @@
+// #include <netinet/in.h>
+#include <cstddef>
+#include <cstdio>
+#include <csignal>
+#include <unistd.h>
+#include <cstring>
+#include <string>
+#include <iostream>
+
+#include <sys/reboot.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "control-cmds.h"
+#include "service-constants.h"
+
+// shutdown:  shut down the system
+// This utility communicates with the dinit daemon via a unix socket (/dev/initctl).
+
+void do_system_shutdown(ShutdownType shutdown_type);
+static void unmount_disks();
+static void swap_off();
+
+int main(int argc, char **argv)
+{
+    using namespace std;
+    
+    bool show_help = false;
+    bool sys_shutdown = false;
+    
+    auto shutdown_type = ShutdownType::POWEROFF;
+        
+    for (int i = 1; i < argc; i++) {
+        if (argv[i][0] == '-') {
+            if (strcmp(argv[i], "--help") == 0) {
+                show_help = true;
+                break;
+            }
+            
+            if (strcmp(argv[i], "--system") == 0) {
+                sys_shutdown = true;
+            }
+            else if (strcmp(argv[i], "-r") == 0) {
+                shutdown_type = ShutdownType::REBOOT;
+            }
+            else if (strcmp(argv[i], "-h") == 0) {
+                shutdown_type = ShutdownType::HALT;
+            }
+            else if (strcmp(argv[i], "-p") == 0) {
+                shutdown_type = ShutdownType::POWEROFF;
+            }
+            else {
+                cerr << "Unrecognized command-line parameter: " << argv[i] << endl;
+                return 1;
+            }
+        }
+        else {
+            // time argument? TODO
+            show_help = true;
+        }
+    }
+
+    if (show_help) {
+        cout << "dinit-shutdown :   shutdown the system" << endl;
+        cout << "  --help           : show this help" << endl;
+        cout << "  -r               : reboot" << endl;
+        cout << "  -h               : halt system" << endl;
+        cout << "  -p               : power down (default)" << endl;
+        cout << "  --system         : perform shutdown immediately, instead of issuing shutdown" << endl;
+        cout << "                     command to the init program. Not recommended for use" << endl;
+        cout << "                     by users." << endl;
+        return 1;
+    }
+    
+    if (sys_shutdown) {
+        do_system_shutdown(shutdown_type);
+        return 0;
+    }
+    
+    int socknum = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (socknum == -1) {
+        perror("socket");
+        return 1;
+    }
+
+    const char *naddr = "/dev/dinitctl";
+    
+    struct sockaddr_un name;
+    name.sun_family = AF_UNIX;
+    strcpy(name.sun_path, naddr);
+    int sunlen = offsetof(struct sockaddr_un, sun_path) + strlen(naddr) + 1; // family, (string), nul
+    
+    int connr = connect(socknum, (struct sockaddr *) &name, sunlen);
+    if (connr == -1) {
+        perror("connect");
+        return 1;
+    }
+    
+    // Build buffer;
+    //uint16_t sname_len = strlen(service_name);
+    int bufsize = 2;
+    char * buf = new char[bufsize];
+    
+    buf[0] = DINIT_CP_SHUTDOWN;
+    buf[1] = static_cast<char>(shutdown_type);
+    
+    cout << "Issuing shutdown command..." << endl; // DAV
+    
+    // TODO make sure to write the whole buffer
+    int r = write(socknum, buf, bufsize);
+    if (r == -1) {
+        perror("write");
+    }
+    
+    // Wait for ACK/NACK
+    r = read(socknum, buf, 1);
+    // TODO: check result
+    
+    return 0;
+}
+
+void do_system_shutdown(ShutdownType shutdown_type)
+{
+    using namespace std;
+    
+    int reboot_type = 0;
+    if (shutdown_type == ShutdownType::REBOOT) reboot_type = RB_AUTOBOOT;
+    else if (shutdown_type == ShutdownType::POWEROFF) reboot_type = RB_POWER_OFF;
+    else reboot_type = RB_HALT_SYSTEM;
+    
+    // Write to console rather than any terminal, since we lose the terminal it seems:
+    close(STDOUT_FILENO);
+    int consfd = open("/dev/console", O_WRONLY);
+    if (consfd != STDOUT_FILENO) {
+        dup2(consfd, STDOUT_FILENO);
+    }
+    
+    cout << "Sending TERM/KILL to all processes..." << endl; // DAV
+    
+    // Send TERM/KILL to all (remaining) processes
+    kill(-1, SIGTERM);
+    sleep(1);
+    kill(-1, SIGKILL);
+    
+    // cout << "Sending QUIT to init..." << endl; // DAV
+    
+    // Tell init to exec reboot:
+    // TODO what if it's not PID=1? probably should have dinit pass us its PID
+    // kill(1, SIGQUIT);
+    
+    // TODO can we wait somehow for above to work?
+    // maybe have a pipe/socket and we read from our end...
+    
+    // TODO: close all ancillary file descriptors.
+    
+    // perform shutdown
+    cout << "Turning off swap..." << endl;
+    swap_off();
+    cout << "Unmounting disks..." << endl;
+    unmount_disks();
+    sync();
+    
+    cout << "Issuing shutdown via kernel..." << endl;
+    reboot(reboot_type);
+}
+
+static void unmount_disks()
+{
+    pid_t chpid = fork();
+    if (chpid == 0) {
+        // umount -a -r
+        //  -a : all filesystems (except proc)
+        //  -r : mount readonly if can't unmount
+        execl("/bin/umount", "/bin/umount", "-a", "-r", nullptr);
+    }
+    else if (chpid > 0) {
+        int status;
+        waitpid(chpid, &status, 0);
+    }
+}
+
+static void swap_off()
+{
+    pid_t chpid = fork();
+    if (chpid == 0) {
+        // swapoff -a
+        execl("/sbin/swapoff", "/sbin/swapoff", "-a", nullptr);
+    }
+    else if (chpid > 0) {
+        int status;
+        waitpid(chpid, &status, 0);
+    }
+}