88//! Set of functions to manage xattr on files and dirs
99use itertools:: Itertools ;
1010use rustc_hash:: FxHashMap ;
11+ use std:: collections:: HashMap ;
1112use std:: ffi:: { OsStr , OsString } ;
13+ use std:: fs:: File ;
1214#[ cfg( unix) ]
1315use std:: os:: unix:: ffi:: OsStrExt ;
1416use std:: path:: Path ;
17+ use xattr:: FileExt ;
1518
1619/// Copies extended attributes (xattrs) from one file or directory to another.
1720///
@@ -45,6 +48,29 @@ pub fn copy_xattrs_skip_selinux<P: AsRef<Path>>(source: P, dest: P) -> std::io::
4548 Ok ( ( ) )
4649}
4750
51+ /// Copies extended attributes (xattrs) from one file to another using file descriptors.
52+ ///
53+ /// This version avoids TOCTOU (time-of-check to time-of-use) races by operating
54+ /// on open file descriptors rather than paths, ensuring all operations target
55+ /// the same inodes throughout.
56+ ///
57+ /// # Arguments
58+ ///
59+ /// * `source` - A reference to the source file (open file descriptor).
60+ /// * `dest` - A reference to the destination file (open file descriptor).
61+ ///
62+ /// # Returns
63+ ///
64+ /// A result indicating success or failure.
65+ pub fn copy_xattrs_fd ( source : & File , dest : & File ) -> std:: io:: Result < ( ) > {
66+ for attr_name in source. list_xattr ( ) ? {
67+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
68+ dest. set_xattr ( & attr_name, & value) ?;
69+ }
70+ }
71+ Ok ( ( ) )
72+ }
73+
4874/// Retrieves the extended attributes (xattrs) of a given file or directory.
4975///
5076/// # Arguments
@@ -64,6 +90,28 @@ pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<FxHashMap<O
6490 Ok ( attrs)
6591}
6692
93+ /// Retrieves the extended attributes (xattrs) of a given file using a file descriptor.
94+ ///
95+ /// This version avoids TOCTOU races by operating on an open file descriptor
96+ /// rather than a path, ensuring all operations target the same inode.
97+ ///
98+ /// # Arguments
99+ ///
100+ /// * `source` - A reference to the file (open file descriptor).
101+ ///
102+ /// # Returns
103+ ///
104+ /// A result containing a HashMap of attribute names and values, or an error.
105+ pub fn retrieve_xattrs_fd ( source : & File ) -> std:: io:: Result < FxHashMap < OsString , Vec < u8 > > > {
106+ let mut attrs = FxHashMap :: default ( ) ;
107+ for attr_name in source. list_xattr ( ) ? {
108+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
109+ attrs. insert ( attr_name, value) ;
110+ }
111+ }
112+ Ok ( attrs)
113+ }
114+
67115/// Applies extended attributes (xattrs) to a given file or directory.
68116///
69117/// # Arguments
@@ -84,6 +132,26 @@ pub fn apply_xattrs<P: AsRef<Path>>(
84132 Ok ( ( ) )
85133}
86134
135+ /// Applies extended attributes (xattrs) to a given file using a file descriptor.
136+ ///
137+ /// This version avoids TOCTOU races by operating on an open file descriptor
138+ /// rather than a path, ensuring all operations target the same inode.
139+ ///
140+ /// # Arguments
141+ ///
142+ /// * `dest` - A reference to the file (open file descriptor).
143+ /// * `xattrs` - A HashMap containing attribute names and their corresponding values.
144+ ///
145+ /// # Returns
146+ ///
147+ /// A result indicating success or failure.
148+ pub fn apply_xattrs_fd ( dest : & File , xattrs : FxHashMap < OsString , Vec < u8 > > ) -> std:: io:: Result < ( ) > {
149+ for ( attr, value) in xattrs {
150+ dest. set_xattr ( & attr, & value) ?;
151+ }
152+ Ok ( ( ) )
153+ }
154+
87155/// Checks if a file has an Access Control List (ACL) based on its extended attributes.
88156///
89157/// # Arguments
@@ -299,4 +367,59 @@ mod tests {
299367 assert ! ( has_security_cap_acl( & file_path) ) ;
300368 }
301369 }
370+
371+ #[ test]
372+ fn test_copy_xattrs_fd ( ) {
373+ use std:: fs:: OpenOptions ;
374+
375+ let temp_dir = tempdir ( ) . unwrap ( ) ;
376+ let source_path = temp_dir. path ( ) . join ( "source.txt" ) ;
377+ let dest_path = temp_dir. path ( ) . join ( "dest.txt" ) ;
378+
379+ File :: create ( & source_path) . unwrap ( ) ;
380+ File :: create ( & dest_path) . unwrap ( ) ;
381+
382+ let test_attr = "user.test_fd" ;
383+ let test_value = b"test value fd" ;
384+ xattr:: set ( & source_path, test_attr, test_value) . unwrap ( ) ;
385+
386+ let source_file = File :: open ( & source_path) . unwrap ( ) ;
387+ let dest_file = OpenOptions :: new ( ) . write ( true ) . open ( & dest_path) . unwrap ( ) ;
388+
389+ copy_xattrs_fd ( & source_file, & dest_file) . unwrap ( ) ;
390+
391+ let copied_value = xattr:: get ( & dest_path, test_attr) . unwrap ( ) . unwrap ( ) ;
392+ assert_eq ! ( copied_value, test_value) ;
393+ }
394+
395+ #[ test]
396+ fn test_apply_and_retrieve_xattrs_fd ( ) {
397+ use std:: fs:: OpenOptions ;
398+
399+ let temp_dir = tempdir ( ) . unwrap ( ) ;
400+ let file_path = temp_dir. path ( ) . join ( "test_file.txt" ) ;
401+
402+ File :: create ( & file_path) . unwrap ( ) ;
403+
404+ let mut test_xattrs = HashMap :: new ( ) ;
405+ let test_attr = "user.test_attr_fd" ;
406+ let test_value = b"test value fd" ;
407+ test_xattrs. insert ( OsString :: from ( test_attr) , test_value. to_vec ( ) ) ;
408+
409+ // Apply using file descriptor
410+ let file = OpenOptions :: new ( ) . write ( true ) . open ( & file_path) . unwrap ( ) ;
411+ apply_xattrs_fd ( & file, test_xattrs) . unwrap ( ) ;
412+ drop ( file) ;
413+
414+ // Retrieve using file descriptor
415+ let file = File :: open ( & file_path) . unwrap ( ) ;
416+ let retrieved_xattrs = retrieve_xattrs_fd ( & file) . unwrap ( ) ;
417+ assert ! ( retrieved_xattrs. contains_key( OsString :: from( test_attr) . as_os_str( ) ) ) ;
418+ assert_eq ! (
419+ retrieved_xattrs
420+ . get( OsString :: from( test_attr) . as_os_str( ) )
421+ . unwrap( ) ,
422+ test_value
423+ ) ;
424+ }
302425}
0 commit comments