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