tempfile/file/imp/
unix.rs

1use std::env;
2use std::ffi::{CString, OsStr};
3use std::fs::{self, File, OpenOptions};
4use std::io;
5use std::os::unix::ffi::OsStrExt;
6use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
7use std::path::Path;
8use crate::util;
9
10#[cfg(not(target_os = "redox"))]
11use libc::{c_char, c_int, link, rename, unlink};
12
13#[cfg(not(target_os = "redox"))]
14#[inline(always)]
15pub fn cvt_err(result: c_int) -> io::Result<c_int> {
16    if result == -1 {
17        Err(io::Error::last_os_error())
18    } else {
19        Ok(result)
20    }
21}
22
23#[cfg(target_os = "redox")]
24#[inline(always)]
25pub fn cvt_err(result: Result<usize, syscall::Error>) -> io::Result<usize> {
26    result.map_err(|err| io::Error::from_raw_os_error(err.errno))
27}
28
29// Stolen from std.
30pub fn cstr(path: &Path) -> io::Result<CString> {
31    CString::new(path.as_os_str().as_bytes())
32        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "path contained a null"))
33}
34
35pub fn create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result<File> {
36    open_options
37        .read(true)
38        .write(true)
39        .create_new(true)
40        .mode(0o600)
41        .open(path)
42}
43
44fn create_unlinked(path: &Path) -> io::Result<File> {
45    let tmp;
46    // shadow this to decrease the lifetime. It can't live longer than `tmp`.
47    let mut path = path;
48    if !path.is_absolute() {
49        let cur_dir = env::current_dir()?;
50        tmp = cur_dir.join(path);
51        path = &tmp;
52    }
53
54    let f = create_named(path, &mut OpenOptions::new())?;
55    // don't care whether the path has already been unlinked,
56    // but perhaps there are some IO error conditions we should send up?
57    let _ = fs::remove_file(path);
58    Ok(f)
59}
60
61#[cfg(target_os = "linux")]
62pub fn create(dir: &Path) -> io::Result<File> {
63    use libc::{EISDIR, ENOENT, EOPNOTSUPP, O_EXCL, O_TMPFILE};
64    OpenOptions::new()
65        .read(true)
66        .write(true)
67        .custom_flags(O_TMPFILE | O_EXCL) // do not mix with `create_new(true)`
68        .open(dir)
69        .or_else(|e| {
70            match e.raw_os_error() {
71                // These are the three "not supported" error codes for O_TMPFILE.
72                Some(EOPNOTSUPP) | Some(EISDIR) | Some(ENOENT) => create_unix(dir),
73                _ => Err(e),
74            }
75        })
76}
77
78#[cfg(not(target_os = "linux"))]
79pub fn create(dir: &Path) -> io::Result<File> {
80    create_unix(dir)
81}
82
83fn create_unix(dir: &Path) -> io::Result<File> {
84    util::create_helper(
85        dir,
86        OsStr::new(".tmp"),
87        OsStr::new(""),
88        crate::NUM_RAND_CHARS,
89        |path| create_unlinked(&path),
90    )
91}
92
93pub fn reopen(file: &File, path: &Path) -> io::Result<File> {
94    let new_file = OpenOptions::new().read(true).write(true).open(path)?;
95    let old_meta = file.metadata()?;
96    let new_meta = new_file.metadata()?;
97    if old_meta.dev() != new_meta.dev() || old_meta.ino() != new_meta.ino() {
98        return Err(io::Error::new(
99            io::ErrorKind::NotFound,
100            "original tempfile has been replaced",
101        ));
102    }
103    Ok(new_file)
104}
105
106#[cfg(not(target_os = "redox"))]
107pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
108    unsafe {
109        let old_path = cstr(old_path)?;
110        let new_path = cstr(new_path)?;
111        if overwrite {
112            cvt_err(rename(
113                old_path.as_ptr() as *const c_char,
114                new_path.as_ptr() as *const c_char,
115            ))?;
116        } else {
117            cvt_err(link(
118                old_path.as_ptr() as *const c_char,
119                new_path.as_ptr() as *const c_char,
120            ))?;
121            // Ignore unlink errors. Can we do better?
122            // On recent linux, we can use renameat2 to do this atomically.
123            let _ = unlink(old_path.as_ptr() as *const c_char);
124        }
125        Ok(())
126    }
127}
128
129#[cfg(target_os = "redox")]
130pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
131    // XXX implement when possible
132    Err(io::Error::from_raw_os_error(syscall::ENOSYS))
133}
134
135pub fn keep(_: &Path) -> io::Result<()> {
136    Ok(())
137}