tempfile/file/imp/
unix.rs
1use std::ffi::OsStr;
2use std::fs::{self, File, OpenOptions};
3use std::io;
4
5use crate::util;
6use std::path::Path;
7
8#[cfg(not(target_os = "redox"))]
9use {
10 rustix::fs::{rename, unlink},
11 std::fs::hard_link,
12};
13
14pub fn create_named(
15 path: &Path,
16 open_options: &mut OpenOptions,
17 #[cfg_attr(target_os = "wasi", allow(unused))] permissions: Option<&std::fs::Permissions>,
18) -> io::Result<File> {
19 open_options.read(true).write(true).create_new(true);
20
21 #[cfg(not(target_os = "wasi"))]
22 {
23 use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
24 open_options.mode(permissions.map(|p| p.mode()).unwrap_or(0o600));
25 }
26
27 open_options.open(path)
28}
29
30fn create_unlinked(path: &Path) -> io::Result<File> {
31 let tmp;
32 let mut path = path;
34 if !path.is_absolute() {
35 let cur_dir = std::env::current_dir()?;
36 tmp = cur_dir.join(path);
37 path = &tmp;
38 }
39
40 let f = create_named(path, &mut OpenOptions::new(), None)?;
41 let _ = fs::remove_file(path);
44 Ok(f)
45}
46
47#[cfg(target_os = "linux")]
48pub fn create(dir: &Path) -> io::Result<File> {
49 use rustix::{fs::OFlags, io::Errno};
50 use std::os::unix::fs::OpenOptionsExt;
51 OpenOptions::new()
52 .read(true)
53 .write(true)
54 .custom_flags(OFlags::TMPFILE.bits() as i32) .open(dir)
56 .or_else(|e| {
57 match Errno::from_io_error(&e) {
58 Some(Errno::OPNOTSUPP) | Some(Errno::ISDIR) | Some(Errno::NOENT) => {
60 create_unix(dir)
61 }
62 _ => Err(e),
63 }
64 })
65}
66
67#[cfg(not(target_os = "linux"))]
68pub fn create(dir: &Path) -> io::Result<File> {
69 create_unix(dir)
70}
71
72fn create_unix(dir: &Path) -> io::Result<File> {
73 util::create_helper(
74 dir,
75 OsStr::new(".tmp"),
76 OsStr::new(""),
77 crate::NUM_RAND_CHARS,
78 |path| create_unlinked(&path),
79 )
80}
81
82#[cfg(any(not(target_os = "wasi"), feature = "nightly"))]
83pub fn reopen(file: &File, path: &Path) -> io::Result<File> {
84 #[cfg(not(target_os = "wasi"))]
85 use std::os::unix::fs::MetadataExt;
86 #[cfg(target_os = "wasi")]
87 use std::os::wasi::fs::MetadataExt;
88
89 let new_file = OpenOptions::new().read(true).write(true).open(path)?;
90 let old_meta = file.metadata()?;
91 let new_meta = new_file.metadata()?;
92 if old_meta.dev() != new_meta.dev() || old_meta.ino() != new_meta.ino() {
93 return Err(io::Error::new(
94 io::ErrorKind::NotFound,
95 "original tempfile has been replaced",
96 ));
97 }
98 Ok(new_file)
99}
100
101#[cfg(all(target_os = "wasi", not(feature = "nightly")))]
102pub fn reopen(_file: &File, _path: &Path) -> io::Result<File> {
103 return Err(io::Error::new(
104 io::ErrorKind::Other,
105 "this operation is supported on WASI only on nightly Rust (with `nightly` feature enabled)",
106 ));
107}
108
109#[cfg(not(target_os = "redox"))]
110pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
111 if overwrite {
112 rename(old_path, new_path)?;
113 } else {
114 #[cfg(any(
117 target_os = "android",
118 target_os = "linux",
119 target_os = "macos",
120 target_os = "ios",
121 target_os = "tvos",
122 target_os = "visionos",
123 target_os = "watchos",
124 ))]
125 {
126 use rustix::fs::{renameat_with, RenameFlags, CWD};
127 use rustix::io::Errno;
128 use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
129
130 static NOSYS: AtomicBool = AtomicBool::new(false);
131 if !NOSYS.load(Relaxed) {
132 match renameat_with(CWD, old_path, CWD, new_path, RenameFlags::NOREPLACE) {
133 Ok(()) => return Ok(()),
134 Err(Errno::NOSYS) => NOSYS.store(true, Relaxed),
135 Err(Errno::INVAL) => {}
136 Err(e) => return Err(e.into()),
137 }
138 }
139 }
140
141 hard_link(old_path, new_path)?;
145
146 let _ = unlink(old_path);
148 }
149 Ok(())
150}
151
152#[cfg(target_os = "redox")]
153pub fn persist(_old_path: &Path, _new_path: &Path, _overwrite: bool) -> io::Result<()> {
154 use rustix::io::Errno;
156 Err(Errno::NOSYS.into())
157}
158
159pub fn keep(_: &Path) -> io::Result<()> {
160 Ok(())
161}