Skip to content

Commit 06e581a

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

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-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: 145 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,145 @@ 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().as_slice()
1696+
// Rust encodes characters differently between platforms.
1697+
};
1698+
1699+
#[cfg(target_family = "windows")]
1700+
let opaque_iter = {
1701+
template.encode_wide() // Rust encodes characters differently between platforms.
1702+
};
1703+
1704+
let pos = opaque_iter
1705+
// Operate on groups of 6 ...
1706+
.windows(6)
1707+
// ... starting from the end ...
1708+
.rev()
1709+
// ... count the groups ...
1710+
.enumerate()
1711+
// ... and see if last group matches the template.
1712+
.position(|(w, x)| w == 0 && matches!(x, b"XXXXXX"));
1713+
1714+
if pos.is_none() {
1715+
let einval = this.eval_libc("EINVAL")?;
1716+
this.set_last_error(einval)?;
1717+
return Ok(-1);
1718+
}
1719+
1720+
let pos = pos.expect("checked above");
1721+
1722+
// At this point we know we have 6 ASCII 'X' characters as a suffix.
1723+
1724+
// From <https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/sysdeps/posix/tempname.c#L175>
1725+
let substitutions = &[
1726+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
1727+
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
1728+
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
1729+
'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
1730+
];
1731+
// Create the random distribution of substitutions.
1732+
let between = Uniform::from(0..substitutions.len());
1733+
let mut rng = this.machine.rng.clone();
1734+
1735+
// The file is opened with specific options, the implementation of which depends on
1736+
// the host OS.
1737+
let mut fopts = OpenOptions::new();
1738+
fopts.read(true).write(true).create_new(true);
1739+
1740+
#[cfg(target_family = "unix")]
1741+
{
1742+
fopts.mode(0o600);
1743+
// Do not allow others to read or modify this file.
1744+
fopts.custom_flags(libc::O_EXCL);
1745+
}
1746+
#[cfg(target_family = "windows")]
1747+
{
1748+
// Do not allow others to read or modify this file.
1749+
fopts.share_mode(0);
1750+
}
1751+
1752+
// If the generated file already exists, we will try again this many times.
1753+
let attempts = if this.eval_context_ref().tcx.sess.target.os == "linux" {
1754+
TEMPFILE_ATTEMPTS_GLIBC
1755+
} else {
1756+
TEMPFILE_ATTEMPTS_MIN
1757+
};
1758+
1759+
for _ in 0..attempts {
1760+
let unique_suffix: OsString = between
1761+
.sample_iter(rng.get_mut())
1762+
.take(6)
1763+
.map(|idx| substitutions[idx])
1764+
.collect::<String>()
1765+
.into();
1766+
1767+
#[cfg(target_family = "unix")]
1768+
let mut base = OsStr::from_bytes(&template.as_bytes()[..pos]).to_os_string();
1769+
1770+
#[cfg(target_family = "windows")]
1771+
let mut base =
1772+
OsString::from_wide(opaque_iter.take(pos).collect::<Vec<u16>>().as_slice());
1773+
1774+
base.push(unique_suffix);
1775+
1776+
let possibly_unique = std::env::temp_dir().join::<PathBuf>(base.into());
1777+
1778+
let file = fopts.open(&possibly_unique);
1779+
1780+
match file {
1781+
Ok(f) => {
1782+
let fh = &mut this.machine.file_handler;
1783+
let fd = fh.insert_fd(Box::new(FileHandle { file: f, writable: true }));
1784+
return Ok(fd);
1785+
}
1786+
Err(e) =>
1787+
match e.kind() {
1788+
// If the random file already exists, keep trying.
1789+
ErrorKind::AlreadyExists => continue,
1790+
// Any other errors are returned to the caller.
1791+
_ => {
1792+
// "On error, -1 is returned, and errno is set to
1793+
// indicate the error"
1794+
this.set_last_error_from_io_error(e.kind())?;
1795+
return Ok(-1);
1796+
}
1797+
},
1798+
}
1799+
}
1800+
1801+
// We ran out of attempts to create the file, return an error.
1802+
let eexist = this.eval_libc("EEXIST")?;
1803+
this.set_last_error(eexist)?;
1804+
Ok(-1)
1805+
}
16621806
}
16631807

16641808
/// 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)