Skip to content

Commit e837bee

Browse files
committed
Add mkstemp shim for unix
1 parent 622963a commit e837bee

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

src/shims/unix/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
163163
// fadvise is only informational, we can ignore it.
164164
this.write_null(dest)?;
165165
}
166+
"mkstemp" => {
167+
let [template] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
168+
let result = this.mkstemp(template)?;
169+
this.write_scalar(Scalar::from_i32(result), dest)?;
170+
}
166171

167172
// Time related shims
168173
"gettimeofday" => {

src/shims/unix/fs.rs

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs::{
44
read_dir, remove_dir, remove_file, rename, DirBuilder, File, FileType, OpenOptions, ReadDir,
55
};
66
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
7-
use std::path::Path;
7+
use std::path::{Path, PathBuf};
88
use std::time::SystemTime;
99

1010
use log::trace;
@@ -17,6 +17,11 @@ use crate::*;
1717
use shims::os_str::os_str_to_bytes;
1818
use shims::time::system_time_to_duration;
1919

20+
// <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L203>
21+
const TEMPFILE_ATTEMPTS_MIN: u32 = 62 * 62 * 62;
22+
// <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L36>
23+
const TEMPFILE_ATTEMPTS_GLIBC: u32 = 238328;
24+
2025
#[derive(Debug)]
2126
struct FileHandle {
2227
file: File,
@@ -1659,6 +1664,146 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
16591664
}
16601665
}
16611666
}
1667+
1668+
fn mkstemp(&mut self, template_op: &OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
1669+
use rand::distributions::{Distribution, Uniform};
1670+
#[cfg(target_family = "unix")]
1671+
use std::ffi::OsStr;
1672+
use std::ffi::OsString;
1673+
#[cfg(target_family = "unix")]
1674+
use std::os::unix::{ffi::OsStrExt, fs::OpenOptionsExt};
1675+
#[cfg(target_family = "windows")]
1676+
use std::os::windows::{
1677+
ffi::{OsStrExt, OsStringExt},
1678+
fs::OpenOptionsExt,
1679+
};
1680+
1681+
let this = self.eval_context_mut();
1682+
1683+
let template = this.read_os_str_from_c_str(this.read_pointer(template_op)?)?.to_os_string();
1684+
1685+
// Reject if isolation is enabled.
1686+
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
1687+
this.reject_in_isolation("`mkstemp`", reject_with)?;
1688+
let eacc = this.eval_libc("EACCES")?;
1689+
this.set_last_error(eacc)?;
1690+
return Ok(-1);
1691+
}
1692+
1693+
#[cfg(target_family = "unix")]
1694+
let opaque_iter = {
1695+
template.as_bytes().iter() // Rust encodes characters differently between platforms.
1696+
};
1697+
1698+
#[cfg(target_family = "windows")]
1699+
let opaque_iter = {
1700+
template.encode_wide() // Rust encodes characters differently between platforms.
1701+
};
1702+
1703+
let pos = opaque_iter
1704+
// Operate on raw bytes ...
1705+
.as_slice()
1706+
// ... in groups of 6 ...
1707+
.windows(6)
1708+
// ... starting from the end ...
1709+
.rev()
1710+
// ... count the groups ...
1711+
.enumerate()
1712+
// ... and see if last group matches the template.
1713+
.position(|(w, x)| w == 0 && matches!(x, b"XXXXXX"));
1714+
1715+
if pos.is_none() {
1716+
let einval = this.eval_libc("EINVAL")?;
1717+
this.set_last_error(einval)?;
1718+
return Ok(-1);
1719+
}
1720+
1721+
let pos = pos.expect("checked above");
1722+
1723+
// At this point we know we have 6 ASCII 'X' characters as a suffix.
1724+
1725+
// From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1726+
let substitutions = &[
1727+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1728+
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1729+
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1730+
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1731+
];
1732+
// Create the random distribution of substitutions.
1733+
let between = Uniform::from(0..substitutions.len());
1734+
let mut rng = this.machine.rng.clone();
1735+
1736+
// The file is opened with specific options, the implementation of which depends on
1737+
// the host OS.
1738+
let mut fopts = OpenOptions::new();
1739+
fopts.read(true).write(true).create_new(true);
1740+
1741+
#[cfg(target_family = "unix")]
1742+
{
1743+
fopts.mode(0o600);
1744+
// Do not allow others to read or modify this file.
1745+
fopts.custom_flags(libc::O_EXCL);
1746+
}
1747+
#[cfg(target_family = "windows")]
1748+
{
1749+
// Do not allow others to read or modify this file.
1750+
fopts.share_mode(0);
1751+
}
1752+
1753+
// If the generated file already exists, we will try again this many times.
1754+
let attempts = if this.eval_context_ref().tcx.sess.target.os == "linux" {
1755+
TEMPFILE_ATTEMPTS_GLIBC
1756+
} else {
1757+
TEMPFILE_ATTEMPTS_MIN
1758+
};
1759+
1760+
for _ in 0..attempts {
1761+
let unique_suffix: OsString = between
1762+
.sample_iter(rng.get_mut())
1763+
.take(6)
1764+
.map(|idx| substitutions[idx])
1765+
.collect::<String>()
1766+
.into();
1767+
1768+
#[cfg(target_family = "unix")]
1769+
let mut base = OsStr::from_bytes(&template.as_bytes()[..pos]).to_os_string();
1770+
1771+
#[cfg(target_family = "windows")]
1772+
let mut base =
1773+
OsString::from_wide(opaque_iter.take(pos).collect::<Vec<u16>>().as_slice());
1774+
1775+
base.push(unique_suffix);
1776+
1777+
let possibly_unique = std::env::temp_dir().join::<PathBuf>(base.into());
1778+
1779+
let file = fopts.open(&possibly_unique);
1780+
1781+
match file {
1782+
Ok(f) => {
1783+
let fh = &mut this.machine.file_handler;
1784+
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1785+
return Ok(fd);
1786+
}
1787+
Err(e) =>
1788+
match e.kind() {
1789+
// If the random file already exists, keep trying.
1790+
ErrorKind::AlreadyExists => continue,
1791+
// Any other errors are returned to the caller.
1792+
_ => {
1793+
// "On error, -1 is returned, and errno is set to
1794+
// indicate the error"
1795+
this.set_last_error_from_io_error(e.kind())?;
1796+
return Ok(-1);
1797+
}
1798+
},
1799+
}
1800+
}
1801+
1802+
// We ran out of attempts to create the file, return an error.
1803+
let eexist = this.eval_libc("EEXIST")?;
1804+
this.set_last_error(eexist)?;
1805+
Ok(-1)
1806+
}
16621807
}
16631808

