Browse Source

dinitctl: allow enable/disable without contacting running daemon

Add an "--offline" (-o) mode that can be used with enable/disable
subcommands, to enable or disable a service without contacting the
running dinit instance (i.e. even if dinit isn't running).

Useful for installation environments.

Pending: documentation, tests
Davin McCall 8 months ago
parent
commit
788d65ff99
2 changed files with 175 additions and 82 deletions
  1. 2 2
      src/Makefile
  2. 173 80
      src/dinitctl.cc

+ 2 - 2
src/Makefile

@@ -23,8 +23,8 @@ all: dinit dinitctl dinitcheck dinit-monitor $(SHUTDOWN)
 dinit: $(dinit_objects)
 	$(CXX) -o dinit $(dinit_objects) $(ALL_LDFLAGS)
 
-dinitctl: dinitctl.o
-	$(CXX) -o dinitctl dinitctl.o $(ALL_LDFLAGS)
+dinitctl: dinitctl.o options-processing.o
+	$(CXX) -o dinitctl dinitctl.o options-processing.o $(ALL_LDFLAGS)
 
 dinitcheck: dinitcheck.o options-processing.o
 	$(CXX) -o dinitcheck dinitcheck.o options-processing.o $(ALL_LDFLAGS)

+ 173 - 80
src/dinitctl.cc

@@ -23,6 +23,7 @@
 #include "dinit-client.h"
 #include "load-service.h"
 #include "dinit-util.h"
+#include "options-processing.h"
 #include "mconfig.h"
 
 // dinitctl:  utility to control the Dinit daemon, including starting and stopping of services.
@@ -48,8 +49,8 @@ static int service_status(int socknum, cpbuffer_t &rbuffer, const char *service_
 static int shutdown_dinit(int soclknum, cpbuffer_t &, bool verbose);
 static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, const char *service_from,
         const char *service_to, dependency_type dep_type, bool verbose);
-static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to,
-        bool enable, bool verbose);
+static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, service_dir_opt &service_dir_opts,
+        const char *from, const char *to, bool enable, bool verbose);
 static int do_setenv(int socknum, cpbuffer_t &rbuffer, std::vector<const char *> &env_names);
 static int trigger_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool trigger_value);
 static int cat_service_log(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool do_clear);
@@ -100,6 +101,8 @@ int dinitctl_main(int argc, char **argv)
     const char * control_socket_path = nullptr;
     bool verbose = true;
     bool user_dinit = (getuid() != 0);  // communicate with user daemon
+    service_dir_opt service_dir_opts;
+    bool offline = false;
 
     // general command options
     command_t command = command_t::NONE;
@@ -200,6 +203,18 @@ int dinitctl_main(int argc, char **argv)
                     break;
                 }
             }
+            else if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
+                if (++i < argc) {
+                    service_dir_opts.set_specified_service_dir(argv[i]);
+                }
+                else {
+                    cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
+                    return 1;
+                }
+            }
+            else if (strcmp(argv[i], "--offline") == 0 || strcmp(argv[i], "-o") == 0) {
+                offline = true;
+            }
             else {
                 cerr << "dinitctl: unrecognized/invalid option: " << argv[i] << " (use --help for help)\n";
                 return 1;
@@ -455,12 +470,16 @@ int dinitctl_main(int argc, char **argv)
           "  --socket-path <path>, -p <path>\n"
           "                   : specify socket for communication with daemon\n"
           "  --use-passed-cfd : use the socket file descriptor identified by the DINIT_CS_FD\n"
-          "                     environment variable to communicate with the dinit daemon.\n"
+          "                     environment variable to communicate with the dinit daemon\n"
+          "  -o, --offline    : do not contact running dinit daemon\n"
+          "  -d, --services-dir <dir>\n"
+          "                   : specify directory for service definitions (offline mode)\n"
           "\n"
           "Command options:\n"
           "  --no-wait        : don't wait for service startup/shutdown to complete\n"
           "  --pin            : pin the service in the requested state\n"
-          "  --force          : force stop even if dependents will be affected\n";
+          "  --force          : force stop even if dependents will be affected\n"
+          "  -l, --list       : (signal) list supported signals\n";
         return 0;
     }
 
