@@ -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;
@@ -17,6 +17,11 @@ use crate::*;
17
17
use shims:: os_str:: os_str_to_bytes;
18
18
use shims:: time:: system_time_to_duration;
19
19
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
+
20
25
#[ derive( Debug ) ]
21
26
struct FileHandle {
22
27
file : File ,
@@ -1659,6 +1664,146 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
1659
1664
}
1660
1665
}
1661
1666
}
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
+ }
1662
1807
}
1663
1808
1664
1809
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
0 commit comments