@@ -5,7 +5,7 @@ use std::fs::{
55 read_dir, remove_dir, remove_file, rename, DirBuilder , File , FileType , OpenOptions , ReadDir ,
66} ;
77use std:: io:: { self , ErrorKind , Read , Seek , SeekFrom , Write } ;
8- use std:: path:: Path ;
8+ use std:: path:: { Path , PathBuf } ;
99use std:: time:: SystemTime ;
1010
1111use log:: trace;
@@ -14,6 +14,7 @@ use rustc_data_structures::fx::FxHashMap;
1414use rustc_middle:: ty:: { self , layout:: LayoutOf } ;
1515use rustc_target:: abi:: { Align , Size } ;
1616
17+ use crate :: shims:: os_str:: bytes_to_os_str;
1718use crate :: * ;
1819use shims:: os_str:: os_str_to_bytes;
1920use 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
0 commit comments