Revert "Revert "Move apex sessions directory to /metadata""

This reverts commit 7645e253a16b7874cc7d4736dfecec24e411abb1.

Reason for revert: Could not repro error locally, will run Forrest tests to see if error persists

Change-Id: Ic328d3987d9ada1f5e96db33a131f376047b0f99
diff --git a/apexd/aidl/android/apex/ApexSessionInfo.aidl b/apexd/aidl/android/apex/ApexSessionInfo.aidl
index d8be1b5..a7a6daf 100644
--- a/apexd/aidl/android/apex/ApexSessionInfo.aidl
+++ b/apexd/aidl/android/apex/ApexSessionInfo.aidl
@@ -28,4 +28,5 @@
     boolean isSuccess;
     boolean isReverted;
     boolean isRevertFailed;
+    @utf8InCpp String crashingNativeProcess;
 }
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 3a4fcac..f81c016 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -827,12 +827,6 @@
   return {};
 }
 
-Result<void> RevertStagedSession(ApexSession& session) {
-  // If the session is staged, it hasn't been activated yet, and we just need
-  // to update its state to prevent it from being activated later.
-  return session.UpdateStateAndCommit(SessionState::REVERTED);
-}
-
 Result<void> UnmountPackage(const ApexFile& apex, bool allow_latest) {
   LOG(VERBOSE) << "Unmounting " << GetPackageId(apex.GetManifest());
 
@@ -1236,6 +1230,35 @@
   }
 }
 
+//  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.";
@@ -1466,23 +1489,6 @@
   return {};
 }
 
