@@ -115,6 +115,11 @@ pub trait OpenatDirExt {
115
115
/// following the link.
116
116
fn update_timestamps < P : openat:: AsPath > ( & self , path : P ) -> io:: Result < ( ) > ;
117
117
118
+ /// Update permissions for the given path (see `fchmodat(2)`).
119
+ ///
120
+ /// If the entry at `path` is a symlink, no action is performed.
121
+ fn set_mode < P : openat:: AsPath > ( & self , path : P , mode : libc:: mode_t ) -> io:: Result < ( ) > ;
122
+
118
123
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
119
124
/// If the target exists, it will be overwritten. The mode bits (permissions) will match, but
120
125
/// owner/group will be derived from the current process. Extended attributes are not
@@ -379,6 +384,36 @@ impl OpenatDirExt for openat::Dir {
379
384
. map_err( map_nix_error) )
380
385
}
381
386
387
+ fn set_mode < P : openat:: AsPath > ( & self , p : P , mode : libc:: mode_t ) -> io:: Result < ( ) > {
388
+ use nix:: sys:: stat:: { fchmodat, FchmodatFlags , Mode } ;
389
+ use openat:: SimpleType ;
390
+
391
+ let path = p
392
+ . to_path ( )
393
+ . ok_or_else ( || io:: Error :: new ( io:: ErrorKind :: Other , "null byte in path" ) ) ?;
394
+
395
+ {
396
+ // NOTE(lucab): `AT_SYMLINK_NOFOLLOW` used to short-circuit to `ENOTSUP`
397
+ // in older glibc versions, so we don't use it. Instead we try to detect
398
+ // any symlink, and skip it.
399
+ let entry_meta = self . metadata ( path. as_ref ( ) ) ?;
400
+ if entry_meta. simple_type ( ) == SimpleType :: Symlink {
401
+ return Ok ( ( ) ) ;
402
+ } ;
403
+ }
404
+
405
+ let perms = Mode :: from_bits_truncate ( mode) ;
406
+ fchmodat (
407
+ Some ( self . as_raw_fd ( ) ) ,
408
+ path. as_ref ( ) ,
409
+ perms,
410
+ FchmodatFlags :: FollowSymlink ,
411
+ )
412
+ . map_err ( map_nix_error) ?;
413
+
414
+ Ok ( ( ) )
415
+ }
416
+
382
417
fn new_file_writer < ' a > ( & ' a self , mode : libc:: mode_t ) -> io:: Result < FileWriter > {
383
418
let ( tmpf, name) = if let Some ( tmpf) = self . new_unnamed_file ( mode) . ok ( ) {
384
419
( tmpf, None )
@@ -859,6 +894,15 @@ mod tests {
859
894
}
860
895
}
861
896
897
+ #[ test]
898
+ fn test_syncfs ( ) {
899
+ let td = tempfile:: tempdir ( ) . unwrap ( ) ;
900
+ let d = openat:: Dir :: open ( td. path ( ) ) . unwrap ( ) ;
901
+ d. ensure_dir_all ( "foo/bar" , 0o755 ) . unwrap ( ) ;
902
+ d. syncfs ( ) . unwrap ( ) ;
903
+ assert_eq ! ( d. exists( "foo/bar" ) . unwrap( ) , true ) ;
904
+ }
905
+
862
906
#[ test]
863
907
fn test_update_timestamps ( ) {
864
908
let td = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -881,6 +925,34 @@ mod tests {
881
925
) ;
882
926
}
883
927
928
+ #[ test]
929
+ fn test_fchmodat ( ) {
930
+ let td = tempfile:: tempdir ( ) . unwrap ( ) ;
931
+ let d = openat:: Dir :: open ( td. path ( ) ) . unwrap ( ) ;
932
+ d. ensure_dir ( "foo" , 0o777 ) . unwrap ( ) ;
933
+ d. set_mode ( "foo" , 0o750 ) . unwrap ( ) ;
934
+ assert_eq ! (
935
+ d. metadata( "foo" ) . unwrap( ) . stat( ) . st_mode & !libc:: S_IFMT ,
936
+ 0o750
937
+ ) ;
938
+ d. set_mode ( "foo" , 0o700 ) . unwrap ( ) ;
939
+ assert_eq ! (
940
+ d. metadata( "foo" ) . unwrap( ) . stat( ) . st_mode & !libc:: S_IFMT ,
941
+ 0o700
942
+ ) ;
943
+
944
+ d. symlink ( "bar" , "foo" ) . unwrap ( ) ;
945
+ d. set_mode ( "bar" , 0o000 ) . unwrap ( ) ;
946
+ assert_ne ! (
947
+ d. metadata( "bar" ) . unwrap( ) . stat( ) . st_mode & !libc:: S_IFMT ,
948
+ 0o000
949
+ ) ;
950
+ assert_ne ! (
951
+ d. metadata( "foo" ) . unwrap( ) . stat( ) . st_mode & !libc:: S_IFMT ,
952
+ 0o000
953
+ ) ;
954
+ }
955
+
884
956
fn find_test_file ( tempdir : & Path ) -> Result < PathBuf > {
885
957
for p in [ "/proc/self/exe" , "/usr/bin/bash" ] . iter ( ) {
886
958
let p = Path :: new ( p) ;
@@ -893,15 +965,6 @@ mod tests {
893
965
Ok ( fallback)
894
966
}
895
967
896
- #[ test]
897
- fn test_syncfs ( ) {
898
- let td = tempfile:: tempdir ( ) . unwrap ( ) ;
899
- let d = openat:: Dir :: open ( td. path ( ) ) . unwrap ( ) ;
900
- d. ensure_dir_all ( "foo/bar" , 0o755 ) . unwrap ( ) ;
901
- d. syncfs ( ) . unwrap ( ) ;
902
- assert_eq ! ( d. exists( "foo/bar" ) . unwrap( ) , true ) ;
903
- }
904
-
905
968
#[ test]
906
969
fn copy_fallback ( ) -> Result < ( ) > {
907
970
use std:: io:: Read ;
0 commit comments