Implement service unloading.
authorDavin McCall <davmac@davmac.org>
Sat, 10 Feb 2018 17:22:41 +0000 (17:22 +0000)
committerDavin McCall <davmac@davmac.org>
Sat, 10 Feb 2018 17:22:41 +0000 (17:22 +0000)
Implements the requisite control protocol parts and the dinitctl
"unload" command.

TODO
doc/manpages/dinitctl.8
src/control.cc
src/dinitctl.cc
src/includes/control-cmds.h
src/includes/control.h
src/includes/service.h

diff --git a/TODO b/TODO
index 72220a4b6d00640ca0193c31d423851fa00442bf..1dc702dea5c59b2ff834416388e4a2e8078950cb 100644 (file)
--- 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,
index cdb84e1ac97393e6cd1c7829fa53dbe1809ed1ab..0784e792648bcc3d962ad040400fe33d214ca3d2 100644 (file)
@@ -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:
index a804b51e89a67cdd2048e82a71a7f610b5bf8a7b..7ead46e42190c23b22afa2fc6f4e64b94d16882b 100644 (file)
@@ -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;
index 9e47bd8525e0cc220dd71f474a645a05ba8d30c8..983f123fe297b3eae806eed16f51242e79314e56 100644 (file)
@@ -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] <service-name>  : start but do not mark activated" << endl;
         cout << "    dinitctl [options] release [options] <service-name> : release activation, stop if no dependents" << endl;
         cout << "    dinitctl [options] unpin <service-name>           : un-pin the service (after a previous pin)" << endl;
+        cout << "    dinitctl unload <service-name>                    : 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<char[]> 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<char[]> 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;
index 8e7967fb56d0a2db625d85f53b4601497854f836..088e0d59dba10e1c6a4dae2c9ba7a033a091d45f 100644 (file)
@@ -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
index bbbda5204edb659ad297d79272449966cc5c471f..eae7a893dcc7f6d4c88f252d6886244aca7664b2 100644 (file)
@@ -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
index 64da4bca3434ad6a0b17990dc85ed2c3a4bdbf67..e7f96a50e61daa17af68ca79e55c2101bff95105 100644 (file)
@@ -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.