service: improve robustness of read_pid_file().
authorDavin McCall <davmac@davmac.org>
Mon, 26 Jun 2017 18:26:46 +0000 (19:26 +0100)
committerDavin McCall <davmac@davmac.org>
Mon, 26 Jun 2017 18:27:22 +0000 (19:27 +0100)
Cleans up some TODOs.

src/dinit-util.h [new file with mode: 0644]
src/service.cc

diff --git a/src/dinit-util.h b/src/dinit-util.h
new file mode 100644 (file)
index 0000000..3b4c42c
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef DINIT_UTIL_H_INCLUDED
+#define DINIT_UTIL_H_INCLUDED 1
+
+#include <cerrno>
+
+// Signal-safe read. Read and re-try if interrupted by signal (EINTR).
+// *May* affect errno even on a successful read (when the return is less than n).
+inline int ss_read(int fd, void * buf, size_t n)
+{
+    char * cbuf = static_cast<char *>(buf);
+    int r = 0;
+    while (r < n) {
+        int res = read(fd, cbuf + r, n - r);
+        if (res == 0) {
+            return r;
+        }
+        if (res < 0) {
+            if (res == EINTR) {
+                continue;
+            }
+
+            // If any other error, and we have successfully read some, return it:
+            if (r == 0) {
+                return -1;
+            }
+            else {
+                return r;
+            }
+        }
+        r += res;
+    }
+    return n;
+}
+
+#endif
index cda831e416afb514db9bafad79a6e2c0cded343a..4a57bad32fdfcfd6ddd4f6e9727bec17cdae7cb0 100644 (file)
@@ -17,6 +17,7 @@
 #include "service.h"
 #include "dinit-log.h"
 #include "dinit-socket.h"
+#include "dinit-util.h"
 
 /*
  * service.cc - Service management.
@@ -704,7 +705,7 @@ bgproc_service::read_pid_file(int *exit_status) noexcept
     }
 
     char pidbuf[21]; // just enough to hold any 64-bit integer
-    int r = read(fd, pidbuf, 20); // TODO signal-safe read
+    int r = ss_read(fd, pidbuf, 20);
     if (r < 0) {
         // Could not read from PID file
         log(LogLevel::ERROR, service_name, ": could not read from pidfile; ", strerror(errno));
@@ -714,37 +715,52 @@ bgproc_service::read_pid_file(int *exit_status) noexcept
 
     close(fd);
     pidbuf[r] = 0; // store nul terminator
-    // TODO may need stoull; what if int isn't big enough...
-    pid = std::atoi(pidbuf);
-    pid_t wait_r = waitpid(pid, exit_status, WNOHANG);
-    if (wait_r == -1 && errno == ECHILD) {
-        // We can't track this child - check process exists:
-        if (kill(pid, 0) == 0) {
-            tracking_child = false;
-            return pid_result_t::OK;
-        }
-        else {
-            log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
-            pid = -1;
-            return pid_result_t::FAILED;
+
+    bool valid_pid = false;
+    try {
+        unsigned long long v = std::stoull(pidbuf, nullptr, 0);
+        if (v <= std::numeric_limits<pid_t>::max()) {
+            pid = (pid_t) v;
+            valid_pid = true;
         }
     }
-    else if (wait_r == pid) {
-        pid = -1;
-        return pid_result_t::TERMINATED;
+    catch (std::out_of_range &exc) {
+        // Too large?
     }
-    else if (wait_r == 0) {
-        // We can track the child
-        child_listener.add_reserved(eventLoop, pid, DEFAULT_PRIORITY - 10);
-        tracking_child = true;
-        reserved_child_watch = true;
-        return pid_result_t::OK;
+    catch (std::invalid_argument &exc) {
+        // Ok, so it doesn't look like a number: proceed...
     }
-    else {
-        log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
-        pid = -1;
-        return pid_result_t::FAILED;
+
+    if (valid_pid) {
+        pid_t wait_r = waitpid(pid, exit_status, WNOHANG);
+        if (wait_r == -1 && errno == ECHILD) {
+            // We can't track this child - check process exists:
+            if (kill(pid, 0) == 0) {
+                tracking_child = false;
+                return pid_result_t::OK;
+            }
+            else {
+                log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
+                pid = -1;
+                return pid_result_t::FAILED;
+            }
+        }
+        else if (wait_r == pid) {
+            pid = -1;
+            return pid_result_t::TERMINATED;
+        }
+        else if (wait_r == 0) {
+            // We can track the child
+            child_listener.add_reserved(eventLoop, pid, DEFAULT_PRIORITY - 10);
+            tracking_child = true;
+            reserved_child_watch = true;
+            return pid_result_t::OK;
+        }
     }
+
+    log(LogLevel::ERROR, service_name, ": pid read from pidfile (", pid, ") is not valid");
+    pid = -1;
+    return pid_result_t::FAILED;
 }
 
 void service_record::started() noexcept