Implement "list services" command in control protocol, and use it in
authorDavin McCall <davmac@davmac.org>
Thu, 23 Jun 2016 11:33:36 +0000 (12:33 +0100)
committerDavin McCall <davmac@davmac.org>
Thu, 23 Jun 2016 11:33:36 +0000 (12:33 +0100)
"dinitctl list" command.

TODO
src/control-cmds.h
src/control.cc
src/control.h
src/dinitctl.cc
src/service.cc
src/service.h

diff --git a/TODO b/TODO
index cd0ad0b1cab31060e5f7722f69924d8d3ec63ca4..70528a48fe6da28728c1d3f6cd2eb153e2d87b7c 100644 (file)
--- 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.
 
index 98be5e5d517c051f38518af5392816edf8928332..8e7967fb56d0a2db625d85f53b4601497854f836 100644 (file)
@@ -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:
 
index 4e0c72a076b83666e5e037904b8149cb0d4a95d1..0a0462cd512f1f81f6a604782f8e38ce2d739f43 100644 (file)
@@ -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<char> 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<char>(sptr->getState());
+            pkt_buf[3] = static_cast<char>(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;
index 05b48459ffb7444203057c1f90b2bb0fae38716e..7a472de0a2df4495c399dc72dc770842e54a1bf5 100644 (file)
@@ -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.
index bd56180f3fb3e590a692da62380ce1788ae9237b..95e0337d5b1c9d4f291028c2dc4baa292f9e7117 100644 (file)
@@ -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 <service-name>           : un-pin the service (after a previous pin)" << endl;
         // TODO:
         // cout << "    dinitctl [options] wake <service-name>  : 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<ServiceState>(rbuffer[2]);
+            ServiceState target = static_cast<ServiceState>(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;
+}
index 2c11965863d3a3832376848cfb7d7eb5bf468cb3..490abd962f6e5154db7d061b4ae2e4d312112de2 100644 (file)
@@ -34,7 +34,7 @@ static ServiceRecord * findService(const std::list<ServiceRecord *> & records,
     using std::list;
     list<ServiceRecord *>::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;
         }
     }
index eed59e621cb3094e34b33df119fa7c607bc1493f..f4612c27a3956f4b115a8c31a771b9215a574255 100644 (file)
@@ -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<ServiceRecord *> &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;