fsmgr: support multiple fs-types/mountpoint

Previous attempt was broken.
It would incorrectly be affected by mount failures.

This changes allows an fstab to contain multiple lines for a given
mount point.
The lines sharing a mount MUST be after each other.

The 1st matching line is the primary when it comes to mounting
and look ups for wiping.

Mounting based on a mount_point will attempt each dup in turn
until one succeeds.
The reported error will be that of the last failed attempt.

This is to allow quick experimentation between different FSes.

Bug: 15702546
Change-Id: I378d68ad13eb0098ec1ccb8dcf108b82acbe9ebb
Signed-off-by: JP Abgrall <jpa@google.com>
diff --git a/fs_mgr/fs_mgr.c b/fs_mgr/fs_mgr.c
index d2938cf..183712a 100644
--- a/fs_mgr/fs_mgr.c
+++ b/fs_mgr/fs_mgr.c
@@ -113,6 +113,7 @@
          * fix the filesystem.
          */
         ret = mount(blk_device, target, fs_type, tmpmnt_flags, tmpmnt_opts);
+        INFO("%s(): mount(%s,%s,%s)=%d\n", __func__, blk_device, target, fs_type, ret);
         if (!ret) {
             umount(target);
         }
@@ -190,16 +191,19 @@
  * sets the underlying block device to read-only if the mount is read-only.
  * See "man 2 mount" for return values.
  */
-static int __mount(const char *source, const char *target,
-                   const char *filesystemtype, unsigned long mountflags,
-                   const void *data)
+static int __mount(const char *source, const char *target, const struct fstab_rec *rec)
 {
-    int ret = mount(source, target, filesystemtype, mountflags, data);
+    unsigned long mountflags = rec->flags;
+    int ret;
+    int save_errno;
 
+    ret = mount(source, target, rec->fs_type, mountflags, rec->fs_options);
+    save_errno = errno;
+    INFO("%s(source=%s,target=%s,type=%s)=%d\n", __func__, source, target, rec->fs_type, ret);
     if ((ret == 0) && (mountflags & MS_RDONLY) != 0) {
         fs_set_blk_ro(source);
     }
-
+    errno = save_errno;
     return ret;
 }
 
@@ -232,13 +236,18 @@
     return strcmp(value, "1") ? 0 : 1;
 }
 
+/* When multiple fstab records share the same mount_point, it will
+ * try to mount each one in turn, and ignore any duplicates after a
+ * first successful mount.
+ */
 int fs_mgr_mount_all(struct fstab *fstab)
 {
-    int i = 0;
+    int i = 0, j = 0;
     int encryptable = 0;
     int error_count = 0;
-    int mret;
-    int mount_errno;
+    int mret = -1;
+    int mount_errno = 0;
+    const char *last_ok_mount_point = NULL;
 
     if (!fstab) {
         return -1;
@@ -261,11 +270,6 @@
             wait_for_file(fstab->recs[i].blk_device, WAIT_TIMEOUT);
         }
 
-        if (fstab->recs[i].fs_mgr_flags & MF_CHECK) {
-            check_fs(fstab->recs[i].blk_device, fstab->recs[i].fs_type,
-                     fstab->recs[i].mount_point);
-        }
-
         if ((fstab->recs[i].fs_mgr_flags & MF_VERIFY) &&
             !device_is_debuggable()) {
             if (fs_mgr_setup_verity(&fstab->recs[i]) < 0) {
@@ -274,10 +278,41 @@
             }
         }
 
-        mret = __mount(fstab->recs[i].blk_device, fstab->recs[i].mount_point,
-                       fstab->recs[i].fs_type, fstab->recs[i].flags,
-                       fstab->recs[i].fs_options);
+        /*
+         * Don't try to mount/encrypt the same mount point again.
+         * Deal with alternate entries for the same point which are required to be all following
+         * each other.
+         */
+        if (last_ok_mount_point && !strcmp(last_ok_mount_point, fstab->recs[i].mount_point)) {
+            INFO("%s(): skipping fstab dup mountpoint=%s rec[%d].fs_type=%s already mounted.\n", __func__,
+                 last_ok_mount_point, i, fstab->recs[i].fs_type);
+            continue;
+        }
+        /* Hunt down an fstab entry for the same mount point that might succeed */
+        for (j = i;
+             /* We required that fstab entries for the same mountpoint be consecutive */
+             j < fstab->num_entries && !strcmp(fstab->recs[i].mount_point, fstab->recs[j].mount_point);
+             j++) {
+                if (fstab->recs[i].fs_mgr_flags & MF_CHECK) {
+                    check_fs(fstab->recs[j].blk_device, fstab->recs[j].fs_type,
+                             fstab->recs[j].mount_point);
+                }
+                mret = __mount(fstab->recs[j].blk_device, fstab->recs[j].mount_point, &fstab->recs[j]);
+                if (!mret) {
+                    last_ok_mount_point = fstab->recs[j].mount_point;
+                    if (i != j) {
+                        INFO("%s(): some alternate mount worked for mount_point=%s fs_type=%s instead of fs_type=%s\n", __func__,
+                             last_ok_mount_point, fstab->recs[j].fs_type, fstab->recs[i].fs_type);
+                        i = j;   /* We advance the recs index to the working entry */
+                    }
+                    break;
+                } else {
+                    /* back up errno for crypto decisions */
+                    mount_errno = errno;
+                }
+        }
 
+        /* Deal with encryptability. */
         if (!mret) {
             /* If this is encryptable, need to trigger encryption */
             if ((fstab->recs[i].fs_mgr_flags & MF_FORCECRYPT)) {
@@ -294,24 +329,18 @@
                     continue;
                 }
             }
-
             /* Success!  Go get the next one */
             continue;
         }
 
