1 use std::ffi::OsStr;
2 use std::fs::{self, File, OpenOptions};
3 use std::io;
4 cfg_if::cfg_if! {
5     if #[cfg(not(target_os = "wasi"))] {
6         use std::os::unix::fs::MetadataExt;
7     } else {
8         #[cfg(feature = "nightly")]
9         use std::os::wasi::fs::MetadataExt;
10     }
11 }
12 use crate::util;
13 use std::path::Path;
14 
15 #[cfg(not(target_os = "redox"))]
16 use {
17     rustix::fs::{rename, unlink},
18     std::fs::hard_link,
19 };
20 
create_named( path: &Path, open_options: &mut OpenOptions, #[cfg_attr(target_os = "wasi", allow(unused))] permissions: Option<&std::fs::Permissions>, ) -> io::Result<File>21 pub fn create_named(
22     path: &Path,
23     open_options: &mut OpenOptions,
24     #[cfg_attr(target_os = "wasi", allow(unused))] permissions: Option<&std::fs::Permissions>,
25 ) -> io::Result<File> {
26     open_options.read(true).write(true).create_new(true);
27 
28     #[cfg(not(target_os = "wasi"))]
29     {
30         use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
31         open_options.mode(permissions.map(|p| p.mode()).unwrap_or(0o600));
32     }
33 
34     open_options.open(path)
35 }
36 
create_unlinked(path: &Path) -> io::Result<File>37 fn create_unlinked(path: &Path) -> io::Result<File> {
38     let tmp;
39     // shadow this to decrease the lifetime. It can't live longer than `tmp`.
40     let mut path = path;
41     if !path.is_absolute() {
42         let cur_dir = std::env::current_dir()?;
43         tmp = cur_dir.join(path);
44         path = &tmp;
45     }
46 
47     let f = create_named(path, &mut OpenOptions::new(), None)?;
48     // don't care whether the path has already been unlinked,
49     // but perhaps there are some IO error conditions we should send up?
50     let _ = fs::remove_file(path);
51     Ok(f)
52 }
53 
54 #[cfg(target_os = "linux")]
create(dir: &Path) -> io::Result<File>55 pub fn create(dir: &Path) -> io::Result<File> {
56     use rustix::{fs::OFlags, io::Errno};
57     use std::os::unix::fs::OpenOptionsExt;
58     OpenOptions::new()
59         .read(true)
60         .write(true)
61         .custom_flags(OFlags::TMPFILE.bits() as i32) // do not mix with `create_new(true)`
62         .open(dir)
63         .or_else(|e| {
64             match Errno::from_io_error(&e) {
65                 // These are the three "not supported" error codes for O_TMPFILE.
66                 Some(Errno::OPNOTSUPP) | Some(Errno::ISDIR) | Some(Errno::NOENT) => {
67                     create_unix(dir)
68                 }
69                 _ => Err(e),
70             }
71         })
72 }
73 
74 #[cfg(not(target_os = "linux"))]
create(dir: &Path) -> io::Result<File>75 pub fn create(dir: &Path) -> io::Result<File> {
76     create_unix(dir)
77 }
78 
create_unix(dir: &Path) -> io::Result<File>79 fn create_unix(dir: &Path) -> io::Result<File> {
80     util::create_helper(
81         dir,
82         OsStr::new(".tmp"),
83         OsStr::new(""),
84         crate::NUM_RAND_CHARS,
85         |path| create_unlinked(&path),
86     )
87 }
88 
89 #[cfg(any(not(target_os = "wasi"), feature = "nightly"))]
reopen(file: &File, path: &Path) -> io::Result<File>90 pub fn reopen(file: &File, path: &Path) -> io::Result<File> {
91     let new_file = OpenOptions::new().read(true).write(true).open(path)?;
92     let old_meta = file.metadata()?;
93     let new_meta = new_file.metadata()?;
94     if old_meta.dev() != new_meta.dev() || old_meta.ino() != new_meta.ino() {
95         return Err(io::Error::new(
96             io::ErrorKind::NotFound,
97             "original tempfile has been replaced",
98         ));
99     }
100     Ok(new_file)
101 }
102 
103 #[cfg(all(target_os = "wasi", not(feature = "nightly")))]
reopen(_file: &File, _path: &Path) -> io::Result<File>104 pub fn reopen(_file: &File, _path: &Path) -> io::Result<File> {
105     return Err(io::Error::new(
106         io::ErrorKind::Other,
107         "this operation is supported on WASI only on nightly Rust (with `nightly` feature enabled)",
108     ));
109 }
110 
111 #[cfg(not(target_os = "redox"))]
persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()>112 pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
113     if overwrite {
114         rename(old_path, new_path)?;
115     } else {
116         // On Linux, use `renameat_with` to avoid overwriting an existing name,
117         // if the kernel and the filesystem support it.
118         #[cfg(any(target_os = "android", target_os = "linux"))]
119         {
120             use rustix::fs::{renameat_with, RenameFlags, CWD};
121             use rustix::io::Errno;
122             use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
123 
124             static NOSYS: AtomicBool = AtomicBool::new(false);
125             if !NOSYS.load(Relaxed) {
126                 match renameat_with(CWD, old_path, CWD, new_path, RenameFlags::NOREPLACE) {
127                     Ok(()) => return Ok(()),
128                     Err(Errno::NOSYS) => NOSYS.store(true, Relaxed),
129                     Err(Errno::INVAL) => {}
130                     Err(e) => return Err(e.into()),
131                 }
132             }
133         }
134 
135         // Otherwise use `hard_link` to create the new filesystem name, which
136         // will fail if the name already exists, and then `unlink` to remove
137         // the old name.
138         hard_link(old_path, new_path)?;
139 
140         // Ignore unlink errors. Can we do better?
141         let _ = unlink(old_path);
142     }
143     Ok(())
144 }
145 
146 #[cfg(target_os = "redox")]
persist(_old_path: &Path, _new_path: &Path, _overwrite: bool) -> io::Result<()>147 pub fn persist(_old_path: &Path, _new_path: &Path, _overwrite: bool) -> io::Result<()> {
148     // XXX implement when possible
149     use rustix::io::Errno;
150     Err(Errno::NOSYS.into())
151 }
152 
keep(_: &Path) -> io::Result<()>153 pub fn keep(_: &Path) -> io::Result<()> {
154     Ok(())
155 }
156