Skip to content

Commit f1e7b23

Browse files
committed
tempfile: add a new_with_mode method
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_mode() to create temp files with a chosen mode directly. The chosen mode is still restricted by the current umask.
1 parent 54716a1 commit f1e7b23

File tree

1 file changed

+48
-13
lines changed

1 file changed

+48
-13
lines changed

cap-tempfile/src/tempfile.rs

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Temporary files.
22
3-
use cap_std::fs::{Dir, File};
3+
use cap_std::fs::{Dir, File, OpenOptionsExt};
44
use std::ffi::OsStr;
55
use std::fmt::Debug;
66
use std::io::{self, Read, Seek, Write};
@@ -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,10 @@ 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 = match mode {
74+
Some(mode) => Mode::from(mode),
75+
None => Mode::from(0o666),
76+
};
7477
// Happy path - Linux with O_TMPFILE
7578
match rustix::fs::openat(d, ".", oflags, mode) {
7679
Ok(r) => Ok(Some(File::from(r))),
@@ -100,17 +103,21 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100103
/// Create a new temporary file in the target directory, which may or may not
101104
/// have a (randomly generated) name at this point. If anonymous is specified,
102105
/// the file will be deleted
103-
fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)> {
106+
fn new_tempfile(d: &Dir, anonymous: bool, mode: Option<u32>) -> io::Result<(File, Option<String>)> {
104107
// On Linux, try O_TMPFILE
105108
#[cfg(any(target_os = "android", target_os = "linux"))]
106-
if let Some(f) = new_tempfile_linux(d, anonymous)? {
109+
if let Some(f) = new_tempfile_linux(d, anonymous, mode)? {
107110
return Ok((f, None));
108111
}
109112
// Otherwise, fall back to just creating a randomly named file.
110113
let mut opts = cap_std::fs::OpenOptions::new();
111114
opts.read(true);
112115
opts.write(true);
113116
opts.create_new(true);
117+
#[cfg(unix)]
118+
if let Some(mode) = mode {
119+
opts.mode(mode);
120+
}
114121
let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| {
115122
d.open_with(name, &opts)
116123
})?;
@@ -125,7 +132,15 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125132
impl<'d> TempFile<'d> {
126133
/// Create a new temporary file in the provided directory.
127134
pub fn new(dir: &'d Dir) -> io::Result<Self> {
128-
let (fd, name) = new_tempfile(dir, false)?;
135+
let (fd, name) = new_tempfile(dir, false, None)?;
136+
Ok(Self { dir, fd, name })
137+
}
138+
139+
/// Create a new temporary file in the provided directory, with the provided mode.
140+
/// Process umask is taken into account for the actual file mode.
141+
#[cfg(unix)]
142+
pub fn new_with_mode(dir: &'d Dir, mode: u32) -> io::Result<Self> {
143+
let (fd, name) = new_tempfile(dir, false, Some(mode))?;
129144
Ok(Self { dir, fd, name })
130145
}
131146

@@ -134,7 +149,7 @@ impl<'d> TempFile<'d> {
134149
///
135150
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136151
pub fn new_anonymous(dir: &'d Dir) -> io::Result<File> {
137-
new_tempfile(dir, true).map(|v| v.0)
152+
new_tempfile(dir, true, None).map(|v| v.0)
138153
}
139154

140155
/// Get a reference to the underlying file.
@@ -264,13 +279,10 @@ mod test {
264279
// Test that we created with the right permissions
265280
#[cfg(any(target_os = "android", target_os = "linux"))]
266281
{
267-
use cap_std::fs_utf8::MetadataExt;
268-
use rustix::fs::Mode;
282+
use cap_std::fs::MetadataExt;
269283
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);
284+
let mode = tf.as_file().metadata()?.mode();
285+
assert_eq!(0o666 & !umask, mode & 0o777);
274286
}
275287
// And that we can write
276288
tf.write_all(b"hello world")?;
@@ -295,6 +307,29 @@ mod test {
295307
eprintln!("notice: Detected older Windows");
296308
}
297309

310+
// Test that we can create with 0o000 mode
311+
#[cfg(any(target_os = "android", target_os = "linux"))]
312+
{
313+
use cap_std::fs::MetadataExt;
314+
let mut tf = TempFile::new_with_mode(&td, 0o000)?;
315+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o000);
316+
tf.write_all(b"mode 0")?;
317+
tf.replace("testfile")?;
318+
let metadata = td.metadata("testfile")?;
319+
assert_eq!(metadata.len(), 6);
320+
assert_eq!(metadata.mode() & 0o777, 0o000);
321+
}
322+
323+
// Test that mode is limited by umask
324+
#[cfg(any(target_os = "android", target_os = "linux"))]
325+
{
326+
use cap_std::fs::MetadataExt;
327+
let tf = TempFile::new_with_mode(&td, 0o777)?;
328+
let umask = get_process_umask()?;
329+
assert_ne!(umask & 0o777, 0o000);
330+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o777 & !umask);
331+
}
332+
298333
td.close()
299334
}
300335
}

0 commit comments

Comments
 (0)