-        /* back up errno as partition_wipe clobbers the value */
-        mount_errno = errno;
         /* mount(2) returned an error, check if it's encryptable and deal with it */
-        if (mount_errno != EBUSY && mount_errno != EACCES &&
+        if (mret && mount_errno != EBUSY && mount_errno != EACCES &&
             (fstab->recs[i].fs_mgr_flags & (MF_CRYPT | MF_FORCECRYPT)) &&
             !partition_wiped(fstab->recs[i].blk_device)) {
             /* Need to mount a tmpfs at this mountpoint for now, and set
              * properties that vold will query later for decrypting
              */
-            if (mount("tmpfs", fstab->recs[i].mount_point, "tmpfs",
-                      MS_NOATIME | MS_NOSUID | MS_NODEV, CRYPTO_TMPFS_OPTIONS) < 0) {
-                ERROR("Cannot mount tmpfs filesystem for encryptable fs at %s error: %s\n",
-                       fstab->recs[i].mount_point, strerror(errno));
+            if (fs_mgr_do_tmpfs_mount(fstab->recs[i].mount_point) < 0) {
                 ++error_count;
                 continue;
             }
@@ -335,12 +364,16 @@
 
 /* If tmp_mount_point is non-null, mount the filesystem there.  This is for the
  * tmp mount we do to check the user password
+ * If multiple fstab entries are to be mounted on "n_name", it will try to mount each one
+ * in turn, and stop on 1st success, or no more match.
  */
 int fs_mgr_do_mount(struct fstab *fstab, char *n_name, char *n_blk_device,
                     char *tmp_mount_point)
 {
     int i = 0;
     int ret = -1;
+    int mount_errors = 0;
+    int first_mount_errno = 0;
     char *m;
 
     if (!fstab) {
@@ -386,19 +419,23 @@
         } else {
             m = fstab->recs[i].mount_point;
         }
-        if (__mount(n_blk_device, m, fstab->recs[i].fs_type,
-                    fstab->recs[i].flags, fstab->recs[i].fs_options)) {
-            ERROR("Cannot mount filesystem on %s at %s options: %s error: %s\n",
-                n_blk_device, m, fstab->recs[i].fs_options, strerror(errno));
-            goto out;
+        if (__mount(n_blk_device, m, &fstab->recs[i])) {
+            if (!first_mount_errno) first_mount_errno = errno;
+            mount_errors++;
+            continue;
         } else {
             ret = 0;
             goto out;
         }
     }
-
-    /* We didn't find a match, say so and return an error */
-    ERROR("Cannot find mount point %s in fstab\n", fstab->recs[i].mount_point);
+    if (mount_errors) {
+        ERROR("Cannot mount filesystem on %s at %s. error: %s\n",
+            n_blk_device, m, strerror(first_mount_errno));
+        ret = -1;
+    } else {
+        /* We didn't find a match, say so and return an error */
+        ERROR("Cannot find mount point %s in fstab\n", fstab->recs[i].mount_point);
+    }
 
 out:
     return ret;
diff --git a/fs_mgr/fs_mgr_fstab.c b/fs_mgr/fs_mgr_fstab.c
index 6c21425..3f84179 100644
--- a/fs_mgr/fs_mgr_fstab.c
+++ b/fs_mgr/fs_mgr_fstab.c
@@ -367,25 +367,47 @@
      return 0;
 }
 
-struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path)
+/*
+ * Returns the 1st matching fstab_rec that follows the start_rec.
+ * start_rec is the result of a previous search or NULL.
+ */
+struct fstab_rec *fs_mgr_get_entry_for_mount_point_after(struct fstab_rec *start_rec, struct fstab *fstab, const char *path)
 {
     int i;
-
     if (!fstab) {
         return NULL;
     }
 
-    for (i = 0; i < fstab->num_entries; i++) {
+    if (start_rec) {
+        for (i = 0; i < fstab->num_entries; i++) {
+            if (&fstab->recs[i] == start_rec) {
+                i++;
+                break;
+            }
+        }
+    } else {
+        i = 0;
+    }
+    for (; i < fstab->num_entries; i++) {
         int len = strlen(fstab->recs[i].mount_point);
         if (strncmp(path, fstab->recs[i].mount_point, len) == 0 &&
             (path[len] == '\0' || path[len] == '/')) {
             return &fstab->recs[i];
         }
     }
-
     return NULL;
 }
 
+/*
+ * Returns the 1st matching mount point.
+ * There might be more. To look for others, use fs_mgr_get_entry_for_mount_point_after()
+ * and give the fstab_rec from the previous search.
+ */
+struct fstab_rec *fs_mgr_get_entry_for_mount_point(struct fstab *fstab, const char *path)
+{
+    return fs_mgr_get_entry_for_mount_point_after(NULL, fstab, path);
+}
+
 int fs_mgr_is_voldmanaged(struct fstab_rec *fstab)
 {
     return fstab->fs_mgr_flags & MF_VOLDMANAGED;
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 835cf64..b8bb5aa 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -24,6 +24,11 @@
 extern "C" {
 #endif
 
+/*
+ * The entries must be kept in the same order as they were seen in the fstab.
+ * Unless explicitly requested, a lookup on mount point should always
+ * return the 1st one.
+ */
 struct fstab {
     int num_entries;
     struct fstab_rec *recs;