Add UnmountTreeWithPrefix util method.

Add a utility method to unmount all mountpoints
that start with a prefix.

Bug: 122905493
Test: manual
Change-Id: I11739e40e7849c1b4ca9e0b90c5c3f243691257a
diff --git a/Utils.cpp b/Utils.cpp
index 5e12194..4464afc 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -753,9 +753,52 @@
     return android::base::GetBoolProperty("ro.kernel.qemu", false);
 }
 
-status_t UnmountTree(const std::string& prefix) {
-    if (umount2(prefix.c_str(), MNT_DETACH)) {
-        PLOG(ERROR) << "Failed to unmount " << prefix;
+static status_t findMountPointsWithPrefix(const std::string& prefix,
+                                          std::list<std::string>& mountPoints) {
+    // Add a trailing slash if the client didn't provide one so that we don't match /foo/barbaz
+    // when the prefix is /foo/bar
+    std::string prefixWithSlash(prefix);
+    if (prefix.back() != '/') {
+        android::base::StringAppendF(&prefixWithSlash, "/");
+    }
+
+    std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
+    if (!mnts) {
+        PLOG(ERROR) << "Unable to open /proc/mounts";
+        return -errno;
+    }
+
+    // Some volumes can be stacked on each other, so force unmount in
+    // reverse order to give us the best chance of success.
+    struct mntent* mnt;  // getmntent returns a thread local, so it's safe.
+    while ((mnt = getmntent(mnts.get())) != nullptr) {
+        auto mountPoint = std::string(mnt->mnt_dir) + "/";
+        if (android::base::StartsWith(mountPoint, prefixWithSlash)) {
+            mountPoints.push_front(mountPoint);
+        }
+    }
+    return OK;
+}
+
+// Unmount all mountpoints that start with prefix. prefix itself doesn't need to be a mountpoint.
+status_t UnmountTreeWithPrefix(const std::string& prefix) {
+    std::list<std::string> toUnmount;
+    status_t result = findMountPointsWithPrefix(prefix, toUnmount);
+    if (result < 0) {
+        return result;
+    }
+    for (const auto& path : toUnmount) {
+        if (umount2(path.c_str(), MNT_DETACH)) {
+            PLOG(ERROR) << "Failed to unmount " << path;
+            result = -errno;
+        }
+    }
+    return result;
+}
+
+status_t UnmountTree(const std::string& mountPoint) {
+    if (umount2(mountPoint.c_str(), MNT_DETACH)) {
+        PLOG(ERROR) << "Failed to unmount " << mountPoint;
         return -errno;
     }
     return OK;
diff --git a/Utils.h b/Utils.h
index 0b35a7b..48a57d9 100644
--- a/Utils.h
+++ b/Utils.h
@@ -127,7 +127,8 @@
 /* Checks if Android is running in QEMU */
 bool IsRunningInEmulator();
 
-status_t UnmountTree(const std::string& prefix);
+status_t UnmountTreeWithPrefix(const std::string& prefix);
+status_t UnmountTree(const std::string& mountPoint);
 
 status_t WaitForFile(const char* filename, std::chrono::nanoseconds timeout);