11//! Temporary files.
22
3+ #[ cfg( unix) ]
4+ use cap_std:: fs:: OpenOptionsExt ;
5+
36use cap_std:: fs:: { Dir , File } ;
47use std:: ffi:: OsStr ;
58use std:: fmt:: Debug ;
@@ -60,7 +63,11 @@ impl<'d> Debug for TempFile<'d> {
6063}
6164
6265#[ 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 (
67+ d : & Dir ,
68+ anonymous : bool ,
69+ mode : Option < rustix:: fs:: Mode > ,
70+ ) -> io:: Result < Option < File > > {
6471 use rustix:: fs:: { Mode , OFlags } ;
6572 // openat's API uses WRONLY. There may be use cases for reading too, so let's
6673 // support it.
@@ -70,7 +77,10 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
7077 }
7178 // We default to 0o666, same as main rust when creating new files; this will be
7279 // 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 ) ;
80+ let mode = match mode {
81+ Some ( mode) => mode,
82+ None => Mode :: from ( 0o666 ) ,
83+ } ;
7484 // Happy path - Linux with O_TMPFILE
7585 match rustix:: fs:: openat ( d, "." , oflags, mode) {
7686 Ok ( r) => Ok ( Some ( File :: from ( r) ) ) ,
@@ -100,17 +110,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100110/// Create a new temporary file in the target directory, which may or may not
101111/// have a (randomly generated) name at this point. If anonymous is specified,
102112/// the file will be deleted
103- fn new_tempfile ( d : & Dir , anonymous : bool ) -> io:: Result < ( File , Option < String > ) > {
113+ fn new_tempfile (
114+ d : & Dir ,
115+ anonymous : bool ,
116+ #[ cfg( unix) ] mode : Option < rustix:: fs:: Mode > ,
117+ ) -> io:: Result < ( File , Option < String > ) > {
104118 // On Linux, try O_TMPFILE
105119 #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
106- if let Some ( f) = new_tempfile_linux ( d, anonymous) ? {
120+ if let Some ( f) = new_tempfile_linux ( d, anonymous, mode ) ? {
107121 return Ok ( ( f, None ) ) ;
108122 }
109123 // Otherwise, fall back to just creating a randomly named file.
110124 let mut opts = cap_std:: fs:: OpenOptions :: new ( ) ;
111125 opts. read ( true ) ;
112126 opts. write ( true ) ;
113127 opts. create_new ( true ) ;
128+ #[ cfg( unix) ]
129+ if let Some ( mode) = mode {
130+ opts. mode ( mode. as_raw_mode ( ) ) ;
131+ }
114132 let ( f, name) = super :: retry_with_name_ignoring ( io:: ErrorKind :: AlreadyExists , |name| {
115133 d. open_with ( name, & opts)
116134 } ) ?;
@@ -125,7 +143,20 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125143impl < ' d > TempFile < ' d > {
126144 /// Create a new temporary file in the provided directory.
127145 pub fn new ( dir : & ' d Dir ) -> io:: Result < Self > {
128- let ( fd, name) = new_tempfile ( dir, false ) ?;
146+ let ( fd, name) = new_tempfile (
147+ dir,
148+ false ,
149+ #[ cfg( unix) ]
150+ None ,
151+ ) ?;
152+ Ok ( Self { dir, fd, name } )
153+ }
154+
155+ /// Create a new temporary file in the provided directory, with the provided mode.
156+ /// Process umask is taken into account for the actual file mode.
157+ #[ cfg( unix) ]
158+ pub fn new_with_mode ( dir : & ' d Dir , mode : u32 ) -> io:: Result < Self > {
159+ let ( fd, name) = new_tempfile ( dir, false , Some ( rustix:: fs:: Mode :: from ( mode) ) ) ?;
129160 Ok ( Self { dir, fd, name } )
130161 }
131162
@@ -134,7 +165,13 @@ impl<'d> TempFile<'d> {
134165 ///
135166 /// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136167 pub fn new_anonymous ( dir : & ' d Dir ) -> io:: Result < File > {
137- new_tempfile ( dir, true ) . map ( |v| v. 0 )
168+ new_tempfile (
169+ dir,
170+ true ,
171+ #[ cfg( unix) ]
172+ None ,
173+ )
174+ . map ( |v| v. 0 )
138175 }
139176
140177 /// Get a reference to the underlying file.
@@ -264,13 +301,10 @@ mod test {
264301 // Test that we created with the right permissions
265302 #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
266303 {
267- use cap_std:: fs_utf8:: MetadataExt ;
268- use rustix:: fs:: Mode ;
304+ use cap_std:: fs:: MetadataExt ;
269305 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 ) ;
306+ let mode = tf. as_file ( ) . metadata ( ) ?. mode ( ) ;
307+ assert_eq ! ( 0o666 & !umask, mode & 0o777 ) ;
274308 }
275309 // And that we can write
276310 tf. write_all ( b"hello world" ) ?;
@@ -295,6 +329,29 @@ mod test {
295329 eprintln ! ( "notice: Detected older Windows" ) ;
296330 }
297331
332+ // Test that we can create with 0o000 mode
333+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
334+ {
335+ use cap_std:: fs:: MetadataExt ;
336+ let mut tf = TempFile :: new_with_mode ( & td, 0o000 ) ?;
337+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o000 ) ;
338+ tf. write_all ( b"mode 0" ) ?;
339+ tf. replace ( "testfile" ) ?;
340+ let metadata = td. metadata ( "testfile" ) ?;
341+ assert_eq ! ( metadata. len( ) , 6 ) ;
342+ assert_eq ! ( metadata. mode( ) & 0o777 , 0o000 ) ;
343+ }
344+
345+ // Test that mode is limited by umask
346+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
347+ {
348+ use cap_std:: fs:: MetadataExt ;
349+ let tf = TempFile :: new_with_mode ( & td, 0o777 ) ?;
350+ let umask = get_process_umask ( ) ?;
351+ assert_ne ! ( umask & 0o777 , 0o000 ) ;
352+ assert_eq ! ( tf. as_file( ) . metadata( ) ?. mode( ) & 0o777 , 0o777 & !umask) ;
353+ }
354+
298355 td. close ( )
299356 }
300357}
0 commit comments