Merge "Add init-mmd-prop.rc in case mmd is not built" into main
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index b614aab..adfb16b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -351,9 +351,14 @@
 
     LOG(INFO) << "Removing all update state.";
 
-    if (!RemoveAllSnapshots(lock)) {
-        LOG(ERROR) << "Could not remove all snapshots";
-        return false;
+    if (ReadUpdateState(lock) != UpdateState::None) {
+        // Only call this if we're actually cancelling an update. It's not
+        // expected to yield anything otherwise, and firing up gsid on normal
+        // boot is expensive.
+        if (!RemoveAllSnapshots(lock)) {
+            LOG(ERROR) << "Could not remove all snapshots";
+            return false;
+        }
     }
 
     // It's okay if these fail:
diff --git a/init/Android.bp b/init/Android.bp
index dbdf80b..b209c47 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -599,6 +599,7 @@
     ],
     static_libs: [
         "libbase",
+        "libfstab",
         "libselinux",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
diff --git a/init/libprefetch/prefetch/prefetch.rc b/init/libprefetch/prefetch/prefetch.rc
index dee37bb..56fb827 100644
--- a/init/libprefetch/prefetch/prefetch.rc
+++ b/init/libprefetch/prefetch/prefetch.rc
@@ -1,28 +1,36 @@
-on init && property:ro.prefetch_boot.enabled=true
-    start prefetch
-
-service prefetch /system/bin/prefetch start
-    class main
-    user root
-    group root system
-    disabled
-    oneshot
-
-on property:prefetch_boot.record=true
-    start prefetch_record
+# Reads data from disk in advance and populates page cache
+# to speed up subsequent disk access.
+#
+# Record:
+#   start by `start prefetch_record` at appropriate timing.
+#   stop by setting `prefetch_boot.record_stop` to 1.
+#   set --duration to only capture for a certain duration instead.
+#
+# Replay:
+#   start by `start prefetch_replay` at appropriate timing.
+#   it will depend on several files generated from record.
+#
+#   replay is I/O intensive. make sure you pick appropriate
+#   timing to run each, so that you can maximize the page cache
+#   hit for subsequent disk access.
+#
+# Example:
+#   on early-init && property:ro.prefetch_boot.enabled=true
+#     start prefetch_replay
+#
+#   on init && property:ro.prefetch_boot.enabled=true
+#     start prefetch_record
+#
+#   on property:sys.boot_completed=1 && property:ro.prefetch_boot.enabled=true
+#     setprop prefetch_boot.record_stop 1
 
 service prefetch_record /system/bin/prefetch record --duration ${ro.prefetch_boot.duration_s:-0}
-    class main
     user root
     group root system
     disabled
     oneshot
 
-on property:prefetch_boot.replay=true
-    start prefetch_replay
-
-service prefetch_replay /system/bin/prefetch replay --io-depth ${ro.prefetch_boot.io_depth:-2} --max-fds ${ro.prefetch_boot.max_fds:-128}
-    class main
+service prefetch_replay /system/bin/prefetch replay --io-depth ${ro.prefetch_boot.io_depth:-2} --max-fds ${ro.prefetch_boot.max_fds:-1024}
     user root
     group root system
     disabled
diff --git a/init/libprefetch/prefetch/src/arch/android.rs b/init/libprefetch/prefetch/src/arch/android.rs
index a11767e..7d446ba 100644
--- a/init/libprefetch/prefetch/src/arch/android.rs
+++ b/init/libprefetch/prefetch/src/arch/android.rs
@@ -1,20 +1,23 @@
 use crate::Error;
 use crate::RecordArgs;
-use crate::StartArgs;
-use log::info;
 use log::warn;
 use std::fs::File;
 use std::fs::OpenOptions;
 use std::io::Write;
+use std::path::Path;
 use std::time::Duration;
 
 use rustutils::system_properties::error::PropertyWatcherError;
 use rustutils::system_properties::PropertyWatcher;
 
-const PREFETCH_RECORD_PROPERTY: &str = "prefetch_boot.record";
-const PREFETCH_REPLAY_PROPERTY: &str = "prefetch_boot.replay";
 const PREFETCH_RECORD_PROPERTY_STOP: &str = "prefetch_boot.record_stop";
 
+fn is_prefetch_enabled() -> Result<bool, Error> {
+    rustutils::system_properties::read_bool("ro.prefetch_boot.enabled", false).map_err(|e| {
+        Error::Custom { error: format!("Failed to read ro.prefetch_boot.enabled: {}", e) }
+    })
+}
+
 fn wait_for_property_true(
     property_name: &str,
     timeout: Option<Duration>,
@@ -31,68 +34,49 @@
     });
 }
 
