diff --git a/crates/tmpfiles/src/lib.rs b/crates/tmpfiles/src/lib.rs index 990cecabe..395e9ddf2 100644 --- a/crates/tmpfiles/src/lib.rs +++ b/crates/tmpfiles/src/lib.rs @@ -20,6 +20,7 @@ use rustix::path::Arg; use thiserror::Error; const TMPFILESD: &str = "usr/lib/tmpfiles.d"; +const ETC_TMPFILESD: &str = "etc/tmpfiles.d"; /// The path to the file we use for generation const BOOTC_GENERATED_PREFIX: &str = "bootc-autogenerated-var"; @@ -452,14 +453,16 @@ pub fn find_missing_tmpfiles_current_root() -> Result { }) } -/// Read all tmpfiles.d entries in the target directory, and return a mapping -/// from (file path) => (single tmpfiles.d entry line) -fn read_tmpfiles(rootfs: &Dir) -> Result<(BTreeMap, BootcTmpfilesGeneration)> { - let Some(tmpfiles_dir) = rootfs.open_dir_optional(TMPFILESD)? else { +/// Read all tmpfiles.d entries from a single directory +fn read_tmpfiles_from_dir( + rootfs: &Dir, + dir_path: &str, + generation: &mut BootcTmpfilesGeneration, +) -> Result> { + let Some(tmpfiles_dir) = rootfs.open_dir_optional(dir_path)? else { return Ok(Default::default()); }; let mut result = BTreeMap::new(); - let mut generation = BootcTmpfilesGeneration::default(); for entry in tmpfiles_dir.entries()? { let entry = entry?; let name = entry.file_name(); @@ -486,6 +489,25 @@ fn read_tmpfiles(rootfs: &Dir) -> Result<(BTreeMap, BootcTmpfil result.insert(path.to_owned(), line); } } + Ok(result) +} + +/// Read all tmpfiles.d entries in the target directory, and return a mapping +/// from (file path) => (single tmpfiles.d entry line) +/// +/// This function reads from both `/usr/lib/tmpfiles.d` and `/etc/tmpfiles.d`, +/// with `/etc` entries taking precedence (matching systemd's behavior). +fn read_tmpfiles(rootfs: &Dir) -> Result<(BTreeMap, BootcTmpfilesGeneration)> { + let mut generation = BootcTmpfilesGeneration::default(); + + // Read from /usr/lib/tmpfiles.d first (system/package-provided) + let mut result = read_tmpfiles_from_dir(rootfs, TMPFILESD, &mut generation)?; + + // Read from /etc/tmpfiles.d and merge (user-provided, takes precedence) + let etc_result = read_tmpfiles_from_dir(rootfs, ETC_TMPFILESD, &mut generation)?; + // /etc entries override /usr/lib entries for the same path + result.extend(etc_result); + Ok((result, generation)) } @@ -569,10 +591,18 @@ mod tests { "#}, )?; + // Also test /etc/tmpfiles.d (user-provided configs) + rootfs.create_dir_all(ETC_TMPFILESD)?; + rootfs.write( + Path::new(ETC_TMPFILESD).join("user.conf"), + "d /var/lib/user 0755 root root - -\n", + )?; + // Add test content. rootfs.ensure_dir_with("var/lib/systemd", &db)?; rootfs.ensure_dir_with("var/lib/private", &db)?; rootfs.ensure_dir_with("var/lib/nfs", &db)?; + rootfs.ensure_dir_with("var/lib/user", &db)?; let global_rwx = Permissions::from_mode(0o777); rootfs.ensure_dir_with("var/lib/test/nested", &db).unwrap(); rootfs.set_permissions("var/lib/test", global_rwx.clone())?;