Sync encryptedstorage after payload execution

Microdroid manager will syncfs the encryptedstore's filesystem post
payload is finished.

This patch also include refactoring of TestService to include quit()
method so that the service can be gracefully shutdown triggering the
sync above^.

Test: atest MicrodroidTests#encryptedStorageIsPersistent
Bug: 264520098
Change-Id: I1c196fda1fb0818c30cc55a64603bebd1077d09b
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index f1c41b9..13bc9e3 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -53,6 +53,7 @@
 use std::borrow::Cow::{Borrowed, Owned};
 use std::convert::TryInto;
 use std::env;
+use std::ffi::CString;
 use std::fs::{self, create_dir, OpenOptions};
 use std::io::Write;
 use std::os::unix::process::CommandExt;
@@ -216,13 +217,21 @@
 
     match try_run_payload(&service) {
         Ok(code) => {
-            info!("notifying payload finished");
-            service.notifyPayloadFinished(code)?;
             if code == 0 {
                 info!("task successfully finished");
             } else {
                 error!("task exited with exit code: {}", code);
             }
+            if let Err(e) = post_payload_work() {
+                error!(
+                    "Failed to run post payload work. It is possible that certain tasks
+                    like syncing encrypted store might be incomplete. Error: {:?}",
+                    e
+                );
+            };
+
+            info!("notifying payload finished");
+            service.notifyPayloadFinished(code)?;
             Ok(())
         }
         Err(err) => {
@@ -233,6 +242,28 @@
     }
 }
 
+fn post_payload_work() -> Result<()> {
+    // Sync the encrypted storage filesystem (flushes the filesystem caches).
+    if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+        let mountpoint = CString::new(ENCRYPTEDSTORE_MOUNTPOINT).unwrap();
+
+        let ret = unsafe {
+            let dirfd = libc::open(
+                mountpoint.as_ptr(),
+                libc::O_DIRECTORY | libc::O_RDONLY | libc::O_CLOEXEC,
+            );
+            ensure!(dirfd >= 0, "Unable to open {:?}", mountpoint);
+            let ret = libc::syncfs(dirfd);
+            libc::close(dirfd);
+            ret
+        };
+        if ret != 0 {
+            error!("failed to sync encrypted storage.");
+            return Err(anyhow!(std::io::Error::last_os_error()));
+        }
+    }
+    Ok(())
+}
 fn dice_derivation(
     dice: DiceDriver,
     verified_data: &MicrodroidData,
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 7ee1f01..4611134 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -55,4 +55,10 @@
 
     /* get the content of the specified file. */
     String readFromFile(String path);
+
+    /**
+     * Request the service to exit, triggering the termination of the VM. This may cause any
+     * requests in flight to fail.
+     */
+    oneway void quit();
 }
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index c6915e5..9cafd68 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1520,15 +1520,34 @@
             throws Exception {
         CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>();
         CompletableFuture<Boolean> payloadReady = new CompletableFuture<>();
+        CompletableFuture<Boolean> payloadFinished = new CompletableFuture<>();
         TestResults testResults = new TestResults();
         VmEventListener listener =
                 new VmEventListener() {
-                    private void testVMService(VirtualMachine vm) {
+                    ITestService mTestService = null;
+
+                    private void initializeTestService(VirtualMachine vm) {
                         try {
-                            ITestService testService =
+                            mTestService =
                                     ITestService.Stub.asInterface(
                                             vm.connectToVsockServer(ITestService.SERVICE_PORT));
-                            testsToRun.runTests(testService, testResults);
+                        } catch (Exception e) {
+                            testResults.mException = e;
+                        }
+                    }
+
+                    private void testVMService(VirtualMachine vm) {
+                        try {
+                            if (mTestService == null) initializeTestService(vm);
+                            testsToRun.runTests(mTestService, testResults);
+                        } catch (Exception e) {
+                            testResults.mException = e;
+                        }
+                    }
+
+                    private void quitVMService(VirtualMachine vm) {
+                        try {
+                            mTestService.quit();
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
@@ -1539,7 +1558,7 @@
                         Log.i(TAG, "onPayloadReady");
                         payloadReady.complete(true);
                         testVMService(vm);
-                        forceStop(vm);
+                        quitVMService(vm);
                     }
 
                     @Override
@@ -1547,10 +1566,19 @@
                         Log.i(TAG, "onPayloadStarted");
                         payloadStarted.complete(true);
                     }
+
+                    @Override
+                    public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+                        Log.i(TAG, "onPayloadFinished: " + exitCode);
+                        payloadFinished.complete(true);
+                        forceStop(vm);
+                    }
                 };
+
         listener.runToFinish(TAG, vm);
         assertThat(payloadStarted.getNow(false)).isTrue();
         assertThat(payloadReady.getNow(false)).isTrue();
+        assertThat(payloadFinished.getNow(false)).isTrue();
         return testResults;
     }
 
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 4ba502a..365ea75 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -240,9 +240,6 @@
                 return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
                                                                    msg.c_str());
             }
-            // TODO(b/264520098): Remove sync() once TestService supports quit() method
-            // and Microdroid manager flushes filesystem caches on shutdown.
-            sync();
             return ScopedAStatus::ok();
         }
 
@@ -255,6 +252,8 @@
             }
             return ScopedAStatus::ok();
         }
+
+        ScopedAStatus quit() override { exit(0); }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();