From b92fb6ffeb276a9b4c1cec7d9cdf0d6a49b08c5d Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Thu, 23 Jun 2016 12:33:36 +0100 Subject: [PATCH] Implement "list services" command in control protocol, and use it in "dinitctl list" command. --- TODO | 6 ++-- src/control-cmds.h | 7 +++- src/control.cc | 46 +++++++++++++++++++++++++ src/control.h | 2 ++ src/dinitctl.cc | 85 ++++++++++++++++++++++++++++++++++++++++++++-- src/service.cc | 2 +- src/service.h | 8 ++++- 7 files changed, 148 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index cd0ad0b..70528a4 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -* Complete control socket handling and protocol - - support for listing all services and their state * Implement a control utility to start/stop services after dinit has started - basic version exists, needs a little more work to support all start/stop command variants @@ -8,12 +6,14 @@ For version 1.0: ---------------- * Documentation including sample service definitions + - Man pages * Better error handling, logging of errors (largely done, still some patches of code where it may be missing - check TODO's in code). * Write wtmp entry on startup (see simpleinit) * Allow running services as a different UID * Must be able to specify kill timeout (after which kill -9!) -* Must rate-limit restart of services +* Must rate-limit restart of services. Trying to start at a greater rate + should trigger failed-to-start notification. * "triggered" service type: external process notifies Dinit when the service has started. diff --git a/src/control-cmds.h b/src/control-cmds.h index 98be5e5..8e7967f 100644 --- a/src/control-cmds.h +++ b/src/control-cmds.h @@ -19,6 +19,9 @@ constexpr static int DINIT_CP_RELEASESERVICE = 6; constexpr static int DINIT_CP_UNPINSERVICE = 7; +// List services: +constexpr static int DINIT_CP_LISTSERVICES = 8; + // Shutdown: constexpr static int DINIT_CP_SHUTDOWN = 10; // followed by 1-byte shutdown type @@ -57,7 +60,9 @@ constexpr static int DINIT_RP_NOSERVICE = 60; // Service is already started/stopped constexpr static int DINIT_RP_ALREADYSS = 61; - +// Information on a service / list complete: +constexpr static int DINIT_RP_SVCINFO = 62; +constexpr static int DINIT_RP_LISTDONE = 63; // Information: diff --git a/src/control.cc b/src/control.cc index 4e0c72a..0a0462c 100644 --- a/src/control.cc +++ b/src/control.cc @@ -46,6 +46,9 @@ bool ControlConn::processPacket() chklen = 0; return true; } + if (pktType == DINIT_CP_LISTSERVICES) { + return listServices(); + } else { // Unrecognized: give error response char outbuf[] = { DINIT_RP_BADREQ }; @@ -239,6 +242,49 @@ bool ControlConn::processUnpinService() return true; } +bool ControlConn::listServices() +{ + rbuf.consume(1); // clear request packet + chklen = 0; + + try { + auto slist = service_set->listServices(); + for (auto sptr : slist) { + std::vector pkt_buf; + + const std::string &name = sptr->getServiceName(); + int nameLen = std::min((size_t)256, name.length()); + pkt_buf.resize(8 + nameLen); + + pkt_buf[0] = DINIT_RP_SVCINFO; + pkt_buf[1] = nameLen; + pkt_buf[2] = static_cast(sptr->getState()); + pkt_buf[3] = static_cast(sptr->getTargetState()); + + pkt_buf[4] = 0; // reserved + pkt_buf[5] = 0; + pkt_buf[6] = 0; + pkt_buf[7] = 0; + + for (int i = 0; i < nameLen; i++) { + pkt_buf[8+i] = name[i]; + } + + if (! queuePacket(std::move(pkt_buf))) return false; + } + + char ack_buf[] = { (char) DINIT_RP_LISTDONE }; + if (! queuePacket(ack_buf, 1)) return false; + + return true; + } + catch (std::bad_alloc &exc) + { + doOomClose(); + return true; + } +} + ControlConn::handle_t ControlConn::allocateServiceHandle(ServiceRecord *record) { bool is_unique = true; diff --git a/src/control.h b/src/control.h index 05b4845..7a472de 100644 --- a/src/control.h +++ b/src/control.h @@ -144,6 +144,8 @@ class ControlConn : private ServiceListener // Process an UNPINSERVICE packet. May throw std::bad_alloc. bool processUnpinService(); + + bool listServices(); // Notify that data is ready to be read from the socket. Returns true if the connection should // be closed. diff --git a/src/dinitctl.cc b/src/dinitctl.cc index bd56180..95e0337 100644 --- a/src/dinitctl.cc +++ b/src/dinitctl.cc @@ -35,6 +35,7 @@ static int issueLoadService(int socknum, const char *service_name); static int checkLoadReply(int socknum, CPBuffer<1024> &rbuffer, handle_t *handle_p, ServiceState *state_p); static int startStopService(int socknum, const char *service_name, int command, bool do_pin, bool wait_for_service, bool verbose); static int unpinService(int socknum, const char *service_name, bool verbose); +static int listServices(int socknum); // Fill a circular buffer from a file descriptor, reading at least _rlength_ bytes. @@ -112,6 +113,7 @@ static int write_all(int fd, const void *buf, size_t count) static const int START_SERVICE = 1; static const int STOP_SERVICE = 2; static const int UNPIN_SERVICE = 3; +static const int LIST_SERVICES = 4; // Entry point. @@ -164,6 +166,9 @@ int main(int argc, char **argv) else if (strcmp(argv[i], "unpin") == 0) { command = UNPIN_SERVICE; } + else if (strcmp(argv[i], "list") == 0) { + command = LIST_SERVICES; + } else { show_help = true; break; @@ -177,7 +182,7 @@ int main(int argc, char **argv) } } - if (service_name == nullptr || command == 0) { + if ((service_name == nullptr && command != LIST_SERVICES) || command == 0) { show_help = true; } @@ -190,8 +195,9 @@ int main(int argc, char **argv) cout << " dinitctl [options] unpin : un-pin the service (after a previous pin)" << endl; // TODO: // cout << " dinitctl [options] wake : start but don't activate service" << endl; + cout << " dinitctl list : list loaded services" << endl; - cout << "\nNote: An activated service keeps its dependencies running when possible." << 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; @@ -257,6 +263,9 @@ int main(int argc, char **argv) if (command == UNPIN_SERVICE) { return unpinService(socknum, service_name, verbose); } + else if (command == LIST_SERVICES) { + return listServices(socknum); + } return startStopService(socknum, service_name, command, do_pin, wait_for_service, verbose); } @@ -522,3 +531,75 @@ static int unpinService(int socknum, const char *service_name, bool verbose) } return 0; } + +static int listServices(int socknum) +{ + using namespace std; + + try { + char cmdbuf[] = { (char)DINIT_CP_LISTSERVICES }; + int r = write_all(socknum, cmdbuf, 1); + + if (r == -1) { + perror("dinitctl: write"); + return 1; + } + + CPBuffer<1024> rbuffer; + wait_for_reply(rbuffer, socknum); + while (rbuffer[0] == DINIT_RP_SVCINFO) { + fillBufferTo(&rbuffer, socknum, 8); + int nameLen = rbuffer[1]; + ServiceState current = static_cast(rbuffer[2]); + ServiceState target = static_cast(rbuffer[3]); + + fillBufferTo(&rbuffer, socknum, nameLen + 8); + + char *name_ptr = rbuffer.get_ptr(8); + int clength = std::min(rbuffer.get_contiguous_length(name_ptr), nameLen); + + string name = string(name_ptr, clength); + name.append(rbuffer.get_buf_base(), nameLen - clength); + + cout << "["; + + cout << (target == ServiceState::STARTED ? "{" : " "); + cout << (current == ServiceState::STARTED ? "+" : " "); + cout << (target == ServiceState::STARTED ? "}" : " "); + + if (current == ServiceState::STARTING) { + cout << "<<"; + } + else if (current == ServiceState::STOPPING) { + cout << ">>"; + } + else { + cout << " "; + } + + cout << (target == ServiceState::STOPPED ? "{" : " "); + cout << (current == ServiceState::STOPPED ? "-" : " "); + cout << (target == ServiceState::STOPPED ? "}" : " "); + + cout << "] " << name << endl; + + rbuffer.consume(8 + nameLen); + wait_for_reply(rbuffer, socknum); + } + + if (rbuffer[0] != DINIT_RP_LISTDONE) { + cerr << "dinitctl: Control socket protocol error" << endl; + return 1; + } + } + catch (ReadCPException &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; + } + + return 0; +} diff --git a/src/service.cc b/src/service.cc index 2c11965..490abd9 100644 --- a/src/service.cc +++ b/src/service.cc @@ -34,7 +34,7 @@ static ServiceRecord * findService(const std::list & records, using std::list; list::const_iterator i = records.begin(); for ( ; i != records.end(); i++ ) { - if (strcmp((*i)->getServiceName(), name) == 0) { + if (strcmp((*i)->getServiceName().c_str(), name) == 0) { return *i; } } diff --git a/src/service.h b/src/service.h index eed59e6..f4612c2 100644 --- a/src/service.h +++ b/src/service.h @@ -505,7 +505,7 @@ class ServiceRecord this->socket_gid = socket_gid; } - const char *getServiceName() const noexcept { return service_name.c_str(); } + const std::string &getServiceName() const noexcept { return service_name; } ServiceState getState() const noexcept { return service_state; } void start(bool activate = true) noexcept; // start the service @@ -626,6 +626,12 @@ class ServiceSet return record; } + // Get the list of all loaded services. + const std::list &listServices() + { + return records; + } + // Stop the service with the given name. The named service will begin // transition to the 'stopped' state. void stopService(const std::string &name) noexcept; -- 2.25.1