init: early_mount: add support to mount non-verity partitions early

This is done by parsing 'androidboot.fstab=<fstab>' kernel cmdline
option to get the fstab file that *only* specifies partitions to be
mounted early (i.e. in init's first stage).

Note that, the same fstab file may not be used as an argument to
mount_all later in the boot as that will cause fs_mgr to fail with
EBUSY.

TODO:
- Possibly add a new mount_mode so the same fstab can be used for
early_mount, 'mount_all --early/--late' etc.
- Add support for dm-verity enabled partitions to mount early.
- Add support for getting fstab arguments through DT instead of kernel
cmdline.

Bug: 27805372

Test:
Boot angler by passing a seperate fstab file using the kernel
cmdline option to mount vendor partition early, remove the vendor
partition entry from the main fstab file for the test.

Boot sailfish by passing a seperate fstab entry via device tree
to mount vendor partition early. Remove vendor partition entry from
the main fstab file for the test

Change-Id: I18785b893c54c8cee960ab44d5e8f83e5d624aa8
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/init/init.cpp b/init/init.cpp
index 48d63e9..973573f 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -638,14 +638,135 @@
 }
 
 /* Early mount vendor and ODM partitions. The fstab info is read from kernel cmdline. */
-static void early_mount() {
-    // TODO: read fstab entries from device tree, so early mount
-    // entries can be specified for A/B devices where system = root
-    std::string fstab = import_cmdline_fstab();
-    if (fstab.empty()) {
+static bool early_mount() {
+    // TODO:  read fstab entries from device tree instead of
+    // kernel cmdline
+    std::string fstab_file = import_cmdline_fstab();
+    if (fstab_file.empty()) {
         LOG(INFO) << "Early mount skipped (missing fstab argument)";
-        return;
+        return true;
     }
+
+    std::unique_ptr<struct fstab, void(*)(fstab*)> tab(fs_mgr_read_fstab(fstab_file.c_str()),
+                                                       fs_mgr_free_fstab);
+    if (!tab) {
+        LOG(ERROR) << "Early mount failed to read fstab: " << fstab_file;
+        // continue to allow booting normally if this happened.
+        return true;
+    }
+
+    // find out fstab records for odm, system and vendor
+    fstab_rec* odm_rec = fs_mgr_get_entry_for_mount_point(tab.get(), "/odm");
+    fstab_rec* system_rec = fs_mgr_get_entry_for_mount_point(tab.get(), "/system");
+    fstab_rec* vendor_rec = fs_mgr_get_entry_for_mount_point(tab.get(), "/vendor");
+    if (!odm_rec && !system_rec && !vendor_rec) {
+        // nothing to early mount
+        return true;
+    }
+
+    // assume A/B device if we find 'slotselect' in any fstab entry
+    bool is_ab = ((odm_rec && fs_mgr_is_slotselect(odm_rec)) ||
+                  (system_rec && fs_mgr_is_slotselect(system_rec)) ||
+                  (vendor_rec && fs_mgr_is_slotselect(vendor_rec)));
+    bool found_odm = !odm_rec;
+    bool found_system = !system_rec;
+    bool found_vendor = !vendor_rec;
+    int count_odm = 0, count_vendor = 0, count_system = 0;
+
+    // create the devices we need..
+    device_init(nullptr,
+        [&](uevent* uevent) -> coldboot_action_t {
+            if (!strncmp(uevent->subsystem, "firmware", 8)) {
+                return COLDBOOT_CONTINUE;
+            }
+
+            // we need platform devices to create symlinks
+            if (!strncmp(uevent->subsystem, "platform", 8)) {
+                return COLDBOOT_CREATE;
+            }
+
+            // Ignore everything that is not a block device
+            if (strncmp(uevent->subsystem, "block", 5)) {
+                return COLDBOOT_CONTINUE;
+            }
+
+            coldboot_action_t ret;
+            bool create_this_node = false;
+            if (uevent->partition_name) {
+                // prefix match partition names so we create device nodes for
+                // A/B-ed partitions
+                if (!found_odm && !strncmp(uevent->partition_name, "odm", 3)) {
+                    LOG(VERBOSE) << "early_mount: found (" << uevent->partition_name
+                                 << ") partition";
+
+                    // wait twice for A/B-ed partitions
+                    count_odm++;
+                    if (!is_ab) {
+                        found_odm = true;
+                    } else if (count_odm == 2) {
+                        found_odm = true;
+                    }
+
+                    create_this_node = true;
+                } else if (!found_system && !strncmp(uevent->partition_name, "system", 6)) {
+                    LOG(VERBOSE) << "early_mount: found (" << uevent->partition_name
+                                 << ") partition";
+
+                    count_system++;
+                    if (!is_ab) {
+                        found_system = true;
+                    } else if (count_system == 2) {
+                        found_system = true;
+                    }
+
+                    create_this_node = true;
+                } else if (!found_vendor && !strncmp(uevent->partition_name, "vendor", 6)) {
+                    LOG(VERBOSE) << "early_mount: found (" << uevent->partition_name
+                                 << ") partition";
+                    count_vendor++;
+                    if (!is_ab) {
+                        found_vendor = true;
+                    } else if (count_vendor == 2) {
+                        found_vendor = true;
+                    }
+
+                    create_this_node = true;
+                }
+            }
+
+            // if we found all other partitions already, create this
+            // node and stop coldboot. If this is a prefix matched
+            // partition, create device node and continue. For everything
+            // else skip the device node
+            if (found_odm && found_system && found_vendor) {
+                ret = COLDBOOT_STOP;
+            } else if (create_this_node) {
+                ret = COLDBOOT_CREATE;
+            } else {
+                ret = COLDBOOT_CONTINUE;
+            }
+
+            return ret;
+        });
+
+    // TODO: add support to mount partitions w/ verity
+
+    int ret = 0;
+    if (odm_rec &&
+            (ret = fs_mgr_do_mount(tab.get(), odm_rec->mount_point, odm_rec->blk_device, NULL))) {
+        PLOG(ERROR) << "early_mount: fs_mgr_do_mount returned error for mounting odm";
+        return false;
+    }
+
+    if (vendor_rec &&
+            (ret = fs_mgr_do_mount(tab.get(), vendor_rec->mount_point, vendor_rec->blk_device, NULL))) {
+        PLOG(ERROR) << "early_mount: fs_mgr_do_mount returned error for mounting vendor";
+        return false;
+    }
+
+    device_close();
+
+    return true;
 }
 
 int main(int argc, char** argv) {
@@ -694,8 +815,10 @@
     LOG(INFO) << "init " << (is_first_stage ? "first" : "second") << " stage started!";
 
     if (is_first_stage) {
-        // Mount devices defined in android.early.* kernel commandline
-        early_mount();
+        if (!early_mount()) {
+            LOG(ERROR) << "Failed to mount required partitions early ...";
+            panic();
+        }
 
         // Set up SELinux, loading the SELinux policy.
         selinux_initialize(true);