99use itertools:: Itertools ;
1010use rustc_hash:: FxHashMap ;
1111use std:: ffi:: { OsStr , OsString } ;
12+ use std:: fs:: File ;
1213#[ cfg( unix) ]
1314use std:: os:: unix:: ffi:: OsStrExt ;
1415use std:: path:: Path ;
16+ use xattr:: FileExt ;
1517
1618/// Copies extended attributes (xattrs) from one file or directory to another.
1719///
@@ -45,6 +47,29 @@ pub fn copy_xattrs_skip_selinux<P: AsRef<Path>>(source: P, dest: P) -> std::io::
4547 Ok ( ( ) )
4648}
4749
50+ /// Copies extended attributes (xattrs) from one file to another using file descriptors.
51+ ///
52+ /// This version avoids TOCTOU (time-of-check to time-of-use) races by operating
53+ /// on open file descriptors rather than paths, ensuring all operations target
54+ /// the same inodes throughout.
55+ ///
56+ /// # Arguments
57+ ///
58+ /// * `source` - A reference to the source file (open file descriptor).
59+ /// * `dest` - A reference to the destination file (open file descriptor).
60+ ///
61+ /// # Returns
62+ ///
63+ /// A result indicating success or failure.
64+ pub fn copy_xattrs_fd ( source : & File , dest : & File ) -> std:: io:: Result < ( ) > {
65+ for attr_name in source. list_xattr ( ) ? {
66+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
67+ dest. set_xattr ( & attr_name, & value) ?;
68+ }
69+ }
70+ Ok ( ( ) )
71+ }
72+
4873/// Retrieves the extended attributes (xattrs) of a given file or directory.
4974///
5075/// # Arguments
@@ -64,6 +89,28 @@ pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<FxHashMap<O
6489 Ok ( attrs)
6590}
6691
92+ /// Retrieves the extended attributes (xattrs) of a given file using a file descriptor.
93+ ///
94+ /// This version avoids TOCTOU races by operating on an open file descriptor
95+ /// rather than a path, ensuring all operations target the same inode.
96+ ///
97+ /// # Arguments
98+ ///
99+ /// * `source` - A reference to the file (open file descriptor).
100+ ///
101+ /// # Returns
102+ ///
103+ /// A result containing a HashMap of attribute names and values, or an error.
104+ pub fn retrieve_xattrs_fd ( source : & File ) -> std:: io:: Result < FxHashMap < OsString , Vec < u8 > > > {
105+ let mut attrs = FxHashMap :: default ( ) ;
106+ for attr_name in source. list_xattr ( ) ? {
107+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
108+ attrs. insert ( attr_name, value) ;
109+ }
110+ }
111+ Ok ( attrs)
112+ }
113+
67114/// Applies extended attributes (xattrs) to a given file or directory.
68115///
69116/// # Arguments
@@ -84,6 +131,26 @@ pub fn apply_xattrs<P: AsRef<Path>>(
84131 Ok ( ( ) )
85132}
86133
134+ /// Applies extended attributes (xattrs) to a given file using a file descriptor.
135+ ///
136+ /// This version avoids TOCTOU races by operating on an open file descriptor
137+ /// rather than a path, ensuring all operations target the same inode.
138+ ///
139+ /// # Arguments
140+ ///
141+ /// * `dest` - A reference to the file (open file descriptor).
142+ /// * `xattrs` - A HashMap containing attribute names and their corresponding values.
143+ ///
144+ /// # Returns
145+ ///
146+ /// A result indicating success or failure.
147+ pub fn apply_xattrs_fd ( dest : & File , xattrs : FxHashMap < OsString , Vec < u8 > > ) -> std:: io:: Result < ( ) > {
148+ for ( attr, value) in xattrs {
149+ dest. set_xattr ( & attr, & value) ?;
150+ }
151+ Ok ( ( ) )
152+ }
153+
87154/// Checks if a file has an Access Control List (ACL) based on its extended attributes.
88155///
89156/// # Arguments
@@ -299,4 +366,59 @@ mod tests {
299366 assert ! ( has_security_cap_acl( & file_path) ) ;
300367 }
301368 }
369+
370+ #[ test]
371+ fn test_copy_xattrs_fd ( ) {
372+ use std:: fs:: OpenOptions ;
373+
374+ let temp_dir = tempdir ( ) . unwrap ( ) ;
375+ let source_path = temp_dir. path ( ) . join ( "source.txt" ) ;
376+ let dest_path = temp_dir. path ( ) . join ( "dest.txt" ) ;
377+
378+ File :: create ( & source_path) . unwrap ( ) ;
379+ File :: create ( & dest_path) . unwrap ( ) ;
380+
381+ let test_attr = "user.test_fd" ;
382+ let test_value = b"test value fd" ;
383+ xattr:: set ( & source_path, test_attr, test_value) . unwrap ( ) ;
384+
385+ let source_file = File :: open ( & source_path) . unwrap ( ) ;
386+ let dest_file = OpenOptions :: new ( ) . write ( true ) . open ( & dest_path) . unwrap ( ) ;
387+
388+ copy_xattrs_fd ( & source_file, & dest_file) . unwrap ( ) ;
389+
390+ let copied_value = xattr:: get ( & dest_path, test_attr) . unwrap ( ) . unwrap ( ) ;
391+ assert_eq ! ( copied_value, test_value) ;
392+ }
393+
394+ #[ test]
395+ fn test_apply_and_retrieve_xattrs_fd ( ) {
396+ use std:: fs:: OpenOptions ;
397+
398+ let temp_dir = tempdir ( ) . unwrap ( ) ;
399+ let file_path = temp_dir. path ( ) . join ( "test_file.txt" ) ;
400+
401+ File :: create ( & file_path) . unwrap ( ) ;
402+
403+ let mut test_xattrs = FxHashMap :: default ( ) ;
404+ let test_attr = "user.test_attr_fd" ;
405+ let test_value = b"test value fd" ;
406+ test_xattrs. insert ( OsString :: from ( test_attr) , test_value. to_vec ( ) ) ;
407+
408+ // Apply using file descriptor
409+ let file = OpenOptions :: new ( ) . write ( true ) . open ( & file_path) . unwrap ( ) ;
410+ apply_xattrs_fd ( & file, test_xattrs) . unwrap ( ) ;
411+ drop ( file) ;
412+
413+ // Retrieve using file descriptor
414+ let file = File :: open ( & file_path) . unwrap ( ) ;
415+ let retrieved_xattrs = retrieve_xattrs_fd ( & file) . unwrap ( ) ;
416+ assert ! ( retrieved_xattrs. contains_key( OsString :: from( test_attr) . as_os_str( ) ) ) ;
417+ assert_eq ! (
418+ retrieved_xattrs
419+ . get( OsString :: from( test_attr) . as_os_str( ) )
420+ . unwrap( ) ,
421+ test_value
422+ ) ;
423+ }
302424}
0 commit comments