99use itertools:: Itertools ;
1010use std:: collections:: HashMap ;
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///
@@ -32,6 +34,29 @@ pub fn copy_xattrs<P: AsRef<Path>>(source: P, dest: P) -> std::io::Result<()> {
3234 Ok ( ( ) )
3335}
3436
37+ /// Copies extended attributes (xattrs) from one file to another using file descriptors.
38+ ///
39+ /// This version avoids TOCTOU (time-of-check to time-of-use) races by operating
40+ /// on open file descriptors rather than paths, ensuring all operations target
41+ /// the same inodes throughout.
42+ ///
43+ /// # Arguments
44+ ///
45+ /// * `source` - A reference to the source file (open file descriptor).
46+ /// * `dest` - A reference to the destination file (open file descriptor).
47+ ///
48+ /// # Returns
49+ ///
50+ /// A result indicating success or failure.
51+ pub fn copy_xattrs_fd ( source : & File , dest : & File ) -> std:: io:: Result < ( ) > {
52+ for attr_name in source. list_xattr ( ) ? {
53+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
54+ dest. set_xattr ( & attr_name, & value) ?;
55+ }
56+ }
57+ Ok ( ( ) )
58+ }
59+
3560/// Retrieves the extended attributes (xattrs) of a given file or directory.
3661///
3762/// # Arguments
@@ -51,6 +76,28 @@ pub fn retrieve_xattrs<P: AsRef<Path>>(source: P) -> std::io::Result<HashMap<OsS
5176 Ok ( attrs)
5277}
5378
79+ /// Retrieves the extended attributes (xattrs) of a given file using a file descriptor.
80+ ///
81+ /// This version avoids TOCTOU races by operating on an open file descriptor
82+ /// rather than a path, ensuring all operations target the same inode.
83+ ///
84+ /// # Arguments
85+ ///
86+ /// * `source` - A reference to the file (open file descriptor).
87+ ///
88+ /// # Returns
89+ ///
90+ /// A result containing a HashMap of attribute names and values, or an error.
91+ pub fn retrieve_xattrs_fd ( source : & File ) -> std:: io:: Result < HashMap < OsString , Vec < u8 > > > {
92+ let mut attrs = HashMap :: new ( ) ;
93+ for attr_name in source. list_xattr ( ) ? {
94+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
95+ attrs. insert ( attr_name, value) ;
96+ }
97+ }
98+ Ok ( attrs)
99+ }
100+
54101/// Applies extended attributes (xattrs) to a given file or directory.
55102///
56103/// # Arguments
@@ -71,6 +118,26 @@ pub fn apply_xattrs<P: AsRef<Path>>(
71118 Ok ( ( ) )
72119}
73120
121+ /// Applies extended attributes (xattrs) to a given file using a file descriptor.
122+ ///
123+ /// This version avoids TOCTOU races by operating on an open file descriptor
124+ /// rather than a path, ensuring all operations target the same inode.
125+ ///
126+ /// # Arguments
127+ ///
128+ /// * `dest` - A reference to the file (open file descriptor).
129+ /// * `xattrs` - A HashMap containing attribute names and their corresponding values.
130+ ///
131+ /// # Returns
132+ ///
133+ /// A result indicating success or failure.
134+ pub fn apply_xattrs_fd ( dest : & File , xattrs : HashMap < OsString , Vec < u8 > > ) -> std:: io:: Result < ( ) > {
135+ for ( attr, value) in xattrs {
136+ dest. set_xattr ( & attr, & value) ?;
137+ }
138+ Ok ( ( ) )
139+ }
140+
74141/// Checks if a file has an Access Control List (ACL) based on its extended attributes.
75142///
76143/// # Arguments
@@ -286,4 +353,59 @@ mod tests {
286353 assert ! ( has_security_cap_acl( & file_path) ) ;
287354 }
288355 }
356+
357+ #[ test]
358+ fn test_copy_xattrs_fd ( ) {
359+ use std:: fs:: OpenOptions ;
360+
361+ let temp_dir = tempdir ( ) . unwrap ( ) ;
362+ let source_path = temp_dir. path ( ) . join ( "source.txt" ) ;
363+ let dest_path = temp_dir. path ( ) . join ( "dest.txt" ) ;
364+
365+ File :: create ( & source_path) . unwrap ( ) ;
366+ File :: create ( & dest_path) . unwrap ( ) ;
367+
368+ let test_attr = "user.test_fd" ;
369+ let test_value = b"test value fd" ;
370+ xattr:: set ( & source_path, test_attr, test_value) . unwrap ( ) ;
371+
372+ let source_file = File :: open ( & source_path) . unwrap ( ) ;
373+ let dest_file = OpenOptions :: new ( ) . write ( true ) . open ( & dest_path) . unwrap ( ) ;
374+
375+ copy_xattrs_fd ( & source_file, & dest_file) . unwrap ( ) ;
376+
377+ let copied_value = xattr:: get ( & dest_path, test_attr) . unwrap ( ) . unwrap ( ) ;
378+ assert_eq ! ( copied_value, test_value) ;
379+ }
380+
381+ #[ test]
382+ fn test_apply_and_retrieve_xattrs_fd ( ) {
383+ use std:: fs:: OpenOptions ;
384+
385+ let temp_dir = tempdir ( ) . unwrap ( ) ;
386+ let file_path = temp_dir. path ( ) . join ( "test_file.txt" ) ;
387+
388+ File :: create ( & file_path) . unwrap ( ) ;
389+
390+ let mut test_xattrs = HashMap :: new ( ) ;
391+ let test_attr = "user.test_attr_fd" ;
392+ let test_value = b"test value fd" ;
393+ test_xattrs. insert ( OsString :: from ( test_attr) , test_value. to_vec ( ) ) ;
394+
395+ // Apply using file descriptor
396+ let file = OpenOptions :: new ( ) . write ( true ) . open ( & file_path) . unwrap ( ) ;
397+ apply_xattrs_fd ( & file, test_xattrs) . unwrap ( ) ;
398+ drop ( file) ;
399+
400+ // Retrieve using file descriptor
401+ let file = File :: open ( & file_path) . unwrap ( ) ;
402+ let retrieved_xattrs = retrieve_xattrs_fd ( & file) . unwrap ( ) ;
403+ assert ! ( retrieved_xattrs. contains_key( OsString :: from( test_attr) . as_os_str( ) ) ) ;
404+ assert_eq ! (
405+ retrieved_xattrs
406+ . get( OsString :: from( test_attr) . as_os_str( ) )
407+ . unwrap( ) ,
408+ test_value
409+ ) ;
410+ }
289411}
0 commit comments