@@ -5,7 +5,7 @@ use std::fs::{
5
5
read_dir, remove_dir, remove_file, rename, DirBuilder , File , FileType , OpenOptions , ReadDir ,
6
6
} ;
7
7
use std:: io:: { self , ErrorKind , Read , Seek , SeekFrom , Write } ;
8
- use std:: path:: Path ;
8
+ use std:: path:: { Path , PathBuf } ;
9
9
use std:: time:: SystemTime ;
10
10
11
11
use log:: trace;
@@ -14,6 +14,7 @@ use rustc_data_structures::fx::FxHashMap;
14
14
use rustc_middle:: ty:: { self , layout:: LayoutOf } ;
15
15
use rustc_target:: abi:: { Align , Size } ;
16
16
17
+ use crate :: shims:: os_str:: bytes_to_os_str;
17
18
use crate :: * ;
18
19
use shims:: os_str:: os_str_to_bytes;
19
20
use shims:: time:: system_time_to_duration;
@@ -1724,6 +1725,133 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1724
1725
}
1725
1726
}
1726
1727
}
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
+ }
1727
1855
}
1728
1856
1729
1857
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
0 commit comments