Merge changes from topic "apexdenrollback"

* changes:
  Snapshot and restore DE(user) apex data.
  Snapshot and restore CE apex data directories.
diff --git a/apexd/aidl/android/apex/IApexService.aidl b/apexd/aidl/android/apex/IApexService.aidl
index 6eeb2b7..0b87473 100644
--- a/apexd/aidl/android/apex/IApexService.aidl
+++ b/apexd/aidl/android/apex/IApexService.aidl
@@ -33,6 +33,18 @@
 
    void abortActiveSession();
 
+   /**
+    * Copies the CE apex data directory for the given user to the backup
+    * location, and returns the inode of the snapshot directory.
+    */
+   long snapshotCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
+
+   /**
+    * Restores the snapshot of the CE apex data directory for the given user and
+    * apex.
+    */
+   void restoreCeData(int user_id, int rollback_id, in @utf8InCpp String apex_name);
+
    void unstagePackages(in @utf8InCpp List<String> active_package_paths);
 
    /**
diff --git a/apexd/apex_constants.h b/apexd/apex_constants.h
index 9f1ce25..e5c305f 100644
--- a/apexd/apex_constants.h
+++ b/apexd/apex_constants.h
@@ -41,6 +41,8 @@
 static constexpr const char* kApexSnapshotSubDir = "apexrollback";
 
 static constexpr const char* kDeSysDataDir = "/data/misc";
+static constexpr const char* kDeNDataDir = "/data/misc_de";
+static constexpr const char* kCeDataDir = "/data/misc_ce";
 
 static constexpr const char* kApexPackageSuffix = ".apex";
 
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 8d4c508..8ad9ff4 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -1311,41 +1311,106 @@
   return ReplaceFiles(from_path, to_path);
 }
 
-void snapshotOrRestoreIfNeeded(const ApexSession& session,
-                               const std::vector<std::string>& apexes) {
+void snapshotOrRestoreIfNeeded(const std::string& base_dir,
+                               const ApexSession& session) {
   if (session.HasRollbackEnabled()) {
-    for (const auto& apex : apexes) {
-      Result<ApexFile> apex_file = ApexFile::Open(apex);
-      if (!apex_file) {
-        LOG(ERROR) << "Cannot open apex for snapshot: " << apex;
-        continue;
-      }
-      auto apex_name = apex_file->GetManifest().name();
-      Result<void> result = snapshotDataDirectory(
-          kDeSysDataDir, session.GetRollbackId(), apex_name);
+    for (const auto& apex_name : session.GetApexNames()) {
+      Result<void> result =
+          snapshotDataDirectory(base_dir, session.GetRollbackId(), apex_name);
       if (!result) {
-        LOG(ERROR) << "Snapshot failed for " << apex << ": " << result.error();
+        LOG(ERROR) << "Snapshot failed for " << apex_name << ": "
+                   << result.error();
       }
     }
   } else if (session.IsRollback()) {
-    for (const auto& apex : apexes) {
-      Result<ApexFile> apex_file = ApexFile::Open(apex);
-      if (!apex_file) {
-        LOG(ERROR) << "Cannot open apex for restore of data: " << apex;
-        continue;
-      }
-      auto apex_name = apex_file->GetManifest().name();
+    for (const auto& apex_name : session.GetApexNames()) {
       // TODO: Back up existing files in case rollback is reverted.
-      Result<void> result = restoreDataDirectory(
-          kDeSysDataDir, session.GetRollbackId(), apex_name);
+      Result<void> result =
+          restoreDataDirectory(base_dir, session.GetRollbackId(), apex_name);
       if (!result) {
-        LOG(ERROR) << "Restore of data failed for " << apex << ": "
+        LOG(ERROR) << "Restore of data failed for " << apex_name << ": "
                    << result.error();
       }
     }
   }
 }
 
+int snapshotOrRestoreDeUserData() {
+  auto filter_fn = [](const std::filesystem::directory_entry& entry) {
+    std::error_code ec;
+    bool result = entry.is_directory(ec);
+    if (ec) {
+      LOG(ERROR) << "Failed to check is_directory : " << ec.message();
+      return false;
+    }
+    return result;
+  };
+  auto user_dirs = ReadDir(kDeNDataDir, filter_fn);
+
+  if (!user_dirs) {
+    LOG(ERROR) << "Error reading dirs " << user_dirs.error();
+    return 1;
+  }
+
+  auto sessions = ApexSession::GetSessionsInState(SessionState::ACTIVATED);
+
+  for (const ApexSession& session : sessions) {
+    for (const auto& user_dir : *user_dirs) {
+      snapshotOrRestoreIfNeeded(user_dir, session);
+    }
+  }
+
+  return 0;
+}
+
+Result<ino_t> snapshotCeData(const int user_id, const int rollback_id,
+                             const std::string& apex_name) {
+  auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
+  Result<void> result = snapshotDataDirectory(base_dir, rollback_id, apex_name);
+  if (!result) {
+    return result.error();
+  }
+  auto ce_snapshot_path =
+      StringPrintf("%s/%s/%d/%s", base_dir.c_str(), kApexSnapshotSubDir,
+                   rollback_id, apex_name.c_str());
+  return get_path_inode(ce_snapshot_path);
+}
+
+Result<void> restoreCeData(const int user_id, const int rollback_id,
+                           const std::string& apex_name) {
+  auto base_dir = StringPrintf("%s/%d", kCeDataDir, user_id);
+  return restoreDataDirectory(base_dir, rollback_id, apex_name);
+}
+
+//  Migrates sessions directory from /data/apex/sessions to
+//  /metadata/apex/sessions, if necessary.
+Result<void> migrateSessionsDirIfNeeded() {
+  namespace fs = std::filesystem;
+  auto from_path = std::string(kApexDataDir) + "/sessions";
+  auto exists = PathExists(from_path);
+  if (!exists) {
+    return Error() << "Failed to access " << from_path << ": "
+                   << exists.error();
+  }
+  if (!*exists) {
+    LOG(DEBUG) << from_path << " does not exist. Nothing to migrate.";
+    return {};
+  }
+  auto to_path = kApexSessionsDir;
+  std::error_code error_code;
+  fs::copy(from_path, to_path, fs::copy_options::recursive, error_code);
+  if (error_code) {
+    return Error() << "Failed to copy old sessions directory"
+                   << error_code.message();
+  }
+  fs::remove_all(from_path, error_code);
+  if (error_code) {
+    return Error() << "Failed to delete old sessions directory "
+                   << error_code.message();
+  }
+  return {};
+}
+
 void scanStagedSessionsDirAndStage() {
   LOG(INFO) << "Scanning " << kApexSessionsDir
             << " looking for sessions to be activated.";
@@ -1420,7 +1485,17 @@
       continue;
     }
 
-    snapshotOrRestoreIfNeeded(session, apexes);
+    for (const auto& apex : apexes) {
+      // TODO: Avoid opening ApexFile repeatedly.
+      Result<ApexFile> apex_file = ApexFile::Open(apex);
+      if (!apex_file) {
+        LOG(ERROR) << "Cannot open apex file during staging: " << apex;
+        continue;
+      }
+      session.AddApexName(apex_file->GetManifest().name());
+    }
+
+    snapshotOrRestoreIfNeeded(kDeSysDataDir, session);
 
     const Result<void> result = stagePackages(apexes);
     if (!result) {
diff --git a/apexd/apexd.h b/apexd/apexd.h
index c38e858..0549ce2 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -73,10 +73,18 @@
 
 android::base::Result<void> abortActiveSession();
 
+android::base::Result<ino_t> snapshotCeData(const int user_id,
+                                            const int rollback_id,
+                                            const std::string& apex_name);
+android::base::Result<void> restoreCeData(const int user_id,
+                                          const int rollback_id,
+                                          const std::string& apex_name);
+
 int onBootstrap();
 void onStart(CheckpointInterface* checkpoint_service);
 void onAllPackagesReady();
 void unmountDanglingMounts();
+int snapshotOrRestoreDeUserData();
 
 int unmountAll();
 
diff --git a/apexd/apexd.rc b/apexd/apexd.rc
index ea70ddf..8f9f87f 100644
--- a/apexd/apexd.rc
+++ b/apexd/apexd.rc
@@ -11,3 +11,9 @@
     group system
     oneshot
     disabled
+
+service apexd-snapshotde /system/bin/apexd --snapshotde
+    user root
+    group system
+    oneshot
+    disabled
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 7b42925..c0e0936 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -52,6 +52,11 @@
     return android::apex::unmountAll();
   }
 
+  if (strcmp("--snapshotde", argv[1]) == 0) {
+    LOG(INFO) << "Snapshot DE subcommand detected";
+    return android::apex::snapshotOrRestoreDeUserData();
+  }
+
   LOG(ERROR) << "Unknown subcommand: " << argv[1];
   return 1;
 }
diff --git a/apexd/apexd_session.cpp b/apexd/apexd_session.cpp
index c8fe36d..27963a8 100644
--- a/apexd/apexd_session.cpp
+++ b/apexd/apexd_session.cpp
@@ -210,6 +210,11 @@
                                            child_session_ids.end()};
 }
 
+const google::protobuf::RepeatedPtrField<std::string>
+ApexSession::GetApexNames() const {
+  return state_.apex_names();
+}
+
 void ApexSession::SetBuildFingerprint(const std::string& fingerprint) {
   *(state_.mutable_expected_build_fingerprint()) = fingerprint;
 }
@@ -231,6 +236,10 @@
   state_.set_crashing_native_process(crashing_process);
 }
 
+void ApexSession::AddApexName(const std::string& apex_name) {
+  state_.add_apex_names(apex_name);
+}
+
 Result<void> ApexSession::UpdateStateAndCommit(
     const SessionState::State& session_state) {
   state_.set_state(session_state);
diff --git a/apexd/apexd_session.h b/apexd/apexd_session.h
index d6fa6f5..2ec823c 100644
--- a/apexd/apexd_session.h
+++ b/apexd/apexd_session.h
@@ -50,6 +50,7 @@
   bool HasRollbackEnabled() const;
   bool IsRollback() const;
   int GetRollbackId() const;
+  const google::protobuf::RepeatedPtrField<std::string> GetApexNames() const;
 
   void SetChildSessionIds(const std::vector<int>& child_session_ids);
   void SetBuildFingerprint(const std::string& fingerprint);
@@ -57,6 +58,7 @@
   void SetIsRollback(const bool is_rollback);
   void SetRollbackId(const int rollback_id);
   void SetCrashingNativeProcess(const std::string& crashing_process);
+  void AddApexName(const std::string& apex_name);
 
   android::base::Result<void> UpdateStateAndCommit(
       const ::apex::proto::SessionState::State& state);
diff --git a/apexd/apexd_utils.h b/apexd/apexd_utils.h
index 436c704..5d096f1 100644
--- a/apexd/apexd_utils.h
+++ b/apexd/apexd_utils.h
@@ -199,6 +199,16 @@
   return {};
 }
 
+inline Result<ino_t> get_path_inode(const std::string& path) {
+  struct stat buf;
+  memset(&buf, 0, sizeof(buf));
+  if (stat(path.c_str(), &buf) != 0) {
+    return ErrnoError() << "Failed to stat " << path;
+  } else {
+    return buf.st_ino;
+  }
+}
+
 inline Result<bool> PathExists(const std::string& path) {
   namespace fs = std::filesystem;
 
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 013e13b..816af10 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -79,6 +79,11 @@
   BinderStatus abortActiveSession() override;
   BinderStatus rollbackActiveSession() override;
   BinderStatus resumeRollbackIfNeeded() override;
+  BinderStatus snapshotCeData(int user_id, int rollback_id,
+                              const std::string& apex_name,
+                              int64_t* _aidl_return) override;
+  BinderStatus restoreCeData(int user_id, int rollback_id,
+                             const std::string& apex_name) override;
 
   status_t dump(int fd, const Vector<String16>& args) override;
 
@@ -468,6 +473,34 @@
   return BinderStatus::ok();
 }
 
+BinderStatus ApexService::snapshotCeData(int user_id, int rollback_id,
+                                         const std::string& apex_name,
+                                         int64_t* _aidl_return) {
+  LOG(DEBUG) << "snapshotCeData() received by ApexService.";
+  Result<ino_t> res =
+      ::android::apex::snapshotCeData(user_id, rollback_id, apex_name);
+  if (!res) {
+    return BinderStatus::fromExceptionCode(
+        BinderStatus::EX_SERVICE_SPECIFIC,
+        String8(res.error().message().c_str()));
+  }
+  *_aidl_return = static_cast<uint64_t>(*res);
+  return BinderStatus::ok();
+}
+
+BinderStatus ApexService::restoreCeData(int user_id, int rollback_id,
+                                        const std::string& apex_name) {
+  LOG(DEBUG) << "restoreCeData() received by ApexService.";
+  Result<void> res =
+      ::android::apex::restoreCeData(user_id, rollback_id, apex_name);
+  if (!res) {
+    return BinderStatus::fromExceptionCode(
+        BinderStatus::EX_SERVICE_SPECIFIC,
+        String8(res.error().message().c_str()));
+  }
+  return BinderStatus::ok();
+}
+
 status_t ApexService::onTransact(uint32_t _aidl_code, const Parcel& _aidl_data,
                                  Parcel* _aidl_reply, uint32_t _aidl_flags) {
   switch (_aidl_code) {
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index 701b08a..2178ed9 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -290,6 +290,15 @@
     return data;
   }
 
+  static void DeleteIfExists(const std::string& path) {
+    if (fs::exists(path)) {
+      std::error_code ec;
+      fs::remove_all(path, ec);
+      ASSERT_FALSE(ec) << "Failed to delete dir " << path << " : "
+                       << ec.message();
+    }
+  }
+
   struct PrepareTestApexForInstall {
     static constexpr const char* kTestDir = "/data/app-staging/apexservice_tmp";
 
@@ -434,6 +443,9 @@
                        << ec.message();
     });
     ASSERT_TRUE(IsOk(status));
+
+    DeleteIfExists("/data/misc_ce/0/apexdata/apex.apexd_test");
+    DeleteIfExists("/data/misc_ce/0/apexrollback/123456");
   }
 };
 
@@ -786,6 +798,68 @@
   ASSERT_EQ(0, session->GetRollbackId());
 }
 
+TEST_F(ApexServiceTest, SnapshotCeData) {
+  std::error_code ec;
+  fs::create_directory("/data/misc_ce/0/apexdata/apex.apexd_test");
+  ASSERT_FALSE(ec) << "Failed to create data dir "
+                   << " : " << ec.message();
+
+  std::ofstream ofs("/data/misc_ce/0/apexdata/apex.apexd_test/hello.txt");
+  ASSERT_TRUE(ofs.good());
+  ofs.close();
+
+  ASSERT_TRUE(
+      RegularFileExists("/data/misc_ce/0/apexdata/apex.apexd_test/hello.txt"));
+
+  int64_t result;
+  service_->snapshotCeData(0, 123456, "apex.apexd_test", &result);
+
+  ASSERT_TRUE(RegularFileExists(
+      "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/hello.txt"));
+
+  // Check that the return value is the inode of the snapshot directory.
+  struct stat buf;
+  memset(&buf, 0, sizeof(buf));
+  ASSERT_EQ(0,
+            stat("/data/misc_ce/0/apexrollback/123456/apex.apexd_test", &buf));
+  ASSERT_EQ(int64_t(buf.st_ino), result);
+}
+
+TEST_F(ApexServiceTest, RestoreCeData) {
+  std::error_code ec;
+  fs::create_directory("/data/misc_ce/0/apexdata/apex.apexd_test", ec);
+  ASSERT_FALSE(ec) << "Failed to create data dir "
+                   << " : " << ec.message();
+  fs::create_directory("/data/misc_ce/0/apexrollback/123456", ec);
+  ASSERT_FALSE(ec) << "Failed to create snapshot root dir "
+                   << " : " << ec.message();
+  fs::create_directory("/data/misc_ce/0/apexrollback/123456/apex.apexd_test",
+                       ec);
+  ASSERT_FALSE(ec) << "Failed to create snapshot dir "
+                   << " : " << ec.message();
+
+  std::ofstream newfs("/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt");
+  ASSERT_TRUE(newfs.good());
+  newfs.close();
+
+  std::ofstream oldfs(
+      "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/oldfile.txt");
+  ASSERT_TRUE(oldfs.good());
+  oldfs.close();
+
+  ASSERT_TRUE(RegularFileExists(
+      "/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt"));
+  ASSERT_TRUE(RegularFileExists(
+      "/data/misc_ce/0/apexrollback/123456/apex.apexd_test/oldfile.txt"));
+
+  service_->restoreCeData(0, 123456, "apex.apexd_test");
+
+  ASSERT_TRUE(RegularFileExists(
+      "/data/misc_ce/0/apexdata/apex.apexd_test/oldfile.txt"));
+  ASSERT_FALSE(RegularFileExists(
+      "/data/misc_ce/0/apexdata/apex.apexd_test/newfile.txt"));
+}
+
 template <typename NameProvider>
 class ApexServiceActivationTest : public ApexServiceTest {
  public:
diff --git a/proto/session_state.proto b/proto/session_state.proto
index 63c2f52..ecf00a5 100644
--- a/proto/session_state.proto
+++ b/proto/session_state.proto
@@ -54,4 +54,8 @@
 
   // The crashing native process that has caused this session to be reverted
   string crashing_native_process = 8;
+
+  // The names of the apexes within this session. Only populated for sessions
+  // that have been activated.
+  repeated string apex_names = 9;
 }