@@ -60,7 +60,7 @@ impl<'d> Debug for TempFile<'d> {
60
60
}
61
61
62
62
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
63
- fn new_tempfile_linux ( d : & Dir , anonymous : bool ) -> io:: Result < Option < File > > {
63
+ fn new_tempfile_linux ( d : & Dir , anonymous : bool , mode : Option < u32 > ) -> io:: Result < Option < File > > {
64
64
use rustix:: fs:: { Mode , OFlags } ;
65
65
// openat's API uses WRONLY. There may be use cases for reading too, so let's
66
66
// support it.
@@ -70,7 +70,8 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
70
70
}
71
71
// We default to 0o666, same as main rust when creating new files; this will be
72
72
// 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 ) ;
73
+ let mode = Mode :: from ( mode. unwrap_or ( 0o666 ) ) ;
74
+
74
75
// Happy path - Linux with O_TMPFILE
75
76
match rustix:: fs:: openat ( d, "." , oflags, mode) {
76
77
Ok ( r) => Ok ( Some ( File :: from ( r) ) ) ,
@@ -100,17 +101,29 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100
101
/// Create a new temporary file in the target directory, which may or may not
101
102
/// have a (randomly generated) name at this point. If anonymous is specified,
102
103
/// the file will be deleted
103
- fn new_tempfile ( d : & Dir , anonymous : bool ) -> io:: Result < ( File , Option < String > ) > {
104
+ fn new_tempfile (
105
+ d : & Dir ,
106
+ anonymous : bool ,
107
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ] unnamed_mode : Option < u32 > ,
108
+ #[ cfg( unix) ] named_mode : Option < u32 > ,
109
+ ) -> io:: Result < ( File , Option < String > ) > {
104
110
// On Linux, try O_TMPFILE
105
111
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
106
- if let Some ( f) = new_tempfile_linux ( d, anonymous) ? {
112
+ if let Some ( f) = new_tempfile_linux ( d, anonymous, unnamed_mode ) ? {
107
113
return Ok ( ( f, None ) ) ;
108
114
}
109
115
// Otherwise, fall back to just creating a randomly named file.
110
116
let mut opts = cap_std:: fs:: OpenOptions :: new ( ) ;
111
117
opts. read ( true ) ;
112
118
opts. write ( true ) ;
113
119
opts. create_new ( true ) ;
120
+ #[ cfg( unix) ]
121
+ {
122
+ use cap_std:: fs:: OpenOptionsExt ;
123
+ if let Some ( mode) = named_mode {
124
+ opts. mode ( mode) ;
125
+ }
126
+ }
114
127
let ( f, name) = super :: retry_with_name_ignoring ( io:: ErrorKind :: AlreadyExists , |name| {
115
128
d. open_with ( name, & opts)
116
129
} ) ?;
@@ -125,7 +138,34 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125
138
impl < ' d > TempFile < ' d > {
126
139
/// Create a new temporary file in the provided directory.
127
140
pub fn new ( dir : & ' d Dir ) -> io:: Result < Self > {
128
- let ( fd, name) = new_tempfile ( dir, false ) ?;
141
+ let ( fd, name) = new_tempfile (
142
+ dir,
143
+ false ,
144
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
145
+ None ,
146
+ #[ cfg( unix) ]
147
+ None ,
148
+ ) ?;
149
+ Ok ( Self { dir, fd, name } )
150
+ }
151
+
152
+ /// Create a new temporary file in the provided directory, with the provided modes.
153
+ /// `unnamed_mode` is used when the file is unnamed (created with `O_TMPFILE`).
154
+ /// `named_mode` is used when the file is named (fallback case).
155
+ /// Process umask is taken into account for the actual file mode.
156
+ #[ cfg( unix) ]
157
+ pub fn new_with_modes (
158
+ dir : & ' d Dir ,
159
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ] unnamed_mode : Option < u32 > ,
160
+ named_mode : Option < u32 > ,
161
+ ) -> io:: Result < Self > {
162
+ let ( fd, name) = new_tempfile (
163
+ dir,
164
+ false ,
165
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
166
+ unnamed_mode,
167
+ named_mode,
168
+ ) ?;
129
169
Ok ( Self { dir, fd, name } )
130
170
}
131
171
@@ -134,7 +174,15 @@ impl<'d> TempFile<'d> {
134
174
///
135
175
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136
176
pub fn new_anonymous ( dir : & ' d Dir ) -> io:: Result < File > {
137
- new_tempfile ( dir, true ) . map ( |v| v. 0 )
177
+ new_tempfile (
178
+ dir,
179
+ true ,
180
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
181
+ None ,
182
+ #[ cfg( unix) ]
183
+ Some ( 0o000 ) ,
184
+ )
185
+ . map ( |v| v. 0 )
138
186
}
139
187
140
188
/// Get a reference to the underlying file.
@@ -147,6 +195,11 @@ impl<'d> TempFile<'d> {
147
195
& mut self . fd
148
196
}
149
197
198
+ /// Returns whether the tempfile is named (visible in the folder)
199
+ pub fn is_named ( & self ) -> bool {
200
+ self . name . is_some ( )
201
+ }
202
+
150
203
fn impl_replace ( mut self , destname : & OsStr ) -> io:: Result < ( ) > {
151
204
// At this point on Linux if O_TMPFILE is used, we need to give the file a
152
205
// temporary name in order to link it into place. There are patches to
@@ -264,13 +317,10 @@ mod test {
264
317
// Test that we created with the right permissions
265
318
#[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
266
319
{
267
- use cap_std:: fs_utf8:: MetadataExt ;
268
- use rustix:: fs:: Mode ;
320
+ use cap_std:: fs:: MetadataExt ;
269
321
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 ) ;
322
+ let mode = tf. as_file ( ) . metadata ( ) ?. mode ( ) ;
323
+ assert_eq ! ( 0o666 & !umask, mode & 0o777 ) ;
274
324
}
275
325
// And that we can write
276
326
tf. write_all ( b"hello world" ) ?;
@@ -295,6 +345,29 @@ mod test {
295
345
eprintln ! ( "notice: Detected older Windows" ) ;
296
346
}
297
347
348
+ // Test that we can create with 0o000 mode
349
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
350
+ {
351
+ use cap_std:: fs:: MetadataExt ;
352
+ let mut tf = TempFile :: new_with_modes ( & td, Some ( 0o000 ) , Some ( 0o000 ) ) ?;
353
+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o000 ) ;
354
+ tf. write_all ( b"mode 0" ) ?;
355
+ tf. replace ( "testfile" ) ?;
356
+ let metadata = td. metadata ( "testfile" ) ?;
357
+ assert_eq ! ( metadata. len( ) , 6 ) ;
358
+ assert_eq ! ( metadata. mode( ) & 0o777 , 0o000 ) ;
359
+ }
360
+
361
+ // Test that mode is limited by umask
362
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
363
+ {
364
+ use cap_std:: fs:: MetadataExt ;
365
+ let tf = TempFile :: new_with_modes ( & td, Some ( 0o777 ) , Some ( 0o777 ) ) ?;
366
+ let umask = get_process_umask ( ) ?;
367
+ assert_ne ! ( umask & 0o777 , 0o000 ) ;
368
+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o777 & !umask) ;
369
+ }
370
+
298
371
td. close ( )
299
372
}
300
373
}
0 commit comments