Convert vold_prepare_subdirs to C++

Minimize overhead in boot by replacing shell script invoked multiple
times with a C++ program invoked once.

Bug: 67901036
Test: create user, run adb shell ls -laZ /data/misc_ce/10; delete user
    and check logs.
Change-Id: I886cfd6505cca1f5b5902f2071e13f48e612214d
diff --git a/Android.bp b/Android.bp
index f7ec836..8827d25 100644
--- a/Android.bp
+++ b/Android.bp
@@ -170,24 +170,20 @@
 }
 
 cc_binary {
-    name: "prepare_dir",
-    defaults: ["vold_default_flags"],
-
-    srcs: ["prepare_dir.cpp"],
-    shared_libs: [
-        "libbase",
-        "libcutils",
-        "libselinux",
-    ],
-}
-
-cc_prebuilt_binary {
     name: "vold_prepare_subdirs",
     defaults: ["vold_default_flags"],
 
-    srcs: ["vold_prepare_subdirs"],
-
-    required: ["prepare_dir"],
+    srcs: ["vold_prepare_subdirs.cpp", "Utils.cpp"],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblogwrap",
+        "libselinux",
+        "libutils",
+    ],
+    static_libs: [
+        "libvold_binder",
+    ],
 }
 
 subdirs = ["tests"]
diff --git a/Ext4Crypt.cpp b/Ext4Crypt.cpp
index 646a032..5f59d35 100644
--- a/Ext4Crypt.cpp
+++ b/Ext4Crypt.cpp
@@ -41,6 +41,8 @@
 
 #include <private/android_filesystem_config.h>
 
+#include "android/os/IVold.h"
+
 #include "cryptfs.h"
 
 #define EMULATED_USES_SELINUX 0
@@ -61,10 +63,6 @@
 using android::vold::kEmptyAuthentication;
 using android::vold::KeyBuffer;
 
-// NOTE: keep in sync with StorageManager
-static constexpr int FLAG_STORAGE_DE = 1 << 0;
-static constexpr int FLAG_STORAGE_CE = 1 << 1;
-
 namespace {
 
 const std::string device_key_dir = std::string() + DATA_MNT_POINT + e4crypt_unencrypted_folder;
@@ -73,6 +71,7 @@
 
 const std::string user_key_dir = std::string() + DATA_MNT_POINT + "/misc/vold/user_keys";
 const std::string user_key_temp = user_key_dir + "/temp";
+const std::string prepare_subdirs_path = "/system/bin/vold_prepare_subdirs";
 
 bool s_global_de_initialized = false;
 
@@ -379,7 +378,7 @@
     // We can only safely prepare DE storage here, since CE keys are probably
     // entangled with user credentials.  The framework will always prepare CE
     // storage once CE keys are installed.
-    if (!e4crypt_prepare_user_storage("", 0, 0, FLAG_STORAGE_DE)) {
+    if (!e4crypt_prepare_user_storage("", 0, 0, android::os::IVold::STORAGE_FLAG_DE)) {
         LOG(ERROR) << "Failed to prepare user 0 storage";
         return false;
     }
@@ -594,13 +593,12 @@
     return true;
 }
 
-static bool prepare_subdirs(const std::string& action, const std::string& dirtype,
-                            const std::string& volume_uuid, userid_t user_id,
-                            const std::string& path) {
-    if (0 != android::vold::ForkExecvp(std::vector<std::string>{"/system/bin/vold_prepare_subdirs",
-                                                                action, dirtype, volume_uuid,
-                                                                std::to_string(user_id), path})) {
-        LOG(ERROR) << "vold_prepare_subdirs failed on: " << path;
+static bool prepare_subdirs(const std::string& action, const std::string& volume_uuid,
+                            userid_t user_id, int flags) {
+    if (0 != android::vold::ForkExecvp(
+                 std::vector<std::string>{prepare_subdirs_path, action, volume_uuid,
+                                          std::to_string(user_id), std::to_string(flags)})) {
+        LOG(ERROR) << "vold_prepare_subdirs failed";
         return false;
     }
     return true;
@@ -611,7 +609,7 @@
     LOG(DEBUG) << "e4crypt_prepare_user_storage for volume " << escape_empty(volume_uuid)
                << ", user " << user_id << ", serial " << serial << ", flags " << flags;
 
-    if (flags & FLAG_STORAGE_DE) {
+    if (flags & android::os::IVold::STORAGE_FLAG_DE) {
         // DE_sys key
         auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id);
         auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id);
@@ -644,14 +642,9 @@
             }
             if (!ensure_policy(de_raw_ref, user_de_path)) return false;
         }
-
-        if (volume_uuid.empty()) {
-            if (!prepare_subdirs("prepare", "misc_de", volume_uuid, user_id, misc_de_path))
-                return false;
-        }
     }
 
-    if (flags & FLAG_STORAGE_CE) {
+    if (flags & android::os::IVold::STORAGE_FLAG_CE) {
         // CE_n key
         auto system_ce_path = android::vold::BuildDataSystemCePath(user_id);
         auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
@@ -678,8 +671,6 @@
         }
 
         if (volume_uuid.empty()) {
-            if (!prepare_subdirs("prepare", "misc_ce", volume_uuid, user_id, misc_ce_path))
-                return false;
             // Now that credentials have been installed, we can run restorecon
             // over these paths
             // NOTE: these paths need to be kept in sync with libselinux
@@ -687,6 +678,7 @@
             android::vold::RestoreconRecursive(misc_ce_path);
         }
     }
+    if (!prepare_subdirs("prepare", volume_uuid, user_id, flags)) return false;
 
     return true;
 }
