Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1f34244f authored by Narayan Kamath's avatar Narayan Kamath Committed by Gerrit Code Review
Browse files

Merge "installd: Implement support for userdata snapshot and restore."

parents 9c57f3bf dfdfb795
Loading
Loading
Loading
Loading
+160 −32
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@

#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
@@ -746,6 +747,162 @@ binder::Status InstalldNativeService::fixupAppData(const std::unique_ptr<std::st
    return ok();
}

static int32_t copy_directory_recursive(const char* from, const char* to) {
    char *argv[] = {
        (char*) kCpPath,
        (char*) "-F", /* delete any existing destination file first (--remove-destination) */
        (char*) "-p", /* preserve timestamps, ownership, and permissions */
        (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
        (char*) "-P", /* Do not follow symlinks [default] */
        (char*) "-d", /* don't dereference symlinks */
        (char*) from,
        (char*) to
    };

    LOG(DEBUG) << "Copying " << from << " to " << to;
    return android_fork_execvp(ARRAY_SIZE(argv), argv, nullptr, false, true);
}

// TODO(narayan): We should pass through the ceDataInode so that we can call
// clearAppData(FLAG_CLEAR_CACHE_ONLY | FLAG_CLEAR_CODE_CACHE before we commence
// the copy.
//
// TODO(narayan): For snapshotAppData as well as restoreAppDataSnapshot, we
// should validate that volumeUuid is either nullptr or TEST, we won't support
// anything else.
//
// TODO(narayan): We need to be clearer about the expected behaviour for the
// case where a snapshot already exists. We either need to clear the contents
// of the snapshot directory before we make a copy, or we need to ensure that
// the caller always clears it before requesting a snapshot.
binder::Status InstalldNativeService::snapshotAppData(
        const std::unique_ptr<std::string>& volumeUuid,
        const std::string& packageName, int32_t user, int32_t storageFlags) {
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_UUID(volumeUuid);
    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
    std::lock_guard<std::recursive_mutex> lock(mLock);

    const char* volume_uuid = volumeUuid ? volumeUuid->c_str() : nullptr;
    const char* package_name = packageName.c_str();

    binder::Status res = ok();
    bool clear_ce_on_exit = false;
    bool clear_de_on_exit = false;

    auto deleter = [&clear_ce_on_exit, &clear_de_on_exit, &volume_uuid, &user, &package_name] {
        if (clear_de_on_exit) {
            auto to = create_data_misc_de_rollback_package_path(volume_uuid, user, package_name);
            if (delete_dir_contents(to.c_str(), 1, nullptr) != 0) {
                LOG(WARNING) << "Failed to delete app data snapshot: " << to;
            }
        }

        if (clear_ce_on_exit) {
            auto to = create_data_misc_ce_rollback_package_path(volume_uuid, user, package_name);
            if (delete_dir_contents(to.c_str(), 1, nullptr) != 0) {
                LOG(WARNING) << "Failed to delete app data snapshot: " << to;
            }
        }
    };

    auto scope_guard = android::base::make_scope_guard(deleter);

    // The app may not have any data at all, in which case it's OK to skip here.
    auto from_ce = create_data_user_ce_package_path(volume_uuid, user, package_name);
    if (access(from_ce.c_str(), F_OK) != 0) {
        LOG(INFO) << "Missing source " << from_ce;
        return ok();
    }

    if (storageFlags & FLAG_STORAGE_DE) {
        auto from = create_data_user_de_package_path(volume_uuid, user, package_name);
        auto to = create_data_misc_de_rollback_path(volume_uuid, user);

        int rc = copy_directory_recursive(from.c_str(), to.c_str());
        if (rc != 0) {
            res = error(rc, "Failed copying " + from + " to " + to);
            clear_de_on_exit = true;
            return res;
        }
    }

    if (storageFlags & FLAG_STORAGE_CE) {
        auto from = create_data_user_ce_package_path(volume_uuid, user, package_name);
        auto to = create_data_misc_ce_rollback_path(volume_uuid, user);
        int rc = copy_directory_recursive(from.c_str(), to.c_str());
        if (rc != 0) {
            res = error(rc, "Failed copying " + from + " to " + to);
            clear_ce_on_exit = true;
            return res;
        }
    }

    return res;
}

binder::Status InstalldNativeService::restoreAppDataSnapshot(
        const std::unique_ptr<std::string>& volumeUuid, const std::string& packageName,
        const int32_t appId, const int64_t ceDataInode, const std::string& seInfo,
        const int32_t user, int32_t storageFlags) {
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_UUID(volumeUuid);
    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
    std::lock_guard<std::recursive_mutex> lock(mLock);

    const char* volume_uuid = volumeUuid ? volumeUuid->c_str() : nullptr;
    const char* package_name = packageName.c_str();

    auto from_ce = create_data_misc_ce_rollback_package_path(volume_uuid,
            user, package_name);
    auto from_de = create_data_misc_de_rollback_package_path(volume_uuid,
            user, package_name);

    const bool needs_ce_rollback = (storageFlags & FLAG_STORAGE_CE) &&
        (access(from_ce.c_str(), F_OK) == 0);
    const bool needs_de_rollback = (storageFlags & FLAG_STORAGE_DE) &&
        (access(from_de.c_str(), F_OK) == 0);

    if (!needs_ce_rollback && !needs_de_rollback) {
        return ok();
    }

    // We know we're going to rollback one of the CE or DE data, so we clear
    // application data first. Note that it's possible that we're asked to
    // restore both CE & DE data but that one of the restores fail. Leaving the
    // app with no data in those cases is arguably better than leaving the app
    // with mismatched / stale data.
    LOG(INFO) << "Clearing app data for " << packageName << " to restore snapshot.";
    binder::Status res = clearAppData(volumeUuid, packageName, user, storageFlags, ceDataInode);
    if (!res.isOk()) {
        return res;
    }

    if (needs_ce_rollback) {
        auto to_ce = create_data_user_ce_path(volume_uuid, user);
        int rc = copy_directory_recursive(from_ce.c_str(), to_ce.c_str());
        if (rc != 0) {
            res = error(rc, "Failed copying " + from_ce + " to " + to_ce);
            return res;
        }
    }

    if (needs_de_rollback) {
        auto to_de = create_data_user_de_path(volume_uuid, user);
        int rc = copy_directory_recursive(from_de.c_str(), to_de.c_str());
        if (rc != 0) {
            // TODO(narayan): Should we clear clear the rolled back CE data if
            // something goes wrong here ? We're choosing between leaving the
            // app devoid of all its data or with just its ce data installed.
            res = error(rc, "Failed copying " + from_de + " to " + to_de);
            return res;
        }
    }

    // Finally, restore the SELinux label on the app data.
    return restoreconAppData(volumeUuid, packageName, user, storageFlags, appId, seInfo);
}

binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std::string>& fromUuid,
        const std::unique_ptr<std::string>& toUuid, const std::string& packageName,
        const std::string& dataAppName, int32_t appId, const std::string& seInfo,
@@ -770,19 +927,7 @@ binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std:
        auto to = create_data_app_package_path(to_uuid, data_app_name);
        auto to_parent = create_data_app_path(to_uuid);

        char *argv[] = {
            (char*) kCpPath,
            (char*) "-F", /* delete any existing destination file first (--remove-destination) */
            (char*) "-p", /* preserve timestamps, ownership, and permissions */
            (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
            (char*) "-P", /* Do not follow symlinks [default] */
            (char*) "-d", /* don't dereference symlinks */
            (char*) from.c_str(),
            (char*) to_parent.c_str()
        };

        LOG(DEBUG) << "Copying " << from << " to " << to;
        int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, nullptr, false, true);
        int rc = copy_directory_recursive(from.c_str(), to_parent.c_str());
        if (rc != 0) {
            res = error(rc, "Failed copying " + from + " to " + to);
            goto fail;
@@ -810,25 +955,11 @@ binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std:
            goto fail;
        }

        char *argv[] = {
            (char*) kCpPath,
            (char*) "-F", /* delete any existing destination file first (--remove-destination) */
            (char*) "-p", /* preserve timestamps, ownership, and permissions */
            (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
            (char*) "-P", /* Do not follow symlinks [default] */
            (char*) "-d", /* don't dereference symlinks */
            nullptr,
            nullptr
        };

        {
            auto from = create_data_user_de_package_path(from_uuid, user, package_name);
            auto to = create_data_user_de_path(to_uuid, user);
            argv[6] = (char*) from.c_str();
            argv[7] = (char*) to.c_str();

            LOG(DEBUG) << "Copying " << from << " to " << to;
            int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, nullptr, false, true);
            int rc = copy_directory_recursive(from.c_str(), to.c_str());
            if (rc != 0) {
                res = error(rc, "Failed copying " + from + " to " + to);
                goto fail;
@@ -837,11 +968,8 @@ binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std:
        {
            auto from = create_data_user_ce_package_path(from_uuid, user, package_name);
            auto to = create_data_user_ce_path(to_uuid, user);
            argv[6] = (char*) from.c_str();
            argv[7] = (char*) to.c_str();

            LOG(DEBUG) << "Copying " << from << " to " << to;
            int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, nullptr, false, true);
            int rc = copy_directory_recursive(from.c_str(), to.c_str());
            if (rc != 0) {
                res = error(rc, "Failed copying " + from + " to " + to);
                goto fail;
+6 −0
Original line number Diff line number Diff line
@@ -60,6 +60,12 @@ public:

    binder::Status fixupAppData(const std::unique_ptr<std::string>& uuid, int32_t flags);

    binder::Status snapshotAppData(const std::unique_ptr<std::string>& volumeUuid,
            const std::string& packageName, const int32_t user, int32_t storageFlags);
    binder::Status restoreAppDataSnapshot(const std::unique_ptr<std::string>& volumeUuid,
            const std::string& packageName, const int32_t appId, const int64_t ceDataInode,
            const std::string& seInfo, const int32_t user, int32_t storageFlags);

    binder::Status getAppSize(const std::unique_ptr<std::string>& uuid,
            const std::vector<std::string>& packageNames, int32_t userId, int32_t flags,
            int32_t appId, const std::vector<int64_t>& ceDataInodes,
+12 −0
Original line number Diff line number Diff line
@@ -104,4 +104,16 @@ interface IInstalld {
    boolean prepareAppProfile(@utf8InCpp String packageName,
        int userId, int appId, @utf8InCpp String profileName, @utf8InCpp String codePath,
        @nullable @utf8InCpp String dexMetadata);

    void snapshotAppData(@nullable @utf8InCpp String uuid, in @utf8InCpp String packageName,
            int userId, int storageFlags);
    void restoreAppDataSnapshot(@nullable @utf8InCpp String uuid, in @utf8InCpp String packageName,
            int appId, long ceDataInode, @utf8InCpp String seInfo, int user, int storageflags);

    // TODO(narayan) we need an API to delete the app data snapshot as well.
    // void destroyAppDataSnapshot(@nullable @utf8InCpp String uuid,
    //        in @utf8InCpp String packageName, int userId, int storageFlags);

    const int FLAG_STORAGE_DE = 0x1;
    const int FLAG_STORAGE_CE = 0x2;
}
+177 −0
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@
#include <sys/xattr.h>

#include <android-base/logging.h>
#include <android-base/file.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <cutils/properties.h>
#include <gtest/gtest.h>
@@ -240,5 +242,180 @@ TEST_F(ServiceTest, CalculateCache) {
    EXPECT_EQ("/data/dalvik-cache/isa/path@to@file.apk@classes.dex", std::string(buf));
}

static bool mkdirs(const std::string& path, mode_t mode) {
    struct stat sb;
    if (stat(path.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) {
        return true;
    }

    if (!mkdirs(android::base::Dirname(path), mode)) {
        return false;
    }

    return (::mkdir(path.c_str(), mode) != -1);
}

TEST_F(ServiceTest, CreateAppDataSnapshot) {
  auto rollback_ce_dir = create_data_misc_ce_rollback_path("TEST", 0);
  auto rollback_de_dir = create_data_misc_de_rollback_path("TEST", 0);

  ASSERT_TRUE(mkdirs(rollback_ce_dir, 700));
  ASSERT_TRUE(mkdirs(rollback_de_dir, 700));

  auto fake_package_ce_path = create_data_user_ce_package_path("TEST", 0, "com.foo");
  auto fake_package_de_path = create_data_user_de_package_path("TEST", 0, "com.foo");

  ASSERT_TRUE(mkdirs(fake_package_ce_path, 700));
  ASSERT_TRUE(mkdirs(fake_package_de_path, 700));

  auto deleter = [&rollback_ce_dir, &rollback_de_dir,
          &fake_package_ce_path, &fake_package_de_path]() {
      delete_dir_contents(rollback_ce_dir, true);
      delete_dir_contents(rollback_de_dir, true);
      delete_dir_contents(fake_package_ce_path, true);
      delete_dir_contents(fake_package_de_path, true);
      rmdir(rollback_ce_dir.c_str());
      rmdir(rollback_de_dir.c_str());
  };
  auto scope_guard = android::base::make_scope_guard(deleter);

  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_CE", fake_package_ce_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_DE", fake_package_de_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));

  // Request a snapshot of the CE content but not the DE content.
  ASSERT_TRUE(service->snapshotAppData(std::make_unique<std::string>("TEST"),
          "com.foo", 0, FLAG_STORAGE_CE).isOk());

  std::string ce_content, de_content;
  // At this point, we should have the CE content but not the DE content.
  ASSERT_TRUE(android::base::ReadFileToString(
      rollback_ce_dir + "/com.foo/file1", &ce_content, false /* follow_symlinks */));
  ASSERT_FALSE(android::base::ReadFileToString(
      rollback_de_dir + "/com.foo/file1", &de_content, false /* follow_symlinks */));
  ASSERT_EQ("TEST_CONTENT_CE", ce_content);

  // Modify the CE content, so we can assert later that it's reflected
  // in the snapshot.
  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_CE_MODIFIED", fake_package_ce_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));

  // Request a snapshot of the DE content but not the CE content.
  ASSERT_TRUE(service->snapshotAppData(std::make_unique<std::string>("TEST"),
          "com.foo", 0, FLAG_STORAGE_DE).isOk());

  // At this point, both the CE as well as the DE content should be fully
  // populated.
  ASSERT_TRUE(android::base::ReadFileToString(
      rollback_ce_dir + "/com.foo/file1", &ce_content, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::ReadFileToString(
      rollback_de_dir + "/com.foo/file1", &de_content, false /* follow_symlinks */));
  ASSERT_EQ("TEST_CONTENT_CE", ce_content);
  ASSERT_EQ("TEST_CONTENT_DE", de_content);

  // Modify the DE content, so we can assert later that it's reflected
  // in our final snapshot.
  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_DE_MODIFIED", fake_package_de_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));

  // Request a snapshot of both the CE as well as the DE content.
  ASSERT_TRUE(service->snapshotAppData(std::make_unique<std::string>("TEST"),
          "com.foo", 0, FLAG_STORAGE_DE | FLAG_STORAGE_CE).isOk());

  ASSERT_TRUE(android::base::ReadFileToString(
      rollback_ce_dir + "/com.foo/file1", &ce_content, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::ReadFileToString(
      rollback_de_dir + "/com.foo/file1", &de_content, false /* follow_symlinks */));
  ASSERT_EQ("TEST_CONTENT_CE_MODIFIED", ce_content);
  ASSERT_EQ("TEST_CONTENT_DE_MODIFIED", de_content);
}

TEST_F(ServiceTest, CreateAppDataSnapshot_AppDataAbsent) {
  auto rollback_ce_dir = create_data_misc_ce_rollback_path("TEST", 0);
  auto rollback_de_dir = create_data_misc_de_rollback_path("TEST", 0);

  ASSERT_TRUE(mkdirs(rollback_ce_dir, 700));
  ASSERT_TRUE(mkdirs(rollback_de_dir, 700));

  auto deleter = [&rollback_ce_dir, &rollback_de_dir]() {
      delete_dir_contents(rollback_ce_dir, true);
      delete_dir_contents(rollback_de_dir, true);
      rmdir(rollback_ce_dir.c_str());
      rmdir(rollback_de_dir.c_str());
  };

  auto scope_guard = android::base::make_scope_guard(deleter);

  ASSERT_TRUE(service->snapshotAppData(std::make_unique<std::string>("TEST"),
          "com.foo", 0, FLAG_STORAGE_CE).isOk());
  ASSERT_TRUE(service->snapshotAppData(std::make_unique<std::string>("TEST"),
          "com.foo", 0, FLAG_STORAGE_DE).isOk());

  // The snapshot calls must succeed but there should be no snapshot
  // created.
  struct stat sb;
  ASSERT_EQ(-1, stat((rollback_ce_dir + "/com.foo").c_str(), &sb));
  ASSERT_EQ(-1, stat((rollback_de_dir + "/com.foo").c_str(), &sb));
}

TEST_F(ServiceTest, RestoreAppDataSnapshot) {
  auto rollback_ce_dir = create_data_misc_ce_rollback_path("TEST", 0);
  auto rollback_de_dir = create_data_misc_de_rollback_path("TEST", 0);

  ASSERT_TRUE(mkdirs(rollback_ce_dir, 700));
  ASSERT_TRUE(mkdirs(rollback_de_dir, 700));

  auto fake_package_ce_path = create_data_user_ce_package_path("TEST", 0, "com.foo");
  auto fake_package_de_path = create_data_user_de_package_path("TEST", 0, "com.foo");

  ASSERT_TRUE(mkdirs(fake_package_ce_path, 700));
  ASSERT_TRUE(mkdirs(fake_package_de_path, 700));

  auto deleter = [&rollback_ce_dir, &rollback_de_dir,
          &fake_package_ce_path, &fake_package_de_path]() {
      delete_dir_contents(rollback_ce_dir, true);
      delete_dir_contents(rollback_de_dir, true);
      delete_dir_contents(fake_package_ce_path, true);
      delete_dir_contents(fake_package_de_path, true);
      rmdir(rollback_ce_dir.c_str());
      rmdir(rollback_de_dir.c_str());
  };
  auto scope_guard = android::base::make_scope_guard(deleter);

  // Write contents to the rollback location. We'll write the same files to the
  // app data location and make sure the restore has overwritten them.
  ASSERT_TRUE(mkdirs(rollback_ce_dir + "/com.foo/", 700));
  ASSERT_TRUE(mkdirs(rollback_de_dir + "/com.foo/", 700));
  ASSERT_TRUE(android::base::WriteStringToFile(
          "CE_RESTORE_CONTENT", rollback_ce_dir + "/com.foo/file1",
          0700, 10000, 20000, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::WriteStringToFile(
          "DE_RESTORE_CONTENT", rollback_de_dir + "/com.foo/file1",
          0700, 10000, 20000, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_CE", fake_package_ce_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::WriteStringToFile(
          "TEST_CONTENT_DE", fake_package_de_path + "/file1",
          0700, 10000, 20000, false /* follow_symlinks */));

  ASSERT_TRUE(service->restoreAppDataSnapshot(std::make_unique<std::string>("TEST"),
          "com.foo", 10000, -1, "", 0, FLAG_STORAGE_DE | FLAG_STORAGE_CE).isOk());

  std::string ce_content, de_content;
  ASSERT_TRUE(android::base::ReadFileToString(
      fake_package_ce_path + "/file1", &ce_content, false /* follow_symlinks */));
  ASSERT_TRUE(android::base::ReadFileToString(
      fake_package_de_path + "/file1", &de_content, false /* follow_symlinks */));
  ASSERT_EQ("CE_RESTORE_CONTENT", ce_content);
  ASSERT_EQ("DE_RESTORE_CONTENT", de_content);
}


}  // namespace installd
}  // namespace android
+30 −0
Original line number Diff line number Diff line
@@ -544,5 +544,35 @@ TEST_F(UtilsTest, MatchExtension_Invalid) {
    EXPECT_EQ(0, MatchExtension("docx"));
}

TEST_F(UtilsTest, TestRollbackPaths) {
    EXPECT_EQ("/data/misc_ce/0/rollback/com.foo",
            create_data_misc_ce_rollback_package_path(nullptr, 0, "com.foo"));
    EXPECT_EQ("/data/misc_ce/10/rollback/com.foo",
            create_data_misc_ce_rollback_package_path(nullptr, 10, "com.foo"));

    EXPECT_EQ("/data/misc_de/0/rollback/com.foo",
            create_data_misc_de_rollback_package_path(nullptr, 0, "com.foo"));
    EXPECT_EQ("/data/misc_de/10/rollback/com.foo",
            create_data_misc_de_rollback_package_path(nullptr, 10, "com.foo"));

    EXPECT_EQ("/data/misc_ce/0/rollback",
            create_data_misc_ce_rollback_path(nullptr, 0));
    EXPECT_EQ("/data/misc_ce/10/rollback",
            create_data_misc_ce_rollback_path(nullptr, 10));

    EXPECT_EQ("/data/misc_de/0/rollback",
            create_data_misc_de_rollback_path(nullptr, 0));
    EXPECT_EQ("/data/misc_de/10/rollback",
            create_data_misc_de_rollback_path(nullptr, 10));

    // These last couple of cases are never exercised in production because we
    // only snapshot apps in the primary data partition. Exercise them here for
    // the sake of completeness.
    EXPECT_EQ("/mnt/expand/57f8f4bc-abf4-655f-bf67-946fc0f9f25b/misc_ce/0/rollback/com.example",
            create_data_misc_ce_rollback_package_path("57f8f4bc-abf4-655f-bf67-946fc0f9f25b", 0, "com.example"));
    EXPECT_EQ("/mnt/expand/57f8f4bc-abf4-655f-bf67-946fc0f9f25b/misc_de/0/rollback/com.example",
            create_data_misc_de_rollback_package_path("57f8f4bc-abf4-655f-bf67-946fc0f9f25b", 0, "com.example"));
}

}  // namespace installd
}  // namespace android
Loading