Skip to content

Commit 72ee5b6

Browse files
committed
tempfile: add new_with_modes and is_named methods
On filesystems that do not support `O_TMPFILE` (e.g. `vfat`), TempFile::new() automatically falls back to a potentially world readable named temp file. Add TempFile::new_with_modes() to create temp files with a chosen mode directly. We pass 2 modes, one for unnamed tempfile (happy path, created with `O_TMPFILE`), and one for named tempfile. The final mode is still restricted by the process umask.
1 parent 54716a1 commit 72ee5b6

File tree

1 file changed

+85
-12
lines changed

1 file changed

+85
-12
lines changed

cap-tempfile/src/tempfile.rs

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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>)>
125138
impl<'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

Comments
 (0)