@@ -696,7 +688,9 @@
                << ", user " << user_id << ", flags " << flags;
     bool res = true;
 
-    if (flags & FLAG_STORAGE_CE) {
+    res &= prepare_subdirs("destroy", volume_uuid, user_id, flags);
+
+    if (flags & android::os::IVold::STORAGE_FLAG_CE) {
         // CE_n key
         auto system_ce_path = android::vold::BuildDataSystemCePath(user_id);
         auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
@@ -706,13 +700,12 @@
         res &= destroy_dir(media_ce_path);
         res &= destroy_dir(user_ce_path);
         if (volume_uuid.empty()) {
-            res &= prepare_subdirs("destroy", "misc_ce", volume_uuid, user_id, misc_ce_path);
             res &= destroy_dir(system_ce_path);
             res &= destroy_dir(misc_ce_path);
         }
     }
 
-    if (flags & FLAG_STORAGE_DE) {
+    if (flags & android::os::IVold::STORAGE_FLAG_DE) {
         // DE_sys key
         auto system_legacy_path = android::vold::BuildDataSystemLegacyPath(user_id);
         auto misc_legacy_path = android::vold::BuildDataMiscLegacyPath(user_id);
@@ -725,7 +718,6 @@
 
         res &= destroy_dir(user_de_path);
         if (volume_uuid.empty()) {
-            res &= prepare_subdirs("destroy", "misc_de", volume_uuid, user_id, misc_de_path);
             res &= destroy_dir(system_legacy_path);
 #if MANAGE_MISC_DIRS
             res &= destroy_dir(misc_legacy_path);
diff --git a/Utils.cpp b/Utils.cpp
index 1f6ff21..38edcb3 100644
--- a/Utils.cpp
+++ b/Utils.cpp
@@ -271,14 +271,18 @@
         }
     }
 
-    if (setexeccon(context)) {
-        LOG(ERROR) << "Failed to setexeccon";
-        abort();
+    if (context) {
+        if (setexeccon(context)) {
+            LOG(ERROR) << "Failed to setexeccon";
+            abort();
+        }
     }
     status_t res = android_fork_execvp(argc, argv, NULL, false, true);
-    if (setexeccon(nullptr)) {
-        LOG(ERROR) << "Failed to setexeccon";
-        abort();
+    if (context) {
+        if (setexeccon(nullptr)) {
+            LOG(ERROR) << "Failed to setexeccon";
+            abort();
+        }
     }
 
     free(argv);
@@ -304,14 +308,18 @@
     }
     output.clear();
 
-    if (setexeccon(context)) {
-        LOG(ERROR) << "Failed to setexeccon";
-        abort();
+    if (context) {
+        if (setexeccon(context)) {
+            LOG(ERROR) << "Failed to setexeccon";
+            abort();
+        }
     }
     FILE* fp = popen(cmd.c_str(), "r"); // NOLINT
-    if (setexeccon(nullptr)) {
-        LOG(ERROR) << "Failed to setexeccon";
-        abort();
+    if (context) {
+        if (setexeccon(nullptr)) {
+            LOG(ERROR) << "Failed to setexeccon";
+            abort();
+        }
     }
 
     if (!fp) {
diff --git a/prepare_dir.cpp b/prepare_dir.cpp
deleted file mode 100644
index 1d91458..0000000
--- a/prepare_dir.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-/*
- * Tool to create a directory with the right SELinux context applied, or
- * apply the context if it's absent. Also fixes mode, uid, gid.
- */
-
-#include <string>
-#include <vector>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-
-#include <android-base/logging.h>
-
-#include <cutils/fs.h>
-#include <selinux/android.h>
-
-void usage(const char* progname) {
-    fprintf(stderr, "Usage: %s --mode MODE --uid UID --gid GID -- <path>\n", progname);
-}
-
-bool small_int(const std::string& s) {
-    return !s.empty() && s.size() < 7 && s.find_first_not_of("0123456789") == std::string::npos;
-}
-
-int main(int argc, const char* const argv[]) {
-    setenv("ANDROID_LOG_TAGS", "*:v", 1);
-    android::base::InitLogging(const_cast<char**>(argv));
-    std::vector<std::string> args(argv + 1, argv + argc);
-    // Enforce exact format of arguments. You can always loosen but you can never tighten :)
-    if (args.size() != 8 || args[0] != "--mode" || !small_int(args[1]) || args[2] != "--uid" ||
-        !small_int(args[3]) || args[4] != "--gid" || !small_int(args[5]) || args[6] != "--") {
-        usage(argv[0]);
-        return -1;
-    }
-    mode_t mode = (mode_t)stoi(args[1], nullptr, 8);
-    uid_t uid = (uid_t)stoi(args[3]);
-    gid_t gid = (gid_t)stoi(args[5]);
-    const char* path = args[7].c_str();
-
-    struct selabel_handle* sehandle = selinux_android_file_context_handle();
-    char* secontext = nullptr;
-    if (sehandle) {
-        if (selabel_lookup(sehandle, &secontext, path, S_IFDIR) == 0) {
-            setfscreatecon(secontext);
-        }
-    }
-
-    if (fs_prepare_dir(path, mode, uid, gid) != 0) {
-        return -1;
-    }
-    if (secontext) {
-        char* oldsecontext = nullptr;
-        if (lgetfilecon(path, &oldsecontext) < 0) {
-            PLOG(ERROR) << "Unable to read secontext for: " << path;
-            return -1;
-        }
-        if (strcmp(secontext, oldsecontext) != 0) {
-            LOG(INFO) << "Relabelling from " << oldsecontext << " to " << secontext << ": " << path;
-            if (lsetfilecon(path, secontext) != 0) {
-                PLOG(ERROR) << "Relabelling failed for: " << path;
-            }
-        }
-    }
-    return 0;
-}
diff --git a/vold_prepare_subdirs b/vold_prepare_subdirs
deleted file mode 100644
index cdea243..0000000
--- a/vold_prepare_subdirs
+++ /dev/null
@@ -1,67 +0,0 @@
-#!/system/bin/sh
-#
-# Copyright (C) 2017 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.
-
-# Set up or tear down subdirectories of vold-created directories.
-#
-# This is kept separate from vold because under the SELinux rules, it has privileges vold doesn't
-# have. In particular, prepare_dir sets SELinux labels on subdirectories based on file_contexts,
-# so this script has some relabelling privileges.
-
-
-set -e
-action="$1"
-dirtype="$2"
-volume_uuid="$3"
-user_id="$4"
-path="$5"
-
-case "$user_id" in
-    *[!0-9]* | '')
-        echo "Invalid user id"
-        exit -1
-        ;;
-esac
-
-if [ x"$volume_uuid" != x ] ; then
-    echo "Volume must be root volume"
-    exit -1;
-fi
-
-case "$dirtype" in
-    misc_de|misc_ce)
-        computed_path="/data/$dirtype/$user_id"
-        if [ x"$computed_path" != x"$path" ] ; then
-            echo "Parameter path didn't match computed path: " $computed_path
-            exit -1;
-        fi
-        case "$action" in
-            prepare)
-                /system/bin/prepare_dir --mode 700 --uid 0 --gid 0 -- "$computed_path"/vold
-                ;;
-            destroy)
-                rm -rf "$computed_path"/*
-                ;;
-            *)
-                echo "Unknown action: $action"
-                exit -1
-                ;;
-        esac
-        ;;
-    *)
-        echo "Unknown type: $dirtype"
-        exit -1
-        ;;
-esac
diff --git a/vold_prepare_subdirs.cpp b/vold_prepare_subdirs.cpp
new file mode 100644
index 0000000..6dba5b3
--- /dev/null
+++ b/vold_prepare_subdirs.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/*
+ * Tool to create a directory with the right SELinux context applied, or
+ * apply the context if it's absent. Also fixes mode, uid, gid.
+ */
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <android-base/logging.h>
+#include <android-base/scopeguard.h>
+
+#include <cutils/fs.h>
+#include <selinux/android.h>
+
+#include "Utils.h"
+#include "android/os/IVold.h"
+
+static void usage(const char* progname) {
+    std::cerr << "Usage: " << progname << " [ prepare | destroy ] <volume_uuid> <user_id> <flags>"
+              << std::endl;
+    exit(-1);
+}
+
+static bool small_int(const std::string& s) {
+    return !s.empty() && s.size() < 7 && s.find_first_not_of("0123456789") == std::string::npos;
+}
+
+static bool valid_uuid(const std::string& s) {
+    return s.size() < 40 && s.find_first_not_of("0123456789abcdefABCDEF-_") == std::string::npos;
+}
+
+static bool prepare_dir(struct selabel_handle* sehandle, mode_t mode, uid_t uid, gid_t gid,
+                        const std::string& path) {
+    auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); });
+    auto secontext = std::unique_ptr<char, void (*)(char*)>(nullptr, freecon);
+    char* tmp_secontext;
+    if (sehandle && selabel_lookup(sehandle, &tmp_secontext, path.c_str(), S_IFDIR) == 0) {
+        secontext.reset(tmp_secontext);
+    }
+    LOG(DEBUG) << "Setting up mode " << std::oct << mode << std::dec << " uid " << uid << " gid "
+               << gid << " context " << secontext.get() << " on path: " << path;
+    if (secontext) {
+        if (setfscreatecon(secontext.get()) != 0) {
+            PLOG(ERROR) << "Unable to read setfscreatecon for: " << path;
+            return false;
+        }
+    }
+    if (fs_prepare_dir(path.c_str(), mode, uid, gid) != 0) {
+        return false;
+    }
+    if (secontext) {
+        char* tmp_oldsecontext = nullptr;
+        if (lgetfilecon(path.c_str(), &tmp_oldsecontext) < 0) {
+            PLOG(ERROR) << "Unable to read secontext for: " << path;
+            return false;
+        }
+        auto oldsecontext = std::unique_ptr<char, void (*)(char*)>(tmp_oldsecontext, freecon);
+        if (strcmp(secontext.get(), oldsecontext.get()) != 0) {
+            LOG(INFO) << "Relabelling from " << ((char*)oldsecontext.get()) << " to "
+                      << ((char*)secontext.get()) << ": " << path;
+            if (lsetfilecon(path.c_str(), secontext.get()) != 0) {
+                PLOG(ERROR) << "Relabelling failed for: " << path;
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static bool rmrf_contents(const std::string& path) {
+    auto dirp = std::unique_ptr<DIR, int (*)(DIR*)>(opendir(path.c_str()), closedir);
+    if (!dirp) {
+        PLOG(ERROR) << "Unable to open directory: " << path;
+        return false;
+    }
+    bool res = true;
+    for (;;) {
+        errno = 0;
+        auto const entry = readdir(dirp.get());
+        if (!entry) {
+            if (errno) {
+                PLOG(ERROR) << "readdir failed on: " << path;
+                return false;
+            }
+            return res;
+        }
+        if (entry->d_name[0] == '.') continue;
+        auto subdir = path + "/" + entry->d_name;
+        if (0 !=
+            android::vold::ForkExecvp(std::vector<std::string>{"/system/bin/rm", "-rf", subdir})) {
+            LOG(ERROR) << "rm -rf failed on " << subdir;
+            res = false;
+        }
+    }
+}
+
+static bool prepare_subdirs(const std::string& volume_uuid, int user_id, int flags) {
+    struct selabel_handle* sehandle = selinux_android_file_context_handle();
+
+    if (volume_uuid.empty()) {
+        if (flags & android::os::IVold::STORAGE_FLAG_DE) {
+            auto misc_de_path = android::vold::BuildDataMiscDePath(user_id);
+            if (!prepare_dir(sehandle, 0700, 0, 0, misc_de_path + "/vold")) return false;
+        }
+        if (flags & android::os::IVold::STORAGE_FLAG_CE) {
+            auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
+            if (!prepare_dir(sehandle, 0700, 0, 0, misc_ce_path + "/vold")) return false;
+        }
+    }
+    return true;
+}
+
+static bool destroy_subdirs(const std::string& volume_uuid, int user_id, int flags) {
+    bool res = true;
+    if (volume_uuid.empty()) {
+        if (flags & android::os::IVold::STORAGE_FLAG_CE) {
+            auto misc_ce_path = android::vold::BuildDataMiscCePath(user_id);
+            res &= rmrf_contents(misc_ce_path);
+        }
+        if (flags & android::os::IVold::STORAGE_FLAG_DE) {
+            auto misc_de_path = android::vold::BuildDataMiscDePath(user_id);
+            res &= rmrf_contents(misc_de_path);
+        }
+    }
+    return res;
+}
+
+int main(int argc, const char* const argv[]) {
+    android::base::InitLogging(const_cast<char**>(argv));
+    std::vector<std::string> args(argv + 1, argv + argc);
+
+    if (args.size() != 4 || !valid_uuid(args[1]) || !small_int(args[2]) || !small_int(args[3])) {
+        usage(argv[0]);
+        return -1;
+    }
+
+    auto volume_uuid = args[1];
+    int user_id = stoi(args[2]);
+    int flags = stoi(args[3]);
+    if (args[0] == "prepare") {
+        if (!prepare_subdirs(volume_uuid, user_id, flags)) return -1;
+    } else if (args[0] == "destroy") {
+        if (!destroy_subdirs(volume_uuid, user_id, flags)) return -1;
+    } else {
+        usage(argv[0]);
+        return -1;
+    }
+    return 0;
+}