@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
4
4
use filetime:: FileTime ;
5
5
use std:: env;
6
6
use std:: ffi:: { OsStr , OsString } ;
7
- use std:: fs:: { self , File , Metadata , OpenOptions } ;
7
+ use std:: fs:: { self , File , Metadata , OpenOptions , Permissions } ;
8
8
use std:: io;
9
9
use std:: io:: prelude:: * ;
10
10
use std:: iter;
@@ -185,10 +185,34 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
185
185
/// write_atomic uses tempfile::persist to accomplish atomic writes.
186
186
pub fn write_atomic < P : AsRef < Path > , C : AsRef < [ u8 ] > > ( path : P , contents : C ) -> Result < ( ) > {
187
187
let path = path. as_ref ( ) ;
188
+
189
+ // On unix platforms, get the permissions of the original file. Copy only the user/group/other
190
+ // read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600,
191
+ // and we'll set the proper permissions after creating the file.
192
+ #[ cfg( unix) ]
193
+ let perms = path. metadata ( ) . ok ( ) . map ( |meta| {
194
+ use std:: os:: unix:: fs:: PermissionsExt ;
195
+
196
+ // these constants are u16 on macOS
197
+ let mask = u32:: from ( libc:: S_IRWXU | libc:: S_IRWXG | libc:: S_IRWXO ) ;
198
+ let mode = meta. permissions ( ) . mode ( ) & mask;
199
+
200
+ Permissions :: from_mode ( mode)
201
+ } ) ;
202
+
188
203
let mut tmp = TempFileBuilder :: new ( )
189
204
. prefix ( path. file_name ( ) . unwrap ( ) )
190
205
. tempfile_in ( path. parent ( ) . unwrap ( ) ) ?;
191
206
tmp. write_all ( contents. as_ref ( ) ) ?;
207
+
208
+ // On unix platforms, set the permissions on the newly created file. We can use fchmod (called
209
+ // by the std lib; subject to change) which ignores the umask so that the new file has the same
210
+ // permissions as the old file.
211
+ #[ cfg( unix) ]
212
+ if let Some ( perms) = perms {
213
+ tmp. as_file ( ) . set_permissions ( perms) ?;
214
+ }
215
+
192
216
tmp. persist ( path) ?;
193
217
Ok ( ( ) )
194
218
}
@@ -823,6 +847,32 @@ mod tests {
823
847
assert_eq ! ( contents, original_contents) ;
824
848
}
825
849
850
+ #[ test]
851
+ #[ cfg( unix) ]
852
+ fn write_atomic_permissions ( ) {
853
+ use std:: os:: unix:: fs:: PermissionsExt ;
854
+
855
+ let original_perms = std:: fs:: Permissions :: from_mode ( u32:: from (
856
+ libc:: S_IRWXU | libc:: S_IRGRP | libc:: S_IWGRP | libc:: S_IROTH ,
857
+ ) ) ;
858
+
859
+ let tmp = tempfile:: Builder :: new ( ) . tempfile ( ) . unwrap ( ) ;
860
+
861
+ // need to set the permissions after creating the file to avoid umask
862
+ tmp. as_file ( )
863
+ . set_permissions ( original_perms. clone ( ) )
864
+ . unwrap ( ) ;
865
+
866
+ // after this call, the file at `tmp.path()` will not be the same as the file held by `tmp`
867
+ write_atomic ( tmp. path ( ) , "new" ) . unwrap ( ) ;
868
+ assert_eq ! ( std:: fs:: read_to_string( tmp. path( ) ) . unwrap( ) , "new" ) ;
869
+
870
+ let new_perms = std:: fs:: metadata ( tmp. path ( ) ) . unwrap ( ) . permissions ( ) ;
871
+
872
+ let mask = u32:: from ( libc:: S_IRWXU | libc:: S_IRWXG | libc:: S_IRWXO ) ;
873
+ assert_eq ! ( original_perms. mode( ) , new_perms. mode( ) & mask) ;
874
+ }
875
+
826
876
#[ test]
827
877
fn join_paths_lists_paths_on_error ( ) {
828
878
let valid_paths = vec ! [ "/testing/one" , "/testing/two" ] ;
0 commit comments