-fn start_prefetch_service(property_name: &str) -> Result<(), Error> {
-    match rustutils::system_properties::write(property_name, "true") {
-        Ok(_) => {}
-        Err(_) => {
-            return Err(Error::Custom { error: "Failed to start prefetch service".to_string() });
-        }
-    }
-    Ok(())
-}
-
-/// Start prefetch service
-///
-/// 1: Check the presence of the file 'prefetch_ready'. If it doesn't
-/// exist then the device is booting for the first time after wipe.
-/// Thus, we would just create the file and exit as we do not want
-/// to initiate the record after data wipe primiarly because boot
-/// after data wipe is long and the I/O pattern during first boot may not actually match
-/// with subsequent boot.
-///
-/// 2: If the file 'prefetch_ready' is present:
-///
-///   a: Compare the build-finger-print of the device with the one record format
-///   is associated with by reading the file 'build_finger_print'. If they match,
-///   start the prefetch_replay.
-///
-///   b: If they don't match, then the device was updated through OTA. Hence, start
-///   a fresh record and delete the build-finger-print file. This should also cover
-///   the case of device rollback.
-///
-///   c: If the build-finger-print file doesn't exist, then just restart the record
-///   from scratch.
-pub fn start_prefetch(args: &StartArgs) -> Result<(), Error> {
-    if !args.path.exists() {
-        match File::create(args.path.clone()) {
-            Ok(_) => {}
-            Err(_) => {
-                return Err(Error::Custom { error: "File Creation failed".to_string() });
-            }
-        }
-        return Ok(());
+/// Checks if we can perform replay phase.
+/// Ensure that the pack file exists and is up-to-date, returns false otherwise.
+pub fn can_perform_replay(pack_path: &Path, fingerprint_path: &Path) -> Result<bool, Error> {
+    if !is_prefetch_enabled()? {
+        return Ok(false);
     }
 
-    if args.build_fingerprint_path.exists() {
-        let device_build_fingerprint = rustutils::system_properties::read("ro.build.fingerprint")
-            .map_err(|e| Error::Custom {
+    if !pack_path.exists() || !fingerprint_path.exists() {
+        return Ok(false);
+    }
+
+    let saved_fingerprint = std::fs::read_to_string(fingerprint_path)?;
+
+    let current_device_fingerprint = rustutils::system_properties::read("ro.build.fingerprint")
+        .map_err(|e| Error::Custom {
             error: format!("Failed to read ro.build.fingerprint: {}", e),
         })?;
-        let pack_build_fingerprint = std::fs::read_to_string(&args.build_fingerprint_path)?;
-        if pack_build_fingerprint.trim() == device_build_fingerprint.as_deref().unwrap_or_default()
-        {
-            info!("Start replay");
-            start_prefetch_service(PREFETCH_REPLAY_PROPERTY)?;
-        } else {
-            info!("Start record");
-            std::fs::remove_file(&args.build_fingerprint_path)?;
-            start_prefetch_service(PREFETCH_RECORD_PROPERTY)?;
-        }
-    } else {
-        info!("Start record");
-        start_prefetch_service(PREFETCH_RECORD_PROPERTY)?;
+
+    Ok(current_device_fingerprint.is_some_and(|fp| fp == saved_fingerprint.trim()))
+}
+
+/// Checks if we can perform record phase.
+/// Ensure that following conditions hold:
+///   - File specified in ready_path exists. otherwise, create a new file and return false.
+///   - can_perform_replay is false.
+pub fn ensure_record_is_ready(
+    ready_path: &Path,
+    pack_path: &Path,
+    fingerprint_path: &Path,
+) -> Result<bool, Error> {
+    if !is_prefetch_enabled()? {
+        return Ok(false);
     }
-    Ok(())
+
+    if !ready_path.exists() {
+        File::create(ready_path)
+            .map_err(|_| Error::Custom { error: "File Creation failed".to_string() })?;
+
+        return Ok(false);
+    }
+
+    let can_replay = can_perform_replay(pack_path, fingerprint_path)?;
+    Ok(!can_replay)
 }
 
 /// Write build finger print to associate prefetch pack file
diff --git a/init/libprefetch/prefetch/src/args.rs b/init/libprefetch/prefetch/src/args.rs
index e534210..4c1e689 100644
--- a/init/libprefetch/prefetch/src/args.rs
+++ b/init/libprefetch/prefetch/src/args.rs
@@ -25,8 +25,6 @@
 
 pub use args_internal::OutputFormat;
 pub use args_internal::ReplayArgs;
-#[cfg(target_os = "android")]
-pub use args_internal::StartArgs;
 pub use args_internal::TracerType;
 pub use args_internal::{DumpArgs, MainArgs, RecordArgs, SubCommands};
 use serde::Deserialize;
@@ -68,8 +66,6 @@
         SubCommands::Dump(arg) => {
             ensure_path_exists(&arg.path)?;
         }
-        #[cfg(target_os = "android")]
-        SubCommands::Start(_arg) => return Ok(()),
     }
     Ok(())
 }
diff --git a/init/libprefetch/prefetch/src/args/args_argh.rs b/init/libprefetch/prefetch/src/args/args_argh.rs
index 65084ee..d2251e6 100644
--- a/init/libprefetch/prefetch/src/args/args_argh.rs
+++ b/init/libprefetch/prefetch/src/args/args_argh.rs
@@ -40,12 +40,6 @@
     Replay(ReplayArgs),
     /// Dump prefetch data in human readable format
     Dump(DumpArgs),
-    /// Start prefetch service if possible
-    /// If the pack file is present, then prefetch replay is started
-    /// If the pack file is absent or if the build fingerprint
-    /// of the current pack file is different, then prefetch record is started.
-    #[cfg(target_os = "android")]
-    Start(StartArgs),
 }
 
 #[cfg(target_os = "android")]
@@ -58,22 +52,6 @@
     PathBuf::from("/metadata/prefetch/build_finger_print")
 }
 
-#[cfg(target_os = "android")]
-#[derive(Eq, PartialEq, Debug, Default, FromArgs)]
-/// Start prefetch service based on if pack file is present.
-#[argh(subcommand, name = "start")]
-pub struct StartArgs {
-    /// file path to check if prefetch_ready is present.
-    ///
-    /// A new file is created at the given path if it's not present.
-    #[argh(option, default = "default_ready_path()")]
-    pub path: PathBuf,
-
-    /// file path where build fingerprint is stored
-    #[argh(option, default = "default_build_finger_print_path()")]
-    pub build_fingerprint_path: PathBuf,
-}
-
 impl Default for SubCommands {
     fn default() -> Self {
         Self::Dump(DumpArgs::default())
@@ -147,6 +125,13 @@
     /// store build_finger_print to tie the pack format
     #[argh(option, default = "default_build_finger_print_path()")]
     pub build_fingerprint_path: PathBuf,
+
+    #[cfg(target_os = "android")]
+    /// file path to check if prefetch_ready is present.
+    ///
+    /// A new file is created at the given path if it's not present.
+    #[argh(option, default = "default_ready_path()")]
+    pub ready_path: PathBuf,
 }
 
 /// Type of tracing subsystem to use.
@@ -204,6 +189,11 @@
     /// file path from where the prefetch config file will be read
     #[argh(option, default = "PathBuf::new()")]
     pub config_path: PathBuf,
+
+    #[cfg(target_os = "android")]
+    /// store build_finger_print to tie the pack format
+    #[argh(option, default = "default_build_finger_print_path()")]
+    pub build_fingerprint_path: PathBuf,
 }
 
 /// dump records file in given format
diff --git a/init/libprefetch/prefetch/src/lib.rs b/init/libprefetch/prefetch/src/lib.rs
index 6564c4b..ea84c59 100644
--- a/init/libprefetch/prefetch/src/lib.rs
+++ b/init/libprefetch/prefetch/src/lib.rs
@@ -42,8 +42,6 @@
 pub use args::args_from_env;
 use args::OutputFormat;
 pub use args::ReplayArgs;
-#[cfg(target_os = "android")]
-pub use args::StartArgs;
 pub use args::{DumpArgs, MainArgs, RecordArgs, SubCommands};
 pub use error::Error;
 pub use format::FileId;
@@ -59,6 +57,13 @@
 
 /// Records prefetch data for the given configuration
 pub fn record(args: &RecordArgs) -> Result<(), Error> {
+    #[cfg(target_os = "android")]
+    if !ensure_record_is_ready(&args.ready_path, &args.path, &args.build_fingerprint_path)? {
+        info!("Cannot perform record -- skipping");
+        return Ok(());
+    }
+
+    info!("Starting record.");
     let (mut tracer, exit_tx) = tracer::Tracer::create(
         args.trace_buffer_size_kib,
         args.tracing_subsystem.clone(),
@@ -109,6 +114,13 @@
 
 /// Replays prefetch data for the given configuration
 pub fn replay(args: &ReplayArgs) -> Result<(), Error> {
+    #[cfg(target_os = "android")]
+    if !can_perform_replay(&args.path, &args.build_fingerprint_path)? {
+        info!("Cannot perform replay -- exiting.");
+        return Ok(());
+    }
+
+    info!("Starting replay.");
     let replay = Replay::new(args)?;
     replay.replay()
 }
diff --git a/init/libprefetch/prefetch/src/main.rs b/init/libprefetch/prefetch/src/main.rs
index eab826f..046e07e 100644
--- a/init/libprefetch/prefetch/src/main.rs
+++ b/init/libprefetch/prefetch/src/main.rs
@@ -22,8 +22,6 @@
 use prefetch_rs::init_logging;
 use prefetch_rs::record;
 use prefetch_rs::replay;
-#[cfg(target_os = "android")]
-use prefetch_rs::start_prefetch;
 use prefetch_rs::LogLevel;
 use prefetch_rs::MainArgs;
 use prefetch_rs::SubCommands;
@@ -35,8 +33,6 @@
         SubCommands::Record(args) => record(args),
         SubCommands::Replay(args) => replay(args),
         SubCommands::Dump(args) => dump(args),
-        #[cfg(target_os = "android")]
-        SubCommands::Start(args) => start_prefetch(args),
     };
 
     if let Err(err) = ret {
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 53b53ed..d75d4f1 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -27,6 +27,7 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <fstab/fstab.h>
 #include <selinux/selinux.h>
 #include <sys/signalfd.h>
 #include "lmkd_service.h"
@@ -280,5 +281,74 @@
 
 INSTANTIATE_TEST_SUITE_P(service, ServiceStopTest, testing::Values(false, true));
 
+// Entering a network namespace requires remounting sysfs to update contents of
+// /sys/class/net whose contents depend on the network namespace of the process
+// that mounted it rather than the effective network namespace of the reading
+// process.
+//
+// A side effect of the remounting is unmounting all filesystems mounted under
+// /sys, like tracefs. Verify that init doesn't leave them unmounted by
+// accident.
+TEST(service, enter_namespace_net_preserves_mounts) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Must be run as root.";
+        return;
+    }
+
+    struct ScopedNetNs {
+        std::string name;
+        ScopedNetNs(std::string n) : name(n) {
+            EXPECT_EQ(system(("/system/bin/ip netns add " + name).c_str()), 0);
+        }
+        ~ScopedNetNs() { EXPECT_EQ(system(("/system/bin/ip netns delete " + name).c_str()), 0); }
+    };
+    const ScopedNetNs netns("test_ns");
+
+    static constexpr std::string_view kServiceName = "ServiceA";
+    static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+    user shell
+    group shell
+    seclabel $selabel
+    enter_namespace net /mnt/run/$ns_name
+)init";
+
+    std::string script = StringReplace(kScriptTemplate, "$name", kServiceName, false);
+    script = StringReplace(script, "$selabel", GetSecurityContext(), false);
+    script = StringReplace(script, "$ns_name", netns.name, false);
+
+    ServiceList& service_list = ServiceList::GetInstance();
+    Parser parser;
+    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
+
+    TemporaryFile tf;
+    ASSERT_GE(tf.fd, 0);
+    ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+    Service* const service = ServiceList::GetInstance().FindService(kServiceName);
+    ASSERT_NE(service, nullptr);
+    ASSERT_RESULT_OK(service->Start());
+    ASSERT_TRUE(service->IsRunning());
+
+    android::fs_mgr::Fstab root_mounts;
+    ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &root_mounts));
+
+    android::fs_mgr::Fstab ns_mounts;
+    ASSERT_TRUE(ReadFstabFromFile(StringReplace("/proc/$pid/mounts", "$pid",
+                                                std::to_string(service->pid()), /*all=*/false),
+                                  &ns_mounts));
+
+    for (const auto& expected_mount : root_mounts) {
+        auto it = std::find_if(ns_mounts.begin(), ns_mounts.end(), [&](const auto& ns_mount) {
+            return ns_mount.mount_point == expected_mount.mount_point;
+        });
+        EXPECT_TRUE(it != ns_mounts.end()) << StringPrintf(
+                "entering network namespace unmounted %s", expected_mount.mount_point.c_str());
+    }
+
+    ServiceList::GetInstance().RemoveService(*service);
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 0e19bcc..f8821a0 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -18,11 +18,11 @@
 
 #include <fcntl.h>
 #include <grp.h>