16641809
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when

tests/pass/libc.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,11 +311,50 @@ fn test_posix_gettimeofday() {
311311
assert_eq!(is_error, -1);
312312
}
313313

314+
fn test_posix_mkstemp() {
315+
use std::ffi::CString;
316+
use std::ffi::OsStr;
317+
use std::fs::File;
318+
use std::os::unix::ffi::OsStrExt;
319+
use std::os::unix::io::FromRawFd;
320+
use std::path::Path;
321+
322+
let valid_template = "fooXXXXXX";
323+
// C needs to own this as `mkstemp(3)` says:
324+
// "Since it will be modified, `template` must not be a string constant, but
325+
// should be declared as a character array."
326+
let ptr = CString::new(valid_template).unwrap().into_raw();
327+
let fd = unsafe { libc::mkstemp(ptr) };
328+
// Take ownership back in Rust to not leak memory.
329+
let slice = unsafe { CString::from_raw(ptr) };
330+
assert!(fd > 0);
331+
let osstr = OsStr::from_bytes(slice.to_bytes());
332+
let path: &Path = osstr.as_ref();
333+
let name = path.file_name().unwrap().to_string_lossy();
334+
assert!(name.contains("foo"));
335+
let file = unsafe { File::from_raw_fd(fd) };
336+
assert!(file.set_len(0).is_ok());
337+
338+
let invalid_templates = vec!["foo", "barXX", "XXXXXXbaz", "whatXXXXXXever", "X"];
339+
for t in invalid_templates {
340+
let ptr = CString::new(t).unwrap().into_raw();
341+
let fd = unsafe { libc::mkstemp(ptr) };
342+
let _ = unsafe { CString::from_raw(ptr) };
343+
// "On error, -1 is returned, and errno is set to
344+
// indicate the error"
345+
assert_eq!(fd, -1);
346+
let e = std::io::Error::last_os_error();
347+
assert_eq!(e.raw_os_error(), Some(libc::EINVAL));
348+
assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
349+
}
350+
}
351+
314352
fn main() {
315353
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
316354
test_posix_fadvise();
317355

318356
test_posix_gettimeofday();
357+
test_posix_mkstemp();
319358

320359
#[cfg(any(target_os = "linux"))]
321360
test_sync_file_range();

0 commit comments

Comments
 (0)