init: refactor: add support for doing early coldboot

We don't want to spend time creating devices that are unncessesary
during early (init first-stage) mount. So, refactor the devices code
tha allows us to call into coldboot and has the

- ability to only create devices that are specified by the caller
- ability to stop coldboot cycle when all devices that the caller is
interested in
- ability to run coldboot for a specific syspath
- ability to run ueventd code unmodified

Test: Tested boot on angler, sailfish

Change-Id: Id8f3492380696760414eadc20d624d300c904f8e
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/init/devices.cpp b/init/devices.cpp
index fbc0359..c7f3efa 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -64,18 +64,6 @@
 
 static int device_fd = -1;
 
-struct uevent {
-    const char *action;
-    const char *path;
-    const char *subsystem;
-    const char *firmware;
-    const char *partition_name;
-    const char *device_name;
-    int partition_num;
-    int major;
-    int minor;
-};
-
 struct perms_ {
     char *name;
     char *attr;
@@ -879,9 +867,15 @@
     }
 }
 
+static bool inline should_stop_coldboot(coldboot_action_t act)
+{
+    return (act == COLDBOOT_STOP || act == COLDBOOT_FINISH);
+}
+
 #define UEVENT_MSG_LEN  2048
 
-static inline void handle_device_fd_with(void (handle_uevent)(struct uevent*))
+template<typename T>
+static inline coldboot_action_t handle_device_fd_with(const T& handle_uevent)
 {
     char msg[UEVENT_MSG_LEN+2];
     int n;
@@ -894,14 +888,18 @@
 
         struct uevent uevent;
         parse_event(msg, &uevent);
-        handle_uevent(&uevent);
+        coldboot_action_t act = handle_uevent(&uevent);
+        if (should_stop_coldboot(act))
+            return act;
     }
+
+    return COLDBOOT_CONTINUE;
 }
 