@@ -475,6 +494,26 @@ int dinitctl_main(int argc, char **argv)
         return signal_list();
     }
 
+    cpbuffer_t rbuffer;
+
+    if (offline) {
+        if (command != command_t::ENABLE_SERVICE && command != command_t::DISABLE_SERVICE) {
+            cerr << "dinitctl: offline mode (--offline/-o) not supported for this command\n";
+            return 1;
+        }
+
+        service_dir_opts.build_paths(!user_dinit);
+
+        if (command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE) {
+            // If only one service specified, assume that we enable for 'boot' service:
+            if (service_name == nullptr) {
+                service_name = "boot";
+            }
+            return enable_disable_service(-1, rbuffer, service_dir_opts, service_name, to_service_name,
+                    command == command_t::ENABLE_SERVICE, verbose);
+        }
+    }
+
     // Begin the real work: connect to dinit
 
     signal(SIGPIPE, SIG_IGN);
@@ -509,7 +548,6 @@ int dinitctl_main(int argc, char **argv)
         }
 
         // Start by querying protocol version:
-        cpbuffer_t rbuffer;
         uint16_t daemon_protocol_ver = check_protocol_version(min_cp_version, max_cp_version, rbuffer, socknum);
 
         if (command == command_t::UNPIN_SERVICE) {
@@ -540,7 +578,7 @@ int dinitctl_main(int argc, char **argv)
             if (service_name == nullptr) {
                 service_name = "boot";
             }
-            return enable_disable_service(socknum, rbuffer, service_name, to_service_name,
+            return enable_disable_service(socknum, rbuffer, service_dir_opts, service_name, to_service_name,
                     command == command_t::ENABLE_SERVICE, verbose);
         }
         else if (command == command_t::SETENV) {
@@ -1590,25 +1628,10 @@ static int shutdown_dinit(int socknum, cpbuffer_t &rbuffer, bool verbose)
     return 0;
 }
 
-// exception for cancelling a service operation
-class service_op_cancel { };
-
-static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to,
-        bool enable, bool verbose)
+static std::vector<std::string> get_service_description_dirs(int socknum, cpbuffer_t &rbuffer)
 {
     using namespace std;
 
-    service_state_t from_state = service_state_t::STARTED;
-    handle_t from_handle;
-
-    handle_t to_handle;
-
-    if (!load_service(socknum, rbuffer, from, &from_handle, &from_state)
-            || !load_service(socknum, rbuffer, to, &to_handle, nullptr)) {
-        return 1;
-    }
-
-    // Get service load path
     char buf[1] = { DINIT_CP_QUERY_LOAD_MECH };
     write_all_x(socknum, buf, 1);
 
@@ -1616,7 +1639,7 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
 
     if (rbuffer[0] != DINIT_RP_LOADER_MECH) {
         cerr << "dinitctl: control socket protocol error" << endl;
-        return 1;
+        return {};
     }
 
     // Packet type, load mechanism type, packet size:
@@ -1624,7 +1647,7 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
 
     if (rbuffer[1] != SSET_TYPE_DIRLOAD) {
         cerr << "dinitctl: unknown configuration, unable to load service descriptions" << endl;
-        return 1;
+        return {};
     }
 
     vector<string> paths;
@@ -1652,17 +1675,22 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
         fill_buffer_to(rbuffer, socknum, sizeof(uint32_t));
         rbuffer.extract(&plen, 0, sizeof(uint32_t));
         rbuffer.consume(sizeof(uint32_t));
-        paths.push_back(read_string(socknum, rbuffer, plen));
+        //paths.push_back(read_string(socknum, rbuffer, plen));
+        string sd_rel_path = read_string(socknum, rbuffer, plen);
+        paths.push_back(combine_paths(dinit_cwd, sd_rel_path.c_str()));
     }
 
