@@ -20,6 +20,7 @@ use rustix::path::Arg;
2020use thiserror:: Error ;
2121
2222const TMPFILESD : & str = "usr/lib/tmpfiles.d" ;
23+ const ETC_TMPFILESD : & str = "etc/tmpfiles.d" ;
2324/// The path to the file we use for generation
2425const BOOTC_GENERATED_PREFIX : & str = "bootc-autogenerated-var" ;
2526
@@ -452,14 +453,16 @@ pub fn find_missing_tmpfiles_current_root() -> Result<TmpfilesResult> {
452453 } )
453454}
454455
455- /// Read all tmpfiles.d entries in the target directory, and return a mapping
456- /// from (file path) => (single tmpfiles.d entry line)
457- fn read_tmpfiles ( rootfs : & Dir ) -> Result < ( BTreeMap < PathBuf , String > , BootcTmpfilesGeneration ) > {
458- let Some ( tmpfiles_dir) = rootfs. open_dir_optional ( TMPFILESD ) ? else {
456+ /// Read all tmpfiles.d entries from a single directory
457+ fn read_tmpfiles_from_dir (
458+ rootfs : & Dir ,
459+ dir_path : & str ,
460+ generation : & mut BootcTmpfilesGeneration ,
461+ ) -> Result < BTreeMap < PathBuf , String > > {
462+ let Some ( tmpfiles_dir) = rootfs. open_dir_optional ( dir_path) ? else {
459463 return Ok ( Default :: default ( ) ) ;
460464 } ;
461465 let mut result = BTreeMap :: new ( ) ;
462- let mut generation = BootcTmpfilesGeneration :: default ( ) ;
463466 for entry in tmpfiles_dir. entries ( ) ? {
464467 let entry = entry?;
465468 let name = entry. file_name ( ) ;
@@ -486,6 +489,25 @@ fn read_tmpfiles(rootfs: &Dir) -> Result<(BTreeMap<PathBuf, String>, BootcTmpfil
486489 result. insert ( path. to_owned ( ) , line) ;
487490 }
488491 }
492+ Ok ( result)
493+ }
494+
495+ /// Read all tmpfiles.d entries in the target directory, and return a mapping
496+ /// from (file path) => (single tmpfiles.d entry line)
497+ ///
498+ /// This function reads from both `/usr/lib/tmpfiles.d` and `/etc/tmpfiles.d`,
499+ /// with `/etc` entries taking precedence (matching systemd's behavior).
500+ fn read_tmpfiles ( rootfs : & Dir ) -> Result < ( BTreeMap < PathBuf , String > , BootcTmpfilesGeneration ) > {
501+ let mut generation = BootcTmpfilesGeneration :: default ( ) ;
502+
503+ // Read from /usr/lib/tmpfiles.d first (system/package-provided)
504+ let mut result = read_tmpfiles_from_dir ( rootfs, TMPFILESD , & mut generation) ?;
505+
506+ // Read from /etc/tmpfiles.d and merge (user-provided, takes precedence)
507+ let etc_result = read_tmpfiles_from_dir ( rootfs, ETC_TMPFILESD , & mut generation) ?;
508+ // /etc entries override /usr/lib entries for the same path
509+ result. extend ( etc_result) ;
510+
489511 Ok ( ( result, generation) )
490512}
491513
@@ -569,10 +591,18 @@ mod tests {
569591 "# } ,
570592 ) ?;
571593
594+ // Also test /etc/tmpfiles.d (user-provided configs)
595+ rootfs. create_dir_all ( ETC_TMPFILESD ) ?;
596+ rootfs. write (
597+ Path :: new ( ETC_TMPFILESD ) . join ( "user.conf" ) ,
598+ "d /var/lib/user 0755 root root - -\n " ,
599+ ) ?;
600+
572601 // Add test content.
573602 rootfs. ensure_dir_with ( "var/lib/systemd" , & db) ?;
574603 rootfs. ensure_dir_with ( "var/lib/private" , & db) ?;
575604 rootfs. ensure_dir_with ( "var/lib/nfs" , & db) ?;
605+ rootfs. ensure_dir_with ( "var/lib/user" , & db) ?;
576606 let global_rwx = Permissions :: from_mode ( 0o777 ) ;
577607 rootfs. ensure_dir_with ( "var/lib/test/nested" , & db) . unwrap ( ) ;
578608 rootfs. set_permissions ( "var/lib/test" , global_rwx. clone ( ) ) ?;
0 commit comments