tempfile/file/imp/
unix.rs

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