-    // all service directories are now in the 'paths' vector
-    // Load/read service description for 'from' service:
+    return paths;
+}
 
-    ifstream service_file;
-    string service_file_path;
+// find (and open) a service description file in a set of paths
+static void find_service_desc(const char *svc_name, const std::vector<std::string> &paths,
+        std::ifstream &service_file, std::string &service_file_path)
+{
+    using namespace std;
 
     for (std::string path : paths) {
-        string test_path = combine_paths(combine_paths(dinit_cwd, path.c_str()), from);
+        string test_path = combine_paths(path, svc_name);
 
         service_file.open(test_path.c_str(), ios::in);
         if (service_file) {
@@ -1670,12 +1698,68 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
             break;
         }
     }
+}
+
+// exception for cancelling a service operation
+class service_op_cancel { };
+
+static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, service_dir_opt &service_dir_opts,
+        const char *from, const char *to, bool enable, bool verbose)
+{
+    using namespace std;
+
+    service_state_t from_state = service_state_t::STARTED;
+    handle_t from_handle;
+
+    handle_t to_handle;
+
+    vector<string> paths;
+
+    if (socknum != -1) {
+        if (!load_service(socknum, rbuffer, from, &from_handle, &from_state)
+                || !load_service(socknum, rbuffer, to, &to_handle, nullptr)) {
+            return 1;
+        }
+
+        paths = get_service_description_dirs(socknum, rbuffer);
+        if (paths.empty()) {
+            return 1;
+        }
+    }
+    else {
+        // offline case
+        const auto &path_list = service_dir_opts.get_paths();
+        for (auto &path : path_list) {
+            paths.emplace_back(path.get_dir());
+        }
+    }
 
-    if (! service_file) {
+    // all service directories are now in the 'paths' vector
+    // Load/read service description for 'from' service:
+
+    ifstream service_file;
+    string service_file_path;
+
+    find_service_desc(from, paths, service_file, service_file_path);
+    if (!service_file) {
         cerr << "dinitctl: could not locate service file for service '" << from << "'" << endl;
         return 1;
     }
 
+    ifstream to_service_file;
+    string to_service_file_path;
+    find_service_desc(to, paths, to_service_file, to_service_file_path);
+    if (!to_service_file) {
+        cerr << "dinitctl: ";
+        if (socknum >= 0) {
+            cerr << "warning: ";
+        }
+        cerr << "dinitctl: could not locate service file for target service '" << to << "'" << endl;
+        if (socknum < 0) {
+            return 1;
+        }
+    }
+
     // We now need to read the service file, identify the waits-for.d directory (bail out if more than one),
     // make sure the service is not listed as a dependency individually.
 
@@ -1735,36 +1819,38 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
         }
     }
 
-    // warn if 'from' service is not started
-    if (enable && from_state != service_state_t::STARTED) {
-        cerr << "dinitctl: warning: enabling dependency for non-started service" << endl;
-    }
+    if (socknum >= 0) {
+        // warn if 'from' service is not started
+        if (enable && from_state != service_state_t::STARTED) {
+            cerr << "dinitctl: warning: enabling dependency for non-started service" << endl;
+        }
 
-    // add/remove dependency
-    constexpr int enable_pktsize = 2 + sizeof(handle_t) * 2;
-    char cmdbuf[enable_pktsize] = { char(enable ? DINIT_CP_ENABLESERVICE : DINIT_CP_REM_DEP),
-            char(dependency_type::WAITS_FOR)};
-    memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
-    memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
-    write_all_x(socknum, cmdbuf, enable_pktsize);
+        // add/remove dependency
+        constexpr int enable_pktsize = 2 + sizeof(handle_t) * 2;
+        char cmdbuf[enable_pktsize] = { char(enable ? DINIT_CP_ENABLESERVICE : DINIT_CP_REM_DEP),
+                char(dependency_type::WAITS_FOR)};
+        memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle));
+        memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle));
+        write_all_x(socknum, cmdbuf, enable_pktsize);
 
