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