@@ -60,7 +60,7 @@ impl<'d> Debug for TempFile<'d> {
6060}
6161
6262#[ 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 > > {
6464 use rustix:: fs:: { Mode , OFlags } ;
6565 // openat's API uses WRONLY. There may be use cases for reading too, so let's
6666 // support it.
@@ -70,7 +70,8 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
7070 }
7171 // We default to 0o666, same as main rust when creating new files; this will be
7272 // 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+
7475 // Happy path - Linux with O_TMPFILE
7576 match rustix:: fs:: openat ( d, "." , oflags, mode) {
7677 Ok ( r) => Ok ( Some ( File :: from ( r) ) ) ,
@@ -100,17 +101,29 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100101/// Create a new temporary file in the target directory, which may or may not
101102/// have a (randomly generated) name at this point. If anonymous is specified,
102103/// 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 > ) > {
104110 // On Linux, try O_TMPFILE
105111 #[ 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 ) ? {
107113 return Ok ( ( f, None ) ) ;
108114 }
109115 // Otherwise, fall back to just creating a randomly named file.
110116 let mut opts = cap_std:: fs:: OpenOptions :: new ( ) ;
111117 opts. read ( true ) ;
112118 opts. write ( true ) ;
113119 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+ }
114127 let ( f, name) = super :: retry_with_name_ignoring ( io:: ErrorKind :: AlreadyExists , |name| {
115128 d. open_with ( name, & opts)
116129 } ) ?;
@@ -125,7 +138,34 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125138impl < ' d > TempFile < ' d > {
126139 /// Create a new temporary file in the provided directory.
127140 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+ ) ?;
129169 Ok ( Self { dir, fd, name } )
130170 }
131171
@@ -134,7 +174,15 @@ impl<'d> TempFile<'d> {
134174 ///
135175 /// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136176 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 )
138186 }
139187
140188 /// Get a reference to the underlying file.
@@ -147,6 +195,11 @@ impl<'d> TempFile<'d> {
147195 & mut self . fd
148196 }
149197
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+
150203 fn impl_replace ( mut self , destname : & OsStr ) -> io:: Result < ( ) > {
151204 // At this point on Linux if O_TMPFILE is used, we need to give the file a
152205 // temporary name in order to link it into place. There are patches to
@@ -264,13 +317,10 @@ mod test {
264317 // Test that we created with the right permissions
265318 #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
266319 {
267- use cap_std:: fs_utf8:: MetadataExt ;
268- use rustix:: fs:: Mode ;
320+ use cap_std:: fs:: MetadataExt ;
269321 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 ) ;
274324 }
275325 // And that we can write
276326 tf. write_all ( b"hello world" ) ?;
@@ -295,6 +345,29 @@ mod test {
295345 eprintln ! ( "notice: Detected older Windows" ) ;
296346 }
297347
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+
298371 td. close ( )
299372 }
300373}
0 commit comments