-    wait_for_reply(rbuffer, socknum);
+        wait_for_reply(rbuffer, socknum);
 
-    // check reply
-    if (rbuffer[0] == DINIT_RP_NAK) {
-        if (enable) {
-            cerr << "dinitctl: could not enable service: possible circular dependency" << endl;
+        // check reply
+        if (rbuffer[0] == DINIT_RP_NAK) {
+            if (enable) {
+                cerr << "dinitctl: could not enable service: possible circular dependency" << endl;
+            }
+            else {
+                cerr << "dinitctl: service not currently enabled" << endl;
+            }
+            return 1;
         }
-        else {
-            cerr << "dinitctl: service not currently enabled" << endl;
+        if (rbuffer[0] != DINIT_RP_ACK) {
+            cerr << "dinitctl: control socket protocol error" << endl;
+            return 1;
         }
-        return 1;
-    }
-    if (rbuffer[0] != DINIT_RP_ACK) {
-        cerr << "dinitctl: control socket protocol error" << endl;
-        return 1;
+        rbuffer.consume(1);
     }
-    rbuffer.consume(1);
 
     // create link
     if (enable) {
@@ -1784,38 +1870,45 @@ static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *
         }
     }
 
-    // Check status of the service now
-    auto m = membuf()
-            .append<char>(DINIT_CP_SERVICESTATUS)
-            .append(to_handle);
-    write_all_x(socknum, m);
+    if (socknum >= 0) {
+        // Check status of the service now
+        auto m = membuf()
+                .append<char>(DINIT_CP_SERVICESTATUS)
+                .append(to_handle);
+        write_all_x(socknum, m);
 
-    wait_for_reply(rbuffer, socknum);
-    if (rbuffer[0] != DINIT_RP_SERVICESTATUS) {
-        cerr << "dinitctl: protocol error." << endl;
-        return 1;
-    }
-    rbuffer.consume(1);
+        wait_for_reply(rbuffer, socknum);
+        if (rbuffer[0] != DINIT_RP_SERVICESTATUS) {
+            cerr << "dinitctl: protocol error." << endl;
+            return 1;
+        }
+        rbuffer.consume(1);
 
-    int statussize = 6 + std::max(sizeof(pid_t), sizeof(int));;
-    fill_buffer_to(rbuffer, socknum, statussize + 1 /* reserved */);
-    rbuffer.consume(1);
-    service_state_t current = static_cast<service_state_t>(rbuffer[0]);
-    service_state_t target = static_cast<service_state_t>(rbuffer[1]);
-    rbuffer.consume(statussize);
+        int statussize = 6 + std::max(sizeof(pid_t), sizeof(int));;
+        fill_buffer_to(rbuffer, socknum, statussize + 1 /* reserved */);
+        rbuffer.consume(1);
+        service_state_t current = static_cast<service_state_t>(rbuffer[0]);
+        service_state_t target = static_cast<service_state_t>(rbuffer[1]);
+        rbuffer.consume(statussize);
 
-    if (verbose) {
-        cout << "Service '" << to << "' has been " << (enable ? "enabled" : "disabled") << "." << endl;
-    }
+        if (verbose) {
+            cout << "Service '" << to << "' has been " << (enable ? "enabled" : "disabled") << "." << endl;
+        }
 
-    if (enable) {
-        if (current != service_state_t::STARTED) {
-            wait_service_state(socknum, rbuffer, to_handle, to, false /* start */, verbose);
+        if (enable) {
+            if (current != service_state_t::STARTED) {
+                wait_service_state(socknum, rbuffer, to_handle, to, false /* start */, verbose);
+            }
+        }
+        else {
+            if (target != service_state_t::STOPPED) {
+                std::cerr << "dinitctl: note: disabled service may have other dependents\n";
+            }
         }
     }
     else {
-        if (target != service_state_t::STOPPED) {
-            std::cerr << "dinitctl: note: disabled service may have other dependents\n";
+        if (verbose) {
+            cout << "Service '" << to << "' has been " << (enable ? "enabled" : "disabled") << "." << endl;
         }
     }