From 742adcb8a9d507259ccf60b7d7a86b7ca114ea4e Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Sat, 10 Feb 2018 17:22:41 +0000 Subject: [PATCH] Implement service unloading. Implements the requisite control protocol parts and the dinitctl "unload" command. --- TODO | 1 - doc/manpages/dinitctl.8 | 7 ++++ src/control.cc | 61 ++++++++++++++++++++++++++-- src/dinitctl.cc | 79 +++++++++++++++++++++++++++++++++++-- src/includes/control-cmds.h | 3 ++ src/includes/control.h | 3 ++ src/includes/service.h | 22 ++++++++++- 7 files changed, 168 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index 72220a4..1dc702d 100644 --- a/TODO +++ b/TODO @@ -15,7 +15,6 @@ For version 1.0: * Write wtmp entry on startup (see simpleinit) * "triggered" service type: external process notifies Dinit when the service has started. -* Ability to reload stopped services (reload service description) * Some way to specify environment (perhaps a common environment for all processes) * Load services from several different directories, with an order of precedence, diff --git a/doc/manpages/dinitctl.8 b/doc/manpages/dinitctl.8 index cdb84e1..0784e79 100644 --- a/doc/manpages/dinitctl.8 +++ b/doc/manpages/dinitctl.8 @@ -20,6 +20,9 @@ dinitctl \- control services supervised by Dinit [\-s] [\-\-quiet] unpin [\fIservice-name\fR] .br .B dinitctl +unload [\fIservice-name\fR] +.br +.B dinitctl [\-s] list .\" .SH DESCRIPTION @@ -70,6 +73,10 @@ has no active dependents, it will stop. If a started service has a dependency se it will stop. If a stopped service has a dependent service which is starting, it will start. Otherwise, any pending start/stop commands will be carried out. .TP +\fBunload\fR +Completely unload a service. This can only be done if the service is stopped and has no loaded dependents +(i.e. dependents must be unloaded before their dependencies). +.TP \fBlist\fR List loaded services and their state. Before each service, one of the following state indicators is displayed: diff --git a/src/control.cc b/src/control.cc index a804b51..7ead46e 100644 --- a/src/control.cc +++ b/src/control.cc @@ -33,6 +33,9 @@ bool control_conn_t::process_packet() if (pktType == DINIT_CP_UNPINSERVICE) { return process_unpin_service(); } + if (pktType == DINIT_CP_UNLOADSERVICE) { + return process_unload_service(); + } if (pktType == DINIT_CP_SHUTDOWN) { // Shutdown/reboot if (rbuf.get_length() < 2) { @@ -234,13 +237,65 @@ bool control_conn_t::process_unpin_service() iob.set_watches(OUT_EVENTS); return true; } + + service->unpin(); + services->process_queues(); + char ack_buf[] = { (char) DINIT_RP_ACK }; + if (! queue_packet(ack_buf, 1)) return false; + + // Clear the packet from the buffer + rbuf.consume(pkt_size); + chklen = 0; + return true; +} + +bool control_conn_t::process_unload_service() +{ + using std::string; + + constexpr int pkt_size = 1 + sizeof(handle_t); + + if (rbuf.get_length() < pkt_size) { + chklen = pkt_size; + return true; + } + + // 1 byte: packet type + // 4 bytes: service handle + + handle_t handle; + rbuf.extract((char *) &handle, 1, sizeof(handle)); + + service_record *service = find_service_for_key(handle); + if (service == nullptr) { + // Service handle is bad + char badreq_rep[] = { DINIT_RP_BADREQ }; + if (! queue_packet(badreq_rep, 1)) return false; + bad_conn_close = true; + iob.set_watches(OUT_EVENTS); + return true; + } + + if (! service->has_lone_ref() || service->get_state() != service_state_t::STOPPED) { + // Cannot unload: has other references + char nak_rep[] = { DINIT_RP_NAK }; + if (! queue_packet(nak_rep, 1)) return false; + } else { - service->unpin(); - services->process_queues(); + // unload + service->prepare_for_unload(); + services->remove_service(service); + delete service; + + // drop handle + service_key_map.erase(service); + key_service_map.erase(handle); + + // send ack char ack_buf[] = { (char) DINIT_RP_ACK }; if (! queue_packet(ack_buf, 1)) return false; } - + // Clear the packet from the buffer rbuf.consume(pkt_size); chklen = 0; diff --git a/src/dinitctl.cc b/src/dinitctl.cc index 9e47bd8..983f123 100644 --- a/src/dinitctl.cc +++ b/src/dinitctl.cc @@ -33,10 +33,11 @@ class read_cp_exception enum class command_t; -static int issue_load_service(int socknum, const char *service_name); +static int issue_load_service(int socknum, const char *service_name, bool find_only = false); static int check_load_reply(int socknum, cpbuffer<1024> &rbuffer, handle_t *handle_p, service_state_t *state_p); static int start_stop_service(int socknum, const char *service_name, command_t command, bool do_pin, bool wait_for_service, bool verbose); static int unpin_service(int socknum, const char *service_name, bool verbose); +static int unload_service(int socknum, const char *service_name); static int list_services(int socknum); static int shutdown_dinit(int soclknum); @@ -131,6 +132,7 @@ enum class command_t { STOP_SERVICE, RELEASE_SERVICE, UNPIN_SERVICE, + UNLOAD_SERVICE, LIST_SERVICES, SHUTDOWN }; @@ -191,6 +193,9 @@ int main(int argc, char **argv) else if (strcmp(argv[i], "unpin") == 0) { command = command_t::UNPIN_SERVICE; } + else if (strcmp(argv[i], "unload") == 0) { + command = command_t::UNLOAD_SERVICE; + } else if (strcmp(argv[i], "list") == 0) { command = command_t::LIST_SERVICES; } @@ -232,6 +237,7 @@ int main(int argc, char **argv) cout << " dinitctl [options] wake [options] : start but do not mark activated" << endl; cout << " dinitctl [options] release [options] : release activation, stop if no dependents" << endl; cout << " dinitctl [options] unpin : un-pin the service (after a previous pin)" << endl; + cout << " dinitctl unload : unload the service" << endl; cout << " dinitctl list : list loaded services" << endl; cout << " dinitctl shutdown : stop all services and terminate dinit" << endl; @@ -301,6 +307,9 @@ int main(int argc, char **argv) if (command == command_t::UNPIN_SERVICE) { return unpin_service(socknum, service_name, verbose); } + else if (command == command_t::UNLOAD_SERVICE) { + return unload_service(socknum, service_name); + } else if (command == command_t::LIST_SERVICES) { return list_services(socknum); } @@ -475,7 +484,7 @@ static int start_stop_service(int socknum, const char *service_name, command_t c // Issue a "load service" command (DINIT_CP_LOADSERVICE), without waiting for // a response. Returns 1 on failure (with error logged), 0 on success. -static int issue_load_service(int socknum, const char *service_name) +static int issue_load_service(int socknum, const char *service_name, bool find_only) { // Build buffer; uint16_t sname_len = strlen(service_name); @@ -486,7 +495,7 @@ static int issue_load_service(int socknum, const char *service_name) std::unique_ptr ubuf(new char[bufsize]); auto buf = ubuf.get(); - buf[0] = DINIT_CP_LOADSERVICE; + buf[0] = find_only ? DINIT_CP_FINDSERVICE : DINIT_CP_LOADSERVICE; memcpy(buf + 1, &sname_len, 2); memcpy(buf + 3, service_name, sname_len); @@ -589,6 +598,70 @@ static int unpin_service(int socknum, const char *service_name, bool verbose) return 0; } +static int unload_service(int socknum, const char *service_name) +{ + using namespace std; + + // Build buffer; + if (issue_load_service(socknum, service_name, true) == 1) { + return 1; + } + + // Now we expect a reply: + + try { + cpbuffer<1024> rbuffer; + wait_for_reply(rbuffer, socknum); + + handle_t handle; + + if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) { + return 1; + } + + // Issue UNLOAD command. + { + int r; + + { + char *buf = new char[1 + sizeof(handle)]; + unique_ptr ubuf(buf); + buf[0] = DINIT_CP_UNLOADSERVICE; + memcpy(buf + 1, &handle, sizeof(handle)); + r = write_all(socknum, buf, 2 + sizeof(handle)); + } + + if (r == -1) { + perror("dinitctl: write"); + return 1; + } + + wait_for_reply(rbuffer, socknum); + if (rbuffer[0] == DINIT_RP_NAK) { + cerr << "dinitctl: Could not unload service; service not stopped, or is a dependency of " + "other service." << endl; + return 1; + } + if (rbuffer[0] != DINIT_RP_ACK) { + cerr << "dinitctl: Protocol error." << endl; + return 1; + } + rbuffer.consume(1); + } + } + catch (read_cp_exception &exc) { + cerr << "dinitctl: Control socket read failure or protocol error" << endl; + return 1; + } + catch (std::bad_alloc &exc) { + cerr << "dinitctl: Out of memory" << endl; + return 1; + } + + cout << "Service unloaded." << endl; + return 0; +} + static int list_services(int socknum) { using namespace std; diff --git a/src/includes/control-cmds.h b/src/includes/control-cmds.h index 8e7967f..088e0d5 100644 --- a/src/includes/control-cmds.h +++ b/src/includes/control-cmds.h @@ -22,6 +22,9 @@ constexpr static int DINIT_CP_UNPINSERVICE = 7; // List services: constexpr static int DINIT_CP_LISTSERVICES = 8; +// Unload a service: +constexpr static int DINIT_CP_UNLOADSERVICE = 9; + // Shutdown: constexpr static int DINIT_CP_SHUTDOWN = 10; // followed by 1-byte shutdown type diff --git a/src/includes/control.h b/src/includes/control.h index bbbda52..eae7a89 100644 --- a/src/includes/control.h +++ b/src/includes/control.h @@ -141,6 +141,9 @@ class control_conn_t : private service_listener // Process an UNPINSERVICE packet. May throw std::bad_alloc. bool process_unpin_service(); + // Process an UNLOADSERVICE packet. + bool process_unload_service(); + bool list_services(); // Notify that data is ready to be read from the socket. Returns true if the connection should diff --git a/src/includes/service.h b/src/includes/service.h index 64da4bc..e7f96a5 100644 --- a/src/includes/service.h +++ b/src/includes/service.h @@ -571,6 +571,26 @@ class service_record { listeners.erase(listener); } + + // Assuming there is one reference (from a control link), return true if this is the only reference, + // or false if there are others (including dependents). + bool has_lone_ref() noexcept + { + if (! dependents.empty()) return false; + auto i = listeners.begin(); + return (++i == listeners.end()); + } + + // Prepare this service to be unloaded. + void prepare_for_unload() noexcept + { + // Remove all dependencies: + for (auto &dep : depends_on) { + auto &dep_dpts = dep.get_to()->dependents; + dep_dpts.erase(std::find(dep_dpts.begin(), dep_dpts.end(), &dep)); + } + depends_on.clear(); + } }; inline auto extract_prop_queue(service_record *sr) -> decltype(sr->prop_queue_node) & @@ -687,7 +707,7 @@ class service_set void remove_service(service_record *svc) { - std::remove(records.begin(), records.end(), svc); + records.erase(std::find(records.begin(), records.end(), svc)); } // Get the list of all loaded services. -- 2.25.1