-#include <map>
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/wait.h>
 #include <unistd.h>
+#include <map>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -31,6 +31,7 @@
 #include <android-base/strings.h>
 #include <cutils/android_get_control_file.h>
 #include <cutils/sockets.h>
+#include <fstab/fstab.h>
 #include <processgroup/processgroup.h>
 
 #include "mount_namespace.h"
@@ -82,12 +83,29 @@
         }
     }
     if (remount_sys) {
+        android::fs_mgr::Fstab mounts;
+        if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
+            LOG(ERROR) << "Could not read /proc/mounts";
+        }
         if (umount2("/sys", MNT_DETACH) == -1) {
             return ErrnoError() << "Could not umount(/sys)";
         }
-        if (mount("", "/sys", "sysfs", kSafeFlags, "") == -1) {
+        if (mount("sysfs", "/sys", "sysfs", kSafeFlags, "") == -1) {
             return ErrnoError() << "Could not mount(/sys)";
         }
+        // Unmounting /sys also unmounts all nested mounts like tracefs.
+        //
+        // Look up the filesystems that were mounted under /sys before we wiped
+        // it and attempt to restore them.
+        for (const auto& entry : mounts) {
+            if (entry.mount_point.starts_with("/sys/")) {
+                if (mount(entry.blk_device.c_str(), entry.mount_point.c_str(),
+                          entry.fs_type.c_str(), entry.flags, "")) {
+                    LOG(WARNING) << "Could not mount(" << entry.mount_point
+                                 << ") after switching netns: " << ErrnoError().str();
+                }
+            }
+        }
     }
     return {};
 }
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index cfc2d3a..80c4f4c 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -44,6 +44,8 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
+#include "ashmem-internal.h"
+
 /* ashmem identity */
 static dev_t __ashmem_rdev;
 /*
@@ -114,15 +116,8 @@
     // Check if kernel support exists, otherwise fall back to ashmem.
     // This code needs to build on old API levels, so we can't use the libc
     // wrapper.
-    //
-    // MFD_NOEXEC_SEAL is used to match the semantics of the ashmem device,
-    // which did not have executable permissions. This also seals the executable
-    // permissions of the buffer (i.e. they cannot be changed by fchmod()).
-    //
-    // MFD_NOEXEC_SEAL implies MFD_ALLOW_SEALING.
-
     android::base::unique_fd fd(
-            syscall(__NR_memfd_create, "test_android_memfd", MFD_CLOEXEC | MFD_NOEXEC_SEAL));
+            syscall(__NR_memfd_create, "test_android_memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING));
     if (fd == -1) {
         ALOGE("memfd_create failed: %m, no memfd support");
         return false;
@@ -156,7 +151,7 @@
     return true;
 }
 
-static bool has_memfd_support() {
+bool has_memfd_support() {
     static bool memfd_supported = __has_memfd_support();
     return memfd_supported;
 }
@@ -290,13 +285,7 @@
 static int memfd_create_region(const char* name, size_t size) {
     // This code needs to build on old API levels, so we can't use the libc
     // wrapper.
-    //
-    // MFD_NOEXEC_SEAL to match the semantics of the ashmem device, which did
-    // not have executable permissions. This also seals the executable
-    // permissions of the buffer (i.e. they cannot be changed by fchmod()).
-    //
-    // MFD_NOEXEC_SEAL implies MFD_ALLOW_SEALING.
-    android::base::unique_fd fd(syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_NOEXEC_SEAL));
+    android::base::unique_fd fd(syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_ALLOW_SEALING));
 
     if (fd == -1) {
         ALOGE("memfd_create(%s, %zd) failed: %m", name, size);
diff --git a/libcutils/ashmem-internal.h b/libcutils/ashmem-internal.h
new file mode 100644
index 0000000..7bd037b
--- /dev/null
+++ b/libcutils/ashmem-internal.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+bool has_memfd_support();
diff --git a/libcutils/ashmem_test.cpp b/libcutils/ashmem_test.cpp
index 96f20db..2bf274c 100644
--- a/libcutils/ashmem_test.cpp
+++ b/libcutils/ashmem_test.cpp
@@ -29,6 +29,8 @@
 #include <cutils/ashmem.h>
 #include <gtest/gtest.h>
 
+#include "ashmem-internal.h"
+
 using android::base::unique_fd;
 
 static void TestCreateRegion(size_t size, unique_fd &fd, int prot) {
@@ -316,3 +318,119 @@
 
     ASSERT_NO_FATAL_FAILURE(ForkMultiRegionTest(fds, nRegions, size));
 }
+
+class AshmemTestMemfdAshmemCompat : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    if (!has_memfd_support()){
+        GTEST_SKIP() << "No memfd support; skipping memfd-ashmem compat tests";
+    }
+  }
+};
+
+TEST_F(AshmemTestMemfdAshmemCompat, SetNameTest) {
+    unique_fd fd;
+
+    // ioctl() should fail, since memfd names cannot be changed after the buffer has been created.
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+    ASSERT_LT(ioctl(fd, ASHMEM_SET_NAME, "invalid-command"), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, GetNameTest) {
+    unique_fd fd;
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+
+    char testBuf[ASHMEM_NAME_LEN];
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_GET_NAME, &testBuf));
+    // ashmem_create_region(nullptr, ...) creates memfds with the name "none".
+    ASSERT_STREQ(testBuf, "none");
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, SetSizeTest) {
+    unique_fd fd;
+
+    // ioctl() should fail, since libcutils sets and seals the buffer size after creating it.
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+    ASSERT_LT(ioctl(fd, ASHMEM_SET_SIZE, 2 * getpagesize()), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, GetSizeTest) {
+    unique_fd fd;
+    size_t bufSize = getpagesize();
+
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(bufSize, fd, PROT_READ | PROT_WRITE | PROT_EXEC));
+    ASSERT_EQ(static_cast<int>(bufSize), ioctl(fd, ASHMEM_GET_SIZE, 0));
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, ProtMaskTest) {
+    unique_fd fd;
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+
+    // We can only change PROT_WRITE for memfds since memfd implements ashmem's prot_mask through
+    // file seals, and only write seals exist.
+    //
+    // All memfd files start off as being writable (i.e. PROT_WRITE is part of the prot_mask).
+    // Test to ensure that the implementation only clears the PROT_WRITE bit when requested.
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_WRITE | PROT_EXEC));
+    int prot = ioctl(fd, ASHMEM_GET_PROT_MASK, 0);
+    ASSERT_NE(prot, -1);
+    ASSERT_TRUE(prot & PROT_WRITE) << prot;
+
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_EXEC));
+    prot = ioctl(fd, ASHMEM_GET_PROT_MASK, 0);
+    ASSERT_NE(prot, -1);
+    ASSERT_TRUE(!(prot & PROT_WRITE)) << prot;
+
+    // The shim layer should implement clearing PROT_WRITE via file seals, so check the file
+    // seals to ensure that F_SEAL_FUTURE_WRITE is set.
+    int seals = fcntl(fd, F_GET_SEALS, 0);
+    ASSERT_NE(seals, -1);
+    ASSERT_TRUE(seals & F_SEAL_FUTURE_WRITE) << seals;
+
+    // Similarly, ensure that file seals affect prot_mask
+    unique_fd fd2;
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd2, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+    ASSERT_EQ(0, fcntl(fd2, F_ADD_SEALS, F_SEAL_FUTURE_WRITE));
+    prot = ioctl(fd2, ASHMEM_GET_PROT_MASK, 0);
+    ASSERT_NE(prot, -1);
+    ASSERT_TRUE(!(prot & PROT_WRITE)) << prot;
+
+    // And finally, ensure that adding back permissions fails
+    ASSERT_LT(ioctl(fd2, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, FileIDTest) {
+    unique_fd fd;
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+
+    unsigned long ino;
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_GET_FILE_ID, &ino));
+    struct stat st;
+    ASSERT_EQ(0, fstat(fd, &st));
+    ASSERT_EQ(ino, st.st_ino);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, UnpinningTest) {
+    unique_fd fd;
+    size_t bufSize = getpagesize();
+    ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+                                                                PROT_EXEC));
+
+    struct ashmem_pin pin = {
+        .offset = 0,
+        .len = static_cast<uint32_t>(bufSize),
+    };
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_UNPIN, &pin));
+    // ASHMEM_UNPIN should just be a nop
+    ASSERT_EQ(ASHMEM_IS_PINNED, ioctl(fd, ASHMEM_GET_PIN_STATUS, 0));
+
+    // This shouldn't do anything; when we pin the page, it shouldn't have been purged.
+    ASSERT_EQ(0, ioctl(fd, ASHMEM_PURGE_ALL_CACHES, 0));
+    ASSERT_EQ(ASHMEM_NOT_PURGED, ioctl(fd, ASHMEM_PIN, &pin));
+}
\ No newline at end of file