33#![ allow( dead_code) ]
44
55use fn_error_context:: context;
6- use std:: ffi:: OsStr ;
6+ use std:: cell:: RefCell ;
7+ use std:: collections:: BTreeMap ;
8+ use std:: ffi:: { OsStr , OsString } ;
79use std:: io:: BufReader ;
10+ use std:: os:: fd:: { AsFd , AsRawFd } ;
811use std:: os:: unix:: ffi:: OsStrExt ;
9- use std:: path:: PathBuf ;
12+ use std:: path:: { Path , PathBuf } ;
1013use std:: rc:: Rc ;
1114
1215use anyhow:: Context ;
@@ -16,7 +19,7 @@ use cap_std_ext::dirext::CapStdExtDirExt;
1619use composefs:: fsverity:: { FsVerityHashValue , Sha256HashValue , Sha512HashValue } ;
1720use composefs:: generic_tree:: { Directory , Inode , Leaf , LeafContent , Stat } ;
1821use composefs:: tree:: ImageError ;
19- use rustix:: fs:: { AtFlags , Gid , Uid , readlinkat} ;
22+ use rustix:: fs:: { AtFlags , Gid , Uid , XattrFlags , getxattr , listxattr , lsetxattr , readlinkat} ;
2023
2124#[ derive( Debug ) ]
2225struct CustomMetadata {
@@ -33,16 +36,18 @@ impl CustomMetadata {
3336 }
3437}
3538
39+ type Xattrs = RefCell < BTreeMap < Box < OsStr > , Box < [ u8 ] > > > ;
40+
3641struct MyStat ( Stat ) ;
3742
38- impl From < & cap_std:: fs:: Metadata > for MyStat {
39- fn from ( value : & cap_std:: fs:: Metadata ) -> Self {
43+ impl From < ( & cap_std:: fs:: Metadata , Xattrs ) > for MyStat {
44+ fn from ( value : ( & cap_std:: fs:: Metadata , Xattrs ) ) -> Self {
4045 Self ( Stat {
41- st_mode : value. mode ( ) ,
42- st_uid : value. uid ( ) ,
43- st_gid : value. gid ( ) ,
44- st_mtim_sec : value. mtime ( ) ,
45- xattrs : Default :: default ( ) ,
46+ st_mode : value. 0 . mode ( ) ,
47+ st_uid : value. 0 . uid ( ) ,
48+ st_gid : value. 0 . gid ( ) ,
49+ st_mtim_sec : value. 0 . mtime ( ) ,
50+ xattrs : value . 1 ,
4651 } )
4752 }
4853}
@@ -288,6 +293,58 @@ fn compute_diff(
288293 Ok ( diff)
289294}
290295
296+ #[ context( "Collecting xattrs" ) ]
297+ fn collect_xattrs ( etc_fd : & CapStdDir , rel_path : & OsString ) -> anyhow:: Result < Xattrs > {
298+ let link = format ! ( "/proc/self/fd/{}" , etc_fd. as_fd( ) . as_raw_fd( ) ) ;
299+ let path = Path :: new ( & link) . join ( rel_path) ;
300+
301+ const DEFAULT_SIZE : usize = 128 ;
302+
303+ // Start with a guess for size
304+ let mut buf: Vec < u8 > = vec ! [ 0 ; DEFAULT_SIZE ] ;
305+ let size = listxattr ( & path, & mut buf) . context ( "listxattr" ) ?;
306+
307+ if size > DEFAULT_SIZE {
308+ buf = vec ! [ 0 ; size] ;
309+ listxattr ( & path, & mut buf) . context ( "listxattr" ) ?;
310+ }
311+
312+ let xattrs: Xattrs = RefCell :: new ( BTreeMap :: new ( ) ) ;
313+
314+ for name_buf in buf[ ..size]
315+ . split_inclusive ( |& b| b == 0 )
316+ . filter ( |x| !x. is_empty ( ) )
317+ {
318+ let name = OsStr :: from_bytes ( name_buf) ;
319+
320+ let mut buf = vec ! [ 0 ; DEFAULT_SIZE ] ;
321+ let size = getxattr ( & path, name_buf, & mut buf) . context ( "getxattr" ) ?;
322+
323+ if size > DEFAULT_SIZE {
324+ buf = vec ! [ 0 ; size] ;
325+ getxattr ( & path, name_buf, & mut buf) . context ( "getxattr" ) ?;
326+ }
327+
328+ xattrs
329+ . borrow_mut ( )
330+ . insert ( Box :: < OsStr > :: from ( name) , Box :: < [ u8 ] > :: from ( & buf[ ..size] ) ) ;
331+ }
332+
333+ Ok ( xattrs)
334+ }
335+
336+ #[ context( "Copying xattrs" ) ]
337+ fn copy_xattrs ( xattrs : & Xattrs , new_etc_fd : & CapStdDir , file : & PathBuf ) -> anyhow:: Result < ( ) > {
338+ for ( attr, value) in xattrs. borrow ( ) . iter ( ) {
339+ let path = Path :: new ( & format ! ( "/proc/self/fd/{}" , new_etc_fd. as_raw_fd( ) ) ) . join ( file) ;
340+
341+ lsetxattr ( path, attr. as_ref ( ) , value, XattrFlags :: empty ( ) )
342+ . context ( format ! ( "setxattr for {file:?}" ) ) ?;
343+ }
344+
345+ Ok ( ( ) )
346+ }
347+
291348fn recurse_dir ( dir : & CapStdDir , root : & mut Directory < CustomMetadata > ) -> anyhow:: Result < ( ) > {
292349 for entry in dir. entries ( ) ? {
293350 let entry = entry. context ( format ! ( "Getting entry" ) ) ?;
@@ -298,12 +355,14 @@ fn recurse_dir(dir: &CapStdDir, root: &mut Directory<CustomMetadata>) -> anyhow:
298355 . metadata ( )
299356 . context ( format ! ( "Getting metadata for {entry_name:?}" ) ) ?;
300357
358+ let xattrs = collect_xattrs ( & dir, & entry_name) ?;
359+
301360 if entry_type. is_dir ( ) {
302361 let dir = dir
303362 . open_dir ( & entry_name)
304363 . with_context ( || format ! ( "Opening dir {entry_name:?} inside {dir:?}" ) ) ?;
305364
306- let mut directory = Directory :: new ( MyStat :: from ( & entry_meta) . 0 ) ;
365+ let mut directory = Directory :: new ( MyStat :: from ( ( & entry_meta, xattrs ) ) . 0 ) ;
307366
308367 recurse_dir ( & dir, & mut directory) ?;
309368
@@ -328,7 +387,7 @@ fn recurse_dir(dir: &CapStdDir, root: &mut Directory<CustomMetadata>) -> anyhow:
328387 root. insert (
329388 & entry_name,
330389 Inode :: Leaf ( Rc :: new ( Leaf {
331- stat : MyStat :: from ( & entry_meta) . 0 ,
390+ stat : MyStat :: from ( ( & entry_meta, xattrs ) ) . 0 ,
332391 content : LeafContent :: Symlink ( Box :: from ( os_str) ) ,
333392 } ) ) ,
334393 ) ;
@@ -357,7 +416,7 @@ fn recurse_dir(dir: &CapStdDir, root: &mut Directory<CustomMetadata>) -> anyhow:
357416 root. insert (
358417 & entry_name,
359418 Inode :: Leaf ( Rc :: new ( Leaf {
360- stat : MyStat :: from ( & entry_meta) . 0 ,
419+ stat : MyStat :: from ( ( & entry_meta, xattrs ) ) . 0 ,
361420 content : LeafContent :: Regular ( CustomMetadata :: new (
362421 "" . into ( ) ,
363422 Some ( measured_verity) ,
@@ -382,7 +441,7 @@ fn recurse_dir(dir: &CapStdDir, root: &mut Directory<CustomMetadata>) -> anyhow:
382441 root. insert (
383442 & entry_name,
384443 Inode :: Leaf ( Rc :: new ( Leaf {
385- stat : MyStat :: from ( & entry_meta) . 0 ,
444+ stat : MyStat :: from ( ( & entry_meta, xattrs ) ) . 0 ,
386445 content : LeafContent :: Regular ( CustomMetadata :: new ( content_digest, None ) ) ,
387446 } ) ) ,
388447 ) ;
@@ -447,6 +506,8 @@ fn create_dir_with_perms(
447506 )
448507 . context ( format ! ( "chown {dir_name:?}" ) ) ?;
449508
509+ copy_xattrs ( & stat. xattrs , new_etc_fd, dir_name) ?;
510+
450511 Ok ( ( ) )
451512}
452513
@@ -481,6 +542,8 @@ fn handle_leaf(
481542 )
482543 . context ( format ! ( "chown {file:?}" ) ) ?;
483544
545+ copy_xattrs ( & leaf. stat . xattrs , new_etc_fd, file) ?;
546+
484547 "file"
485548 }
486549
@@ -507,6 +570,8 @@ fn handle_leaf(
507570 )
508571 . context ( format ! ( "chown {file:?}" ) ) ?;
509572
573+ copy_xattrs ( & leaf. stat . xattrs , new_etc_fd, file) ?;
574+
510575 "symlink"
511576 }
512577
@@ -864,7 +929,6 @@ mod tests {
864929
865930 let ( pristine_etc_files, current_etc_files, new_etc_files) = traverse_etc ( & p, & c, & n) ?;
866931 let diff = compute_diff ( & pristine_etc_files, & current_etc_files) ?;
867- println ! ( "current_etc_files: {current_etc_files:#?}" ) ;
868932 merge ( & c, & current_etc_files, & n, & new_etc_files, diff) ?;
869933
870934 assert ! ( files_eq( & c, & n, "new_file.txt" ) ?) ;
0 commit comments