@@ -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,145 @@ 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 ( ) . 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
+ }
1662
1806
}
1663
1807
1664
1808
/// Extracts the number of seconds and nanoseconds elapsed between `time` and the unix epoch when
0 commit comments