Skip to content

Commit 242819e

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 242819e

File tree

1 file changed

+63
-12
lines changed

1 file changed

+63
-12
lines changed

cap-tempfile/src/tempfile.rs

Lines changed: 63 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,8 @@ 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 = Mode::from(mode.unwrap_or(0o666));
77+
7478
// Happy path - Linux with O_TMPFILE
7579
match rustix::fs::openat(d, ".", oflags, mode) {
7680
Ok(r) => Ok(Some(File::from(r))),
@@ -100,17 +104,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100104
/// Create a new temporary file in the target directory, which may or may not
101105
/// have a (randomly generated) name at this point. If anonymous is specified,
102106
/// the file will be deleted
103-
fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)> {
107+
fn new_tempfile(
108+
d: &Dir,
109+
anonymous: bool,
110+
#[cfg(unix)] mode: Option<u32>,
111+
) -> io::Result<(File, Option<String>)> {
104112
// On Linux, try O_TMPFILE
105113
#[cfg(any(target_os = "android", target_os = "linux"))]
106-
if let Some(f) = new_tempfile_linux(d, anonymous)? {
114+
if let Some(f) = new_tempfile_linux(d, anonymous, mode)? {
107115
return Ok((f, None));
108116
}
109117
// Otherwise, fall back to just creating a randomly named file.
110118
let mut opts = cap_std::fs::OpenOptions::new();
111119
opts.read(true);
112120
opts.write(true);
113121
opts.create_new(true);
122+
#[cfg(unix)]
123+
if let Some(mode) = mode {
124+
opts.mode(mode);
125+
}
114126
let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| {
115127
d.open_with(name, &opts)
116128
})?;
@@ -125,7 +137,20 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125137
impl<'d> TempFile<'d> {
126138
/// Create a new temporary file in the provided directory.
127139
pub fn new(dir: &'d Dir) -> io::Result<Self> {
128-
let (fd, name) = new_tempfile(dir, false)?;
140+
let (fd, name) = new_tempfile(
141+
dir,
142+
false,
143+
#[cfg(unix)]
144+
None,
145+
)?;
146+
Ok(Self { dir, fd, name })
147+
}
148+
149+
/// Create a new temporary file in the provided directory, with the provided mode.
150+
/// Process umask is taken into account for the actual file mode.
151+
#[cfg(unix)]
152+
pub fn new_with_mode(dir: &'d Dir, mode: u32) -> io::Result<Self> {
153+
let (fd, name) = new_tempfile(dir, false, Some(mode))?;
129154
Ok(Self { dir, fd, name })
130155
}
131156

@@ -134,7 +159,13 @@ impl<'d> TempFile<'d> {
134159
///
135160
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136161
pub fn new_anonymous(dir: &'d Dir) -> io::Result<File> {
137-
new_tempfile(dir, true).map(|v| v.0)
162+
new_tempfile(
163+
dir,
164+
true,
165+
#[cfg(unix)]
166+
None,
167+
)
168+
.map(|v| v.0)
138169
}
139170

140171
/// Get a reference to the underlying file.
@@ -264,13 +295,10 @@ mod test {
264295
// Test that we created with the right permissions
265296
#[cfg(any(target_os = "android", target_os = "linux"))]
266297
{
267-
use cap_std::fs_utf8::MetadataExt;
268-
use rustix::fs::Mode;
298+
use cap_std::fs::MetadataExt;
269299
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);
300+
let mode = tf.as_file().metadata()?.mode();
301+
assert_eq!(0o666 & !umask, mode & 0o777);
274302
}
275303
// And that we can write
276304
tf.write_all(b"hello world")?;
@@ -295,6 +323,29 @@ mod test {
295323
eprintln!("notice: Detected older Windows");
296324
}
297325

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

0 commit comments

Comments
 (0)