-void handle_device_fd()
+coldboot_action_t handle_device_fd(coldboot_callback fn)
 {
-    handle_device_fd_with(
-        [](struct uevent *uevent) {
+    coldboot_action_t ret = handle_device_fd_with(
+        [&](uevent* uevent) -> coldboot_action_t {
             if (selinux_status_updated() > 0) {
                 struct selabel_handle *sehandle2;
                 sehandle2 = selinux_android_file_context_handle();
@@ -911,9 +909,21 @@
                 }
             }
 
-            handle_device_event(uevent);
-            handle_firmware_event(uevent);
+            // default is to always create the devices
+            coldboot_action_t act = COLDBOOT_CREATE;
+            if (fn) {
+                act = fn(uevent);
+            }
+
+            if (act == COLDBOOT_CREATE || act == COLDBOOT_STOP) {
+                handle_device_event(uevent);
+                handle_firmware_event(uevent);
+            }
+
+            return act;
         });
+
+    return ret;
 }
 
 /* Coldboot walks parts of the /sys tree and pokes the uevent files
@@ -925,21 +935,24 @@
 ** socket's buffer.
 */
 
-static void do_coldboot(DIR *d)
+static coldboot_action_t do_coldboot(DIR *d, coldboot_callback fn)
 {
     struct dirent *de;
     int dfd, fd;
+    coldboot_action_t act = COLDBOOT_CONTINUE;
 
     dfd = dirfd(d);
 
     fd = openat(dfd, "uevent", O_WRONLY);
-    if(fd >= 0) {
+    if (fd >= 0) {
         write(fd, "add\n", 4);
         close(fd);
-        handle_device_fd();
+        act = handle_device_fd(fn);
+        if (should_stop_coldboot(act))
+            return act;
     }
 
-    while((de = readdir(d))) {
+    while (!should_stop_coldboot(act) && (de = readdir(d))) {
         DIR *d2;
 
         if(de->d_type != DT_DIR || de->d_name[0] == '.')
@@ -953,34 +966,39 @@
         if(d2 == 0)
             close(fd);
         else {
-            do_coldboot(d2);
+            act = do_coldboot(d2, fn);
             closedir(d2);
         }
     }
+
+    // default is always to continue looking for uevents
+    return act;
 }
 
-static void coldboot(const char *path)
+static coldboot_action_t coldboot(const char *path, coldboot_callback fn)
 {
     std::unique_ptr<DIR, decltype(&closedir)> d(opendir(path), closedir);
-    if(d) {
-        do_coldboot(d.get());
+    if (d) {
+        return do_coldboot(d.get(), fn);
     }
+
+    return COLDBOOT_CONTINUE;
 }
 
-static void early_uevent_handler(struct uevent *uevent, const char *base, bool is_block)
+static coldboot_action_t early_uevent_handler(struct uevent *uevent, const char *base, bool is_block)
 {
     const char *name;
     char devpath[DEVPATH_LEN];
 
     if (is_block && strncmp(uevent->subsystem, "block", 5))
-        return;
+        return COLDBOOT_STOP;
 
     name = parse_device_name(uevent, MAX_DEV_NAME);
     if (!name) {
         LOG(ERROR) << "Failed to parse dev name from uevent: " << uevent->action
                    << " " << uevent->partition_name << " " << uevent->partition_num
                    << " " << uevent->major << ":" << uevent->minor;
-        return;
+        return COLDBOOT_STOP;
     }
 
     snprintf(devpath, sizeof(devpath), "%s%s", base, name);
@@ -989,6 +1007,8 @@
     dev_t dev = makedev(uevent->major, uevent->minor);
     mode_t mode = 0600 | (is_block ? S_IFBLK : S_IFCHR);
     mknod(devpath, mode, dev);
+
+    return COLDBOOT_STOP;
 }
 
 void early_create_dev(const std::string& syspath, early_device_type dev_type)
@@ -1009,11 +1029,11 @@
 
     write(fd, "add\n", 4);
     handle_device_fd_with(dev_type == EARLY_BLOCK_DEV ?
-        [](struct uevent *uevent) {
-            early_uevent_handler(uevent, "/dev/block/", true);
+        [](uevent* uevent) -> coldboot_action_t {
+            return early_uevent_handler(uevent, "/dev/block/", true);
         } :
-        [](struct uevent *uevent) {
-            early_uevent_handler(uevent, "/dev/", false);
+        [](uevent* uevent) -> coldboot_action_t {
+            return early_uevent_handler(uevent, "/dev/", false);
         });
 }
 
@@ -1026,7 +1046,7 @@
     close(device_fd);
 }
 
-void device_init() {
+void device_init(const char* path, coldboot_callback fn) {
     sehandle = selinux_android_file_context_handle();
     selinux_status_open(true);
 
@@ -1043,10 +1063,25 @@
     }
 
     Timer t;
-    coldboot("/sys/class");
-    coldboot("/sys/block");
-    coldboot("/sys/devices");
-    close(open(COLDBOOT_DONE, O_WRONLY|O_CREAT|O_CLOEXEC, 0000));
+    coldboot_action_t act;
+    if (!path) {
+        act = coldboot("/sys/class", fn);
+        if (!should_stop_coldboot(act)) {
+            act = coldboot("/sys/block", fn);
+            if (!should_stop_coldboot(act)) {
+                act = coldboot("/sys/devices", fn);
+            }
+        }
+    } else {
+        act = coldboot(path, fn);
+    }
+
+    // If we have a callback, then do as it says. If no, then the default is
+    // to always create COLDBOOT_DONE file.
+    if (!fn || (act == COLDBOOT_FINISH)) {
+        close(open(COLDBOOT_DONE, O_WRONLY|O_CREAT|O_CLOEXEC, 0000));
+    }
+
     LOG(INFO) << "Coldboot took " << t;
 }
 
diff --git a/init/devices.h b/init/devices.h
index 8e9ab7d..d1e489b 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -17,10 +17,36 @@
 #ifndef _INIT_DEVICES_H
 #define _INIT_DEVICES_H
 
+#include <functional>
 #include <sys/stat.h>
 
-extern void handle_device_fd();
-extern void device_init(void);
+enum coldboot_action_t {
+    // coldboot continues without creating the device for the uevent
+    COLDBOOT_CONTINUE = 0,
+    // coldboot continues after creating the device for the uevent
+    COLDBOOT_CREATE,
+    // coldboot stops after creating the device for uevent but doesn't
+    // create the COLDBOOT_DONE file
+    COLDBOOT_STOP,
+    // same as COLDBOOT_STOP, but creates the COLDBOOT_DONE file
+    COLDBOOT_FINISH
+};
+
+struct uevent {
+    const char* action;
+    const char* path;
+    const char* subsystem;
+    const char* firmware;
+    const char* partition_name;
+    const char* device_name;
+    int partition_num;
+    int major;
+    int minor;
+};
+
+typedef std::function<coldboot_action_t(struct uevent* uevent)> coldboot_callback;
+extern coldboot_action_t handle_device_fd(coldboot_callback fn = nullptr);
+extern void device_init(const char* path = nullptr, coldboot_callback fn = nullptr);
 
 enum early_device_type { EARLY_BLOCK_DEV, EARLY_CHAR_DEV };