Skip to content

Commit c684034

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 c684034

1 file changed

Lines changed: 69 additions & 12 deletions

File tree

cap-tempfile/src/tempfile.rs

Lines changed: 69 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,11 @@ 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(
67+
d: &Dir,
68+
anonymous: bool,
69+
mode: Option<rustix::fs::Mode>,
70+
) -> io::Result<Option<File>> {
6471
use rustix::fs::{Mode, OFlags};
6572
// openat's API uses WRONLY. There may be use cases for reading too, so let's
6673
// support it.
@@ -70,7 +77,10 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
7077
}
7178
// We default to 0o666, same as main rust when creating new files; this will be
7279
// 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);
80+
let mode = match mode {
81+
Some(mode) => mode,
82+
None => Mode::from(0o666),
83+
};
7484
// Happy path - Linux with O_TMPFILE
7585
match rustix::fs::openat(d, ".", oflags, mode) {
7686
Ok(r) => Ok(Some(File::from(r))),
@@ -100,17 +110,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100110
/// Create a new temporary file in the target directory, which may or may not
101111
/// have a (randomly generated) name at this point. If anonymous is specified,
102112
/// the file will be deleted
103-
fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)> {
113+
fn new_tempfile(
114+
d: &Dir,
115+
anonymous: bool,
116+
#[cfg(unix)] mode: Option<rustix::fs::Mode>,
117+
) -> io::Result<(File, Option<String>)> {
104118
// On Linux, try O_TMPFILE
105119
#[cfg(any(target_os = "android", target_os = "linux"))]
106-
if let Some(f) = new_tempfile_linux(d, anonymous)? {
120+
if let Some(f) = new_tempfile_linux(d, anonymous, mode)? {
107121
return Ok((f, None));
108122
}
109123
// Otherwise, fall back to just creating a randomly named file.
110124
let mut opts = cap_std::fs::OpenOptions::new();
111125
opts.read(true);
112126
opts.write(true);
113127
opts.create_new(true);
128+
#[cfg(unix)]
129+
if let Some(mode) = mode {
130+
opts.mode(mode.as_raw_mode());
131+
}
114132
let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| {
115133
d.open_with(name, &opts)
116134
})?;
@@ -125,7 +143,20 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125143
impl<'d> TempFile<'d> {
126144
/// Create a new temporary file in the provided directory.
127145
pub fn new(dir: &'d Dir) -> io::Result<Self> {
128-
let (fd, name) = new_tempfile(dir, false)?;
146+
let (fd, name) = new_tempfile(
147+
dir,
148+
false,
149+
#[cfg(unix)]
150+
None,
151+
)?;
152+
Ok(Self { dir, fd, name })
153+
}
154+
155+
/// Create a new temporary file in the provided directory, with the provided mode.
156+
/// Process umask is taken into account for the actual file mode.
157+
#[cfg(unix)]
158+
pub fn new_with_mode(dir: &'d Dir, mode: u32) -> io::Result<Self> {
159+
let (fd, name) = new_tempfile(dir, false, Some(rustix::fs::Mode::from(mode)))?;
129160
Ok(Self { dir, fd, name })
130161
}
131162

@@ -134,7 +165,13 @@ impl<'d> TempFile<'d> {
134165
///
135166
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136167
pub fn new_anonymous(dir: &'d Dir) -> io::Result<File> {
137-
new_tempfile(dir, true).map(|v| v.0)
168+
new_tempfile(
169+
dir,
170+
true,
171+
#[cfg(unix)]
172+
None,
173+
)
174+
.map(|v| v.0)
138175
}
139176

140177
/// Get a reference to the underlying file.
@@ -264,13 +301,10 @@ mod test {
264301
// Test that we created with the right permissions
265302
#[cfg(any(target_os = "android", target_os = "linux"))]
266303
{
267-
use cap_std::fs_utf8::MetadataExt;
268-
use rustix::fs::Mode;
304+
use cap_std::fs::MetadataExt;
269305
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);
306+
let mode = tf.as_file().metadata()?.mode();
307+
assert_eq!(0o666 & !umask, mode & 0o777);
274308
}
275309
// And that we can write
276310
tf.write_all(b"hello world")?;
@@ -295,6 +329,29 @@ mod test {
295329
eprintln!("notice: Detected older Windows");
296330
}
297331

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

0 commit comments

Comments
 (0)