33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // spell-checker:ignore getxattr posix_acl_default posix_acl_access
6+ // spell-checker:ignore getxattr posix_acl_default posix_acl_access ENOTSUP EOPNOTSUPP renamer
77
88//! Set of functions to manage xattr on files and dirs
99use itertools:: Itertools ;
@@ -13,20 +13,74 @@ use std::ffi::{OsStr, OsString};
1313use std:: os:: unix:: ffi:: OsStrExt ;
1414use std:: path:: Path ;
1515
16+ /// Returns true if the error is the kernel/filesystem signaling that
17+ /// extended attributes are not supported (`ENOTSUP` / `EOPNOTSUPP`).
18+ /// On Linux these are the same errno; on the BSDs they differ, so we
19+ /// match on either.
20+ #[ cfg( unix) ]
21+ fn is_xattr_unsupported ( err : & std:: io:: Error ) -> bool {
22+ matches ! (
23+ err. raw_os_error( ) ,
24+ Some ( e) if e == libc:: ENOTSUP || e == libc:: EOPNOTSUPP
25+ )
26+ }
27+
28+ #[ cfg( not( unix) ) ]
29+ fn is_xattr_unsupported ( _err : & std:: io:: Error ) -> bool {
30+ false
31+ }
32+
1633/// Copies extended attributes (xattrs) from one file or directory to another.
1734///
35+ /// Returns `Ok(())` if the destination filesystem signals that xattrs are
36+ /// not supported (`ENOTSUP` / `EOPNOTSUPP`), since cross-filesystem moves
37+ /// onto e.g. tmpfs without xattr support are a legitimate scenario. All
38+ /// other errors propagate so the caller can surface (for example)
39+ /// permission failures on `security.*` namespaces.
40+ ///
1841/// # Arguments
1942///
2043/// * `source` - A reference to the source path.
2144/// * `dest` - A reference to the destination path.
22- ///
23- /// # Returns
24- ///
25- /// A result indicating success or failure.
2645pub fn copy_xattrs < P : AsRef < Path > > ( source : P , dest : P ) -> std:: io:: Result < ( ) > {
27- for attr_name in xattr:: list ( & source) ? {
46+ let attrs = match xattr:: list ( & source) {
47+ Ok ( a) => a,
48+ Err ( e) if is_xattr_unsupported ( & e) => return Ok ( ( ) ) ,
49+ Err ( e) => return Err ( e) ,
50+ } ;
51+ for attr_name in attrs {
2852 if let Some ( value) = xattr:: get ( & source, & attr_name) ? {
29- xattr:: set ( & dest, & attr_name, & value) ?;
53+ if let Err ( e) = xattr:: set ( & dest, & attr_name, & value) {
54+ if is_xattr_unsupported ( & e) {
55+ return Ok ( ( ) ) ;
56+ }
57+ return Err ( e) ;
58+ }
59+ }
60+ }
61+ Ok ( ( ) )
62+ }
63+
64+ /// Copies xattrs between two open file descriptors. Pins both inodes so
65+ /// list/get/set calls cannot be redirected by a concurrent renamer, unlike
66+ /// the path-based [`copy_xattrs`]. `ENOTSUP` / `EOPNOTSUPP` is treated as
67+ /// success.
68+ #[ cfg( unix) ]
69+ pub fn copy_xattrs_fd ( source : & std:: fs:: File , dest : & std:: fs:: File ) -> std:: io:: Result < ( ) > {
70+ use xattr:: FileExt ;
71+ let attrs = match source. list_xattr ( ) {
72+ Ok ( a) => a,
73+ Err ( e) if is_xattr_unsupported ( & e) => return Ok ( ( ) ) ,
74+ Err ( e) => return Err ( e) ,
75+ } ;
76+ for attr_name in attrs {
77+ if let Some ( value) = source. get_xattr ( & attr_name) ? {
78+ if let Err ( e) = dest. set_xattr ( & attr_name, & value) {
79+ if is_xattr_unsupported ( & e) {
80+ return Ok ( ( ) ) ;
81+ }
82+ return Err ( e) ;
83+ }
3084 }
3185 }
3286 Ok ( ( ) )
@@ -222,6 +276,35 @@ mod tests {
222276 assert_eq ! ( copied_value, test_value) ;
223277 }
224278
279+ #[ test]
280+ #[ cfg( unix) ]
281+ fn test_copy_xattrs_fd ( ) {
282+ let temp_dir = tempdir ( ) . unwrap ( ) ;
283+ let source_path = temp_dir. path ( ) . join ( "source.txt" ) ;
284+ let dest_path = temp_dir. path ( ) . join ( "dest.txt" ) ;
285+
286+ File :: create ( & source_path) . unwrap ( ) ;
287+ File :: create ( & dest_path) . unwrap ( ) ;
288+
289+ let test_attr = "user.fd_test" ;
290+ let test_value = b"fd value" ;
291+ // Skip silently if the test fs doesn't support user xattrs.
292+ if xattr:: set ( & source_path, test_attr, test_value) . is_err ( ) {
293+ return ;
294+ }
295+
296+ let src = File :: open ( & source_path) . unwrap ( ) ;
297+ let dst = std:: fs:: OpenOptions :: new ( )
298+ . write ( true )
299+ . open ( & dest_path)
300+ . unwrap ( ) ;
301+
302+ copy_xattrs_fd ( & src, & dst) . unwrap ( ) ;
303+
304+ let copied = xattr:: get ( & dest_path, test_attr) . unwrap ( ) . unwrap ( ) ;
305+ assert_eq ! ( copied, test_value) ;
306+ }
307+
225308 #[ test]
226309 fn test_apply_and_retrieve_xattrs ( ) {
227310 let temp_dir = tempdir ( ) . unwrap ( ) ;
0 commit comments