1 use std::env;
2 use std::ffi::{CString, OsStr};
3 use std::fs::{self, File, OpenOptions};
4 use std::io;
5 use std::os::unix::ffi::OsStrExt;
6 use std::os::unix::fs::{MetadataExt, OpenOptionsExt};
7 use std::path::Path;
8 use crate::util;
9 
10 #[cfg(not(target_os = "redox"))]
11 use libc::{c_char, c_int, link, rename, unlink};
12 
13 #[cfg(not(target_os = "redox"))]
14 #[inline(always)]
cvt_err(result: c_int) -> io::Result<c_int>15 pub 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)]
cvt_err(result: Result<usize, syscall::Error>) -> io::Result<usize>25 pub 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.
cstr(path: &Path) -> io::Result<CString>30 pub 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 
create_named(path: &Path, open_options: &mut OpenOptions) -> io::Result<File>35 pub 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 
create_unlinked(path: &Path) -> io::Result<File>44 fn 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")]
create(dir: &Path) -> io::Result<File>62 pub 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"))]
create(dir: &Path) -> io::Result<File>79 pub fn create(dir: &Path) -> io::Result<File> {
80     create_unix(dir)
81 }
82 
create_unix(dir: &Path) -> io::Result<File>83 fn 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 
reopen(file: &File, path: &Path) -> io::Result<File>93 pub 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"))]
persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()>107 pub 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")]
persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()>130 pub 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 
keep(_: &Path) -> io::Result<()>135 pub fn keep(_: &Path) -> io::Result<()> {
136     Ok(())
137 }
138