@@ -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