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