Ensure vold doesn't hang when a child process dies

Add a monitoring thread that notifies the main parent thread when the child
process terminates. The return code from the child is then propagated back up
to the parent.

Change-Id: Idf46e100e8dee2e8f1aaa331317f3c4955632183
diff --git a/logwrapper.c b/logwrapper.c
index 6e47cb8..e8a90e6 100644
--- a/logwrapper.c
+++ b/logwrapper.c
@@ -15,6 +15,8 @@
  */
 
 #include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <stdio.h>
@@ -22,75 +24,93 @@
 #include <unistd.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <pthread.h>
 
 #include "private/android_filesystem_config.h"
 #include "cutils/log.h"
 #include "cutils/sched_policy.h"
 
-int parent(const char *tag, int parent_read) {
+struct monitor_data {
+    const char *tag;
+    int fd;
+};
+
+static int parent(const char *tag, int parent_read, int monitor_read) {
     int status;
     char buffer[4096];
+    fd_set fds;
+    int maxfd;
+    int rc = -EAGAIN;
 
     int a = 0;  // start index of unprocessed data
     int b = 0;  // end index of unprocessed data
     int sz;
-    while ((sz = read(parent_read, &buffer[b], sizeof(buffer) - 1 - b)) > 0) {
 
-        sz += b;
-        // Log one line at a time
-        for (b = 0; b < sz; b++) {
-            if (buffer[b] == '\r') {
-                buffer[b] = '\0';
-            } else if (buffer[b] == '\n') {
-                buffer[b] = '\0';
+    maxfd = 1 + (parent_read > monitor_read ? parent_read : monitor_read);
 
+    do {
+        FD_ZERO(&fds);
+        FD_SET(parent_read, &fds);
+        FD_SET(monitor_read, &fds);
+
+        if (select(maxfd, &fds, NULL, NULL, NULL) <= 0) {
+            ALOG(LOG_INFO, "logwrapper", "select failed");
+            break;
+        }
+
+        if (FD_ISSET(parent_read, &fds)) {
+            sz = read(parent_read, &buffer[b], sizeof(buffer) - 1 - b);
+
+            sz += b;
+            // Log one line at a time
+            for (b = 0; b < sz; b++) {
+                if (buffer[b] == '\r') {
+                    buffer[b] = '\0';
+                } else if (buffer[b] == '\n') {
+                    buffer[b] = '\0';
+
+                    ALOG(LOG_INFO, tag, "%s", &buffer[a]);
+                    a = b + 1;
+                }
+            }
+
+            if (a == 0 && b == sizeof(buffer) - 1) {
+                // buffer is full, flush
+                buffer[b] = '\0';
                 ALOG(LOG_INFO, tag, "%s", &buffer[a]);
-                a = b + 1;
+                b = 0;
+            } else if (a != b) {
+                // Keep left-overs
+                b -= a;
+                memmove(buffer, &buffer[a], b);
+                a = 0;
+            } else {
+                a = 0;
+                b = 0;
             }
         }
 
-        if (a == 0 && b == sizeof(buffer) - 1) {
-            // buffer is full, flush
-            buffer[b] = '\0';
-            ALOG(LOG_INFO, tag, "%s", &buffer[a]);
-            b = 0;
-        } else if (a != b) {
-            // Keep left-overs
-            b -= a;
-            memmove(buffer, &buffer[a], b);
-            a = 0;
-        } else {
-            a = 0;
-            b = 0;
+        // Child exited, get return status and exit loop
+        if (FD_ISSET(monitor_read, &fds)) {
+            if (read(monitor_read, &rc, sizeof(rc)) != sizeof(rc)) {
+                ALOG(LOG_ERROR, "logwrapper", "Unable to read child return "
+                        "status");
+                rc = -ECHILD;
+            }
+            break;
         }
+    } while (1);
 
-    }
     // Flush remaining data
     if (a != b) {
         buffer[b] = '\0';
         ALOG(LOG_INFO, tag, "%s", &buffer[a]);
     }
-    status = 0xAAAA;
-    if (wait(&status) != -1) {  // Wait for child
-        if (WIFEXITED(status)) {
-            if (WEXITSTATUS(status) != 0) {
-                ALOG(LOG_INFO, "logwrapper", "%s terminated by exit(%d)", tag,
-                        WEXITSTATUS(status));
-            }
-            return WEXITSTATUS(status);
-        } else if (WIFSIGNALED(status))
-            ALOG(LOG_INFO, "logwrapper", "%s terminated by signal %d", tag,
-                    WTERMSIG(status));
-        else if (WIFSTOPPED(status))
-            ALOG(LOG_INFO, "logwrapper", "%s stopped by signal %d", tag,
-                    WSTOPSIG(status));
-    } else
-        ALOG(LOG_INFO, "logwrapper", "%s wait() failed: %s (%d)", tag,
-                strerror(errno), errno);
-    return -EAGAIN;
+
+    return rc;
 }
 
-void child(int argc, const char**argv) {
+static void child(int argc, const char**argv) {
     // create null terminated argv_child array
     char* argv_child[argc + 1];
     memcpy(argv_child, argv, argc * sizeof(char *));
@@ -104,6 +124,33 @@
     }
 }
 
+static void *monitor(void *arg)
+{
+    struct monitor_data *data = arg;
+    int status;
+    int rc = -EAGAIN;
+
+    if (wait(&status) != -1) {  // Wait for child
+        if (WIFEXITED(status)) {
+            if (WEXITSTATUS(status) != 0) {
+                ALOG(LOG_INFO, "logwrapper", "%s terminated by exit(%d)",
+                        data->tag, WEXITSTATUS(status));
+            }
+            rc = WEXITSTATUS(status);
+        } else if (WIFSIGNALED(status))
+            ALOG(LOG_INFO, "logwrapper", "%s terminated by signal %d",
+                    data->tag, WTERMSIG(status));
+        else if (WIFSTOPPED(status))
+            ALOG(LOG_INFO, "logwrapper", "%s stopped by signal %d", data->tag,
+                    WSTOPSIG(status));
+    } else
+        ALOG(LOG_INFO, "logwrapper", "%s wait() failed: %s (%d)", data->tag,
+                strerror(errno), errno);
+
+    write(data->fd, &rc, sizeof(rc));
+    return NULL;
+}
+
 int logwrap(int argc, const char* argv[], int background)
 {
     pid_t pid;
@@ -135,15 +182,14 @@
         /*
          * Child
          */
+        close(parent_ptty);
         child_ptty = open(child_devname, O_RDWR);
         if (child_ptty < 0) {
-            close(parent_ptty);
             ALOG(LOG_ERROR, "logwrapper", "Problem with child ptty");
             _exit(-errno);
         }
 
         // redirect stdout and stderr
-        close(parent_ptty);
         dup2(child_ptty, 1);
         dup2(child_ptty, 2);
         close(child_ptty);
@@ -161,8 +207,29 @@
         /*
          * Parent
          */
-        int rc = parent(argv[0], parent_ptty);
+        int rc, err;
+        int sockets[2];
+        pthread_t thread_id;
+        struct monitor_data data;
+
+        if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)) {
+            err = errno;
+            ALOG(LOG_ERROR, "logwrapper", "Unable to create monitoring "
+                    "socket: %s (%d)", strerror(err), err);
+            exit(-err);
+        }
+        data.tag = argv[0];
+        data.fd = sockets[1];
+        err = pthread_create(&thread_id, NULL, monitor, &data);
+        if (err != 0) {
+            ALOG(LOG_ERROR, "logwrapper", "Unable to create monitoring "
+                    "thread: %s (%d)", strerror(err), err);
+            exit(-err);
+        }
+        rc = parent(argv[0], parent_ptty, sockets[0]);
         close(parent_ptty);
+        close(sockets[0]);
+        close(sockets[1]);
         return rc;
     }