1
1
//! Temporary files.
2
2
3
+ #[ cfg( unix) ]
4
+ use cap_std:: fs:: OpenOptionsExt ;
5
+
3
6
use cap_std:: fs:: { Dir , File } ;
4
7
use std:: ffi:: OsStr ;
5
8
use std:: fmt:: Debug ;
@@ -60,7 +63,7 @@ impl<'d> Debug for TempFile<'d> {
60
63
}
61
64
62
65
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
63
- fn new_tempfile_linux ( d : & Dir , anonymous : bool ) -> io:: Result < Option < File > > {
66
+ fn new_tempfile_linux ( d : & Dir , anonymous : bool , mode : Option < u32 > ) -> io:: Result < Option < File > > {
64
67
use rustix:: fs:: { Mode , OFlags } ;
65
68
// openat's API uses WRONLY. There may be use cases for reading too, so let's
66
69
// support it.
@@ -70,7 +73,10 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
70
73
}
71
74
// We default to 0o666, same as main rust when creating new files; this will be
72
75
// modified by umask: <https://github.com/rust-lang/rust/blob/44628f7273052d0bb8e8218518dacab210e1fe0d/library/std/src/sys/unix/fs.rs#L762>
73
- let mode = Mode :: from_raw_mode ( 0o666 ) ;
76
+ let mode = match mode {
77
+ Some ( mode) => Mode :: from ( mode) ,
78
+ None => Mode :: from ( 0o666 ) ,
79
+ } ;
74
80
// Happy path - Linux with O_TMPFILE
75
81
match rustix:: fs:: openat ( d, "." , oflags, mode) {
76
82
Ok ( r) => Ok ( Some ( File :: from ( r) ) ) ,
@@ -100,17 +106,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100
106
/// Create a new temporary file in the target directory, which may or may not
101
107
/// have a (randomly generated) name at this point. If anonymous is specified,
102
108
/// the file will be deleted
103
- fn new_tempfile ( d : & Dir , anonymous : bool ) -> io:: Result < ( File , Option < String > ) > {
109
+ fn new_tempfile (
110
+ d : & Dir ,
111
+ anonymous : bool ,
112
+ #[ cfg( unix) ] mode : Option < u32 > ,
113
+ ) -> io:: Result < ( File , Option < String > ) > {
104
114
// On Linux, try O_TMPFILE
105
115
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
106
- if let Some ( f) = new_tempfile_linux ( d, anonymous) ? {
116
+ if let Some ( f) = new_tempfile_linux ( d, anonymous, mode ) ? {
107
117
return Ok ( ( f, None ) ) ;
108
118
}
109
119
// Otherwise, fall back to just creating a randomly named file.
110
120
let mut opts = cap_std:: fs:: OpenOptions :: new ( ) ;
111
121
opts. read ( true ) ;
112
122
opts. write ( true ) ;
113
123
opts. create_new ( true ) ;
124
+ #[ cfg( unix) ]
125
+ if let Some ( mode) = mode {
126
+ opts. mode ( mode) ;
127
+ }
114
128
let ( f, name) = super :: retry_with_name_ignoring ( io:: ErrorKind :: AlreadyExists , |name| {
115
129
d. open_with ( name, & opts)
116
130
} ) ?;
@@ -125,7 +139,20 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125
139
impl < ' d > TempFile < ' d > {
126
140
/// Create a new temporary file in the provided directory.
127
141
pub fn new ( dir : & ' d Dir ) -> io:: Result < Self > {
128
- let ( fd, name) = new_tempfile ( dir, false ) ?;
142
+ let ( fd, name) = new_tempfile (
143
+ dir,
144
+ false ,
145
+ #[ cfg( unix) ]
146
+ None ,
147
+ ) ?;
148
+ Ok ( Self { dir, fd, name } )
149
+ }
150
+
151
+ /// Create a new temporary file in the provided directory, with the provided mode.
152
+ /// Process umask is taken into account for the actual file mode.
153
+ #[ cfg( unix) ]
154
+ pub fn new_with_mode ( dir : & ' d Dir , mode : u32 ) -> io:: Result < Self > {
155
+ let ( fd, name) = new_tempfile ( dir, false , Some ( mode) ) ?;
129
156
Ok ( Self { dir, fd, name } )
130
157
}
131
158
@@ -134,7 +161,13 @@ impl<'d> TempFile<'d> {
134
161
///
135
162
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136
163
pub fn new_anonymous ( dir : & ' d Dir ) -> io:: Result < File > {
137
- new_tempfile ( dir, true ) . map ( |v| v. 0 )
164
+ new_tempfile (
165
+ dir,
166
+ true ,
167
+ #[ cfg( unix) ]
168
+ None ,
169
+ )
170
+ . map ( |v| v. 0 )
138
171
}
139
172
140
173
/// Get a reference to the underlying file.
@@ -264,13 +297,10 @@ mod test {
264
297
// Test that we created with the right permissions
265
298
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
266
299
{
267
- use cap_std:: fs_utf8:: MetadataExt ;
268
- use rustix:: fs:: Mode ;
300
+ use cap_std:: fs:: MetadataExt ;
269
301
let umask = get_process_umask ( ) ?;
270
- let metadata = tf. as_file ( ) . metadata ( ) . unwrap ( ) ;
271
- let mode = metadata. mode ( ) ;
272
- let mode = Mode :: from_bits_truncate ( mode) ;
273
- assert_eq ! ( 0o666 & !umask, mode. bits( ) & 0o777 ) ;
302
+ let mode = tf. as_file ( ) . metadata ( ) ?. mode ( ) ;
303
+ assert_eq ! ( 0o666 & !umask, mode & 0o777 ) ;
274
304
}
275
305
// And that we can write
276
306
tf. write_all ( b"hello world" ) ?;
@@ -295,6 +325,29 @@ mod test {
295
325
eprintln ! ( "notice: Detected older Windows" ) ;
296
326
}
297
327
328
+ // Test that we can create with 0o000 mode
329
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
330
+ {
331
+ use cap_std:: fs:: MetadataExt ;
332
+ let mut tf = TempFile :: new_with_mode ( & td, 0o000 ) ?;
333
+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o000 ) ;
334
+ tf. write_all ( b"mode 0" ) ?;
335
+ tf. replace ( "testfile" ) ?;
336
+ let metadata = td. metadata ( "testfile" ) ?;
337
+ assert_eq ! ( metadata. len( ) , 6 ) ;
338
+ assert_eq ! ( metadata. mode( ) & 0o777 , 0o000 ) ;
339
+ }
340
+
341
+ // Test that mode is limited by umask
342
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
343
+ {
344
+ use cap_std:: fs:: MetadataExt ;
345
+ let tf = TempFile :: new_with_mode ( & td, 0o777 ) ?;
346
+ let umask = get_process_umask ( ) ?;
347
+ assert_ne ! ( umask & 0o777 , 0o000 ) ;
348
+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o777 & !umask) ;
349
+ }
350
+
298
351
td. close ( )
299
352
}
300
353
}
0 commit comments