For scripted services, accept both a "command" and "stop-command" setting.
authorDavin McCall <davmac@davmac.org>
Thu, 31 Dec 2015 23:50:24 +0000 (23:50 +0000)
committerDavin McCall <davmac@davmac.org>
Thu, 31 Dec 2015 23:50:24 +0000 (23:50 +0000)
Do not automatically add "start" or "stop" to the command.

load_service.cc
service.cc
service.h

index 49bd28f6d8ebe3d5026ca0ed8163e2c4dae6e9da..8957f0e28fc4de93d2ec26892770e33d3b47ce1c 100644 (file)
@@ -187,6 +187,9 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
     using std::locale;
     using std::isspace;
     
+    using std::list;
+    using std::pair;
+    
     // First try and find an existing record...
     ServiceRecord * rval = findService(string(name));
     if (rval != 0) {
@@ -204,7 +207,9 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
     service_filename += name;
     
     string command;
-    std::list<std::pair<unsigned,unsigned>> command_offsets;
+    list<pair<unsigned,unsigned>> command_offsets;
+    string stop_command;
+    list<pair<unsigned,unsigned>> stop_command_offsets;
 
     ServiceType service_type = ServiceType::PROCESS;
     std::list<ServiceRecord *> depends_on;
@@ -253,6 +258,9 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                 if (setting == "command") {
                     command = read_setting_value(i, end, &command_offsets);
                 }
+                else if (setting == "stop-command") {
+                    stop_command = read_setting_value(i, end, &stop_command_offsets);
+                }
                 else if (setting == "depends-on") {
                     string dependency_name = read_setting_value(i, end);
                     depends_on.push_back(loadServiceRecord(dependency_name.c_str()));
@@ -330,6 +338,7 @@ ServiceRecord * ServiceSet::loadServiceRecord(const char * name)
                 delete rval;
                 rval = new ServiceRecord(this, string(name), service_type, std::move(command), command_offsets,
                         & depends_on, & depends_soft);
+                rval->setStopCommand(stop_command, stop_command_offsets);
                 rval->setLogfile(logfile);
                 rval->setAutoRestart(auto_restart);
                 rval->setOnstartFlags(onstart_flags);
index ddb5328a5d26746349c43d861a875483c2b82956..41d04a4f7f6e3cc8b2dae6348beae65fcbed217b 100644 (file)
@@ -220,7 +220,7 @@ void ServiceRecord::allDepsStarted() noexcept
     }
     else if (service_type == ServiceType::SCRIPTED) {
         // Script-controlled service
-        bool start_success = start_ps_process(std::vector<std::string>(1, "start"));
+        bool start_success = start_ps_process();
         if (! start_success) {
             failed_to_start();
         }
@@ -291,7 +291,7 @@ void ServiceRecord::failed_to_start()
 bool ServiceRecord::start_ps_process() noexcept
 {
     try {
-        return start_ps_process(std::vector<std::string>());
+        return start_ps_process(exec_arg_parts);
     }
     catch (std::bad_alloc & bad_alloc_exc) {
         // TODO log error
@@ -299,7 +299,7 @@ bool ServiceRecord::start_ps_process() noexcept
     }
 }
 
-bool ServiceRecord::start_ps_process(const std::vector<std::string> &pargs) noexcept
+bool ServiceRecord::start_ps_process(const std::vector<const char *> &cmd) noexcept
 {
     // In general, you can't tell whether fork/exec is successful. We use a pipe to communicate
     // success/failure from the child to the parent. The pipe is set CLOEXEC so a successful
@@ -319,89 +319,72 @@ bool ServiceRecord::start_ps_process(const std::vector<std::string> &pargs) noex
     }
     
     // Set up the argument array and other data now (before fork), in case memory allocation fails.
+    
+    auto args = cmd.data();
+    
+    const char * logfile = this->logfile.c_str();
+    if (*logfile == 0) {
+        logfile = "/dev/null";
+    }
 
-    try {
-        //auto argsv = std::vector<const char *>(num_args + pargs.size() + 1);
-        auto argsv = std::vector<const char *>(num_args + pargs.size() + 1);
-        auto args = argsv.data();
-        int i;
-        for (i = 0; i < num_args; i++) {
-            args[i] = exec_arg_parts[i];
-        }
-        for (auto progarg : pargs) {
-            args[i] = progarg.c_str();
-            i++;
-        }
-        args[i] = nullptr;
-        
-        string logfile = this->logfile;
-        if (logfile.length() == 0) {
-            logfile = "/dev/null";
-        }
+    // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
+    // until they are not.
 
-        // TODO make sure pipefd's are not 0/1/2 (STDIN/OUT/ERR) - if they are, dup them
-        // until they are not.
+    pid_t forkpid = fork();
+    if (forkpid == -1) {
+        // TODO log error
+        close(pipefd[0]);
+        close(pipefd[1]);
+        return false;
+    }
 
-        pid_t forkpid = fork();
-        if (forkpid == -1) {
-            // TODO log error
-            close(pipefd[0]);
-            close(pipefd[1]);
-            return false;
+    if (forkpid == 0) {
+        // Child process. Must not allocate memory (or otherwise risk throwing any exception)
+        // from here until exit().
+        ev_default_destroy(); // won't need that on this side, free up fds.
+
+        // Re-set stdin, stdout, stderr
+        close(0); close(1); close(2);
+
+        // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
+        if (open("/dev/null", O_RDONLY) == 0) {
+          // stdin = 0. That's what we should have; proceed with opening
+          // stdout and stderr.
+          open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
+          dup2(1, 2);
         }
 
-        if (forkpid == 0) {
-            // Child process. Must not allocate memory (or otherwise risk throwing any exception)
-            // from here until exit().
-            ev_default_destroy(); // won't need that on this side, free up fds.
+        execvp(exec_arg_parts[0], const_cast<char **>(args));
 
-            // Re-set stdin, stdout, stderr
-            close(0); close(1); close(2);
+        // If we got here, the exec failed:
+        int exec_status = errno;
+        write(pipefd[1], &exec_status, sizeof(int));
+        exit(0);
+    }
+    else {
+        // Parent process
+        close(pipefd[1]); // close the 'other end' fd
 
-            // TODO rethink this logic. If we open it at not-0, shouldn't we just dup it to 0?:
-            if (open("/dev/null", O_RDONLY) == 0) {
-              // stdin = 0. That's what we should have; proceed with opening
-              // stdout and stderr.
-              open(logfile.c_str(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
-              dup2(1, 2);
-            }
+        int exec_status;
+        if (read(pipefd[0], &exec_status, sizeof(int)) == 0) {
+            // pipe closed; success
+            pid = forkpid;
 
-            execvp(exec_arg_parts[0], const_cast<char **>(args));
+            // Add a process listener so we can detect when the
+            // service stops
+            ev_child_init(&child_listener, process_child_callback, pid, 0);
+            child_listener.data = this;
+            ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
 
-            // If we got here, the exec failed:
-            int exec_status = errno;
-            write(pipefd[1], &exec_status, sizeof(int));
-            exit(0);
+            close(pipefd[0]);
+            return true;
         }
         else {
-            // Parent process
-            close(pipefd[1]); // close the 'other end' fd
-
-            int exec_status;
-            if (read(pipefd[0], &exec_status, sizeof(int)) == 0) {
-                // pipe closed; success
-                pid = forkpid;
-
-                // Add a process listener so we can detect when the
-                // service stops
-                ev_child_init(&child_listener, process_child_callback, pid, 0);
-                child_listener.data = this;
-                ev_child_start(ev_default_loop(EVFLAG_AUTO), &child_listener);
-
-                close(pipefd[0]);
-                return true;
-            }
-            else {
-                // TODO log error
-                close(pipefd[0]);
-                return false;
-            }
+            // TODO log error
+            close(pipefd[0]);
+            return false;
         }
     }
-    catch (std::bad_alloc &bad_alloc_exc) {
-        log(LogLevel::ERROR, "Out of memory");
-        return false;
-    }
 }
 
 // Mark this and all dependent services as force-stopped.
@@ -537,7 +520,7 @@ void ServiceRecord::allDepsStopped()
     }
     else if (service_type == ServiceType::SCRIPTED) {
         // Scripted service.
-        if (! start_ps_process(std::vector<string>(1, "stop"))) {
+        if (! start_ps_process(stop_arg_parts)) {
             // stop script failed, but there's not much we can do:
             stopped();
         }
index 3704250ea2aed22574060329bf3e6d0c3520fadd..ae05e26017e08e0f280abc5049dd0d399208ba35 100644 (file)
--- a/service.h
+++ b/service.h
@@ -121,12 +121,12 @@ class ServiceDep
 };
 
 // Given a string and a list of pairs of (start,end) indices for each argument in that string,
-// store a null terminator for the argument. Return a `char *` array pointing at the beginning
-// of each argument. (The returned array is invalidated if the string is later modified).
-static const char ** separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
+// store a null terminator for the argument. Return a `char *` vector containing the beginning
+// of each argument and a trailing nullptr. (The returned array is invalidated if the string is later modified).
+static std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
 {
-    const char ** r = new const char *[arg_indices.size()];
-    int i = 0;
+    std::vector<const char *> r;
+    r.reserve(arg_indices.size() + 1);
 
     // First store nul terminator for each part:
     for (auto index_pair : arg_indices) {
@@ -138,9 +138,9 @@ static const char ** separate_args(std::string &s, std::list<std::pair<unsigned,
     // Now we can get the C string (c_str) and store offsets into it:
     const char * cstr = s.c_str();
     for (auto index_pair : arg_indices) {
-        r[i] = cstr + index_pair.first;
-        i++;
+        r.push_back(cstr + index_pair.first);
     }
+    r.push_back(nullptr);
     return r;
 }
 
@@ -155,8 +155,11 @@ class ServiceRecord
     ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
 
     string program_name;          /* storage for program/script and arguments */
-    const char **exec_arg_parts;  /* pointer to each argument/part of the program_name */
-    int num_args;                 /* number of argumrnets (including program) */
+    std::vector<const char *> exec_arg_parts; /* pointer to each argument/part of the program_name */
+    
+    string stop_command;          /* storage for stop program/script and arguments */
+    std::vector<const char *> stop_arg_parts; /* pointer to each argument/part of the stop_command */
+    
     OnstartFlags onstart_flags;
 
     string logfile; /* log file name, empty string specifies /dev/null */
@@ -219,7 +222,7 @@ class ServiceRecord
     
     // For process services, start the process, return true on success
     bool start_ps_process() noexcept;
-    bool start_ps_process(const std::vector<std::string> &args) noexcept;
+    bool start_ps_process(const std::vector<const char *> &args) noexcept;
 
     // Callback from libev when a child process dies
     static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
@@ -274,7 +277,6 @@ class ServiceRecord
 
         program_name = command;
         exec_arg_parts = separate_args(program_name, command_offsets);
-        num_args = command_offsets.size();
 
         for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
             (*i)->dependents.push_back(this);
@@ -291,6 +293,13 @@ class ServiceRecord
     
     // TODO write a destructor
     
+    // Set the stop command and arguments (may throw std::bad_alloc)
+    void setStopCommand(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
+    {
+        stop_command = command;
+        stop_arg_parts = separate_args(stop_command, stop_command_offsets);
+    }
+    
     // Get the current service state.
     ServiceState getState() noexcept
     {