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