Merge "Move encrypted directories into place already-encrypted"
diff --git a/FsCrypt.cpp b/FsCrypt.cpp
index f871a32..b3f90ea 100644
--- a/FsCrypt.cpp
+++ b/FsCrypt.cpp
@@ -323,8 +323,37 @@
 // Prepare a directory and assign it the given encryption policy.
 static bool prepare_dir_with_policy(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
                                     const EncryptionPolicy& policy) {
-    if (!prepare_dir(dir, mode, uid, gid)) return false;
-    if (IsFbeEnabled() && !EnsurePolicy(policy, dir)) return false;
+    if (android::vold::pathExists(dir)) {
+        if (!prepare_dir(dir, mode, uid, gid)) return false;
+        if (IsFbeEnabled() && !EnsurePolicy(policy, dir)) return false;
+    } else {
+        // If the directory does not yet exist, then create it under a temporary name, and only move
+        // it to the final name after it is fully prepared with an encryption policy and the desired
+        // file permissions.  This prevents the directory from being accessed before it is ready.
+        //
+        // Note: this relies on the SELinux file_contexts assigning the same type to the file path
+        // with the ".new" suffix as to the file path without the ".new" suffix.
+
+        const std::string tmp_dir = dir + ".new";
+        if (android::vold::pathExists(tmp_dir)) {
+            android::vold::DeleteDirContentsAndDir(tmp_dir);
+        }
+        if (!prepare_dir(tmp_dir, mode, uid, gid)) return false;
+        if (IsFbeEnabled() && !EnsurePolicy(policy, tmp_dir)) return false;
+
+        // On some buggy kernels, renaming a directory that is both encrypted and case-insensitive
+        // fails in some specific circumstances.  Unfortunately, these circumstances happen here
+        // when processing the "media" directory.  This was already fixed by kernel commit
+        // https://git.kernel.org/linus/b5639bb4313b9d45 ('f2fs: don't use casefolded comparison for
+        // "." and ".."').  But to support kernels that lack that fix, we use the below workaround.
+        // It bypasses the bug by making the encryption key of tmp_dir be loaded before the rename.
+        android::vold::pathExists(tmp_dir + "/subdir");
+
+        if (rename(tmp_dir.c_str(), dir.c_str()) != 0) {
+            PLOG(ERROR) << "Failed to rename " << tmp_dir << " to " << dir;
+            return false;
+        }
+    }
     return true;
 }