From 8b81486ebc66c8688dea773410d1de4be74bbf67 Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Thu, 19 Nov 2015 18:28:34 +0000 Subject: [PATCH] Add a control command to initiate service rollback (and receive notification when rollback is complete). --- Makefile | 6 +-- control-cmds.h | 11 +++++ control.cc | 121 +++++++++++++++++++++++++++++++++++++++++++++++++ control.h | 116 +++++++++-------------------------------------- service.cc | 3 ++ service.h | 23 ++++++++++ 6 files changed, 182 insertions(+), 98 deletions(-) create mode 100644 control-cmds.h create mode 100644 control.cc diff --git a/Makefile b/Makefile index 827535c..10d789b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -include mconfig -objects = dinit.o load_service.o service.o dinit-log.o dinit-start.o +objects = dinit.o load_service.o service.o control.o dinit-log.o dinit-start.o -dinit_objects = dinit.o load_service.o service.o dinit-log.o +dinit_objects = dinit.o load_service.o service.o control.o dinit-log.o all: dinit dinit-start @@ -12,7 +12,7 @@ dinit: $(dinit_objects) dinit-start: dinit-start.o $(CXX) -o dinit-start dinit-start.o $(EXTRA_LIBS) -$(objects): %.o: %.cc service.h dinit-log.h +$(objects): %.o: %.cc service.h dinit-log.h control.h control-cmds.h $(CXX) -D_GLIBCXX_USE_CXX11_ABI=0 -std=gnu++11 -c -Os -Wall $< -o $@ #install: all diff --git a/control-cmds.h b/control-cmds.h new file mode 100644 index 0000000..dd208ca --- /dev/null +++ b/control-cmds.h @@ -0,0 +1,11 @@ +// Dinit control command packet types + +// Start or stop a named service: +constexpr static int DINIT_CP_STARTSERVICE = 0; +constexpr static int DINIT_CP_STOPSERVICE = 1; + +// Roll-back all services: +constexpr static int DINIT_CP_ROLLBACKALL = 2; + +// Reply: request completed +constexpr static int DINIT_RP_COMPLETED = 50; diff --git a/control.cc b/control.cc new file mode 100644 index 0000000..d32a602 --- /dev/null +++ b/control.cc @@ -0,0 +1,121 @@ +#include "control.h" +#include "service.h" + +void ControlConn::processPacket() +{ + using std::string; + + int pktType = iobuf[0]; + if (pktType == DINIT_CP_STARTSERVICE || pktType == DINIT_CP_STOPSERVICE) { + if (bufidx < 4) { + chklen = 4; + return; + } + + uint16_t svcSize; + memcpy(&svcSize, iobuf + 1, 2); + if (svcSize <= 0) { + // TODO error response + bufidx = 1024; // dataReady will delete - TODO clean up + } + + chklen = svcSize + 3; + if (chklen > 1024) { + // We can't have a service name this long + // TODO error response + bufidx = 1024; // TODO cleanup. + } + + if (bufidx < chklen) { + // packet not complete yet; read more + return; + } + + string serviceName(iobuf + 3, (size_t) svcSize); + if (pktType == DINIT_CP_STARTSERVICE) { + // TODO do not allow services to be started during system shutdown + service_set->startService(serviceName.c_str()); + // TODO catch exceptions, error response + } + else { + // TODO verify the named service exists? + service_set->stopService(serviceName.c_str()); + } + + // Clear the packet from the buffer + memmove(iobuf, iobuf + chklen, 1024 - chklen); + bufidx -= chklen; + chklen = 0; + return; + } + else if (pktType == DINIT_CP_ROLLBACKALL) { + // Roll-back all services + if (service_set->setRollbackHandler(this)) { + service_set->stop_all_services(); + log_to_console = true; + // TODO send ACK + } + else { + // TODO send NAK + } + } + else { + // TODO error response + } +} + +void ControlConn::rollbackComplete() +{ + char ackBuf[1] = { DINIT_RP_COMPLETED }; + if (write(iob.fd, ackBuf, 1) == -1) { + // TODO queue or at least re-try if error or 0 bytes written. + log(LogLevel::ERROR, "Couldn't write response to control socket"); + } +} + +void ControlConn::dataReady() +{ + int fd = iob.fd; + int buffree = 1024 - bufidx; + + int r = read(fd, iobuf + bufidx, buffree); + + // Note file descriptor is non-blocking + if (r == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return; + } + // TODO log error + delete this; + return; + } + + if (r == 0) { + delete this; + return; + } + + bufidx += r; + buffree -= r; + + // complete packet? + if (bufidx >= chklen) { + processPacket(); + } + + if (bufidx == 1024) { + // Too big packet + // TODO log error? + // TODO error response? + delete this; + } +} + +ControlConn::~ControlConn() +{ + close(iob.fd); + ev_io_stop(loop, &iob); + delete [] iobuf; + service_set->clearRollbackHandler(this); + active_control_conns--; +} diff --git a/control.h b/control.h index 51479f1..2454318 100644 --- a/control.h +++ b/control.h @@ -1,4 +1,10 @@ +#ifndef DINIT_CONTROL_H +#define DINIT_CONTROL_H + +#include #include +#include "dinit-log.h" +#include "control-cmds.h" // Control connection for dinit @@ -6,10 +12,12 @@ // 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; -// Packet types: -constexpr static int DINIT_CP_STARTSERVICE = 0; -constexpr static int DINIT_CP_STOPSERVICE = 1; +extern int active_control_conns; // "packet" format: // (1 byte) packet type @@ -18,6 +26,8 @@ constexpr static int DINIT_CP_STOPSERVICE = 1; // (2 bytes) service name length // (M buyes) service name (without nul terminator) +class ServiceSet; + class ControlConn { @@ -38,101 +48,15 @@ class ControlConn ev_io_init(&iob, control_conn_cb, fd, EV_READ); iob.data = this; ev_io_start(loop, &iob); - } - - void processPacket() - { - using std::string; - - int pktType = iobuf[0]; - if (pktType == DINIT_CP_STARTSERVICE || pktType == DINIT_CP_STOPSERVICE) { - if (bufidx < 4) { - chklen = 4; - return; - } - - uint16_t svcSize; - memcpy(&svcSize, iobuf + 1, 2); - if (svcSize <= 0) { - // TODO error response - bufidx = 1024; // dataReady will delete - TODO clean up - } - - chklen = svcSize + 3; - if (chklen > 1024) { - // We can't have a service name this long - // TODO error response - bufidx = 1024; // TODO cleanup. - } - - if (bufidx < chklen) { - // packet not complete yet; read more - return; - } - - string serviceName(iobuf + 3, (size_t) svcSize); - if (pktType == DINIT_CP_STARTSERVICE) { - service_set->startService(serviceName.c_str()); - // TODO catch exceptions, error response - } - else { - // TODO verify the named service exists? - service_set->stopService(serviceName.c_str()); - } - - // Clear the packet from the buffer - memmove(iobuf, iobuf + chklen, 1024 - chklen); - bufidx -= chklen; - chklen = 0; - return; - } - - } - - void dataReady() - { - int fd = iob.fd; - int buffree = 1024 - bufidx; - - int r = read(fd, iobuf + bufidx, buffree); - - // Note file descriptor is non-blocking - if (r == -1) { - if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { - return; - } - // TODO log error - delete this; - return; - } - - if (r == 0) { - delete this; - return; - } - - bufidx += r; - buffree -= r; - // complete packet? - if (bufidx >= chklen) { - processPacket(); - } - - if (bufidx == 1024) { - // Too big packet - // TODO log error? - // TODO error response? - delete this; - } + active_control_conns++; } - ~ControlConn() - { - close(iob.fd); - ev_io_stop(loop, &iob); - delete [] iobuf; - } + void processPacket(); + void rollbackComplete(); + void dataReady(); + + ~ControlConn(); }; @@ -141,3 +65,5 @@ static void control_conn_cb(struct ev_loop * loop, ev_io * w, int revents) ControlConn *conn = (ControlConn *) w->data; conn->dataReady(); } + +#endif diff --git a/service.cc b/service.cc index bc98925..c245a30 100644 --- a/service.cc +++ b/service.cc @@ -544,4 +544,7 @@ void ServiceSet::service_active(ServiceRecord *sr) void ServiceSet::service_inactive(ServiceRecord *sr) { active_services--; + if (active_services == 0 && rollback_handler != nullptr) { + rollback_handler->rollbackComplete(); + } } diff --git a/service.h b/service.h index b391cc8..31d277f 100644 --- a/service.h +++ b/service.h @@ -3,6 +3,7 @@ #include #include #include "ev.h" +#include "control.h" /* * Possible service states @@ -323,6 +324,7 @@ class ServiceSet std::list records; const char *service_dir; // directory containing service descriptions bool restart_enabled; // whether automatic restart is enabled (allowed) + ControlConn *rollback_handler; // recieves notification when all services stopped // Private methods @@ -386,4 +388,25 @@ class ServiceSet { return restart_enabled; } + + // Set the rollback handler, which will be notified when all services have stopped. + // There can be only one rollback handler; attempts to set it when already set will + // fail. Returns true if successful. + bool setRollbackHandler(ControlConn *conn) + { + if (rollback_handler == nullptr) { + rollback_handler = conn; + return true; + } + else { + return false; + } + } + + void clearRollbackHandler(ControlConn *conn) + { + if (rollback_handler == conn) { + rollback_handler = nullptr; + } + } }; -- 2.25.1