-void revertAllStagedSessions() {
-  auto sessions = ApexSession::GetSessionsInState(SessionState::STAGED);
-  if (sessions.empty()) {
-    LOG(WARNING) << "No session to revert";
-    return;
-  }
-
-  for (auto& session : sessions) {
-    LOG(INFO) << "Reverting back session " << session;
-    auto st = RevertStagedSession(session);
-    if (!st) {
-      LOG(ERROR) << "Failed to revert staged session " << session << ": "
-                 << st.error();
-    }
-  }
-}
-
 /**
  * During apex installation, staged sessions located in /data/apex/sessions
  * mutate the active sessions in /data/apex/active. If some error occurs during
@@ -1501,14 +1507,6 @@
     return Error() << "Revert requested, when there are no active sessions.";
   }
 
-  if (gInFsCheckpointMode) {
-    LOG(DEBUG) << "Checkpoint mode is enabled";
-    // On checkpointing devices, our modifications on /data will be
-    // automatically reverted when we abort changes. Updating the session
-    // state is pointless here, as it will be reverted as well.
-    return {};
-  }
-
   for (auto& session : activeSessions) {
     if (!crashing_native_process.empty()) {
       session.SetCrashingNativeProcess(crashing_native_process);
@@ -1522,17 +1520,21 @@
     }
   }
 
-  auto restoreStatus = RestoreActivePackages();
-  if (!restoreStatus) {
-    for (auto& session : activeSessions) {
-      auto st = session.UpdateStateAndCommit(SessionState::REVERT_FAILED);
-      LOG(DEBUG) << "Marking " << session << " as failed to revert";
-      if (!st) {
-        LOG(WARNING) << "Failed to mark session " << session
-                     << " as failed to revert : " << st.error();
+  if (!gInFsCheckpointMode) {
+    auto restoreStatus = RestoreActivePackages();
+    if (!restoreStatus) {
+      for (auto& session : activeSessions) {
+        auto st = session.UpdateStateAndCommit(SessionState::REVERT_FAILED);
+        LOG(DEBUG) << "Marking " << session << " as failed to revert";
+        if (!st) {
+          LOG(WARNING) << "Failed to mark session " << session
+                       << " as failed to revert : " << st.error();
+        }
       }
+      return restoreStatus;
     }
-    return restoreStatus;
+  } else {
+    LOG(INFO) << "Not restoring active packages in checkpoint mode.";
   }
 
   for (auto& session : activeSessions) {
@@ -1682,7 +1684,7 @@
     }
   }
 
-  // Ask whether we should revert any staged sessions; this can happen if
+  // Ask whether we should revert any active sessions; this can happen if
   // we've exceeded the retry count on a device that supports filesystem
   // checkpointing.
   if (gSupportsFsCheckpoints) {
@@ -1694,7 +1696,7 @@
       LOG(INFO) << "Exceeded number of session retries ("
                 << kNumRetriesWhenCheckpointingEnabled
                 << "). Starting a revert";
-      revertAllStagedSessions();
+      revertActiveSessions("");
     }
   }
 
diff --git a/apexd/apexd.h b/apexd/apexd.h
index c05a53c..0a85728 100644
--- a/apexd/apexd.h
+++ b/apexd/apexd.h
@@ -36,6 +36,7 @@
 android::base::Result<void> scanPackagesDirAndActivate(
     const char* apex_package_dir);
 void scanStagedSessionsDirAndStage();
+android::base::Result<void> migrateSessionsDirIfNeeded();
 
 android::base::Result<void> preinstallPackages(
     const std::vector<std::string>& paths) WARN_UNUSED;
diff --git a/apexd/apexd_main.cpp b/apexd/apexd_main.cpp
index 7fc2f5f..b119931 100644
--- a/apexd/apexd_main.cpp
+++ b/apexd/apexd_main.cpp
@@ -88,6 +88,7 @@
     vold_service = &*vold_service_st;
   }
 
+  android::apex::migrateSessionsDirIfNeeded();
   android::apex::onStart(vold_service);
   android::apex::binder::CreateAndRegisterService();
   android::apex::binder::StartThreadPool();
diff --git a/apexd/apexd_session.h b/apexd/apexd_session.h
index 1da2084..b2b1c2f 100644
--- a/apexd/apexd_session.h
+++ b/apexd/apexd_session.h
@@ -28,8 +28,7 @@
 namespace android {
 namespace apex {
 
-static const std::string kApexSessionsDir =
-    std::string(kApexDataDir) + "/sessions";
+static const std::string kApexSessionsDir = "/metadata/apex/sessions";
 
 class ApexSession {
  public:
diff --git a/apexd/apexservice.cpp b/apexd/apexservice.cpp
index 8c3f3c6..19c9c4d 100644
--- a/apexd/apexservice.cpp
+++ b/apexd/apexservice.cpp
@@ -210,6 +210,7 @@
 
   ClearSessionInfo(session_info);
   session_info->sessionId = session.GetId();
+  session_info->crashingNativeProcess = session.GetCrashingNativeProcess();
 
   switch (session.GetState()) {
     case SessionState::VERIFIED:
@@ -520,10 +521,15 @@
         child_ids_str += " " + std::to_string(childSessionId);
       }
     }
+    std::string revert_reason = "";
+    std::string crashing_native_process = session.GetCrashingNativeProcess();
+    if (!crashing_native_process.empty()) {
+      revert_reason = " Revert Reason: " + crashing_native_process;
+    }
     std::string msg =
         StringLog() << "Session ID: " << session.GetId() << child_ids_str
                     << " State: " << SessionState_State_Name(session.GetState())
-                    << std::endl;
+                    << revert_reason << std::endl;
     dprintf(fd, "%s", msg.c_str());
   }
 
@@ -705,6 +711,11 @@
     ApexSessionInfo session_info;
     BinderStatus status = getStagedSessionInfo(session_id, &session_info);
     if (status.isOk()) {
+      std::string revert_reason = "";
+      std::string crashing_native_process = session_info.crashingNativeProcess;
+      if (!crashing_native_process.empty()) {
+        revert_reason = " revertReason: " + crashing_native_process;
+      }
       std::string msg = StringLog()
                         << "session_info: "
                         << " isUnknown: " << session_info.isUnknown
@@ -712,7 +723,8 @@
                         << " isStaged: " << session_info.isStaged
                         << " isActivated: " << session_info.isActivated
                         << " isActivationFailed: "
-                        << session_info.isActivationFailed << std::endl;
+                        << session_info.isActivationFailed << revert_reason
+                        << std::endl;
       dprintf(out, "%s", msg.c_str());
       return OK;
     }
diff --git a/apexd/apexservice_test.cpp b/apexd/apexservice_test.cpp
index ca628f9..6760b6c 100644
--- a/apexd/apexservice_test.cpp
+++ b/apexd/apexservice_test.cpp
@@ -433,6 +433,7 @@
       ASSERT_FALSE(ec) << "Failed to delete " << p.path() << " : "
                        << ec.message();
     });
+    fs::remove_all(kApexSessionsDir);
     ASSERT_TRUE(IsOk(status));
   }
 };
@@ -2206,10 +2207,6 @@
 }
 
 TEST_F(ApexServiceRevertTest, RevertStoresCrashingNativeProcess) {
-  if (supports_fs_checkpointing_) {
-    GTEST_SKIP() << "Can't run if filesystem checkpointing is enabled";
-  }
-
   PrepareTestApexForInstall installer(GetTestFile("apex.apexd_test_v2.apex"));
   if (!installer.Prepare()) {
     return;
diff --git a/tests/Android.bp b/tests/Android.bp
index b798c26..9b0a41b 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -268,6 +268,7 @@
     data: [
         "testdata/trigger_watchdog.rc",
         "testdata/trigger_watchdog.sh",
+        "testdata/trigger_reboot.sh",
         ":com.android.apex.cts.shim.v2_prebuilt",
     ],
 }
diff --git a/tests/apex-rollback-tests.xml b/tests/apex-rollback-tests.xml
index 211176a..e5e82bd 100644
--- a/tests/apex-rollback-tests.xml
+++ b/tests/apex-rollback-tests.xml
@@ -22,6 +22,7 @@
       <option name="remount-system" value="true" />
       <option name="push" value="trigger_watchdog.rc->/system/etc/init/trigger_watchdog.rc" />
       <option name="push" value="trigger_watchdog.sh->/system/bin/trigger_watchdog.sh" />
+      <option name="push" value="trigger_reboot.sh->/system/bin/trigger_reboot.sh"/>
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="jar" value="apex_rollback_tests.jar" />
diff --git a/tests/src/com/android/tests/apex/ApexRollbackTests.java b/tests/src/com/android/tests/apex/ApexRollbackTests.java
index b42f9b6..c2d3bee 100644
--- a/tests/src/com/android/tests/apex/ApexRollbackTests.java
+++ b/tests/src/com/android/tests/apex/ApexRollbackTests.java
@@ -51,6 +51,7 @@
         mUtils.uninstallShimApexIfNecessary();
         resetProperty("persist.debug.trigger_watchdog.apex");
         resetProperty("persist.debug.trigger_updatable_crashing_for_testing");
+        resetProperty("persist.debug.trigger_reboot_after_activation");
     }
 
     /**
@@ -63,6 +64,7 @@
         mUtils.uninstallShimApexIfNecessary();
         resetProperty("persist.debug.trigger_watchdog.apex");
         resetProperty("persist.debug.trigger_updatable_crashing_for_testing");
+        resetProperty("persist.debug.trigger_reboot_after_activation");
     }
 
     private void resetProperty(String propertyName) throws Exception {
@@ -92,6 +94,10 @@
         String error = device.installPackage(apexFile, false, "--wait");
         assertThat(error).isNull();
 
+        String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
+                + "--only-parent --only-sessionid").trim();
+        assertThat(sessionIdToCheck).isNotEmpty();
+
         // After we reboot the device, we expect the device to go into boot
         // loop from trigger_watchdog.sh. Native watchdog should detect and
         // report the boot loop, causing apexd to roll back to the previous
@@ -106,6 +112,40 @@
         Set<ApexInfo> activatedApexes = device.getActiveApexes();
         assertThat(activatedApexes).contains(ctsShimV1);
         assertThat(activatedApexes).doesNotContain(ctsShimV2);
+
+        // Assert that a session has failed with the expected reason
+        String sessionInfo = device.executeShellCommand("cmd apexservice getStagedSessionInfo "
+                    + sessionIdToCheck);
+        assertThat(sessionInfo).contains("revertReason: zygote");
+    }
+
+    @Test
+    public void testCheckpointingRevertsSession() throws Exception {
+        assumeTrue("Device does not support updating APEX", mUtils.isApexUpdateSupported());
+        assumeTrue("Device doesn't support fs checkpointing", supportsFsCheckpointing());
+
+        File apexFile = mUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
+
+        ITestDevice device = getDevice();
+        assertThat(device.setProperty("persist.debug.trigger_reboot_after_activation",
+                "com.android.apex.cts.shim@2.apex")).isTrue();
+        String error = device.installPackage(apexFile, false, "--wait");
+        assertThat(error).isNull();
+
+        String sessionIdToCheck = device.executeShellCommand("pm get-stagedsessions --only-ready "
+                + "--only-parent --only-sessionid").trim();
+        assertThat(sessionIdToCheck).isNotEmpty();
+
+        // After we reboot the device, the apexd session should be activated as normal. After this,
+        // trigger_reboot.sh will reboot the device before the system server boots. Checkpointing
+        // will kick in, and at the next boot any non-finalized sessions will be reverted.
+        device.reboot();
+
+        ApexInfo ctsShimV1 = new ApexInfo("com.android.apex.cts.shim", 1L);
+        ApexInfo ctsShimV2 = new ApexInfo("com.android.apex.cts.shim", 2L);
+        Set<ApexInfo> activatedApexes = device.getActiveApexes();
+        assertThat(activatedApexes).contains(ctsShimV1);
+        assertThat(activatedApexes).doesNotContain(ctsShimV2);
     }
 
     // TODO(ioffe): check that we recover from the boot loop in of userspace reboot.
diff --git a/tests/testdata/trigger_reboot.sh b/tests/testdata/trigger_reboot.sh
new file mode 100644
index 0000000..d6606fc
--- /dev/null
+++ b/tests/testdata/trigger_reboot.sh
@@ -0,0 +1,15 @@
+# To trigger a reboot on install of version 300000000 of
+# the com.android.tzdata apex:
+# $ adb shell setprop persist.debug.trigger_reboot_after_activation com.android.tzdata@300000000.apex
+active_apex=/data/apex/active/`/system/bin/getprop persist.debug.trigger_reboot_after_activation`
+if [[ $active_apex == *.apex ]]
+then
+    while :
+    do
+        if [ -a $active_apex ]
+        then
+            /system/bin/log -t TriggerWatchdog "Detected presence of $active_apex"
+            /system/bin/setprop sys.powerctl reboot
+        fi
+    done
+fi
\ No newline at end of file
diff --git a/tests/testdata/trigger_watchdog.rc b/tests/testdata/trigger_watchdog.rc
index 300cca5..47c6f41 100644
--- a/tests/testdata/trigger_watchdog.rc
+++ b/tests/testdata/trigger_watchdog.rc
@@ -4,6 +4,12 @@
     exec -- /system/bin/log -t TriggerWatchdog "Starting trigger_watchdog"
     exec_background u:r:su:s0 -- /system/bin/sh /system/bin/trigger_watchdog.sh
 
+# Only trigger if persist.debug.trigger_reboot_after_activation is non-empty.
+on boot && property:persist.debug.trigger_reboot_after_activation=*
+    setprop debug.trigger_watchdog.status start
+    exec -- /system/bin/log -t TriggerWatchdog "Checking checkpointing revert"
+    exec_background u:r:su:s0 -- /system/bin/sh /system/bin/trigger_reboot.sh
+
 on boot && property:persist.debug.trigger_updatable_crashing_for_testing=1
     # Mock the fact that a service was crashing.
     setprop sys.init.updatable_crashing_process_name test_process