Skip to content

Commit 0c6abfb

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 0c6abfb

File tree

1 file changed

+65
-12
lines changed

1 file changed

+65
-12
lines changed

cap-tempfile/src/tempfile.rs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Temporary files.
22
3+
#[cfg(unix)]
4+
use cap_std::fs::OpenOptionsExt;
5+
36
use cap_std::fs::{Dir, File};
47
use std::ffi::OsStr;
58
use std::fmt::Debug;
@@ -60,7 +63,7 @@ impl<'d> Debug for TempFile<'d> {
6063
}
6164

6265
#[cfg(any(target_os = "android", target_os = "linux"))]
63-
fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
66+
fn new_tempfile_linux(d: &Dir, anonymous: bool, mode: Option<u32>) -> io::Result<Option<File>> {
6467
use rustix::fs::{Mode, OFlags};
6568
// openat's API uses WRONLY. There may be use cases for reading too, so let's
6669
// support it.
@@ -70,7 +73,10 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
7073
}
7174
// We default to 0o666, same as main rust when creating new files; this will be
7275
// 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);
76+
let mode = match mode {
77+
Some(mode) => Mode::from(mode),
78+
None => Mode::from(0o666),
79+
};
7480
// Happy path - Linux with O_TMPFILE
7581
match rustix::fs::openat(d, ".", oflags, mode) {
7682
Ok(r) => Ok(Some(File::from(r))),
@@ -100,17 +106,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100106
/// Create a new temporary file in the target directory, which may or may not
101107
/// have a (randomly generated) name at this point. If anonymous is specified,
102108
/// the file will be deleted
103-
fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)> {
109+
fn new_tempfile(
110+
d: &Dir,
111+
anonymous: bool,
112+
#[cfg(unix)] mode: Option<u32>,
113+
) -> io::Result<(File, Option<String>)> {
104114
// On Linux, try O_TMPFILE
105115
#[cfg(any(target_os = "android", target_os = "linux"))]
106-
if let Some(f) = new_tempfile_linux(d, anonymous)? {
116+
if let Some(f) = new_tempfile_linux(d, anonymous, mode)? {
107117
return Ok((f, None));
108118
}
109119
// Otherwise, fall back to just creating a randomly named file.
110120
let mut opts = cap_std::fs::OpenOptions::new();
111121
opts.read(true);
112122
opts.write(true);
113123
opts.create_new(true);
124+
#[cfg(unix)]
125+
if let Some(mode) = mode {
126+
opts.mode(mode);
127+
}
114128
let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| {
115129
d.open_with(name, &opts)
116130
})?;
@@ -125,7 +139,20 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125139
impl<'d> TempFile<'d> {
126140
/// Create a new temporary file in the provided directory.
127141
pub fn new(dir: &'d Dir) -> io::Result<Self> {
128-
let (fd, name) = new_tempfile(dir, false)?;
142+
let (fd, name) = new_tempfile(
143+
dir,
144+
false,
145+
#[cfg(unix)]
146+
None,
147+
)?;
148+
Ok(Self { dir, fd, name })
149+
}
150+
151+
/// Create a new temporary file in the provided directory, with the provided mode.
152+
/// Process umask is taken into account for the actual file mode.
153+
#[cfg(unix)]
154+
pub fn new_with_mode(dir: &'d Dir, mode: u32) -> io::Result<Self> {
155+
let (fd, name) = new_tempfile(dir, false, Some(mode))?;
129156
Ok(Self { dir, fd, name })
130157
}
131158

@@ -134,7 +161,13 @@ impl<'d> TempFile<'d> {
134161
///
135162
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136163
pub fn new_anonymous(dir: &'d Dir) -> io::Result<File> {
137-
new_tempfile(dir, true).map(|v| v.0)
164+
new_tempfile(
165+
dir,
166+
true,
167+
#[cfg(unix)]
168+
None,
169+
)
170+
.map(|v| v.0)
138171
}
139172

140173
/// Get a reference to the underlying file.
@@ -264,13 +297,10 @@ mod test {
264297
// Test that we created with the right permissions
265298
#[cfg(any(target_os = "android", target_os = "linux"))]
266299
{
267-
use cap_std::fs_utf8::MetadataExt;
268-
use rustix::fs::Mode;
300+
use cap_std::fs::MetadataExt;
269301
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);
302+
let mode = tf.as_file().metadata()?.mode();
303+
assert_eq!(0o666 & !umask, mode & 0o777);
274304
}
275305
// And that we can write
276306
tf.write_all(b"hello world")?;
@@ -295,6 +325,29 @@ mod test {
295325
eprintln!("notice: Detected older Windows");
296326
}
297327

328+
// Test that we can create with 0o000 mode
329+
#[cfg(any(target_os = "android", target_os = "linux"))]
330+
{
331+
use cap_std::fs::MetadataExt;
332+
let mut tf = TempFile::new_with_mode(&td, 0o000)?;
333+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o000);
334+
tf.write_all(b"mode 0")?;
335+
tf.replace("testfile")?;
336+
let metadata = td.metadata("testfile")?;
337+
assert_eq!(metadata.len(), 6);
338+
assert_eq!(metadata.mode() & 0o777, 0o000);
339+
}
340+
341+
// Test that mode is limited by umask
342+
#[cfg(any(target_os = "android", target_os = "linux"))]
343+
{
344+
use cap_std::fs::MetadataExt;
345+
let tf = TempFile::new_with_mode(&td, 0o777)?;
346+
let umask = get_process_umask()?;
347+
assert_ne!(umask & 0o777, 0o000);
348+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o777 & !umask);
349+
}
350+
298351
td.close()
299352
}
300353
}

0 commit comments

Comments
 (0)