99//! [`cap_std::fs::Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html
1010
1111use cap_primitives:: fs:: FileType ;
12+ #[ cfg( unix) ]
13+ use cap_primitives:: fs:: MetadataExt ;
1214use cap_std:: fs:: { Dir , File , Metadata } ;
1315use cap_tempfile:: cap_std;
1416use cap_tempfile:: cap_std:: fs:: DirEntry ;
@@ -172,25 +174,26 @@ pub trait CapStdExtDirExt {
172174 /// require a higher level wrapper which queries the existing file and gathers such metadata
173175 /// before replacement.
174176 ///
175- /// # Example, including setting permissions
177+ /// # Security
176178 ///
177- /// The closure may also perform other file operations beyond writing, such as changing
178- /// file permissions:
179+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
180+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
181+ /// If this is a concern, you must set a more restrictive umask for your program.
182+ ///
183+ /// # Example
179184 ///
180185 /// ```rust
181186 /// # use std::io;
182187 /// # use std::io::Write;
183188 /// # use cap_tempfile::cap_std;
189+ /// # use cap_std::fs::PermissionsExt;
184190 /// # fn main() -> io::Result<()> {
185191 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
186192 /// use cap_std_ext::prelude::*;
187193 /// let contents = b"hello world\n";
188- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
194+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
195+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
189196 /// f.write_all(contents)?;
190- /// f.flush()?;
191- /// use cap_std::fs::PermissionsExt;
192- /// let perms = cap_std::fs::Permissions::from_mode(0o600);
193- /// f.get_mut().as_file_mut().set_permissions(perms)?;
194197 /// Ok(())
195198 /// })
196199 /// # }
@@ -200,6 +203,7 @@ pub trait CapStdExtDirExt {
200203 fn atomic_replace_with < F , T , E > (
201204 & self ,
202205 destname : impl AsRef < Path > ,
206+ perms : Option < cap_std:: fs:: Permissions > ,
203207 f : F ,
204208 ) -> std:: result:: Result < T , E >
205209 where
@@ -318,26 +322,27 @@ pub trait CapStdExtDirExtUtf8 {
318322 /// require a higher level wrapper which queries the existing file and gathers such metadata
319323 /// before replacement.
320324 ///
321- /// # Example, including setting permissions
325+ /// # Security
326+ ///
327+ /// On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), the TempFile is first created
328+ /// with default permissions (mode 0o666 & ~umask) that can be readeable by everyone.
329+ /// If this is a concern, you must set a more restrictive umask for your program.
322330 ///
323- /// The closure may also perform other file operations beyond writing, such as changing
324- /// file permissions:
331+ /// # Example
325332 ///
326333 /// ```rust
327334 /// # use std::io;
328335 /// # use std::io::Write;
329336 /// # use cap_tempfile::cap_std;
337+ /// # use cap_std::fs::PermissionsExt;
330338 /// # fn main() -> io::Result<()> {
331339 /// # let somedir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
332340 /// # let somedir = cap_std::fs_utf8::Dir::from_cap_std((&*somedir).try_clone()?);
333341 /// use cap_std_ext::prelude::*;
334342 /// let contents = b"hello world\n";
335- /// somedir.atomic_replace_with("somefilename", |f| -> io::Result<_> {
343+ /// let perms = cap_std::fs::Permissions::from_mode(0o600);
344+ /// somedir.atomic_replace_with("somefilename", Some(perms), |f| -> io::Result<_> {
336345 /// f.write_all(contents)?;
337- /// f.flush()?;
338- /// use cap_std::fs::PermissionsExt;
339- /// let perms = cap_std::fs::Permissions::from_mode(0o600);
340- /// f.get_mut().as_file_mut().set_permissions(perms)?;
341346 /// Ok(())
342347 /// })
343348 /// # }
@@ -347,6 +352,7 @@ pub trait CapStdExtDirExtUtf8 {
347352 fn atomic_replace_with < F , T , E > (
348353 & self ,
349354 destname : impl AsRef < Utf8Path > ,
355+ perms : Option < cap_std:: fs:: Permissions > ,
350356 f : F ,
351357 ) -> std:: result:: Result < T , E >
352358 where
@@ -710,6 +716,7 @@ impl CapStdExtDirExt for Dir {
710716 fn atomic_replace_with < F , T , E > (
711717 & self ,
712718 destname : impl AsRef < Path > ,
719+ perms : Option < cap_std:: fs:: Permissions > ,
713720 f : F ,
714721 ) -> std:: result:: Result < T , E >
715722 where
@@ -718,23 +725,47 @@ impl CapStdExtDirExt for Dir {
718725 {
719726 let destname = destname. as_ref ( ) ;
720727 let ( d, name) = subdir_of ( self , destname) ?;
721- let existing_metadata = d. symlink_metadata_optional ( destname) ?;
722- // If the target is already a file, then acquire its mode, which we will preserve by default.
723- // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
724- let existing_perms = existing_metadata
725- . filter ( |m| m. is_file ( ) )
726- . map ( |m| m. permissions ( ) ) ;
728+
729+ // cap_tempfile doesn't support passing permissions on creation.
730+ // https://github.com/bytecodealliance/cap-std/pull/390
727731 let mut t = cap_tempfile:: TempFile :: new ( & d) ?;
728- // Apply the permissions, if we have them
729- if let Some ( existing_perms) = existing_perms {
730- t. as_file_mut ( ) . set_permissions ( existing_perms) ?;
732+ #[ cfg( unix) ]
733+ let t_metadata = t. as_file ( ) . metadata ( ) ?;
734+ #[ cfg( unix) ]
735+ if t_metadata. nlink ( ) > 0 {
736+ let zeroperms = cap_std:: fs:: PermissionsExt :: from_mode ( 0o000 ) ;
737+ t. as_file ( ) . set_permissions ( zeroperms) ?;
731738 }
739+
732740 // We always operate in terms of buffered writes
733741 let mut bufw = std:: io:: BufWriter :: new ( t) ;
734742 // Call the provided closure to generate the file content
735743 let r = f ( & mut bufw) ?;
736744 // Flush the buffer, get the TempFile
737745 t = bufw. into_inner ( ) . map_err ( From :: from) ?;
746+ // Set the file permissions
747+ // This must be done after flushing the application buffer else Linux clears the security related bits:
748+ // https://github.com/torvalds/linux/blob/94305e83eccb3120c921cd3a015cd74731140bac/fs/inode.c#L2360
749+ if let Some ( perms) = perms {
750+ t. as_file ( ) . set_permissions ( perms) ?;
751+ } else {
752+ let existing_metadata = d. symlink_metadata_optional ( destname) ?;
753+ // If the target is already a file, then acquire its mode, which we will preserve by default.
754+ // We don't follow symlinks here for replacement, and so we definitely don't want to pick up its mode.
755+ let existing_perms = existing_metadata
756+ . filter ( |m| m. is_file ( ) )
757+ . map ( |m| m. permissions ( ) ) ;
758+ if let Some ( existing_perms) = existing_perms {
759+ // Apply the existing permissions, if we have them
760+ t. as_file ( ) . set_permissions ( existing_perms) ?;
761+ } else {
762+ // Else restore default permissions
763+ #[ cfg( unix) ]
764+ if t_metadata. nlink ( ) > 0 {
765+ t. as_file ( ) . set_permissions ( t_metadata. permissions ( ) ) ?;
766+ }
767+ }
768+ }
738769 // fsync the TempFile
739770 t. as_file ( ) . sync_all ( ) ?;
740771 // rename the TempFile
@@ -745,7 +776,7 @@ impl CapStdExtDirExt for Dir {
745776 }
746777
747778 fn atomic_write ( & self , destname : impl AsRef < Path > , contents : impl AsRef < [ u8 ] > ) -> Result < ( ) > {
748- self . atomic_replace_with ( destname, |f| f. write_all ( contents. as_ref ( ) ) )
779+ self . atomic_replace_with ( destname, None , |f| f. write_all ( contents. as_ref ( ) ) )
749780 }
750781
751782 fn atomic_write_with_perms (
@@ -754,19 +785,8 @@ impl CapStdExtDirExt for Dir {
754785 contents : impl AsRef < [ u8 ] > ,
755786 perms : cap_std:: fs:: Permissions ,
756787 ) -> Result < ( ) > {
757- self . atomic_replace_with ( destname, |f| -> io:: Result < _ > {
758- // If the user is overriding the permissions, let's make the default be
759- // writable by us but not readable by anyone else, in case it has
760- // secret data.
761- #[ cfg( unix) ]
762- {
763- use cap_std:: fs:: PermissionsExt ;
764- let perms = cap_std:: fs:: Permissions :: from_mode ( 0o600 ) ;
765- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
766- }
788+ self . atomic_replace_with ( destname, Some ( perms) , |f| -> io:: Result < _ > {
767789 f. write_all ( contents. as_ref ( ) ) ?;
768- f. flush ( ) ?;
769- f. get_mut ( ) . as_file_mut ( ) . set_permissions ( perms) ?;
770790 Ok ( ( ) )
771791 } )
772792 }
0 commit comments