X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fdinitctl.cc;h=fadc2fb055bd3ce99bd7b40e2521266e0a65fe37;hb=b6eb2f958d1ea7fd6302c3a29c5a4ec9f0023b93;hp=4e7f3f0dc41f1b70f1464ef14f9098c1c8cc9133;hpb=601fd527c64d2f9802cc41affef059476de17afb;p=oweals%2Fdinit.git diff --git a/src/dinitctl.cc b/src/dinitctl.cc index 4e7f3f0..fadc2fb 100644 --- a/src/dinitctl.cc +++ b/src/dinitctl.cc @@ -3,10 +3,13 @@ #include #include #include +#include #include #include +#include #include +#include #include #include #include @@ -18,10 +21,13 @@ #include "service-constants.h" #include "cpbuffer.h" #include "dinit-client.h" +#include "load-service.h" +#include "dinit-util.h" // dinitctl: utility to control the Dinit daemon, including starting and stopping of services. -// This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl, or $HOME/.dinitctl). +// This utility communicates with the dinit daemon via a unix stream socket (/dev/initctl, +// or $HOME/.dinitctl). static constexpr uint16_t min_cp_version = 1; static constexpr uint16_t max_cp_version = 1; @@ -36,9 +42,10 @@ static int unpin_service(int socknum, cpbuffer_t &, const char *service_name, bo static int unload_service(int socknum, cpbuffer_t &, const char *service_name); static int list_services(int socknum, cpbuffer_t &); static int shutdown_dinit(int soclknum, cpbuffer_t &); -static int add_dependency(int socknum, cpbuffer_t &rbuffer, char *service_from, char *service_to, - dependency_type dep_type); - +static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, const char *service_from, + const char *service_to, dependency_type dep_type); +static int enable_disable_service(int socknum, cpbuffer_t &rbuffer, const char *from, const char *to, + bool enable); static const char * describeState(bool stopped) { @@ -60,7 +67,10 @@ enum class command_t { UNLOAD_SERVICE, LIST_SERVICES, SHUTDOWN, - ADD_DEPENDENCY + ADD_DEPENDENCY, + RM_DEPENDENCY, + ENABLE_SERVICE, + DISABLE_SERVICE }; @@ -70,8 +80,8 @@ int main(int argc, char **argv) using namespace std; bool show_help = argc < 2; - char *service_name = nullptr; - char *to_service_name = nullptr; + const char *service_name = nullptr; + const char *to_service_name = nullptr; dependency_type dep_type; bool dep_type_set = false; @@ -103,7 +113,17 @@ int main(int argc, char **argv) else if (strcmp(argv[i], "--pin") == 0) { do_pin = true; } + else if ((command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE) + && strcmp(argv[i], "--from") == 0) { + ++i; + if (i == argc) { + cerr << "dinitctl: --from should be followed by a service name" << std::endl; + return 1; + } + service_name = argv[i]; + } else { + cerr << "dinitctl: unrecognized option: " << argv[i] << " (use --help for help)\n"; return 1; } } @@ -135,14 +155,23 @@ int main(int argc, char **argv) else if (strcmp(argv[i], "add-dep") == 0) { command = command_t::ADD_DEPENDENCY; } + else if (strcmp(argv[i], "rm-dep") == 0) { + command = command_t::RM_DEPENDENCY; + } + else if (strcmp(argv[i], "enable") == 0) { + command = command_t::ENABLE_SERVICE; + } + else if (strcmp(argv[i], "disable") == 0) { + command = command_t::DISABLE_SERVICE; + } else { - show_help = true; - break; + cerr << "dinitctl: unrecognized command: " << argv[i] << " (use --help for help)\n"; + return 1; } } else { // service name / other non-option - if (command == command_t::ADD_DEPENDENCY) { + if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) { if (! dep_type_set) { if (strcmp(argv[i], "regular") == 0) { dep_type = dependency_type::REGULAR; @@ -170,6 +199,13 @@ int main(int argc, char **argv) break; } } + else if (command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE) { + if (to_service_name != nullptr) { + show_help = true; + break; + } + to_service_name = argv[i]; + } else { if (service_name != nullptr) { show_help = true; @@ -183,43 +219,49 @@ int main(int argc, char **argv) bool no_service_cmd = (command == command_t::LIST_SERVICES || command == command_t::SHUTDOWN); - if (service_name != nullptr && no_service_cmd) { + if (command == command_t::ENABLE_SERVICE || command == command_t::DISABLE_SERVICE) { + show_help |= (to_service_name == nullptr); + } + else if ((service_name == nullptr && ! no_service_cmd) || command == command_t::NONE) { show_help = true; } - - if ((service_name == nullptr && ! no_service_cmd) || command == command_t::NONE) { + + if (service_name != nullptr && no_service_cmd) { show_help = true; } - if (command == command_t::ADD_DEPENDENCY && (! dep_type_set || service_name == nullptr - || to_service_name == nullptr)) { + if ((command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) + && (! dep_type_set || service_name == nullptr || to_service_name == nullptr)) { show_help = true; } if (show_help) { - cout << "dinitctl: control Dinit services" << endl; - - cout << "\nUsage:" << endl; - cout << " dinitctl [options] start [options] : start and activate service" << endl; - cout << " dinitctl [options] stop [options] : stop service and cancel explicit activation" << endl; - 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; - cout << " dinitctl add-dep : add a dependency between services" << endl; - - cout << "\nNote: An activated service continues running when its dependents stop." << endl; - - cout << "\nGeneral options:" << endl; - cout << " -s, --system : control system daemon instead of user daemon" << endl; - cout << " --quiet : suppress output (except errors)" << endl; - - cout << "\nCommand options:" << endl; - cout << " --help : show this help" << endl; - cout << " --no-wait : don't wait for service startup/shutdown to complete" << endl; - cout << " --pin : pin the service in the requested (started/stopped) state" << endl; + cout << "dinitctl: control Dinit services\n" + "\n" + "Usage:\n" + " dinitctl [options] start [options] \n" + " dinitctl [options] stop [options] \n" + " dinitctl [options] wake [options] \n" + " dinitctl [options] release [options] \n" + " dinitctl [options] unpin \n" + " dinitctl unload \n" + " dinitctl list\n" + " dinitctl shutdown\n" + " dinitctl add-dep \n" + " dinitctl rm-dep \n" + " dinitctl enable [--from ] \n" + " dinitctl disable [--from ] \n" + "\n" + "Note: An activated service continues running when its dependents stop.\n" + "\n" + "General options:\n" + " -s, --system : control system daemon instead of user daemon\n" + " --quiet : suppress output (except errors)\n" + "\n" + "Command options:\n" + " --help : show this help\n" + " --no-wait : don't wait for service startup/shutdown to complete\n" + " --pin : pin the service in the requested state\n"; return 1; } @@ -288,8 +330,17 @@ int main(int argc, char **argv) else if (command == command_t::SHUTDOWN) { return shutdown_dinit(socknum, rbuffer); } - else if (command == command_t::ADD_DEPENDENCY) { - return add_dependency(socknum, rbuffer, service_name, to_service_name, dep_type); + else if (command == command_t::ADD_DEPENDENCY || command == command_t::RM_DEPENDENCY) { + return add_remove_dependency(socknum, rbuffer, command == command_t::ADD_DEPENDENCY, + service_name, to_service_name, dep_type); + } + else 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(socknum, rbuffer, service_name, to_service_name, + command == command_t::ENABLE_SERVICE); } else { return start_stop_service(socknum, rbuffer, service_name, command, do_pin, @@ -314,6 +365,59 @@ int main(int argc, char **argv) } } +// Extract/read a string of specified length from the buffer/socket. The string is consumed +// from the buffer. +static std::string read_string(int socknum, cpbuffer_t &rbuffer, uint32_t length) +{ + int rb_len = rbuffer.get_length(); + if (uint32_t(rb_len) >= length) { + std::string r = rbuffer.extract_string(0, length); + rbuffer.consume(length); + return r; + } + + std::string r = rbuffer.extract_string(0, rb_len); + uint32_t rlen = length - rb_len; + uint32_t clen; + do { + rbuffer.reset(); + rbuffer.fill(socknum); + char *bptr = rbuffer.get_ptr(0); + clen = rbuffer.get_length(); + clen = std::min(clen, rlen); + r.append(bptr, clen); + rlen -= clen; + } while (rlen > 0); + + rbuffer.consume(clen); + + return r; +} + +// Load a service: issue load command, wait for reply. Return true on success, display error message +// and return false on failure. +// socknum - the socket fd to communicate via +// rbuffer - the buffer for communication +// name - the name of the service to load +// handle - where to store the handle of the loaded service +// state - where to store the state of the loaded service (may be null). +static bool load_service(int socknum, cpbuffer_t &rbuffer, const char *name, handle_t *handle, + service_state_t *state) +{ + // Load 'to' service: + if (issue_load_service(socknum, name)) { + return false; + } + + wait_for_reply(rbuffer, socknum); + + if (check_load_reply(socknum, rbuffer, handle, state) != 0) { + return false; + } + + return true; +} + // Start/stop a service static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, command_t command, bool do_pin, bool wait_for_service, bool verbose) @@ -321,21 +425,12 @@ static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *serv using namespace std; bool do_stop = (command == command_t::STOP_SERVICE || command == command_t::RELEASE_SERVICE); - - if (issue_load_service(socknum, service_name)) { - return 1; - } - - // Now we expect a reply: - - wait_for_reply(rbuffer, socknum); service_state_t state; - //service_state_t target_state; handle_t handle; - - if (check_load_reply(socknum, rbuffer, &handle, &state) != 0) { - return 0; + + if (! load_service(socknum, rbuffer, service_name, &handle, &state)) { + return 1; } service_state_t wanted_state = do_stop ? service_state_t::STOPPED : service_state_t::STARTED; @@ -370,7 +465,8 @@ static int start_stop_service(int socknum, cpbuffer_t &rbuffer, const char *serv if (rbuffer[0] == DINIT_RP_ALREADYSS) { bool already = (state == wanted_state); if (verbose) { - cout << "Service " << (already ? "(already) " : "") << describeState(do_stop) << "." << endl; + cout << "Service " << (already ? "(already) " : "") + << describeState(do_stop) << "." << endl; } return 0; // success! } @@ -473,6 +569,7 @@ static int issue_load_service(int socknum, const char *service_name, bool find_o } // Check that a "load service" reply was received, and that the requested service was found. +// state_p may be null. static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p, service_state_t *state_p) { using namespace std; @@ -498,22 +595,14 @@ static int check_load_reply(int socknum, cpbuffer_t &rbuffer, handle_t *handle_p static int unpin_service(int socknum, cpbuffer_t &rbuffer, const char *service_name, bool verbose) { using namespace std; + + handle_t handle; // Build buffer; - if (issue_load_service(socknum, service_name) == 1) { + if (! load_service(socknum, rbuffer, service_name, &handle, nullptr)) { return 1; } - - // Now we expect a reply: - wait_for_reply(rbuffer, socknum); - - handle_t handle; - - if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) { - return 1; - } - // Issue UNPIN command. { char buf[1 + sizeof(handle)]; @@ -539,17 +628,19 @@ static int unload_service(int socknum, cpbuffer_t &rbuffer, const char *service_ { using namespace std; - // Build buffer; if (issue_load_service(socknum, service_name, true) == 1) { return 1; } - // Now we expect a reply: - wait_for_reply(rbuffer, socknum); handle_t handle; + if (rbuffer[0] == DINIT_RP_NOSERVICE) { + cerr << "dinitctl: service not loaded." << endl; + return 1; + } + if (check_load_reply(socknum, rbuffer, &handle, nullptr) != 0) { return 1; } @@ -691,39 +782,22 @@ static int list_services(int socknum, cpbuffer_t &rbuffer) return 0; } -static int add_dependency(int socknum, cpbuffer_t &rbuffer, char *service_from, char *service_to, - dependency_type dep_type) +static int add_remove_dependency(int socknum, cpbuffer_t &rbuffer, bool add, + const char *service_from, const char *service_to, dependency_type dep_type) { using namespace std; - // First find the "from" service: - if (issue_load_service(socknum, service_from, true) == 1) { - return 1; - } - - wait_for_reply(rbuffer, socknum); handle_t from_handle; - - if (check_load_reply(socknum, rbuffer, &from_handle, nullptr) != 0) { - return 1; - } - - // Then find or load the "to" service: - if (issue_load_service(socknum, service_to, true) == 1) { - return 1; - } - - wait_for_reply(rbuffer, socknum); - handle_t to_handle; - if (check_load_reply(socknum, rbuffer, &to_handle, nullptr) != 0) { + if (! load_service(socknum, rbuffer, service_from, &from_handle, nullptr) + || ! load_service(socknum, rbuffer, service_to, &to_handle, nullptr)) { return 1; } constexpr int pktsize = 2 + sizeof(handle_t) * 2; - char cmdbuf[pktsize] = { (char)DINIT_CP_ADD_DEP, (char)dep_type}; + char cmdbuf[pktsize] = { add ? (char)DINIT_CP_ADD_DEP : (char)DINIT_CP_REM_DEP, (char)dep_type}; memcpy(cmdbuf + 2, &from_handle, sizeof(from_handle)); memcpy(cmdbuf + 2 + sizeof(from_handle), &to_handle, sizeof(to_handle)); write_all_x(socknum, cmdbuf, pktsize); @@ -781,3 +855,191 @@ static int shutdown_dinit(int socknum, cpbuffer_t &rbuffer) 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) +{ + 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); + + wait_for_reply(rbuffer, socknum); + + if (rbuffer[0] != DINIT_RP_LOADER_MECH) { + cerr << "dinitctl: Control socket protocol error" << endl; + return 1; + } + + // Packet type, load mechanism type, packet size: + fill_buffer_to(rbuffer, socknum, 2 + sizeof(uint32_t)); + + if (rbuffer[1] != SSET_TYPE_DIRLOAD) { + cerr << "dinitctl: unknown configuration, unable to load service descriptions" << endl; + return 1; + } + + vector paths; + + uint32_t pktsize; + rbuffer.extract(&pktsize, 2, sizeof(uint32_t)); + + fill_buffer_to(rbuffer, socknum, 2 + sizeof(uint32_t) * 3); // path entries, cwd length + + uint32_t path_entries; // number of service directories + rbuffer.extract(&path_entries, 2 + sizeof(uint32_t), sizeof(uint32_t)); + + uint32_t cwd_len; + rbuffer.extract(&cwd_len, 2 + sizeof(uint32_t) * 2, sizeof(uint32_t)); + rbuffer.consume(2 + sizeof(uint32_t) * 3); + pktsize -= 2 + sizeof(uint32_t) * 3; + + // Read current working directory of daemon: + std::string dinit_cwd = read_string(socknum, rbuffer, cwd_len); + + // dinit daemon base directory against which service paths are resolved is in dinit_cwd + + for (int i = 0; i < (int)path_entries; i++) { + uint32_t plen; + 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)); + } + + // all service directories are now in the 'paths' vector + // Load/read service description for 'from' service: + + ifstream service_file; + string service_file_path; + + for (std::string path : paths) { + string test_path = combine_paths(dinit_cwd + '/' + path, from); + + service_file.open(test_path.c_str(), ios::in); + if (service_file) { + service_file_path = test_path; + break; + } + } + + if (! service_file) { + cerr << "dinitctl: could not locate service file for service '" << from << "'" << endl; + 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. + + string waits_for_d; + + try { + process_service_file(from, service_file, [&](string &line, string &setting, + dinit_load::string_iterator i, dinit_load::string_iterator end) -> void { + if (setting == "waits-for" || setting == "depends-on" || setting == "depends-ms") { + string dname = dinit_load::read_setting_value(i, end); + if (dname == to) { + // There is already a dependency + cerr << "dinitctl: there is a fixed dependency to service '" << to + << "' in the service description of '" << from << "'." << endl; + throw service_op_cancel(); + } + } + else if (setting == "waits-for.d") { + string dname = dinit_load::read_setting_value(i, end); + if (! waits_for_d.empty()) { + cerr << "dinitctl: service '" << from << "' has multiple waits-for.d directories " + << "specified in service description" << endl; + throw service_op_cancel(); + } + waits_for_d = std::move(dname); + } + }); + } + catch (const service_op_cancel &cexc) { + return 1; + } + + // If the from service has no waits-for.d specified, we can't continue + if (waits_for_d.empty()) { + cerr << "dinitctl: service '" << from << "' has no waits-for.d directory specified" << endl; + return 1; + } + + // The waits-for.d path is relative to the service file path, combine: + string waits_for_d_full = combine_paths(parent_path(service_file_path), waits_for_d.c_str()); + + // check if dependency already exists + string dep_link_path = combine_paths(waits_for_d_full, to); + struct stat stat_buf; + if (lstat(dep_link_path.c_str(), &stat_buf) == -1) { + if (errno != ENOENT) { + cerr << "dinitctl: checking for existing dependency link: " << dep_link_path << ": " + << strerror(errno) << endl; + return 1; + } + } + else { + // dependency already exists + if (enable) { + cerr << "dinitctl: service already enabled." << endl; + return 1; + } + } + + // 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); + + wait_for_reply(rbuffer, socknum); + + // check reply + if (enable && rbuffer[0] == DINIT_RP_NAK) { + cerr << "dinitctl: Could not enable service: possible circular dependency" << endl; + return 1; + } + if (rbuffer[0] != DINIT_RP_ACK) { + cerr << "dinitctl: Control socket protocol error" << endl; + return 1; + } + + // create link + if (enable) { + if (symlink((string("../") + to).c_str(), dep_link_path.c_str()) == -1) { + cerr << "dinitctl: Could not create symlink at " << dep_link_path << ": " << strerror(errno) << "\n" + "dinitctl: Note: service was activated, but will not be enabled on restart." << endl; + return 1; + } + } + else { + if (unlink(dep_link_path.c_str()) == -1) { + cerr << "dinitctl: Could not unlink dependency entry " << dep_link_path << ": " << strerror(errno) << "\n" + "dinitctl: Note: service was disabled, but will be re-enabled on restart." << endl; + return 1; + } + } + + return 0; +}