Implement exec.

Change-Id: I20329bc9b378479d745b498d6a00eca0872cd5ab
diff --git a/init/Android.mk b/init/Android.mk
index a3b01e1..ec2861b 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -75,6 +75,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := init_tests
 LOCAL_SRC_FILES := \
+    init_parser_test.cpp \
     util_test.cpp \
 
 LOCAL_SHARED_LIBRARIES += \
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 9f3dcc1..03b143d 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -172,11 +172,16 @@
     return 0;
 }
 
-int do_exec(int nargs, char **args)
-{
-    return -1;
+int do_exec(int nargs, char** args) {
+    service* svc = make_exec_oneshot_service(nargs, args);
+    if (svc == NULL) {
+        return -1;
+    }
+    service_start(svc, NULL);
+    return 0;
 }
 
+// TODO: remove execonce when exec is available.
 int do_execonce(int nargs, char **args)
 {
     pid_t child;
diff --git a/init/init.cpp b/init/init.cpp
index 41ceb0a..579d0e7 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -14,37 +14,37 @@
  * limitations under the License.
  */
 
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <signal.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <signal.h>
-#include <sys/wait.h>
 #include <sys/mount.h>
-#include <sys/stat.h>
 #include <sys/poll.h>
-#include <errno.h>
-#include <stdarg.h>
-#include <mtd/mtd-user.h>
-#include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/un.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <mtd/mtd-user.h>
 
 #include <selinux/selinux.h>
 #include <selinux/label.h>
 #include <selinux/android.h>
 
-#include <libgen.h>
-
-#include <cutils/list.h>
 #include <cutils/android_reboot.h>
-#include <cutils/sockets.h>
-#include <cutils/iosched_policy.h>
 #include <cutils/fs.h>
+#include <cutils/iosched_policy.h>
+#include <cutils/list.h>
+#include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
-#include <termios.h>
 
 #include "devices.h"
 #include "init.h"
@@ -72,22 +72,35 @@
 static struct action *cur_action = NULL;
 static struct command *cur_command = NULL;
 
-void notify_service_state(const char *name, const char *state)
-{
-    char pname[PROP_NAME_MAX];
-    int len = strlen(name);
-    if ((len + 10) > PROP_NAME_MAX)
-        return;
-    snprintf(pname, sizeof(pname), "init.svc.%s", name);
-    property_set(pname, state);
-}
-
 static int have_console;
 static char console_name[PROP_VALUE_MAX] = "/dev/console";
 static time_t process_needs_restart;
 
 static const char *ENV[32];
 
+bool waiting_for_exec = false;
+
+void service::NotifyStateChange(const char* new_state) {
+    if (!properties_inited()) {
+        // If properties aren't available yet, we can't set them.
+        return;
+    }
+
+    if ((flags & SVC_EXEC) != 0) {
+        // 'exec' commands don't have properties tracking their state.
+        return;
+    }
+
+    char prop_name[PROP_NAME_MAX];
+    if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) {
+        // If the property name would be too long, we can't set it.
+        ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state);
+        return;
+    }
+
+    property_set(prop_name, new_state);
+}
+
 /* add_environment - add "key=value" to the current environment */
 int add_environment(const char *key, const char *val)
 {
@@ -160,35 +173,26 @@
 
 void service_start(struct service *svc, const char *dynamic_args)
 {
-    struct stat s;
-    pid_t pid;
-    int needs_console;
-    char *scon = NULL;
-    int rc;
-
-        /* starting a service removes it from the disabled or reset
-         * state and immediately takes it out of the restarting
-         * state if it was in there
-         */
+    // Starting a service removes it from the disabled or reset state and
+    // immediately takes it out of the restarting state if it was in there.
     svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
     svc->time_started = 0;
 
-        /* running processes require no additional work -- if
-         * they're in the process of exiting, we've ensured
-         * that they will immediately restart on exit, unless
-         * they are ONESHOT
-         */
+    // Running processes require no additional work --- if they're in the
+    // process of exiting, we've ensured that they will immediately restart
+    // on exit, unless they are ONESHOT.
     if (svc->flags & SVC_RUNNING) {
         return;
     }
 
-    needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
-    if (needs_console && (!have_console)) {
+    bool needs_console = (svc->flags & SVC_CONSOLE);
+    if (needs_console && !have_console) {
         ERROR("service '%s' requires console\n", svc->name);
         svc->flags |= SVC_DISABLED;
         return;
     }
 
+    struct stat s;
     if (stat(svc->args[0], &s) != 0) {
         ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
         svc->flags |= SVC_DISABLED;
@@ -202,6 +206,7 @@
         return;
     }
 
+    char* scon = NULL;
     if (is_selinux_enabled() > 0) {
         if (svc->seclabel) {
             scon = strdup(svc->seclabel);
@@ -213,7 +218,7 @@
             char *mycon = NULL, *fcon = NULL;
 
             INFO("computing context for service '%s'\n", svc->args[0]);
-            rc = getcon(&mycon);
+            int rc = getcon(&mycon);
             if (rc < 0) {
                 ERROR("could not get context while starting '%s'\n", svc->name);
                 return;
@@ -241,8 +246,7 @@
 
     NOTICE("starting '%s'\n", svc->name);
 
-    pid = fork();
-
+    pid_t pid = fork();
     if (pid == 0) {
         struct socketinfo *si;
         struct svcenvinfo *ei;
@@ -298,7 +302,7 @@
 
         setpgid(0, getpid());
 
-    /* as requested, set our gid, supplemental gids, and uid */
+        // As requested, set our gid, supplemental gids, and uid.
         if (svc->gid) {
             if (setgid(svc->gid) != 0) {
                 ERROR("setgid failed: %s\n", strerror(errno));
@@ -361,8 +365,13 @@
     svc->pid = pid;
     svc->flags |= SVC_RUNNING;
 
-    if (properties_inited())
-        notify_service_state(svc->name, "running");
+    if ((svc->flags & SVC_EXEC) != 0) {
+        INFO("SVC_EXEC pid %d (uid %d gid %d+%d context %s) started; waiting...\n",
+             svc->pid, svc->uid, svc->gid, svc->nr_supp_gids, svc->seclabel);
+        waiting_for_exec = true;
+    }
+
+    svc->NotifyStateChange("running");
 }
 
 /* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
@@ -388,9 +397,9 @@
     if (svc->pid) {
         NOTICE("service '%s' is being killed\n", svc->name);
         kill(-svc->pid, SIGKILL);
-        notify_service_state(svc->name, "stopping");
+        svc->NotifyStateChange("stopping");
     } else {
-        notify_service_state(svc->name, "stopped");
+        svc->NotifyStateChange("stopped");
     }
 }
 
@@ -969,28 +978,18 @@
     security_setenforce(is_enforcing);
 }
 
-int main(int argc, char **argv)
-{
-    size_t fd_count = 0;
-    struct pollfd ufds[4];
-    int property_set_fd_init = 0;
-    int signal_fd_init = 0;
-    int keychord_fd_init = 0;
-    bool is_charger = false;
-
+int main(int argc, char** argv) {
     if (!strcmp(basename(argv[0]), "ueventd"))
         return ueventd_main(argc, argv);
 
     if (!strcmp(basename(argv[0]), "watchdogd"))
         return watchdogd_main(argc, argv);
 
-    /* clear the umask */
+    // Clear the umask.
     umask(0);
 
-        /* Get the basic filesystem setup we need put
-         * together in the initramdisk on / and then we'll
-         * let the rc file figure out the rest.
-         */
+    // Get the basic filesystem setup we need put together in the initramdisk
+    // on / and then we'll let the rc file figure out the rest.
     mkdir("/dev", 0755);
     mkdir("/proc", 0755);
     mkdir("/sys", 0755);
@@ -1002,15 +1001,13 @@
     mount("proc", "/proc", "proc", 0, NULL);
     mount("sysfs", "/sys", "sysfs", 0, NULL);
 
-        /* indicate that booting is in progress to background fw loaders, etc */
+    // Indicate that booting is in progress to background fw loaders, etc.
     close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
 
-        /* We must have some place other than / to create the
-         * device nodes for kmsg and null, otherwise we won't
-         * be able to remount / read-only later on.
-         * Now that tmpfs is mounted on /dev, we can actually
-         * talk to the outside world.
-         */
+    // We must have some place other than / to create the device nodes for
+    // kmsg and null, otherwise we won't be able to remount / read-only
+    // later on. Now that tmpfs is mounted on /dev, we can actually talk
+    // to the outside world.
     open_devnull_stdio();
     klog_init();
     property_init();
@@ -1019,25 +1016,22 @@
 
     process_kernel_cmdline();
 
-    union selinux_callback cb;
+    selinux_callback cb;
     cb.func_log = log_callback;
     selinux_set_callback(SELINUX_CB_LOG, cb);
-
     cb.func_audit = audit_callback;
     selinux_set_callback(SELINUX_CB_AUDIT, cb);
 
     selinux_initialize();
-    /* These directories were necessarily created before initial policy load
-     * and therefore need their security context restored to the proper value.
-     * This must happen before /dev is populated by ueventd.
-     */
+
+    // These directories were necessarily created before initial policy load
+    // and therefore need their security context restored to the proper value.
+    // This must happen before /dev is populated by ueventd.
     restorecon("/dev");
     restorecon("/dev/socket");
     restorecon("/dev/__properties__");
     restorecon_recursive("/sys");
 
-    is_charger = !strcmp(bootmode, "charger");
-
     INFO("property init\n");
     property_load_boot_defaults();
 
@@ -1050,50 +1044,58 @@
     queue_builtin_action(keychord_init_action, "keychord_init");
     queue_builtin_action(console_init_action, "console_init");
 
-    /* execute all the boot actions to get us started */
+    // Execute all the boot actions to get us started.
     action_for_each_trigger("init", action_add_queue_tail);
 
-    /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
-     * wasn't ready immediately after wait_for_coldboot_done
-     */
+    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
+    // wasn't ready immediately after wait_for_coldboot_done
     queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
     queue_builtin_action(property_service_init_action, "property_service_init");
     queue_builtin_action(signal_init_action, "signal_init");
 
-    /* Don't mount filesystems or start core system services if in charger mode. */
-    if (is_charger) {
+    // Don't mount filesystems or start core system services in charger mode.
+    if (strcmp(bootmode, "charger") == 0) {
         action_for_each_trigger("charger", action_add_queue_tail);
     } else {
         action_for_each_trigger("late-init", action_add_queue_tail);
     }
 
-    /* run all property triggers based on current state of the properties */
+    // Run all property triggers based on current state of the properties.
     queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
 
+    // TODO: why do we only initialize ufds after execute_one_command and restart_processes?
+    size_t fd_count = 0;
+    struct pollfd ufds[3];
+    bool property_set_fd_init = false;
+    bool signal_fd_init = false;
+    bool keychord_fd_init = false;
+
     for (;;) {
-        execute_one_command();
-        restart_processes();
+        if (!waiting_for_exec) {
+            execute_one_command();
+            restart_processes();
+        }
 
         if (!property_set_fd_init && get_property_set_fd() > 0) {
             ufds[fd_count].fd = get_property_set_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
-            property_set_fd_init = 1;
+            property_set_fd_init = true;
         }
         if (!signal_fd_init && get_signal_fd() > 0) {
             ufds[fd_count].fd = get_signal_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
-            signal_fd_init = 1;
+            signal_fd_init = true;
         }
         if (!keychord_fd_init && get_keychord_fd() > 0) {
             ufds[fd_count].fd = get_keychord_fd();
             ufds[fd_count].events = POLLIN;
             ufds[fd_count].revents = 0;
             fd_count++;
-            keychord_fd_init = 1;
+            keychord_fd_init = true;
         }
 
         int timeout = -1;
diff --git a/init/init.h b/init/init.h
index eedec27..a104af6 100644
--- a/init/init.h
+++ b/init/init.h
@@ -17,13 +17,11 @@
 #ifndef _INIT_INIT_H
 #define _INIT_INIT_H
 
+#include <sys/types.h>
+
 #include <cutils/list.h>
 #include <cutils/iosched_policy.h>
 
-#include <sys/stat.h>
-
-void handle_control_message(const char *msg, const char *arg);
-
 struct command
 {
         /* list of commands in an action */
@@ -59,8 +57,6 @@
     struct command *current;
 };
 
-void build_triggers_string(char *name_str, int length, struct action *cur_action);
-
 struct socketinfo {
     struct socketinfo *next;
     const char *name;
@@ -77,27 +73,29 @@
     const char *value;
 };
 
-#define SVC_DISABLED    0x01  /* do not autostart with class */
-#define SVC_ONESHOT     0x02  /* do not restart on exit */
-#define SVC_RUNNING     0x04  /* currently active */
-#define SVC_RESTARTING  0x08  /* waiting to restart */
-#define SVC_CONSOLE     0x10  /* requires console */
-#define SVC_CRITICAL    0x20  /* will reboot into recovery if keeps crashing */
-#define SVC_RESET       0x40  /* Use when stopping a process, but not disabling
-                                 so it can be restarted with its class */
-#define SVC_RC_DISABLED 0x80  /* Remember if the disabled flag was set in the rc script */
-#define SVC_RESTART     0x100 /* Use to safely restart (stop, wait, start) a service */
-#define SVC_DISABLED_START 0x200 /* a start was requested but it was disabled at the time */
+#define SVC_DISABLED       0x001  // do not autostart with class
+#define SVC_ONESHOT        0x002  // do not restart on exit
+#define SVC_RUNNING        0x004  // currently active
+#define SVC_RESTARTING     0x008  // waiting to restart
+#define SVC_CONSOLE        0x010  // requires console
+#define SVC_CRITICAL       0x020  // will reboot into recovery if keeps crashing
+#define SVC_RESET          0x040  // Use when stopping a process, but not disabling so it can be restarted with its class.
+#define SVC_RC_DISABLED    0x080  // Remember if the disabled flag was set in the rc script.
+#define SVC_RESTART        0x100  // Use to safely restart (stop, wait, start) a service.
+#define SVC_DISABLED_START 0x200  // A start was requested but it was disabled at the time.
+#define SVC_EXEC           0x400  // This synthetic service corresponds to an 'exec'.
 
 #define NR_SVC_SUPP_GIDS 12    /* twelve supplementary groups */
 
 #define COMMAND_RETRY_TIMEOUT 5
 
 struct service {
+    void NotifyStateChange(const char* new_state);
+
         /* list of all services */
     struct listnode slist;
 
-    const char *name;
+    char *name;
     const char *classname;
 
     unsigned flags;
@@ -105,19 +103,19 @@
     time_t time_started;    /* time of last start */
     time_t time_crashed;    /* first crash within inspection window */
     int nr_crashed;         /* number of times crashed within window */
-    
+
     uid_t uid;
     gid_t gid;
     gid_t supp_gids[NR_SVC_SUPP_GIDS];
     size_t nr_supp_gids;
 
-    char *seclabel;
+    const char* seclabel;
 
     struct socketinfo *sockets;
     struct svcenvinfo *envvars;
 
     struct action onrestart;  /* Actions to execute on restart. */
-    
+
     /* keycodes for triggering this service via /dev/keychord */
     int *keycodes;
     int nkeycodes;
@@ -131,7 +129,13 @@
     char *args[1];
 }; /*     ^-------'args' MUST be at the end of this struct! */
 
-void notify_service_state(const char *name, const char *state);
+extern bool waiting_for_exec;
+extern struct selabel_handle *sehandle;
+extern struct selabel_handle *sehandle_prop;
+
+void build_triggers_string(char *name_str, int length, struct action *cur_action);
+
+void handle_control_message(const char *msg, const char *arg);
 
 struct service *service_find_by_name(const char *name);
 struct service *service_find_by_pid(pid_t pid);
@@ -147,9 +151,8 @@
 void service_start(struct service *svc, const char *dynamic_args);
 void property_changed(const char *name, const char *value);
 
-extern struct selabel_handle *sehandle;
-extern struct selabel_handle *sehandle_prop;
-extern int selinux_reload_policy(void);
+int selinux_reload_policy(void);
+
 void zap_stdio(void);
 
 #endif	/* _INIT_INIT_H */
diff --git a/init/init_parser.cpp b/init/init_parser.cpp
index 61a5e0a..691db76 100644
--- a/init/init_parser.cpp
+++ b/init/init_parser.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -437,7 +438,7 @@
 }
 
 int init_parse_config_file(const char* path) {
-    INFO("Parsing %s...", path);
+    INFO("Parsing %s...\n", path);
     std::string data;
     if (!read_file(path, &data)) {
         return -1;
@@ -660,6 +661,65 @@
     return list_empty(&action_queue);
 }
 
+service* make_exec_oneshot_service(int nargs, char** args) {
+    // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
+    int command_arg = 1;
+    for (int i = 1; i < nargs; ++i) {
+        if (strcmp(args[i], "--") == 0) {
+            command_arg = i + 1;
+            break;
+        }
+    }
+    if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
+        ERROR("exec called with too many supplementary group ids\n");
+        return NULL;
+    }
+
+    int argc = nargs - command_arg;
+    char** argv = (args + command_arg);
+    if (argc < 1) {
+        ERROR("exec called without command\n");
+        return NULL;
+    }
+
+    service* svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * argc);
+    if (svc == NULL) {
+        ERROR("Couldn't allocate service for exec of '%s': %s", argv[0], strerror(errno));
+        return NULL;
+    }
+
+    if (command_arg > 2) {
+        svc->seclabel = args[1];
+    }
+    if (command_arg > 3) {
+        svc->uid = decode_uid(args[2]);
+    }
+    if (command_arg > 4) {
+        svc->gid = decode_uid(args[3]);
+        svc->nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
+        for (size_t i = 0; i < svc->nr_supp_gids; ++i) {
+            svc->supp_gids[i] = decode_uid(args[4 + i]);
+        }
+    }
+
+    static int exec_count; // Every service needs a unique name.
+    char* name = NULL;
+    asprintf(&name, "exec %d (%s)", exec_count++, argv[0]);
+    if (name == NULL) {
+        ERROR("Couldn't allocate name for exec service '%s'\n", argv[0]);
+        free(svc);
+        return NULL;
+    }
+    svc->name = name;
+    svc->classname = "default";
+    svc->flags = SVC_EXEC | SVC_ONESHOT;
+    svc->nargs = argc;
+    memcpy(svc->args, argv, sizeof(char*) * svc->nargs);
+    svc->args[argc] = NULL;
+    list_add_tail(&service_list, &svc->slist);
+    return svc;
+}
+
 static void *parse_service(struct parse_state *state, int nargs, char **args)
 {
     if (nargs < 3) {
@@ -683,7 +743,7 @@
         parse_error(state, "out of memory\n");
         return 0;
     }
-    svc->name = args[1];
+    svc->name = strdup(args[1]);
     svc->classname = "default";
     memcpy(svc->args, args + 2, sizeof(char*) * nargs);
     trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
diff --git a/init/init_parser.h b/init/init_parser.h
index 0047da7..6348607 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -20,6 +20,7 @@
 #define INIT_PARSER_MAXARGS 64
 
 struct action;
+struct service;
 
 struct action *action_remove_queue_head(void);
 void action_add_queue_tail(struct action *act);
@@ -33,4 +34,6 @@
 int init_parse_config_file(const char *fn);
 int expand_props(char *dst, const char *src, int len);
 
+service* make_exec_oneshot_service(int argc, char** argv);
+
 #endif
diff --git a/init/init_parser_test.cpp b/init/init_parser_test.cpp
new file mode 100644
index 0000000..170a73a
--- /dev/null
+++ b/init/init_parser_test.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "init_parser.h"
+
+#include "init.h"
+#include "util.h"
+
+#include <errno.h>
+#include <gtest/gtest.h>
+
+TEST(init_parser, make_exec_oneshot_service_invalid_syntax) {
+    char* argv[10];
+    memset(argv, 0, sizeof(argv));
+
+    // Nothing.
+    ASSERT_EQ(nullptr, make_exec_oneshot_service(0, argv));
+
+    // No arguments to 'exec'.
+    argv[0] = const_cast<char*>("exec");
+    ASSERT_EQ(nullptr, make_exec_oneshot_service(1, argv));
+
+    // No command in "exec --".
+    argv[1] = const_cast<char*>("--");
+    ASSERT_EQ(nullptr, make_exec_oneshot_service(2, argv));
+}
+
+TEST(init_parser, make_exec_oneshot_service_too_many_supplementary_gids) {
+    int argc = 0;
+    char* argv[4 + NR_SVC_SUPP_GIDS + 3];
+    argv[argc++] = const_cast<char*>("exec");
+    argv[argc++] = const_cast<char*>("seclabel");
+    argv[argc++] = const_cast<char*>("root"); // uid.
+    argv[argc++] = const_cast<char*>("root"); // gid.
+    for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) {
+        argv[argc++] = const_cast<char*>("root"); // Supplementary gid.
+    }
+    argv[argc++] = const_cast<char*>("--");
+    argv[argc++] = const_cast<char*>("/system/bin/id");
+    argv[argc] = nullptr;
+    ASSERT_EQ(nullptr, make_exec_oneshot_service(argc, argv));
+}
+
+static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid, bool supplementary_gids) {
+    int argc = 0;
+    char* argv[10];
+    argv[argc++] = const_cast<char*>("exec");
+    if (seclabel) {
+        argv[argc++] = const_cast<char*>("u:r:su:s0"); // seclabel
+        if (uid) {
+            argv[argc++] = const_cast<char*>("log");      // uid
+            if (gid) {
+                argv[argc++] = const_cast<char*>("shell");     // gid
+                if (supplementary_gids) {
+                    argv[argc++] = const_cast<char*>("system");    // supplementary gid 0
+                    argv[argc++] = const_cast<char*>("adb");       // supplementary gid 1
+                }
+            }
+        }
+    }
+    if (dash_dash) {
+        argv[argc++] = const_cast<char*>("--");
+    }
+    argv[argc++] = const_cast<char*>("/system/bin/toybox");
+    argv[argc++] = const_cast<char*>("id");
+    argv[argc] = nullptr;
+    service* svc = make_exec_oneshot_service(argc, argv);
+    ASSERT_NE(nullptr, svc);
+
+    if (seclabel) {
+        ASSERT_STREQ("u:r:su:s0", svc->seclabel);
+    } else {
+        ASSERT_EQ(nullptr, svc->seclabel);
+    }
+    if (uid) {
+        ASSERT_EQ(decode_uid("log"), svc->uid);
+    } else {
+        ASSERT_EQ(0U, svc->uid);
+    }
+    if (gid) {
+        ASSERT_EQ(decode_uid("shell"), svc->gid);
+    } else {
+        ASSERT_EQ(0U, svc->gid);
+    }
+    if (supplementary_gids) {
+        ASSERT_EQ(2U, svc->nr_supp_gids);
+        ASSERT_EQ(decode_uid("system"), svc->supp_gids[0]);
+        ASSERT_EQ(decode_uid("adb"), svc->supp_gids[1]);
+    } else {
+        ASSERT_EQ(0U, svc->nr_supp_gids);
+    }
+
+    ASSERT_EQ(2, svc->nargs);
+    ASSERT_EQ("/system/bin/toybox", svc->args[0]);
+    ASSERT_EQ("id", svc->args[1]);
+    ASSERT_EQ(nullptr, svc->args[2]);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_everything) {
+    Test_make_exec_oneshot_service(true, true, true, true, true);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid_gid) {
+    Test_make_exec_oneshot_service(true, true, true, true, false);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid) {
+    Test_make_exec_oneshot_service(true, true, true, false, false);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_seclabel) {
+    Test_make_exec_oneshot_service(true, true, false, false, false);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_just_command) {
+    Test_make_exec_oneshot_service(true, false, false, false, false);
+}
+
+TEST(init_parser, make_exec_oneshot_service_with_just_command_no_dash) {
+    Test_make_exec_oneshot_service(false, false, false, false, false);
+}
diff --git a/init/readme.txt b/init/readme.txt
index 9c24220..8161858 100644
--- a/init/readme.txt
+++ b/init/readme.txt
@@ -70,11 +70,11 @@
 setenv <name> <value>
    Set the environment variable <name> to <value> in the launched process.
 
-socket <name> <type> <perm> [ <user> [ <group> [ <context> ] ] ]
+socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]
    Create a unix domain socket named /dev/socket/<name> and pass
    its fd to the launched process.  <type> must be "dgram", "stream" or "seqpacket".
    User and group default to 0.
-   Context is the SELinux security context for the socket.
+   'seclabel' is the SELinux security context for the socket.
    It defaults to the service security context, as specified by seclabel or
    computed based on the service executable file security context.
 
@@ -91,8 +91,8 @@
    supplemental groups of the process (via setgroups()).
    Currently defaults to root.  (??? probably should default to nobody)
 
-seclabel <securitycontext>
-  Change to securitycontext before exec'ing this service.
+seclabel <seclabel>
+  Change to 'seclabel' before exec'ing this service.
   Primarily for use by services run from the rootfs, e.g. ueventd, adbd.
   Services on the system partition can instead use policy-defined transitions
   based on their file security context.
@@ -137,14 +137,17 @@
 Commands
 --------
 
-exec <path> [ <argument> ]*
-   This command is not implemented.
+exec [ <seclabel> [ <user> [ <group> ]* ] ] -- <command> [ <argument> ]*
+   Fork and execute command with the given arguments. The command starts
+   after "--" so that an optional security context, user, and supplementary
+   groups can be provided. No other commands will be run until this one
+   finishes.
 
 execonce <path> [ <argument> ]*
    Fork and execute a program (<path>).  This will block until
    the program completes execution.  This command can be run at most
    once during init's lifetime. Subsequent invocations are ignored.
-   It is best to avoid exec as unlike the builtin commands, it runs
+   It is best to avoid execonce as unlike the builtin commands, it runs
    the risk of getting init "stuck".
 
 export <name> <value>
@@ -220,7 +223,7 @@
    Recursively restore the directory tree named by <path> to the
    security contexts specified in the file_contexts configuration.
 
-setcon <securitycontext>
+setcon <seclabel>
    Set the current process security context to the specified string.
    This is typically only used from early-init to set the init context
    before any other process is started.
@@ -275,7 +278,7 @@
 Init updates some system properties to provide some insight into
 what it's doing:
 
-init.action 
+init.action
    Equal to the name of the action currently being executed or "" if none
 
 init.command
diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp
index c0898fb..c428b96 100644
--- a/init/signal_handler.cpp
+++ b/init/signal_handler.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include <stdio.h>
 #include <errno.h>
-#include <signal.h>
-#include <unistd.h>
 #include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
@@ -27,34 +27,28 @@
 #include <cutils/list.h>
 
 #include "init.h"
-#include "util.h"
 #include "log.h"
+#include "util.h"
 
 static int signal_fd = -1;
 static int signal_recv_fd = -1;
 
-static void sigchld_handler(int s)
-{
+static void sigchld_handler(int s) {
     write(signal_fd, &s, 1);
 }
 
 #define CRITICAL_CRASH_THRESHOLD    4       /* if we crash >4 times ... */
-#define CRITICAL_CRASH_WINDOW       (4*60)  /* ... in 4 minutes, goto recovery*/
+#define CRITICAL_CRASH_WINDOW       (4*60)  /* ... in 4 minutes, goto recovery */
 
-static int wait_for_one_process(int block)
-{
+static int wait_for_one_process() {
     int status;
-    struct service *svc;
-    struct socketinfo *si;
-    time_t now;
-    struct listnode *node;
-    struct command *cmd;
-
-    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, block ? 0 : WNOHANG));
-    if (pid <= 0) return -1;
+    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
+    if (pid <= 0) {
+        return -1;
+    }
     INFO("waitpid returned pid %d, status = %08x\n", pid, status);
 
-    svc = service_find_by_pid(pid);
+    service* svc = service_find_by_pid(pid);
     if (!svc) {
         if (WIFEXITED(status)) {
             ERROR("untracked pid %d exited with status %d\n", pid, WEXITSTATUS(status));
@@ -68,36 +62,47 @@
         return 0;
     }
 
+    // TODO: all the code from here down should be a member function on service.
+
     NOTICE("process '%s', pid %d exited\n", svc->name, pid);
 
     if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
-        kill(-pid, SIGKILL);
         NOTICE("process '%s' killing any children in process group\n", svc->name);
+        kill(-pid, SIGKILL);
     }
 
-    /* remove any sockets we may have created */
-    for (si = svc->sockets; si; si = si->next) {
+    // Remove any sockets we may have created.
+    for (socketinfo* si = svc->sockets; si; si = si->next) {
         char tmp[128];
         snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
         unlink(tmp);
     }
 
+    if (svc->flags & SVC_EXEC) {
+        INFO("SVC_EXEC pid %d finished...\n", svc->pid);
+        waiting_for_exec = false;
+        list_remove(&svc->slist);
+        free(svc->name);
+        free(svc);
+        return 0;
+    }
+
     svc->pid = 0;
     svc->flags &= (~SVC_RUNNING);
 
-        /* oneshot processes go into the disabled state on exit,
-         * except when manually restarted. */
+    // Oneshot processes go into the disabled state on exit,
+    // except when manually restarted.
     if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
         svc->flags |= SVC_DISABLED;
     }
 
-        /* disabled and reset processes do not get restarted automatically */
-    if (svc->flags & (SVC_DISABLED | SVC_RESET) )  {
-        notify_service_state(svc->name, "stopped");
+    // Disabled and reset processes do not get restarted automatically.
+    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
+        svc->NotifyStateChange("stopped");
         return 0;
     }
 
-    now = gettime();
+    time_t now = gettime();
     if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
         if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
             if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
@@ -116,36 +121,33 @@
     svc->flags &= (~SVC_RESTART);
     svc->flags |= SVC_RESTARTING;
 
-    /* Execute all onrestart commands for this service. */
+    // Execute all onrestart commands for this service.
+    struct listnode* node;
     list_for_each(node, &svc->onrestart.commands) {
-        cmd = node_to_item(node, struct command, clist);
+        command* cmd = node_to_item(node, struct command, clist);
         cmd->func(cmd->nargs, cmd->args);
     }
-    notify_service_state(svc->name, "restarting");
+    svc->NotifyStateChange("restarting");
     return 0;
 }
 
-void handle_signal(void)
-{
+void handle_signal() {
+    // We got a SIGCHLD - reap and restart as needed.
     char tmp[32];
-
-    /* we got a SIGCHLD - reap and restart as needed */
     read(signal_recv_fd, tmp, sizeof(tmp));
-    while (!wait_for_one_process(0))
-        ;
+    while (!wait_for_one_process()) {
+    }
 }
 
-void signal_init(void)
-{
-    int s[2];
-
+void signal_init() {
     struct sigaction act;
     memset(&act, 0, sizeof(act));
     act.sa_handler = sigchld_handler;
     act.sa_flags = SA_NOCLDSTOP;
     sigaction(SIGCHLD, &act, 0);
 
-    /* create a signalling mechanism for the sigchld handler */
+    // Create a signalling mechanism for the sigchld handler.
+    int s[2];
     if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == 0) {
         signal_fd = s[0];
         signal_recv_fd = s[1];
@@ -154,7 +156,6 @@
     handle_signal();
 }
 
-int get_signal_fd()
-{
+int get_signal_fd() {
     return signal_recv_fd;
 }
diff --git a/init/util_test.cpp b/init/util_test.cpp
index e9a1581..5b3ab50 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -35,3 +35,9 @@
   s[5] = 0;
   EXPECT_STREQ("Linux", s.c_str());
 }
+
+TEST(util, decode_uid) {
+  EXPECT_EQ(0U, decode_uid("root"));
+  EXPECT_EQ(-1U, decode_uid("toot"));
+  EXPECT_EQ(123U, decode_uid("123"));
+}