Skip to content

Commit c24c638

Browse files
committed
Auto merge of rust-lang#2346 - LegNeato:mkstemp, r=RalfJung
Add `mkstemp` shim for unix
2 parents 9f4612a + b29e7b8 commit c24c638

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

src/shims/unix/foreign_items.rs

+5
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
166166
let result = this.realpath(path, resolved_path)?;
167167
this.write_pointer(result, dest)?;
168168
}
169+
"mkstemp" => {
170+
let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
171+
let result = this.mkstemp(template)?;
172+
this.write_scalar(Scalar::from_i32(result), dest)?;
173+
}
169174

170175
// Time related shims
171176
"gettimeofday" => {

src/shims/unix/fs.rs

+129-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::fs::{
55
read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
66
};
77
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
8-
use std::path::Path;
8+
use std::path::{Path, PathBuf};
99
use std::time::SystemTime;
1010

1111
use log::trace;
@@ -14,6 +14,7 @@ use rustc_data_structures::fx::FxHashMap;
1414
use rustc_middle::ty::{self, layout::LayoutOf};
1515
use rustc_target::abi::{Align, Size};
1616

17+
use crate::shims::os_str::bytes_to_os_str;
1718
use crate::*;
1819
use shims::os_str::os_str_to_bytes;
1920
use shims::time::system_time_to_duration;
@@ -1724,6 +1725,133 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
17241725
}
17251726
}
17261727
}
1728+
fn mkstemp(&mut self, template_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
1729+
use rand::seq::SliceRandom;
1730+
1731+
// POSIX defines the template string.
1732+
const TEMPFILE_TEMPLATE_STR: &str = "XXXXXX";
1733+
1734+
let this = self.eval_context_mut();
1735+
this.assert_target_os_is_unix("mkstemp");
1736+
1737+
// POSIX defines the maximum number of attempts before failure.
1738+
//
1739+
// `mkstemp()` relies on `tmpnam()` which in turn relies on `TMP_MAX`.
1740+
// POSIX says this about `TMP_MAX`:
1741+
// * Minimum number of unique filenames generated by `tmpnam()`.
1742+
// * Maximum number of times an application can call `tmpnam()` reliably.
1743+
// * The value of `TMP_MAX` is at least 25.
1744+
// * On XSI-conformant systems, the value of `TMP_MAX` is at least 10000.
1745+
// See <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>.
1746+
let max_attempts = this.eval_libc("TMP_MAX")?.to_u32()?;
1747+
1748+
// Get the raw bytes from the template -- as a byte slice, this is a string in the target
1749+
// (and the target is unix, so a byte slice is the right representation).
1750+
let template_ptr = this.read_pointer(template_op)?;
1751+
let mut template = this.eval_context_ref().read_c_str(template_ptr)?.to_owned();
1752+
let template_bytes = template.as_mut_slice();
1753+
1754+
// Reject if isolation is enabled.
1755+
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1756+
this.reject_in_isolation("`mkstemp`", reject_with)?;
1757+
let eacc = this.eval_libc("EACCES")?;
1758+
this.set_last_error(eacc)?;
1759+
return Ok(-1);
1760+
}
1761+
1762+
// Get the bytes of the suffix we expect in _target_ encoding.
1763+
let suffix_bytes = TEMPFILE_TEMPLATE_STR.as_bytes();
1764+
1765+
// At this point we have one `&[u8]` that represents the template and one `&[u8]`
1766+
// that represents the expected suffix.
1767+
1768+
// Now we figure out the index of the slice we expect to contain the suffix.
1769+
let start_pos = template_bytes.len().saturating_sub(suffix_bytes.len());
1770+
let end_pos = template_bytes.len();
1771+
let last_six_char_bytes = &template_bytes[start_pos..end_pos];
1772+
1773+
// If we don't find the suffix, it is an error.
1774+
if last_six_char_bytes != suffix_bytes {
1775+
let einval = this.eval_libc("EINVAL")?;
1776+
this.set_last_error(einval)?;
1777+
return Ok(-1);
1778+
}
1779+
1780+
// At this point we know we have 6 ASCII 'X' characters as a suffix.
1781+
1782+
// From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1783+
const SUBSTITUTIONS: &[char; 62] = &[
1784+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1785+
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1786+
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1787+
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1788+
];
1789+
1790+
// The file is opened with specific options, which Rust does not expose in a portable way.
1791+
// So we use specific APIs depending on the host OS.
1792+
let mut fopts = OpenOptions::new();
1793+
fopts.read(true).write(true).create_new(true);
1794+
1795+
#[cfg(unix)]
1796+
{
1797+
use std::os::unix::fs::OpenOptionsExt;
1798+
fopts.mode(0o600);
1799+
// Do not allow others to read or modify this file.
1800+
fopts.custom_flags(libc::O_EXCL);
1801+
}
1802+
#[cfg(windows)]
1803+
{
1804+
use std::os::windows::fs::OpenOptionsExt;
1805+
// Do not allow others to read or modify this file.
1806+
fopts.share_mode(0);
1807+
}
1808+
1809+
// If the generated file already exists, we will try again `max_attempts` many times.
1810+
for _ in 0..max_attempts {
1811+
let rng = this.machine.rng.get_mut();
1812+
1813+
// Generate a random unique suffix.
1814+
let unique_suffix = SUBSTITUTIONS.choose_multiple(rng, 6).collect::<String>();
1815+
1816+
// Replace the template string with the random string.
1817+
template_bytes[start_pos..end_pos].copy_from_slice(unique_suffix.as_bytes());
1818+
1819+
// Write the modified template back to the passed in pointer to maintain POSIX semantics.
1820+
this.write_bytes_ptr(template_ptr, template_bytes.iter().copied())?;
1821+
1822+
// To actually open the file, turn this into a host OsString.
1823+
let p = bytes_to_os_str(template_bytes)?.to_os_string();
1824+
1825+
let possibly_unique = std::env::temp_dir().join::<PathBuf>(p.into());
1826+
1827+
let file = fopts.open(&possibly_unique);
1828+
1829+
match file {
1830+
Ok(f) => {
1831+
let fh = &mut this.machine.file_handler;
1832+
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1833+
return Ok(fd);
1834+
}
1835+
Err(e) =>
1836+
match e.kind() {
1837+
// If the random file already exists, keep trying.
1838+
ErrorKind::AlreadyExists => continue,
1839+
// Any other errors are returned to the caller.
1840+
_ => {
1841+
// "On error, -1 is returned, and errno is set to
1842+
// indicate the error"
1843+
this.set_last_error_from_io_error(e.kind())?;
1844+
return Ok(-1);
1845+
}
1846+
},
1847+
}
1848+
}
1849+
1850+
// We ran out of attempts to create the file, return an error.
1851+
let eexist = this.eval_libc("EEXIST")?;
1852+
this.set_last_error(eexist)?;
1853+
Ok(-1)
1854+
}
17271855
}
17281856

