diff --git a/Cargo.lock b/Cargo.lock
index 8434a04c..245a5971 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -88,12 +88,6 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
 [[package]]
 name = "bitflags"
 version = "2.11.0"
@@ -178,6 +172,7 @@ dependencies = [
  "bootc-internal-blockdev",
  "bootc-internal-utils",
  "camino",
+ "cap-std",
  "cap-std-ext",
  "chrono",
  "clap",
@@ -189,8 +184,6 @@ dependencies = [
  "libc",
  "libsystemd",
  "log",
- "openat",
- "openat-ext",
  "openssl",
  "os-release",
  "regex",
@@ -784,7 +777,7 @@ dependencies = [
  "hmac",
  "libc",
  "log",
- "nix 0.29.0",
+ "nix",
  "nom 8.0.0",
  "once_cell",
  "serde",
@@ -836,15 +829,6 @@ version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
-[[package]]
-name = "memoffset"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
-dependencies = [
- "autocfg",
-]
-
 [[package]]
 name = "memoffset"
 version = "0.9.1"
@@ -871,30 +855,17 @@ dependencies = [
  "windows-sys 0.61.2",
 ]
 
-[[package]]
-name = "nix"
-version = "0.23.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
-dependencies = [
- "bitflags 1.3.2",
- "cc",
- "cfg-if",
- "libc",
- "memoffset 0.6.5",
-]
-
 [[package]]
 name = "nix"
 version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "cfg-if",
  "cfg_aliases",
  "libc",
- "memoffset 0.9.1",
+ "memoffset",
 ]
 
 [[package]]
@@ -1021,34 +992,13 @@ version = "1.70.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
 
-[[package]]
-name = "openat"
-version = "0.1.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95aa7c05907b3ebde2610d602f4ddd992145cc6a84493647c30396f30ba83abe"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "openat-ext"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cf3e4baa7f516441f58373f58aaf6e91a5dfa2e2b50e68a0d313b082014c61d"
-dependencies = [
- "libc",
- "nix 0.23.2",
- "openat",
- "rand 0.8.5",
-]
-
 [[package]]
 name = "openssl"
 version = "0.10.80"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "cfg-if",
  "foreign-types",
  "libc",
@@ -1264,7 +1214,7 @@ version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "422ffe5b31ea4c7c76a361dc29b986e11fb6987313453088e1fd95f34ecc1ba6"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "cpio",
  "digest",
  "enum-display-derive",
@@ -1288,7 +1238,7 @@ version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1763,7 +1713,7 @@ version = "0.244.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "hashbrown 0.15.5",
  "indexmap",
  "semver",
@@ -2027,7 +1977,7 @@ version = "0.36.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
 dependencies = [
- "bitflags 2.11.0",
+ "bitflags",
  "windows-sys 0.59.0",
 ]
 
@@ -2089,7 +2039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
 dependencies = [
  "anyhow",
- "bitflags 2.11.0",
+ "bitflags",
  "indexmap",
  "log",
  "serde",
diff --git a/Cargo.toml b/Cargo.toml
index f7c9fae1..490a7952 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,8 +35,7 @@ hex = "0.4.3"
 libc = "^0.2"
 libsystemd = ">= 0.3, < 0.8"
 log = "^0.4"
-openat = "0.1.20"
-openat-ext = ">= 0.2.2, < 0.3.0"
+cap-std = "4.0.2"
 openssl = "^0.10"
 os-release = "0.1.0"
 regex = "1.12.3"
diff --git a/src/backend/statefile.rs b/src/backend/statefile.rs
index 83ffd6b2..4f7bef26 100644
--- a/src/backend/statefile.rs
+++ b/src/backend/statefile.rs
@@ -3,9 +3,11 @@
 use crate::model::SavedState;
 use crate::util::SignalTerminationGuard;
 use anyhow::{bail, Context, Result};
+use cap_std::ambient_authority;
+use cap_std::fs::{Dir, Permissions, PermissionsExt};
+use cap_std_ext::dirext::CapStdExtDirExt;
 use fn_error_context::context;
 use fs2::FileExt;
-use openat_ext::OpenatDirExt;
 use std::fs::File;
 use std::io::prelude::*;
 use std::path::Path;
@@ -23,9 +25,18 @@ impl SavedState {
     /// While ordinarily the daemon runs as a systemd unit (which implicitly
     /// ensures a single instance) this is a double check against other
     /// execution paths.
-    pub(crate) fn acquire_write_lock(sysroot: openat::Dir) -> Result<StateLockGuard> {
-        let lockfile = sysroot.write_file(Self::WRITE_LOCK_PATH, 0o644)?;
-        lockfile.lock_exclusive()?;
+    pub(crate) fn acquire_write_lock(sysroot: Dir) -> Result<StateLockGuard> {
+        sysroot
+            .atomic_write_with_perms(Self::WRITE_LOCK_PATH, "", Permissions::from_mode(0o644))
+            .context("Creating lock file")?;
+
+        let lockfile = sysroot
+            .open(Self::WRITE_LOCK_PATH)
+            .context("Opening lock file")?
+            .into_std();
+
+        lockfile.lock_exclusive().context("Acquiring lock")?;
+
         let guard = StateLockGuard {
             sysroot,
             termguard: Some(SignalTerminationGuard::new()?),
@@ -36,7 +47,7 @@ impl SavedState {
 
     /// Use this for cases when the target root isn't booted, which is
     /// offline installs.
-    pub(crate) fn unlocked(sysroot: openat::Dir) -> Result<StateLockGuard> {
+    pub(crate) fn unlocked(sysroot: Dir) -> Result<StateLockGuard> {
         Ok(StateLockGuard {
             sysroot,
             termguard: None,
@@ -48,11 +59,11 @@ impl SavedState {
     #[context("Loading saved state")]
     pub(crate) fn load_from_disk(root_path: impl AsRef<Path>) -> Result<Option<SavedState>> {
         let root_path = root_path.as_ref();
-        let sysroot = openat::Dir::open(root_path)
+        let sysroot = Dir::open_ambient_dir(root_path, ambient_authority())
             .with_context(|| format!("opening sysroot '{}'", root_path.display()))?;
 
         let statefile_path = Path::new(Self::STATEFILE_DIR).join(Self::STATEFILE_NAME);
-        let saved_state = if let Some(statusf) = sysroot.open_file_optional(&statefile_path)? {
+        let saved_state = if let Some(statusf) = sysroot.open_optional(&statefile_path)? {
             let mut bufr = std::io::BufReader::new(statusf);
             let mut s = String::new();
             bufr.read_to_string(&mut s)?;
@@ -92,7 +103,7 @@ impl SavedState {
 /// Write-lock guard for statefile, protecting against concurrent state updates.
 #[derive(Debug)]
 pub(crate) struct StateLockGuard {
-    pub(crate) sysroot: openat::Dir,
+    pub(crate) sysroot: Dir,
     #[allow(dead_code)]
     termguard: Option<SignalTerminationGuard>,
     #[allow(dead_code)]
@@ -102,11 +113,16 @@ pub(crate) struct StateLockGuard {
 impl StateLockGuard {
     /// Atomically replace the on-disk state with a new version.
     pub(crate) fn update_state(&mut self, state: &SavedState) -> Result<()> {
-        let subdir = self.sysroot.sub_dir(SavedState::STATEFILE_DIR)?;
-        subdir.write_file_with_sync(SavedState::STATEFILE_NAME, 0o644, |w| -> Result<()> {
-            serde_json::to_writer(w, state)?;
-            Ok(())
-        })?;
+        let subdir = self.sysroot.open_dir(SavedState::STATEFILE_DIR)?;
+
+        subdir
+            .atomic_write_with_perms(
+                SavedState::STATEFILE_NAME,
+                serde_json::to_vec(state).context("Serializing state")?,
+                Permissions::from_mode(0o644),
+            )
+            .context("Writing state file")?;
+
         Ok(())
     }
 }
diff --git a/src/bios.rs b/src/bios.rs
index ce7fc540..1ee0b51a 100644
--- a/src/bios.rs
+++ b/src/bios.rs
@@ -1,6 +1,8 @@
 use anyhow::{bail, Context, Result};
 use camino::Utf8PathBuf;
-use openat_ext::OpenatDirExt;
+use cap_std::ambient_authority;
+use cap_std::fs::Dir;
+use cap_std_ext::dirext::CapStdExtDirExt;
 use std::io::prelude::*;
 use std::path::Path;
 use std::process::Command;
@@ -109,7 +111,7 @@ impl Component for Bios {
     ) -> Result<InstalledContent> {
         let device =
             device.ok_or_else(|| anyhow::anyhow!("BIOS component requires a target device"))?;
-        let src_dir = openat::Dir::open(src_root)
+        let src_dir = Dir::open_ambient_dir(src_root, ambient_authority())
             .with_context(|| format!("opening source directory {src_root}"))?;
         let Some(meta) = get_component_update(&src_dir, self)? else {
             anyhow::bail!("No update metadata for component {} found", self.name());
@@ -151,11 +153,11 @@ impl Component for Bios {
     // - Backup "/boot/loader/grub.cfg" to "/boot/grub2/grub.cfg.bak"
     // - Remove symlink "/boot/grub2/grub.cfg"
     // - Replace "/boot/grub2/grub.cfg" symlink with new static "grub.cfg"
-    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()> {
+    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &Dir) -> Result<()> {
         let grub = "boot/grub2";
         // sysroot_path is /, destdir is Dir of /
         let grub_config_path = Utf8PathBuf::from(sysroot_path).join(grub);
-        let grub_config_dir = destdir.sub_dir(grub).context("Opening boot/grub2")?;
+        let grub_config_dir = destdir.open_dir(grub).context("Opening boot/grub2")?;
 
         let grub_config = grub_config_path.join(grubconfigs::GRUBCONFIG);
 
@@ -201,7 +203,7 @@ impl Component for Bios {
         }
 
         // Synchronize the filesystem containing /boot/grub2 to disk.
-        fsfreeze_thaw_cycle(grub_config_dir.open_file(".")?)?;
+        fsfreeze_thaw_cycle(grub_config_dir.reopen_as_ownedfd()?)?;
 
         Ok(())
     }
@@ -243,13 +245,13 @@ impl Component for Bios {
         }))
     }
 
-    fn query_update(&self, sysroot: &openat::Dir) -> Result<Option<ContentMetadata>> {
+    fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>> {
         get_component_update(sysroot, self)
     }
 
-    fn query_requires_update(&self, sysroot: &openat::Dir) -> Result<()> {
+    fn query_requires_update(&self, sysroot: &Dir) -> Result<()> {
         // Failed as expected if booted with BIOS and no update metadata
-        if !sysroot.exists("sys/firmware/efi")? {
+        if !sysroot.exists("sys/firmware/efi") {
             anyhow::bail!("Failed to find BIOS update metadata");
         }
         Ok(())
diff --git a/src/bootupd.rs b/src/bootupd.rs
index 99558be9..16a49138 100644
--- a/src/bootupd.rs
+++ b/src/bootupd.rs
@@ -21,18 +21,19 @@ use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedSt
 use crate::{ostreeutil, util};
 use anyhow::{anyhow, Context, Result};
 use camino::{Utf8Path, Utf8PathBuf};
+use cap_std_ext::dirext::CapStdExtDirExt;
 use clap::crate_version;
 use fn_error_context::context;
-use libc::mode_t;
 use serde::{Deserialize, Serialize};
 use std::borrow::Cow;
 use std::collections::BTreeMap;
 use std::fs::{self, File};
-use std::io::{BufRead, BufReader, BufWriter, Write};
+use std::io::{BufRead, BufReader, Write};
 use std::path::{Path, PathBuf};
 
 use bootc_internal_blockdev::Device;
-use cap_std::fs::Dir;
+use cap_std::ambient_authority;
+use cap_std::fs::{Dir, Permissions, PermissionsExt};
 use cap_std_ext::cap_std;
 
 pub(crate) enum ConfigMode {
@@ -60,7 +61,8 @@ pub(crate) fn install(
     target_components: Option<&[String]>,
     auto_components: bool,
 ) -> Result<()> {
-    let source_root_dir = openat::Dir::open(source_root).context("Opening source root")?;
+    let source_root_dir =
+        Dir::open_ambient_dir(source_root, ambient_authority()).context("Opening source root")?;
     SavedState::ensure_not_present(dest_root)
         .context("failed to install, invalid re-install attempted")?;
 
@@ -175,7 +177,7 @@ pub(crate) fn install(
             }
         }
     }
-    let sysroot = &openat::Dir::open(dest_root)?;
+    let sysroot = &Dir::open_ambient_dir(dest_root, ambient_authority())?;
 
     #[cfg(any(
         target_arch = "x86_64",
@@ -339,7 +341,7 @@ pub(crate) fn update(name: &str, rootcxt: &RootContext) -> Result<ComponentUpdat
     ))]
     {
         let grub2dir = &sysroot
-            .sub_dir(format!("boot/{GRUB2DIR}"))
+            .open_dir(format!("boot/{GRUB2DIR}"))
             .context("Opening /boot/grub2")?;
         ensure_grub_permissions(grub2dir)?;
     }
@@ -391,7 +393,7 @@ pub(crate) fn adopt_and_update(
     ))]
     {
         let grub2dir = &sysroot
-            .sub_dir(format!("boot/{GRUB2DIR}"))
+            .open_dir(format!("boot/{GRUB2DIR}"))
             .context("Opening /boot/grub2")?;
         ensure_grub_permissions(grub2dir)?;
     }
@@ -457,7 +459,7 @@ pub(crate) fn validate(name: &str) -> Result<ValidationResult> {
 pub(crate) fn status() -> Result<Status> {
     let mut ret: Status = Default::default();
     let mut known_components = get_components();
-    let sysroot = openat::Dir::open("/")?;
+    let sysroot = Dir::open_ambient_dir("/", ambient_authority())?;
     let state = SavedState::load_from_disk("/")?;
     if let Some(state) = state {
         for (name, ic) in state.installed.iter() {
@@ -592,7 +594,7 @@ pub(crate) fn print_status(status: &Status) -> Result<()> {
 }
 
 pub struct RootContext {
-    pub sysroot: openat::Dir,
+    pub sysroot: Dir,
     pub path: Utf8PathBuf,
 
     /// The block device backing the root filesystem.
@@ -603,7 +605,7 @@ pub struct RootContext {
 }
 
 impl RootContext {
-    fn new(sysroot: openat::Dir, path: &str, device: Device) -> Self {
+    fn new(sysroot: Dir, path: &str, device: Device) -> Self {
         Self {
             sysroot,
             path: Utf8Path::new(path).into(),
@@ -615,7 +617,7 @@ impl RootContext {
 /// Initialize parent devices to prepare the update
 fn prep_before_update() -> Result<RootContext> {
     let path = "/";
-    let sysroot = openat::Dir::open(path).context("Opening root dir")?;
+    let sysroot = Dir::open_ambient_dir(path, ambient_authority()).context("Opening root dir")?;
     let device = list_dev_current_root()?;
     Ok(RootContext::new(sysroot, path, device))
 }
@@ -741,7 +743,8 @@ pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> {
     ensure_writable_boot()?;
 
     let grub_config_dir = PathBuf::from("/boot/grub2");
-    let dirfd = openat::Dir::open(&grub_config_dir).context("Opening /boot/grub2")?;
+    let dirfd = Dir::open_ambient_dir(&grub_config_dir, ambient_authority())
+        .context("Opening /boot/grub2")?;
 
     // We mark the bootloader as BLS capable to disable the ostree-grub2 logic.
     // We can do that as we know that we are run after the bootloader has been
@@ -789,10 +792,10 @@ pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> {
 
             // Atomically replace the symlink
             dirfd
-                .local_rename(stripped_config, "grub.cfg")
+                .rename(stripped_config, &dirfd, "grub.cfg")
                 .context("Failed to replace symlink with current GRUB config")?;
 
-            fsfreeze_thaw_cycle(dirfd.open_file(".")?)?;
+            fsfreeze_thaw_cycle(dirfd.reopen_as_ownedfd()?)?;
 
             println!("GRUB config symlink successfully replaced with the current config");
         }
@@ -809,15 +812,10 @@ pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> {
 /// `### BEGIN /etc/grub.d/15_ostree ###` and `### END /etc/grub.d/15_ostree ###`.
 fn strip_grub_config_file(
     current_config_content: impl BufRead,
-    dirfd: &openat::Dir,
+    dirfd: &Dir,
     stripped_config_name: &str,
 ) -> Result<()> {
-    // mode = -rw------- (600)
-    let mut writer = BufWriter::new(
-        dirfd
-            .write_file(stripped_config_name, 0o600 as mode_t)
-            .context("Failed to open temporary GRUB config")?,
-    );
+    let mut writer = vec![];
 
     let mut skip = false;
     for line in current_config_content.lines() {
@@ -841,11 +839,10 @@ fn strip_grub_config_file(
             .context("Failed to write stripped GRUB config")?;
     }
 
-    writer
-        .into_inner()
-        .context("Failed to flush stripped GRUB config")?
-        .sync_data()
-        .context("Failed to sync stripped GRUB config")?;
+    // mode = -rw------- (600)
+    dirfd
+        .atomic_write_with_perms(stripped_config_name, writer, Permissions::from_mode(0o600))
+        .with_context(|| format!("Writing to {stripped_config_name}"))?;
 
     Ok(())
 }
@@ -867,8 +864,8 @@ mod tests {
     fn test_strip_grub_config_file() -> Result<()> {
         let root: &tempfile::TempDir = &tempfile::tempdir()?;
         let root_path = root.path();
-        let rootd = openat::Dir::open(root_path)?;
-        let stripped_config = root_path.join("stripped");
+        let rootd = Dir::open_ambient_dir(root_path, ambient_authority())?;
+        let stripped_config = "stripped";
         let content = r"
 ### BEGIN /etc/grub.d/10_linux ###
 
@@ -897,9 +894,9 @@ initrdefi /ostree/rhcos-bf3b382/initramfs.img
         strip_grub_config_file(
             BufReader::new(std::io::Cursor::new(content)),
             &rootd,
-            stripped_config.to_str().unwrap(),
+            stripped_config,
         )?;
-        let stripped_content = fs::read_to_string(stripped_config)?;
+        let stripped_content = rootd.read_to_string(stripped_config)?;
         let expected = r"
 ### BEGIN /etc/grub.d/10_linux ###
 
diff --git a/src/cli/bootupd.rs b/src/cli/bootupd.rs
index 10e0f256..b560910f 100644
--- a/src/cli/bootupd.rs
+++ b/src/cli/bootupd.rs
@@ -1,6 +1,7 @@
 use crate::bootupd::{self, ConfigMode};
 use anyhow::{Context, Result};
 use camino::Utf8Path;
+use cap_std::ambient_authority;
 use cap_std::fs::Dir;
 use cap_std_ext::cap_std;
 use clap::Parser;
@@ -123,7 +124,7 @@ impl DCommand {
         // Resolve devices: either discover backing devices from a filesystem path,
         // or convert explicitly specified device paths to Device objects.
         let devices = if let Some(ref fs_path) = opts.filesystem {
-            let dir = Dir::open_ambient_dir(fs_path, cap_std::ambient_authority())
+            let dir = Dir::open_ambient_dir(fs_path, ambient_authority())
                 .with_context(|| format!("Opening filesystem path {fs_path}"))?;
             let device = bootc_internal_blockdev::list_dev_by_dir(&dir)?;
             device.find_all_roots()?
diff --git a/src/component.rs b/src/component.rs
index 6e148b03..403ffeec 100644
--- a/src/component.rs
+++ b/src/component.rs
@@ -5,8 +5,10 @@
  */
 
 use anyhow::{Context, Result};
+use cap_std::fs::{Dir, PermissionsExt};
+use cap_std::{ambient_authority, fs::Permissions};
+use cap_std_ext::dirext::CapStdExtDirExt;
 use fn_error_context::context;
-use openat_ext::OpenatDirExt;
 use serde::{Deserialize, Serialize};
 use std::path::{Path, PathBuf};
 
@@ -38,7 +40,7 @@ pub(crate) trait Component {
     fn query_adopt(&self, devices: &Option<Vec<Device>>) -> Result<Option<Adoptable>>;
 
     // Backup the current grub config, and install static grub config from tree
-    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()>;
+    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &Dir) -> Result<()>;
 
     /// Given an adoptable system and an update, perform the update.
     fn adopt_update(
@@ -71,12 +73,12 @@ pub(crate) trait Component {
     fn generate_update_metadata(&self, sysroot: &str) -> Result<Option<ContentMetadata>>;
 
     /// Used on the client to query for an update cached in the current booted OS.
-    fn query_update(&self, sysroot: &openat::Dir) -> Result<Option<ContentMetadata>>;
+    fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>>;
 
     /// This is called in the update code if query_update() returned no metadata.
     /// It should return an error if the current booted system should expect some
     /// metadata for this component.
-    fn query_requires_update(&self, sysroot: &openat::Dir) -> Result<()>;
+    fn query_requires_update(&self, sysroot: &Dir) -> Result<()>;
 
     /// Used on the client to run an update.
     fn run_update(
@@ -144,24 +146,28 @@ pub(crate) fn write_update_metadata(
     component: &dyn Component,
     meta: &ContentMetadata,
 ) -> Result<()> {
-    let sysroot = openat::Dir::open(sysroot)?;
-    let dir = sysroot.sub_dir(BOOTUPD_UPDATES_DIR)?;
+    let sysroot = Dir::open_ambient_dir(sysroot, ambient_authority())?;
+    let dir = sysroot.open_dir(BOOTUPD_UPDATES_DIR)?;
     let name = component_update_data_name(component);
-    dir.write_file_with(name, 0o644, |w| -> Result<_> {
-        Ok(serde_json::to_writer(w, &meta)?)
-    })?;
+
+    dir.atomic_write_with_perms(
+        name,
+        serde_json::to_vec(&meta).context("Serializing metadata")?,
+        Permissions::from_mode(0o644),
+    )?;
+
     Ok(())
 }
 
 /// Given a component, return metadata on the available update (if any)
 #[context("Loading update for component {}", component.name())]
 pub(crate) fn get_component_update(
-    sysroot: &openat::Dir,
+    sysroot: &Dir,
     component: &dyn Component,
 ) -> Result<Option<ContentMetadata>> {
     let name = component_update_data_name(component);
     let path = Path::new(BOOTUPD_UPDATES_DIR).join(name);
-    if let Some(f) = sysroot.open_file_optional(&path)? {
+    if let Some(f) = sysroot.open_optional(&path)? {
         let mut f = std::io::BufReader::new(f);
         let u = serde_json::from_reader(&mut f)
             .with_context(|| format!("failed to parse {:?}", &path))?;
@@ -207,6 +213,8 @@ pub(crate) fn query_adopt_state() -> Result<Option<Adoptable>> {
 
 #[cfg(test)]
 mod tests {
+    use cap_std::fs::{DirBuilder, DirBuilderExt, Permissions, PermissionsExt};
+
     use super::*;
 
     #[test]
@@ -214,23 +222,28 @@ mod tests {
         let td = tempfile::tempdir()?;
         let tdp = td.path();
         let tdupdates = "usr/lib/bootupd/updates/EFI";
-        let tdir = openat::Dir::open(tdp)?;
+        let tdir = Dir::open_ambient_dir(tdp, ambient_authority())?;
 
-        tdir.ensure_dir_all(tdupdates, 0o755)?;
-        let efi = tdir.sub_dir(tdupdates)?;
-        efi.create_dir("BOOT", 0o755)?;
-        efi.create_dir("fedora", 0o755)?;
-        efi.create_dir("centos", 0o755)?;
+        let mut dir_builder = DirBuilder::new();
+        dir_builder.mode(0o755);
+        dir_builder.recursive(true);
 
-        efi.write_file_contents(
+        tdir.create_dir_with(tdupdates, &dir_builder)?;
+        let efi = tdir.open_dir(tdupdates)?;
+        efi.create_dir_with("BOOT", &dir_builder)?;
+        efi.create_dir_with("fedora", &dir_builder)?;
+        efi.create_dir_with("centos", &dir_builder)?;
+
+        efi.atomic_write_with_perms(
             format!("fedora/{}", crate::efi::SHIM),
-            0o644,
             "shim data".as_bytes(),
+            Permissions::from_mode(0o644),
         )?;
-        efi.write_file_contents(
+
+        efi.atomic_write_with_perms(
             format!("centos/{}", crate::efi::SHIM),
-            0o644,
             "shim data".as_bytes(),
+            Permissions::from_mode(0o644),
         )?;
 
         let all_components = crate::bootupd::get_components();
@@ -242,16 +255,17 @@ mod tests {
             if component.name() == "EFI" {
                 let x = component.get_efi_vendor(tdp);
                 assert_eq!(x.is_err(), true);
-                efi.remove_all("centos")?;
+                efi.remove_dir_all("centos")?;
                 assert_eq!(component.get_efi_vendor(tdp)?, Some("fedora".to_string()));
                 {
                     let td_vendor = "usr/lib/efi/shim/15.8-3/EFI/centos";
-                    tdir.ensure_dir_all(td_vendor, 0o755)?;
-                    let shim_dir = tdir.sub_dir(td_vendor)?;
-                    shim_dir.write_file_contents(
+                    tdir.create_dir_with(td_vendor, &dir_builder)?;
+                    let shim_dir = tdir.open_dir(td_vendor)?;
+
+                    shim_dir.atomic_write_with_perms(
                         crate::efi::SHIM,
-                        0o644,
                         "shim data".as_bytes(),
+                        Permissions::from_mode(0o644),
                     )?;
 
                     // usr/lib/efi wins and get 'centos'
@@ -268,8 +282,8 @@ mod tests {
                         component.get_efi_vendor(&td_efi)?,
                         Some("fedora".to_string())
                     );
-                    tdir.remove_all("usr/lib/efi")?;
-                    tdir.remove_all(tdupdates)?;
+                    tdir.remove_dir_all("usr/lib/efi")?;
+                    tdir.remove_dir_all(tdupdates)?;
                     let err = component.get_efi_vendor(&td_usr).unwrap_err();
                     assert_eq!(err.to_string(), "Failed to find valid target path");
                 }
diff --git a/src/efi.rs b/src/efi.rs
index 37b4cdb4..cb5c1b0c 100644
--- a/src/efi.rs
+++ b/src/efi.rs
@@ -12,11 +12,11 @@ use std::process::Command;
 use anyhow::{bail, Context, Result};
 use bootc_internal_utils::CommandRunExt;
 use camino::{Utf8Path, Utf8PathBuf};
+use cap_std::ambient_authority;
 use cap_std::fs::Dir;
 use cap_std_ext::cap_std;
 use cap_std_ext::dirext::CapStdExtDirExt;
 use fn_error_context::context;
-use openat_ext::OpenatDirExt;
 use os_release::OsRelease;
 use rustix::{fd::AsFd, fd::BorrowedFd, fs::StatVfsMountFlags};
 use walkdir::WalkDir;
@@ -190,12 +190,7 @@ impl Efi {
     }
 
     #[context("Updating EFI firmware variables")]
-    fn update_firmware(
-        &self,
-        device: &Device,
-        espdir: &openat::Dir,
-        vendordir: &str,
-    ) -> Result<()> {
+    fn update_firmware(&self, device: &Device, espdir: &Dir, vendordir: &str) -> Result<()> {
         if !is_efi_booted()? {
             log::debug!("Not booted via EFI, skipping firmware update");
             return Ok(());
@@ -216,7 +211,7 @@ impl Efi {
 
         // Check shim exists and return earlier if not
         let shim_path = format!("EFI/{vendordir}/{SHIM}");
-        if !espdir.exists(&shim_path)? {
+        if !espdir.exists(&shim_path) {
             anyhow::bail!("Failed to find {shim_path}");
         }
         let loader = format!("\\EFI\\{vendordir}\\{SHIM}");
@@ -336,28 +331,32 @@ impl Component for Efi {
 
     // Backup "/boot/efi/EFI/{vendor}/grub.cfg" to "/boot/efi/EFI/{vendor}/grub.cfg.bak"
     // Replace "/boot/efi/EFI/{vendor}/grub.cfg" with new static "grub.cfg"
-    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &openat::Dir) -> Result<()> {
-        let sysroot =
-            openat::Dir::open(sysroot_path).with_context(|| format!("Opening {sysroot_path}"))?;
+    fn migrate_static_grub_config(&self, sysroot_path: &str, destdir: &Dir) -> Result<()> {
+        let sysroot = Dir::open_ambient_dir(sysroot_path, ambient_authority())
+            .with_context(|| format!("Opening {sysroot_path}"))?;
         let Some(vendor) = self.get_efi_vendor(&Path::new(sysroot_path))? else {
             anyhow::bail!("Failed to find efi vendor");
         };
 
         // destdir is /boot/efi/EFI
         let efidir = destdir
-            .sub_dir(&vendor)
+            .open_dir(&vendor)
             .with_context(|| format!("Opening EFI/{}", vendor))?;
 
-        if !efidir.exists(grubconfigs::GRUBCONFIG_BACKUP)? {
+        if !efidir.exists(grubconfigs::GRUBCONFIG_BACKUP) {
             println!("Creating a backup of the current GRUB config on EFI");
             efidir
-                .copy_file(grubconfigs::GRUBCONFIG, grubconfigs::GRUBCONFIG_BACKUP)
+                .copy(
+                    grubconfigs::GRUBCONFIG,
+                    &efidir,
+                    grubconfigs::GRUBCONFIG_BACKUP,
+                )
                 .context("Failed to backup GRUB config")?;
         }
 
         grubconfigs::install(&sysroot, None, Some(&vendor), true)?;
         // Synchronize the filesystem containing /boot/efi/EFI/{vendor} to disk.
-        fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
+        fsfreeze_thaw_cycle(efidir.reopen_as_ownedfd()?)?;
 
         Ok(())
     }
@@ -385,7 +384,7 @@ impl Component for Efi {
         };
         let updated = rootcxt
             .sysroot
-            .sub_dir(&updated_path)
+            .open_dir(&updated_path)
             .with_context(|| format!("opening update dir {}", updated_path.display()))?;
         let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?;
 
@@ -394,7 +393,8 @@ impl Component for Efi {
             let destpath =
                 &self.ensure_mounted_esp(rootcxt.path.as_ref(), Path::new(&esp.path()))?;
 
-            let efidir = openat::Dir::open(&destpath.join("EFI")).context("opening EFI dir")?;
+            let efidir = Dir::open_ambient_dir(&destpath.join("EFI"), ambient_authority())
+                .context("opening EFI dir")?;
             validate_esp_fstype(&efidir)?;
 
             // For adoption, we should only touch files that we know about.
@@ -417,7 +417,7 @@ impl Component for Efi {
             }
 
             // Do the sync before unmount
-            fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
+            fsfreeze_thaw_cycle(efidir.reopen_as_ownedfd()?)?;
             drop(efidir);
             self.unmount().context("unmount after adopt")?;
         }
@@ -435,7 +435,7 @@ impl Component for Efi {
         device: Option<&Device>,
         update_firmware: bool,
     ) -> Result<InstalledContent> {
-        let src_dir = openat::Dir::open(src_root)
+        let src_dir = Dir::open_ambient_dir(src_root, ambient_authority())
             .with_context(|| format!("opening source directory {src_root}"))?;
         let Some(meta) = get_component_update(&src_dir, self)? else {
             anyhow::bail!("No update metadata for component {} found", self.name());
@@ -462,7 +462,7 @@ impl Component for Efi {
             anyhow::bail!("No device specified and no mounted ESP found");
         };
 
-        let destd = &openat::Dir::open(&destpath)
+        let destd = &Dir::open_ambient_dir(&destpath, ambient_authority())
             .with_context(|| format!("opening dest dir {}", destpath.display()))?;
         validate_esp_fstype(destd)?;
 
@@ -494,7 +494,7 @@ impl Component for Efi {
         };
 
         // Get filetree from efi path
-        let ft = crate::filetree::FileTree::new_from_dir(&src_dir.sub_dir(efi_path)?)?;
+        let ft = crate::filetree::FileTree::new_from_dir(&src_dir.open_dir(efi_path)?)?;
         if update_firmware {
             if let Some(dev) = device {
                 if let Some(vendordir) =
@@ -534,7 +534,7 @@ impl Component for Efi {
 
         let updated = rootcxt
             .sysroot
-            .sub_dir(&updated_path)
+            .open_dir(&updated_path)
             .with_context(|| format!("opening update dir {}", updated_path.display()))?;
         let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?;
         let diff = currentf.diff(&updatef)?;
@@ -546,14 +546,15 @@ impl Component for Efi {
         for esp in esp_devices {
             let destpath =
                 &self.ensure_mounted_esp(rootcxt.path.as_ref(), Path::new(&esp.path()))?;
-            let destdir = openat::Dir::open(&destpath.join("EFI")).context("opening EFI dir")?;
+            let destdir = Dir::open_ambient_dir(&destpath.join("EFI"), ambient_authority())
+                .context("opening EFI dir")?;
             validate_esp_fstype(&destdir)?;
             log::trace!("applying diff: {}", &diff);
             filetree::apply_diff(&updated, &destdir, &diff, None)
                 .context("applying filesystem changes")?;
 
             // Do the sync before unmount
-            fsfreeze_thaw_cycle(destdir.open_file(".")?)?;
+            fsfreeze_thaw_cycle(destdir.reopen_as_ownedfd()?)?;
             drop(destdir);
             self.unmount().context("unmount after update")?;
         }
@@ -612,11 +613,11 @@ impl Component for Efi {
         Ok(Some(metadata))
     }
 
-    fn query_update(&self, sysroot: &openat::Dir) -> Result<Option<ContentMetadata>> {
+    fn query_update(&self, sysroot: &Dir) -> Result<Option<ContentMetadata>> {
         get_component_update(sysroot, self)
     }
 
-    fn query_requires_update(&self, _sysroot: &openat::Dir) -> Result<()> {
+    fn query_requires_update(&self, _sysroot: &Dir) -> Result<()> {
         // Failed as expected if booted with EFI and no update metadata
         if is_efi_booted()? {
             anyhow::bail!("Failed to find EFI update metadata");
@@ -639,7 +640,7 @@ impl Component for Efi {
         for esp in esp_devices.iter() {
             let destpath = &self.ensure_mounted_esp(Path::new("/"), Path::new(&esp.path()))?;
 
-            let efidir = openat::Dir::open(&destpath.join("EFI"))
+            let efidir = Dir::open_ambient_dir(&destpath.join("EFI"), ambient_authority())
                 .with_context(|| format!("opening EFI dir {}", destpath.display()))?;
             let diff = currentf.relative_diff_to(&efidir)?;
 
@@ -696,7 +697,7 @@ impl Drop for Efi {
     }
 }
 
-fn validate_esp_fstype(dir: &openat::Dir) -> Result<()> {
+fn validate_esp_fstype(dir: &Dir) -> Result<()> {
     let dir = unsafe { BorrowedFd::borrow_raw(dir.as_raw_fd()) };
     let stat = rustix::fs::fstatfs(&dir)?;
     if stat.f_type != libc::MSDOS_SUPER_MAGIC {
@@ -904,7 +905,7 @@ fn transfer_ostree_boot_to_bootupd_updates(
         .args([&efisrc, &dest_efidir])
         .run_capture_stderr()?;
 
-    let efidir = openat::Dir::open(dest_efidir.as_std_path())
+    let efidir = Dir::open_ambient_dir(dest_efidir.as_std_path(), ambient_authority())
         .with_context(|| format!("Opening {}", dest_efidir))?;
 
     let files = crate::util::filenames(&efidir)?.into_iter().map(|mut f| {
diff --git a/src/filesystem.rs b/src/filesystem.rs
index 07a96b8f..84238c8f 100644
--- a/src/filesystem.rs
+++ b/src/filesystem.rs
@@ -4,6 +4,7 @@ use std::process::Command;
 
 use anyhow::Result;
 use bootc_internal_utils::CommandRunExt;
+use cap_std::fs::Dir;
 use fn_error_context::context;
 use rustix::fd::BorrowedFd;
 use serde::Deserialize;
@@ -24,7 +25,7 @@ pub(crate) struct Findmnt {
 }
 
 #[context("Inspecting filesystem {path:?}")]
-pub(crate) fn inspect_filesystem(root: &openat::Dir, path: &str) -> Result<Filesystem> {
+pub(crate) fn inspect_filesystem(root: &Dir, path: &str) -> Result<Filesystem> {
     let rootfd = unsafe { BorrowedFd::borrow_raw(root.as_raw_fd()) };
     // SAFETY: This is unsafe just for the pre_exec, when we port to cap-std we can use cap-std-ext
     let o: Findmnt = unsafe {
diff --git a/src/filetree.rs b/src/filetree.rs
index a9eee816..31d0916d 100644
--- a/src/filetree.rs
+++ b/src/filetree.rs
@@ -27,7 +27,8 @@ use camino::{Utf8Path, Utf8PathBuf};
     target_arch = "aarch64",
     target_arch = "riscv64"
 ))]
-use openat_ext::OpenatDirExt;
+use cap_std::fs::Dir;
+use cap_std_ext::dirext::CapStdExtDirExt;
 #[cfg(any(
     target_arch = "x86_64",
     target_arch = "aarch64",
@@ -130,11 +131,11 @@ impl FileMetadata {
         target_arch = "aarch64",
         target_arch = "riscv64"
     ))]
-    pub(crate) fn new_from_path<P: openat::AsPath>(
-        dir: &openat::Dir,
+    pub(crate) fn new_from_path<P: AsRef<std::path::Path>>(
+        dir: &Dir,
         name: P,
     ) -> Result<FileMetadata> {
-        let mut r = dir.open_file(name)?;
+        let mut r = dir.open(name)?;
         let meta = r.metadata()?;
         let mut hasher =
             Hasher::new(MessageDigest::sha512()).expect("openssl sha512 hasher creation failed");
@@ -155,36 +156,30 @@ impl FileTree {
         target_arch = "aarch64",
         target_arch = "riscv64"
     ))]
-    fn unsorted_from_dir(dir: &openat::Dir) -> Result<HashMap<String, FileMetadata>> {
+    fn unsorted_from_dir(dir: &Dir) -> Result<HashMap<String, FileMetadata>> {
         let mut ret = HashMap::new();
-        for entry in dir.list_dir(".")? {
+        for entry in dir.entries_utf8()? {
             let entry = entry?;
-            let Some(name) = entry.file_name().to_str() else {
-                bail!("Invalid UTF-8 filename: {:?}", entry.file_name())
-            };
+            let name = entry.file_name().context("Getting file name")?;
             if name.starts_with(TMP_PREFIX) {
                 bail!("File {} contains our temporary prefix!", name);
             }
-            match dir.get_file_type(&entry)? {
-                openat::SimpleType::File => {
-                    let meta = FileMetadata::new_from_path(dir, name)?;
-                    let _ = ret.insert(name.to_string(), meta);
-                }
-                openat::SimpleType::Dir => {
-                    let child = dir.sub_dir(name)?;
-                    for (mut k, v) in FileTree::unsorted_from_dir(&child)?.drain() {
-                        k.reserve(name.len() + 1);
-                        k.insert(0, '/');
-                        k.insert_str(0, name);
-                        let _ = ret.insert(k, v);
-                    }
-                }
-                openat::SimpleType::Symlink => {
-                    bail!("Unsupported symbolic link {:?}", entry.file_name())
-                }
-                openat::SimpleType::Other => {
-                    bail!("Unsupported non-file/directory {:?}", entry.file_name())
+            let file_type = entry.file_type()?;
+            if file_type.is_file() {
+                let meta = FileMetadata::new_from_path(dir, &name)?;
+                let _ = ret.insert(name.to_string(), meta);
+            } else if file_type.is_dir() {
+                let child = dir.open_dir(&name)?;
+                for (mut k, v) in FileTree::unsorted_from_dir(&child)?.drain() {
+                    k.reserve(name.len() + 1);
+                    k.insert(0, '/');
+                    k.insert_str(0, &name);
+                    let _ = ret.insert(k, v);
                 }
+            } else if file_type.is_symlink() {
+                bail!("Unsupported symbolic link {:?}", entry.file_name())
+            } else {
+                bail!("Unsupported non-file/directory {:?}", entry.file_name())
             }
         }
         Ok(ret)
@@ -196,7 +191,7 @@ impl FileTree {
         target_arch = "aarch64",
         target_arch = "riscv64"
     ))]
-    pub(crate) fn new_from_dir(dir: &openat::Dir) -> Result<Self> {
+    pub(crate) fn new_from_dir(dir: &Dir) -> Result<Self> {
         let mut children = BTreeMap::new();
         for (k, mut v) in Self::unsorted_from_dir(dir)?.drain() {
             let k_path = get_dest_efi_path(Utf8Path::new(&k)).to_string();
@@ -275,26 +270,24 @@ impl FileTree {
         target_arch = "aarch64",
         target_arch = "riscv64"
     ))]
-    pub(crate) fn relative_diff_to(&self, dir: &openat::Dir) -> Result<FileTreeDiff> {
+    pub(crate) fn relative_diff_to(&self, dir: &Dir) -> Result<FileTreeDiff> {
         let mut removals = HashSet::new();
         let mut changes = HashSet::new();
 
         for (path, info) in self.children.iter() {
             assert!(!path.starts_with('/'));
 
-            if let Some(meta) = dir.metadata_optional(path)? {
-                match meta.simple_type() {
-                    openat::SimpleType::File => {
-                        let target_info = FileMetadata::new_from_path(dir, path)?;
-                        if info != &target_info {
-                            // Save the source path for changes
-                            changes.insert(info.source.as_ref().unwrap_or(path).clone());
-                        }
-                    }
-                    _ => {
-                        // If a file became a directory
+            if let Ok(meta) = dir.metadata(path) {
+                let file_type = meta.file_type();
+                if file_type.is_file() {
+                    let target_info = FileMetadata::new_from_path(dir, path)?;
+                    if info != &target_info {
+                        // Save the source path for changes
                         changes.insert(info.source.as_ref().unwrap_or(path).clone());
                     }
+                } else {
+                    // If a file became a directory
+                    changes.insert(info.source.as_ref().unwrap_or(path).clone());
                 }
             } else {
                 removals.insert(path.clone());
@@ -314,30 +307,24 @@ impl FileTree {
     target_arch = "aarch64",
     target_arch = "riscv64"
 ))]
-fn cleanup_tmp(dir: &openat::Dir) -> Result<()> {
-    for entry in dir.list_dir(".")? {
+fn cleanup_tmp(dir: &Dir) -> Result<()> {
+    for entry in dir.entries_utf8()? {
         let entry = entry?;
-        let Some(name) = entry.file_name().to_str() else {
-            // Skip invalid UTF-8 for now, we will barf on it later though.
-            continue;
-        };
+        let name = entry.file_name().context("Getting file name")?;
 
-        match dir.get_file_type(&entry)? {
-            openat::SimpleType::Dir => {
-                if name.starts_with(TMP_PREFIX) {
-                    dir.remove_all(name)?;
-                    continue;
-                } else {
-                    let child = dir.sub_dir(name)?;
-                    cleanup_tmp(&child)?;
-                }
+        let file_type = entry.file_type()?;
+        if file_type.is_dir() {
+            if name.starts_with(TMP_PREFIX) {
+                dir.remove_dir_all(name)?;
+                continue;
+            } else {
+                let child = dir.open_dir(name)?;
+                cleanup_tmp(&child)?;
             }
-            openat::SimpleType::File => {
-                if name.starts_with(TMP_PREFIX) {
-                    dir.remove_file(name)?;
-                }
+        } else if file_type.is_file() {
+            if name.starts_with(TMP_PREFIX) {
+                dir.remove_file(name)?;
             }
-            _ => {}
         }
     }
     Ok(())
@@ -360,7 +347,7 @@ pub(crate) struct ApplyUpdateOptions {
     target_arch = "aarch64",
     target_arch = "riscv64"
 ))]
-pub(crate) fn copy_dir(root: &openat::Dir, src: &str, dst: &str) -> Result<()> {
+pub(crate) fn copy_dir(root: &Dir, src: &str, dst: &str) -> Result<()> {
     copy_dir_with_args(root, src, dst, ["-a"])
 }
 
@@ -370,12 +357,7 @@ pub(crate) fn copy_dir(root: &openat::Dir, src: &str, dst: &str) -> Result<()> {
     target_arch = "aarch64",
     target_arch = "riscv64"
 ))]
-pub(crate) fn copy_dir_with_args<I, S>(
-    root: &openat::Dir,
-    src: &str,
-    dst: &str,
-    args: I,
-) -> Result<()>
+pub(crate) fn copy_dir_with_args<I, S>(root: &Dir, src: &str, dst: &str, args: I) -> Result<()>
 where
     I: IntoIterator<Item = S>,
     S: AsRef<std::ffi::OsStr>,
@@ -436,8 +418,8 @@ fn get_dest_efi_path(path: &Utf8Path) -> Utf8PathBuf {
     target_arch = "riscv64"
 ))]
 pub(crate) fn apply_diff(
-    srcdir: &openat::Dir,
-    destdir: &openat::Dir,
+    srcdir: &Dir,
+    destdir: &Dir,
     diff: &FileTreeDiff,
     opts: Option<&ApplyUpdateOptions>,
 ) -> Result<()> {
@@ -458,7 +440,7 @@ pub(crate) fn apply_diff(
                 path_tmp = Utf8Path::new(&first_dir_tmp).join(path.strip_prefix(&first_dir)?);
                 // copy to temp dir and remember
                 // skip copying if dir not existed in dest
-                if !destdir.exists(&first_dir_tmp)? && destdir.exists(first_dir.as_std_path())? {
+                if !destdir.exists(&first_dir_tmp) && destdir.exists(first_dir.as_std_path()) {
                     copy_dir(destdir, first_dir.as_str(), &first_dir_tmp).with_context(|| {
                         format!("copy {first_dir} to {first_dir_tmp} before removing {pathstr}")
                     })?;
@@ -479,7 +461,7 @@ pub(crate) fn apply_diff(
         let (first_dir, first_dir_tmp) = get_first_dir(&path)?;
         let mut path_tmp = Utf8PathBuf::from(&first_dir_tmp);
         if first_dir != path {
-            if !destdir.exists(&first_dir_tmp)? && destdir.exists(first_dir.as_std_path())? {
+            if !destdir.exists(&first_dir_tmp) && destdir.exists(first_dir.as_std_path()) {
                 // copy to temp dir if not exists
                 copy_dir(destdir, first_dir.as_str(), &first_dir_tmp).with_context(|| {
                     format!("copy {first_dir} to {first_dir_tmp} before updating {pathstr}")
@@ -488,7 +470,11 @@ pub(crate) fn apply_diff(
             path_tmp = path_tmp.join(path.strip_prefix(&first_dir)?);
             // ensure new additions dir exists
             if let Some(parent) = path_tmp.parent() {
-                destdir.ensure_dir_all(parent.as_std_path(), DEFAULT_FILE_MODE)?;
+                use cap_std::fs::{DirBuilder, DirBuilderExt};
+
+                let mut dir_opts = DirBuilder::new();
+                dir_opts.recursive(true).mode(DEFAULT_FILE_MODE);
+                destdir.create_dir_with(parent.as_std_path(), &dir_opts)?;
             }
             // remove changed file before copying
             destdir
@@ -497,7 +483,7 @@ pub(crate) fn apply_diff(
         }
         updates.insert(first_dir, first_dir_tmp);
         srcdir
-            .copy_file_at(src_path.as_std_path(), destdir, path_tmp.as_std_path())
+            .copy(src_path.as_std_path(), destdir, path_tmp.as_std_path())
             .with_context(|| format!("copying {:?} to {:?}", src_path, path_tmp))?;
     }
 
@@ -505,43 +491,47 @@ pub(crate) fn apply_diff(
     for (dst, tmp) in updates.iter() {
         let dst = dst.as_std_path();
         log::trace!("doing local exchange for {} and {:?}", tmp, dst);
-        if destdir.exists(dst)? {
-            destdir
-                .local_exchange(tmp, dst)
+        if destdir.exists(dst) {
+            use rustix::fs::{renameat_with, RenameFlags};
+
+            renameat_with(&destdir, tmp, &destdir, dst, RenameFlags::EXCHANGE)
                 .with_context(|| format!("exchange for {} and {:?}", tmp, dst))?;
         } else {
             destdir
-                .local_rename(tmp, dst)
+                .rename(tmp, destdir, dst)
                 .with_context(|| format!("rename for {} and {:?}", tmp, dst))?;
         }
         crate::try_fail_point!("update::exchange");
     }
     // Ensure all of the updates & changes are written persistently to disk
     if !opts.skip_sync {
-        destdir.syncfs()?;
+        rustix::fs::syncfs(destdir.reopen_as_ownedfd()?)?;
     }
 
     // finally remove the temp dir
     for (_, tmp) in updates.iter() {
         log::trace!("cleanup: {}", tmp);
-        destdir.remove_all(tmp).context("clean up temp")?;
+        // [`remove_all`] from openat is essentially [`remove_all_optional`]
+        destdir.remove_all_optional(tmp).context("clean up temp")?;
     }
     // A second full filesystem sync to narrow any races rather than
     // waiting for writeback to kick in.
     if !opts.skip_sync {
-        fsfreeze_thaw_cycle(destdir.open_file(".")?)?;
+        fsfreeze_thaw_cycle(destdir.reopen_as_ownedfd()?)?;
     }
     Ok(())
 }
 
 #[cfg(test)]
 mod tests {
+    use cap_std::ambient_authority;
+    use cap_std::fs::{DirBuilder, DirBuilderExt, Permissions, PermissionsExt};
+
     use super::*;
     use std::fs;
-    use std::io::Write;
     use std::path::Path;
 
-    fn run_diff(a: &openat::Dir, b: &openat::Dir) -> Result<FileTreeDiff> {
+    fn run_diff(a: &Dir, b: &Dir) -> Result<FileTreeDiff> {
         let ta = FileTree::new_from_dir(a)?;
         let tb = FileTree::new_from_dir(b)?;
         let diff = ta.diff(&tb)?;
@@ -564,9 +554,9 @@ mod tests {
         if !r.success() {
             bail!("failed to cp");
         };
-        let c = openat::Dir::open(&c)?;
-        let da = openat::Dir::open(a)?;
-        let db = openat::Dir::open(b)?;
+        let c = Dir::open_ambient_dir(&c, ambient_authority())?;
+        let da = Dir::open_ambient_dir(a, ambient_authority())?;
+        let db = Dir::open_ambient_dir(b, ambient_authority())?;
         let ta = FileTree::new_from_dir(&da)?;
         let tb = FileTree::new_from_dir(&db)?;
         let diff = ta.diff(&tb)?;
@@ -584,8 +574,8 @@ mod tests {
                 assert_eq!(n, diff.removals.len());
             }
             for f in diff.removals.iter() {
-                assert!(c.exists(f)?);
-                assert!(da.exists(f)?);
+                assert!(c.exists(f));
+                assert!(da.exists(f));
             }
         } else {
             assert_eq!(newdiff.count(), 0);
@@ -613,16 +603,21 @@ mod tests {
         let pb = p.join("b");
         std::fs::create_dir(&pa)?;
         std::fs::create_dir(&pb)?;
-        let a = openat::Dir::open(&pa)?;
-        let b = openat::Dir::open(&pb)?;
+        let a = Dir::open_ambient_dir(&pa, ambient_authority())?;
+        let b = Dir::open_ambient_dir(&pb, ambient_authority())?;
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 0);
-        a.create_dir("foo", 0o755)?;
+        let mut dir_builder = DirBuilder::new();
+        dir_builder.recursive(true).mode(0o755);
+        a.create_dir_with("foo", &dir_builder)?;
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 0);
         {
-            let mut bar = a.write_file("foo/bar", 0o644)?;
-            bar.write_all("foobarcontents".as_bytes())?;
+            a.atomic_write_with_perms(
+                "foo/bar",
+                "foobarcontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 1);
@@ -638,17 +633,23 @@ mod tests {
         let rdiff = ta.relative_diff_to(&b)?;
         assert_eq!(rdiff.removals.len(), cdiff.removals.len());
 
-        b.create_dir("foo", 0o755)?;
+        b.create_dir_with("foo", &dir_builder)?;
         {
-            let mut bar = b.write_file("foo/bar", 0o644)?;
-            bar.write_all("foobarcontents".as_bytes())?;
+            b.atomic_write_with_perms(
+                "foo/bar",
+                "foobarcontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 0);
         test_apply(&pa, &pb).context("testing apply 2")?;
         {
-            let mut bar2 = b.write_file("foo/bar", 0o644)?;
-            bar2.write_all("foobarcontents2".as_bytes())?;
+            b.atomic_write_with_perms(
+                "foo/bar",
+                "foobarcontents2".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 1);
@@ -682,8 +683,8 @@ mod tests {
         fs::write(b.join(&newsubp).join("newgrub.x64"), "newgrub data")?;
         fs::remove_file(b.join(relp).join("shim.x64"))?;
         {
-            let a = openat::Dir::open(&a)?;
-            let b = openat::Dir::open(&b)?;
+            let a = Dir::open_ambient_dir(&a, ambient_authority())?;
+            let b = Dir::open_ambient_dir(&b, ambient_authority())?;
             let ta = FileTree::new_from_dir(&a)?;
             let tb = FileTree::new_from_dir(&b)?;
             let diff = ta.diff(&tb)?;
@@ -713,8 +714,8 @@ mod tests {
             fs::write(c.join(foo).join("grub.x64"), "grub data 3")?;
             // addition: "bar/2.0/EFI/new/newfile"
             fs::write(c.join(bar).join("newfile"), "filedata")?;
-            let a = openat::Dir::open(&a.join("EFI"))?;
-            let c = openat::Dir::open(&c)?;
+            let a = Dir::open_ambient_dir(&a.join("EFI"), ambient_authority())?;
+            let c = Dir::open_ambient_dir(&c, ambient_authority())?;
             let ta = FileTree::new_from_dir(&a)?;
             let tc = FileTree::new_from_dir(&c)?;
             let diff = ta.diff(&tc)?;
@@ -774,26 +775,37 @@ mod tests {
         let pb = p.join(".btmp.b/b");
         std::fs::create_dir_all(&pa)?;
         std::fs::create_dir_all(&pb)?;
-        let dp = openat::Dir::open(p)?;
+        let dp = Dir::open_ambient_dir(p, ambient_authority())?;
         {
-            let mut buf = dp.write_file("a/foo", 0o644)?;
-            buf.write_all("foocontents".as_bytes())?;
-            let mut buf = dp.write_file("a/.btmp.foo", 0o644)?;
-            buf.write_all("foocontents".as_bytes())?;
-            let mut buf = dp.write_file(".btmp.b/foo", 0o644)?;
-            buf.write_all("foocontents".as_bytes())?;
+            dp.atomic_write_with_perms(
+                "a/foo",
+                "foocontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
+
+            dp.atomic_write_with_perms(
+                "a/.btmp.foo",
+                "foocontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
+
+            dp.atomic_write_with_perms(
+                ".btmp.b/foo",
+                "foocontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
-        assert!(dp.exists("a/.btmp.a")?);
-        assert!(dp.exists("a/foo")?);
-        assert!(dp.exists("a/.btmp.foo")?);
-        assert!(dp.exists("a/.btmp.a")?);
-        assert!(dp.exists(".btmp.b/b")?);
-        assert!(dp.exists(".btmp.b/foo")?);
+        assert!(dp.exists("a/.btmp.a"));
+        assert!(dp.exists("a/foo"));
+        assert!(dp.exists("a/.btmp.foo"));
+        assert!(dp.exists("a/.btmp.a"));
+        assert!(dp.exists(".btmp.b/b"));
+        assert!(dp.exists(".btmp.b/foo"));
         cleanup_tmp(&dp)?;
-        assert!(!dp.exists("a/.btmp.a")?);
-        assert!(dp.exists("a/foo")?);
-        assert!(!dp.exists("a/.btmp.foo")?);
-        assert!(!dp.exists(".btmp.b")?);
+        assert!(!dp.exists("a/.btmp.a"));
+        assert!(dp.exists("a/foo"));
+        assert!(!dp.exists("a/.btmp.foo"));
+        assert!(!dp.exists(".btmp.b"));
         Ok(())
     }
     // Waiting on https://github.com/rust-lang/rust/pull/125692
@@ -806,28 +818,47 @@ mod tests {
         let pb = p.join("b");
         std::fs::create_dir(&pa)?;
         std::fs::create_dir(&pb)?;
-        let a = openat::Dir::open(&pa)?;
-        let b = openat::Dir::open(&pb)?;
-        a.create_dir("foo", 0o755)?;
-        a.create_dir("bar", 0o755)?;
+        let a = Dir::open_ambient_dir(&pa, ambient_authority())?;
+        let b = Dir::open_ambient_dir(&pb, ambient_authority())?;
+
+        let mut dir_builder = DirBuilder::new();
+        dir_builder.recursive(true).mode(0o755);
+
+        a.create_dir_with("foo", &dir_builder)?;
+        a.create_dir_with("bar", &dir_builder)?;
+
         let foo = Path::new("foo/bar");
         let bar = Path::new("bar/foo");
         let testfile = "testfile";
         {
-            let mut buf = a.write_file(foo, 0o644)?;
-            buf.write_all("foocontents".as_bytes())?;
-            let mut buf = a.write_file(bar, 0o644)?;
-            buf.write_all("barcontents".as_bytes())?;
-            let mut buf = a.write_file(testfile, 0o644)?;
-            buf.write_all("testfilecontents".as_bytes())?;
+            a.atomic_write_with_perms(
+                foo,
+                "foocontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
+
+            a.atomic_write_with_perms(
+                bar,
+                "barcontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
+
+            a.atomic_write_with_perms(
+                testfile,
+                "testfilecontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
 
         let diff = run_diff(&a, &b)?;
         assert_eq!(diff.count(), 3);
-        b.create_dir("foo", 0o755)?;
+        b.create_dir_with("foo", &dir_builder)?;
         {
-            let mut buf = b.write_file(foo, 0o644)?;
-            buf.write_all("foocontents".as_bytes())?;
+            b.atomic_write_with_perms(
+                foo,
+                "foocontents".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
         }
         let b_btime_foo = fs::metadata(pb.join(foo))?.created()?;
 
@@ -869,7 +900,7 @@ mod tests {
             assert_eq!(diff.count(), 1);
             assert_eq!(diff.removals.len(), 1);
             apply_diff(&a, &b, &diff, None).context("test removed files with relative_diff")?;
-            assert_eq!(b.exists(testfile)?, false);
+            assert_eq!(b.exists(testfile), false);
             // creation time is not changed for unchanged file
             let b_btime_foo_new = fs::metadata(pb.join(foo))?.created()?;
             assert_eq!(b_btime_foo_new, b_btime_foo);
@@ -879,8 +910,8 @@ mod tests {
             let diff = run_diff(&b, &a)?;
             assert_eq!(diff.count(), 2);
             apply_diff(&a, &b, &diff, None).context("test removed files")?;
-            assert_eq!(b.exists(testfile)?, true);
-            assert_eq!(b.exists(bar)?, false);
+            assert_eq!(b.exists(testfile), true);
+            assert_eq!(b.exists(bar), false);
             let diff = run_diff(&b, &a)?;
             assert_eq!(diff.count(), 0);
             // creation time is not changed for unchanged file
diff --git a/src/freezethaw.rs b/src/freezethaw.rs
index 7904a419..251fe49a 100644
--- a/src/freezethaw.rs
+++ b/src/freezethaw.rs
@@ -33,6 +33,7 @@ fn ioctl_fithaw<Fd: AsFd>(fd: Fd) -> io::Result<()> {
 ///
 /// This function always call syncfs() first, then calls
 /// `ioctl(FIFREEZE)` and `ioctl(FITHAW)`, ignoring `EOPNOTSUPP` and `EPERM`
+#[fn_error_context::context("fsfreeze_thaw_cycle")]
 pub(crate) fn fsfreeze_thaw_cycle<Fd: AsFd>(fd: Fd) -> anyhow::Result<()> {
     rustix::fs::syncfs(&fd)?;
 
diff --git a/src/grubconfigs.rs b/src/grubconfigs.rs
index 65196c3d..476f934c 100644
--- a/src/grubconfigs.rs
+++ b/src/grubconfigs.rs
@@ -1,13 +1,14 @@
 use std::fmt::Write;
 use std::io::Read;
-use std::os::unix::fs::PermissionsExt;
 use std::os::unix::io::AsRawFd;
 use std::path::{Path, PathBuf};
 
 use anyhow::{anyhow, Context, Result};
 use bootc_internal_utils::CommandRunExt;
+use cap_std::ambient_authority;
+use cap_std::fs::{Dir, DirBuilder, DirBuilderExt, MetadataExt, Permissions, PermissionsExt};
+use cap_std_ext::dirext::CapStdExtDirExt;
 use fn_error_context::context;
-use openat_ext::OpenatDirExt;
 
 use crate::freezethaw::fsfreeze_thaw_cycle;
 
@@ -29,46 +30,48 @@ pub(crate) const GRUBCONFIG_FILE_MODE: u32 = 0o600;
 /// Install the static GRUB config files.
 #[context("Installing static GRUB configs")]
 pub(crate) fn install(
-    target_root: &openat::Dir,
-    src_root: Option<&openat::Dir>,
+    target_root: &Dir,
+    src_root: Option<&Dir>,
     installed_efi_vendor: Option<&str>,
     write_uuid: bool,
 ) -> Result<()> {
-    let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
+    let bootdir = &target_root.open_dir("boot").context("Opening /boot")?;
     let boot_is_mount = {
-        let root_dev = target_root.self_metadata()?.stat().st_dev;
-        let boot_dev = bootdir.self_metadata()?.stat().st_dev;
+        let root_dev = target_root.metadata(".")?.dev();
+        let boot_dev = bootdir.metadata(".")?.dev();
         log::debug!("root_dev={root_dev} boot_dev={boot_dev}");
         root_dev != boot_dev
     };
 
-    if !bootdir.exists(GRUB2DIR)? {
-        bootdir.create_dir(GRUB2DIR, 0o700)?;
+    if !bootdir.exists(GRUB2DIR) {
+        let mut dir_options = DirBuilder::new();
+        dir_options.recursive(true).mode(0o700);
+        bootdir.create_dir_with(GRUB2DIR, &dir_options)?;
     }
 
     let configdir = if let Some(src_root) = src_root {
         // strip the leading `/` to make the path relative to the given
         // source root
-        src_root.sub_dir(Path::new(CONFIGDIR).strip_prefix("/")?)?
+        src_root.open_dir(Path::new(CONFIGDIR).strip_prefix("/")?)?
     } else {
-        openat::Dir::open(Path::new(CONFIGDIR))?
+        Dir::open_ambient_dir(Path::new(CONFIGDIR), ambient_authority())?
     };
 
     let mut config = String::from("# Generated by bootupd / do not edit\n\n");
 
     let mut pre = String::new();
     configdir
-        .open_file("grub-static-pre.cfg")?
+        .open("grub-static-pre.cfg")?
         .read_to_string(&mut pre)?;
     config.push_str(pre.as_str());
 
-    let dropindir = configdir.sub_dir(Path::new(DROPINDIR))?;
+    let dropindir = configdir.open_dir(Path::new(DROPINDIR))?;
     // Sort the files for reproducibility
     let mut entries = dropindir
-        .list_dir(".")?
+        .entries()?
         .map(|e| e.map_err(anyhow::Error::msg))
         .collect::<Result<Vec<_>>>()?;
-    entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
+    entries.sort_by_key(|a| a.file_name());
     for ent in entries {
         let name = ent.file_name();
         let name = name
@@ -80,16 +83,20 @@ pub(crate) fn install(
         }
         writeln!(config, "\n### BEGIN {name} ###")?;
         let mut dropin = String::new();
-        dropindir.open_file(name)?.read_to_string(&mut dropin)?;
+        dropindir.open(name)?.read_to_string(&mut dropin)?;
 
         config.push_str(dropin.as_str());
         writeln!(config, "### END {name} ###")?;
         println!("Added {name}");
     }
 
-    let grub2dir = bootdir.sub_dir(GRUB2DIR)?;
+    let grub2dir = bootdir.open_dir(GRUB2DIR)?;
     grub2dir
-        .write_file_contents("grub.cfg", GRUBCONFIG_FILE_MODE, config.as_bytes())
+        .atomic_write_with_perms(
+            "grub.cfg",
+            config.as_bytes(),
+            Permissions::from_mode(GRUBCONFIG_FILE_MODE),
+        )
         .context("Copying grub-static.cfg")?;
     println!("Installed: grub.cfg");
 
@@ -104,37 +111,43 @@ pub(crate) fn install(
             .ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?;
         let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n");
         let uuid_path = "bootuuid.cfg";
+
         grub2dir
-            .write_file_contents(uuid_path, GRUBCONFIG_FILE_MODE, grub2_uuid_contents)
+            .atomic_write_with_perms(
+                uuid_path,
+                grub2_uuid_contents,
+                Permissions::from_mode(GRUBCONFIG_FILE_MODE),
+            )
             .context("Writing bootuuid.cfg")?;
         println!("Installed: bootuuid.cfg");
+
         Some(uuid_path)
     } else {
         None
     };
 
-    fsfreeze_thaw_cycle(grub2dir.open_file(".")?)?;
+    fsfreeze_thaw_cycle(grub2dir.reopen_as_ownedfd()?)?;
 
     if let Some(vendordir) = installed_efi_vendor {
         log::debug!("vendordir={:?}", &vendordir);
         let vendor = PathBuf::from(vendordir);
         let target = &vendor.join("grub.cfg");
         let dest_efidir = target_root
-            .sub_dir_optional("boot/efi/EFI")
+            .open_dir_optional("boot/efi/EFI")
             .context("Opening /boot/efi/EFI")?;
         if let Some(efidir) = dest_efidir {
             configdir
-                .copy_file_at("grub-static-efi.cfg", &efidir, target)
+                .copy("grub-static-efi.cfg", &efidir, target)
                 .context("Copying static EFI")?;
             println!("Installed: {target:?}");
             if let Some(uuid_path) = uuid_path {
                 let target = &vendor.join(uuid_path);
                 grub2dir
-                    .copy_file_at(uuid_path, &efidir, target)
+                    .copy(uuid_path, &efidir, target)
                     .context("Writing bootuuid.cfg to efi dir")?;
                 println!("Installed: {target:?}");
             }
-            fsfreeze_thaw_cycle(efidir.open_file(".")?)?;
+            fsfreeze_thaw_cycle(efidir.reopen_as_ownedfd()?)?;
         } else {
             println!("Could not find /boot/efi/EFI when installing {target:?}");
         }
@@ -144,8 +157,8 @@ pub(crate) fn install(
 }
 
 #[context("Create file boot/grub2/grubenv")]
-fn write_grubenv(grubdir: &openat::Dir) -> Result<()> {
-    if grubdir.exists(GRUBENV)? {
+fn write_grubenv(grubdir: &Dir) -> Result<()> {
+    if grubdir.exists(GRUBENV) {
         return Ok(());
     }
     let editenv = Path::new("/usr/bin/grub2-editenv");
@@ -160,7 +173,7 @@ fn write_grubenv(grubdir: &openat::Dir) -> Result<()> {
 }
 
 #[context("Ensure file permissions are 0600")]
-fn ensure_file_permissions(target_dir: &openat::Dir, target_file: &str) -> Result<()> {
+fn ensure_file_permissions(target_dir: &Dir, target_file: &str) -> Result<()> {
     let metadata = match target_dir.metadata(target_file) {
         Ok(metadata) => metadata,
         Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
@@ -173,14 +186,14 @@ fn ensure_file_permissions(target_dir: &openat::Dir, target_file: &str) -> Resul
     let mode = metadata.permissions().mode() & 0o777;
     if mode != GRUBCONFIG_FILE_MODE {
         target_dir
-            .set_mode(target_file, GRUBCONFIG_FILE_MODE)
+            .set_permissions(target_file, Permissions::from_mode(GRUBCONFIG_FILE_MODE))
             .with_context(|| format!("Setting {} permissions to 0600", target_file))?;
     }
     Ok(())
 }
 
 #[context("Ensure grub files permissions are 0600")]
-pub(crate) fn ensure_grub_permissions(grub_dir: &openat::Dir) -> Result<()> {
+pub(crate) fn ensure_grub_permissions(grub_dir: &Dir) -> Result<()> {
     for file_name in GRUB_FILES.iter() {
         ensure_file_permissions(&grub_dir, file_name)
             .with_context(|| format!("Failed to ensure permissions for {}", file_name))?;
@@ -199,14 +212,14 @@ mod tests {
         env_logger::init();
         let td = tempfile::tempdir()?;
         let tdp = td.path();
-        let td = openat::Dir::open(tdp)?;
+        let td = Dir::open_ambient_dir(tdp, ambient_authority())?;
         std::fs::create_dir_all(tdp.join("boot/grub2"))?;
         std::fs::create_dir_all(tdp.join("boot/efi/EFI/BOOT"))?;
         std::fs::create_dir_all(tdp.join("boot/efi/EFI/fedora"))?;
         install(&td, None, Some("fedora"), false).unwrap();
 
-        assert!(td.exists("boot/grub2/grub.cfg")?);
-        assert!(td.exists("boot/efi/EFI/fedora/grub.cfg")?);
+        assert!(td.exists("boot/grub2/grub.cfg"));
+        assert!(td.exists("boot/efi/EFI/fedora/grub.cfg"));
         Ok(())
     }
     #[test]
@@ -220,10 +233,10 @@ mod tests {
         let tdp = td.path();
         let grub = tdp.join("boot/grub2");
         std::fs::create_dir_all(&grub)?;
-        let td = openat::Dir::open(&grub)?;
+        let td = Dir::open_ambient_dir(&grub, ambient_authority())?;
         write_grubenv(&td)?;
 
-        assert!(td.exists("grubenv")?);
+        assert!(td.exists("grubenv"));
         ensure_file_permissions(&td, "grubenv")?;
         // Verify permissions are now 0600
         {
@@ -239,12 +252,20 @@ mod tests {
         let tdp = td.path();
         let grub = tdp.join("boot/grub2");
         std::fs::create_dir_all(&grub)?;
-        let grub_dir = openat::Dir::open(&grub)?;
+        let grub_dir = Dir::open_ambient_dir(&grub, ambient_authority())?;
 
         // Create grubenv, grub.cfg with permissions (0o644), no bootuuid.cfg
         {
-            grub_dir.write_file_contents("grubenv", 0o644, "grubenv content".as_bytes())?;
-            grub_dir.write_file_contents("grub.cfg", 0o644, "grub.cfg content".as_bytes())?;
+            grub_dir.atomic_write_with_perms(
+                "grubenv",
+                "grubenv content".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
+            grub_dir.atomic_write_with_perms(
+                "grub.cfg",
+                "grub.cfg content".as_bytes(),
+                Permissions::from_mode(0o644),
+            )?;
 
             // bootuuid.cfg doesn't exist
             assert!(grub_dir.metadata("bootuuid.cfg").is_err());
diff --git a/src/util.rs b/src/util.rs
index e2d4e237..733bf80c 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -3,9 +3,9 @@ use std::path::Path;
 use std::process::Command;
 
 use anyhow::{bail, Context, Result};
+use cap_std::fs::Dir;
 use chrono::{DateTime, Utc};
 use fn_error_context::context;
-use openat_ext::OpenatDirExt;
 
 /// Parse an environment variable as UTF-8
 #[allow(dead_code)]
@@ -22,32 +22,28 @@ pub(crate) fn getenv_utf8(n: &str) -> Result<Option<String>> {
 }
 
 #[allow(dead_code)]
-pub(crate) fn filenames(dir: &openat::Dir) -> Result<HashSet<String>> {
+pub(crate) fn filenames(dir: &Dir) -> Result<HashSet<String>> {
     let mut ret = HashSet::new();
-    for entry in dir.list_dir(".")? {
+    for entry in dir.entries_utf8()? {
         let entry = entry?;
-        let Some(name) = entry.file_name().to_str() else {
-            bail!("Invalid UTF-8 filename: {:?}", entry.file_name())
+        let Ok(name) = entry.file_name() else {
+            bail!("Invalid filename: {:?}", entry.file_name())
         };
-        match dir.get_file_type(&entry)? {
-            openat::SimpleType::File => {
-                ret.insert(format!("/{name}"));
-            }
-            openat::SimpleType::Dir => {
-                let child = dir.sub_dir(name)?;
-                for mut k in filenames(&child)?.drain() {
-                    k.reserve(name.len() + 1);
-                    k.insert_str(0, name);
-                    k.insert(0, '/');
-                    ret.insert(k);
-                }
-            }
-            openat::SimpleType::Symlink => {
-                bail!("Unsupported symbolic link {:?}", entry.file_name())
-            }
-            openat::SimpleType::Other => {
-                bail!("Unsupported non-file/directory {:?}", entry.file_name())
+        let file_type = entry.file_type()?;
+        if file_type.is_file() {
+            ret.insert(format!("/{name}"));
+        } else if file_type.is_dir() {
+            let child = dir.open_dir(&name)?;
+            for mut k in filenames(&child)?.drain() {
+                k.reserve(name.len() + 1);
+                k.insert_str(0, &name);
+                k.insert(0, '/');
+                ret.insert(k);
             }
+        } else if file_type.is_symlink() {
+            bail!("Unsupported symbolic link {:?}", entry.file_name())
+        } else {
+            bail!("Unsupported non-file/directory {:?}", entry.file_name())
         }
     }
     Ok(ret)
