Skip to content

Commit ebe2c2f

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 ebe2c2f

1 file changed

Lines changed: 56 additions & 13 deletions

File tree

cap-tempfile/src/tempfile.rs

Lines changed: 56 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,11 @@ 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(
64+
d: &Dir,
65+
anonymous: bool,
66+
mode: Option<rustix::fs::RawMode>,
67+
) -> io::Result<Option<File>> {
6468
use rustix::fs::{Mode, OFlags};
6569
// openat's API uses WRONLY. There may be use cases for reading too, so let's
6670
// support it.
@@ -70,7 +74,10 @@ fn new_tempfile_linux(d: &Dir, anonymous: bool) -> io::Result<Option<File>> {
7074
}
7175
// We default to 0o666, same as main rust when creating new files; this will be
7276
// 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);
77+
let mode = match mode {
78+
Some(mode) => Mode::from(mode),
79+
None => Mode::from(0o666),
80+
};
7481
// Happy path - Linux with O_TMPFILE
7582
match rustix::fs::openat(d, ".", oflags, mode) {
7683
Ok(r) => Ok(Some(File::from(r))),
@@ -100,17 +107,25 @@ fn generate_name_in(subdir: &Dir, f: &File) -> io::Result<String> {
100107
/// Create a new temporary file in the target directory, which may or may not
101108
/// have a (randomly generated) name at this point. If anonymous is specified,
102109
/// the file will be deleted
103-
fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)> {
110+
fn new_tempfile(
111+
d: &Dir,
112+
anonymous: bool,
113+
mode: Option<rustix::fs::RawMode>,
114+
) -> io::Result<(File, Option<String>)> {
104115
// On Linux, try O_TMPFILE
105116
#[cfg(any(target_os = "android", target_os = "linux"))]
106-
if let Some(f) = new_tempfile_linux(d, anonymous)? {
117+
if let Some(f) = new_tempfile_linux(d, anonymous, mode)? {
107118
return Ok((f, None));
108119
}
109120
// Otherwise, fall back to just creating a randomly named file.
110121
let mut opts = cap_std::fs::OpenOptions::new();
111122
opts.read(true);
112123
opts.write(true);
113124
opts.create_new(true);
125+
#[cfg(unix)]
126+
if let Some(mode) = mode {
127+
opts.mode(mode);
128+
}
114129
let (f, name) = super::retry_with_name_ignoring(io::ErrorKind::AlreadyExists, |name| {
115130
d.open_with(name, &opts)
116131
})?;
@@ -125,7 +140,15 @@ fn new_tempfile(d: &Dir, anonymous: bool) -> io::Result<(File, Option<String>)>
125140
impl<'d> TempFile<'d> {
126141
/// Create a new temporary file in the provided directory.
127142
pub fn new(dir: &'d Dir) -> io::Result<Self> {
128-
let (fd, name) = new_tempfile(dir, false)?;
143+
let (fd, name) = new_tempfile(dir, false, None)?;
144+
Ok(Self { dir, fd, name })
145+
}
146+
147+
/// Create a new temporary file in the provided directory, with the provided mode.
148+
/// Process umask is taken into account for the actual file mode.
149+
#[cfg(unix)]
150+
pub fn new_with_mode(dir: &'d Dir, mode: rustix::fs::RawMode) -> io::Result<Self> {
151+
let (fd, name) = new_tempfile(dir, false, Some(mode))?;
129152
Ok(Self { dir, fd, name })
130153
}
131154

@@ -134,7 +157,7 @@ impl<'d> TempFile<'d> {
134157
///
135158
/// [`tempfile::tempfile_in`]: https://docs.rs/tempfile/latest/tempfile/fn.tempfile_in.html
136159
pub fn new_anonymous(dir: &'d Dir) -> io::Result<File> {
137-
new_tempfile(dir, true).map(|v| v.0)
160+
new_tempfile(dir, true, None).map(|v| v.0)
138161
}
139162

140163
/// Get a reference to the underlying file.
@@ -264,13 +287,10 @@ mod test {
264287
// Test that we created with the right permissions
265288
#[cfg(any(target_os = "android", target_os = "linux"))]
266289
{
267-
use cap_std::fs_utf8::MetadataExt;
268-
use rustix::fs::Mode;
290+
use cap_std::fs::MetadataExt;
269291
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);
292+
let mode = tf.as_file().metadata()?.mode();
293+
assert_eq!(0o666 & !umask, mode & 0o777);
274294
}
275295
// And that we can write
276296
tf.write_all(b"hello world")?;
@@ -295,6 +315,29 @@ mod test {
295315
eprintln!("notice: Detected older Windows");
296316
}
297317

318+
// Test that we can create with 0o000 mode
319+
#[cfg(any(target_os = "android", target_os = "linux"))]
320+
{
321+
use cap_std::fs::MetadataExt;
322+
let mut tf = TempFile::new_with_mode(&td, 0o000)?;
323+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o000);
324+
tf.write_all(b"mode 0")?;
325+
tf.replace("testfile")?;
326+
let metadata = td.metadata("testfile")?;
327+
assert_eq!(metadata.len(), 6);
328+
assert_eq!(metadata.mode() & 0o777, 0o000);
329+
}
330+
331+
// Test that mode is limited by umask
332+
#[cfg(any(target_os = "android", target_os = "linux"))]
333+
{
334+
use cap_std::fs::MetadataExt;
335+
let tf = TempFile::new_with_mode(&td, 0o777)?;
336+
let umask = get_process_umask()?;
337+
assert_ne!(umask & 0o777, 0o000);
338+
assert_eq!(tf.as_file().metadata()?.mode() & 0o777, 0o777 & !umask);
339+
}
340+
298341
td.close()
299342
}
300343
}

0 commit comments

Comments
 (0)