17291857
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ignore-target-windows: No libc on Windows
2+
//@compile-flags: -Zmiri-disable-isolation
3+
4+
#![feature(rustc_private)]
5+
6+
fn main() {
7+
test_mkstemp_immutable_arg();
8+
}
9+
10+
fn test_mkstemp_immutable_arg() {
11+
let s: *mut libc::c_char = b"fooXXXXXX\0" as *const _ as *mut _;
12+
let _fd = unsafe { libc::mkstemp(s) }; //~ ERROR: Undefined Behavior: writing to alloc1 which is read-only
13+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: writing to ALLOC which is read-only
2+
--> $DIR/mkstemp_immutable_arg.rs:LL:CC
3+
|
4+
LL | let _fd = unsafe { libc::mkstemp(s) };
5+
| ^^^^^^^^^^^^^^^^ writing to ALLOC which is read-only
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: backtrace:
10+
= note: inside `test_mkstemp_immutable_arg` at $DIR/mkstemp_immutable_arg.rs:LL:CC
11+
note: inside `main` at $DIR/mkstemp_immutable_arg.rs:LL:CC
12+
--> $DIR/mkstemp_immutable_arg.rs:LL:CC
13+
|
14+
LL | test_mkstemp_immutable_arg();
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to previous error
20+

tests/pass/libc.rs

+45
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,56 @@ fn test_isatty() {
407407
}
408408
}
409409

410+
fn test_posix_mkstemp() {
411+
use std::ffi::CString;
412+
use std::ffi::OsStr;
413+
use std::os::unix::ffi::OsStrExt;
414+
use std::os::unix::io::FromRawFd;
415+
use std::path::Path;
416+
417+
let valid_template = "fooXXXXXX";
418+
// C needs to own this as `mkstemp(3)` says:
419+
// "Since it will be modified, `template` must not be a string constant, but
420+
// should be declared as a character array."
421+
// There seems to be no `as_mut_ptr` on `CString` so we need to use `into_raw`.
422+
let ptr = CString::new(valid_template).unwrap().into_raw();
423+
let fd = unsafe { libc::mkstemp(ptr) };
424+
// Take ownership back in Rust to not leak memory.
425+
let slice = unsafe { CString::from_raw(ptr) };
426+
assert!(fd > 0);
427+
let osstr = OsStr::from_bytes(slice.to_bytes());
428+
let path: &Path = osstr.as_ref();
429+
let name = path.file_name().unwrap().to_string_lossy();
430+
assert!(name.ne("fooXXXXXX"));
431+
assert!(name.starts_with("foo"));
432+
assert_eq!(name.len(), 9);
433+
assert_eq!(
434+
name.chars().skip(3).filter(char::is_ascii_alphanumeric).collect::<Vec<char>>().len(),
435+
6
436+
);
437+
let file = unsafe { File::from_raw_fd(fd) };
438+
assert!(file.set_len(0).is_ok());
439+
440+
let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"];
441+
for t in invalid_templates {
442+
let ptr = CString::new(t).unwrap().into_raw();
443+
let fd = unsafe { libc::mkstemp(ptr) };
444+
let _ = unsafe { CString::from_raw(ptr) };
445+
// "On error, -1 is returned, and errno is set to
446+
// indicate the error"
447+
assert_eq!(fd, -1);
448+
let e = std::io::Error::last_os_error();
449+
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
450+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
451+
}
452+
}
453+
410454
fn main() {
411455
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
412456
test_posix_fadvise();
413457

414458
test_posix_gettimeofday();
459+
test_posix_mkstemp();
415460

416461
test_posix_realpath_alloc();
417462
test_posix_realpath_noalloc();

0 commit comments

Comments
 (0)