|
1 use std::{ |
|
2 cell::Cell, |
|
3 fs::File, |
|
4 io::Error, |
|
5 os::fd::{AsRawFd, FromRawFd}, |
|
6 path::{Path, PathBuf}, |
|
7 }; |
|
8 |
|
9 use cpython::{ |
|
10 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyObject, |
|
11 PyResult, PyTuple, Python, PythonObject, ToPyObject, |
|
12 }; |
|
13 use hg::{ |
|
14 errors::{HgError, IoResultExt}, |
|
15 exit_codes, |
|
16 utils::files::{get_bytes_from_path, get_path_from_bytes}, |
|
17 vfs::Vfs, |
|
18 }; |
|
19 |
|
20 /// Wrapper around a Python VFS object to call back into Python from `hg-core`. |
|
21 pub struct PyVfs { |
|
22 inner: PyObject, |
|
23 } |
|
24 |
|
25 impl Clone for PyVfs { |
|
26 fn clone(&self) -> Self { |
|
27 let gil = &Python::acquire_gil(); |
|
28 let py = gil.python(); |
|
29 Self { |
|
30 inner: self.inner.clone_ref(py), |
|
31 } |
|
32 } |
|
33 } |
|
34 |
|
35 impl PyVfs { |
|
36 pub fn new(_py: Python, py_vfs: PyObject) -> PyResult<Self> { |
|
37 Ok(Self { inner: py_vfs }) |
|
38 } |
|
39 |
|
40 fn inner_open( |
|
41 &self, |
|
42 filename: &Path, |
|
43 create: bool, |
|
44 check_ambig: bool, |
|
45 atomic_temp: bool, |
|
46 write: bool, |
|
47 ) -> Result<(File, Option<PathBuf>), HgError> { |
|
48 let gil = &Python::acquire_gil(); |
|
49 let py = gil.python(); |
|
50 let mode = if atomic_temp { |
|
51 PyBytes::new(py, b"w") |
|
52 } else if create { |
|
53 PyBytes::new(py, b"w+") |
|
54 } else if write { |
|
55 PyBytes::new(py, b"r+") |
|
56 } else { |
|
57 PyBytes::new(py, b"rb") |
|
58 }; |
|
59 let res = self.inner.call( |
|
60 py, |
|
61 ( |
|
62 PyBytes::new(py, &get_bytes_from_path(filename)), |
|
63 mode, |
|
64 atomic_temp, |
|
65 check_ambig, |
|
66 ), |
|
67 None, |
|
68 ); |
|
69 match res { |
|
70 Ok(tup) => { |
|
71 let tup = tup |
|
72 .extract::<PyTuple>(py) |
|
73 .map_err(|e| vfs_error("vfs did not return a tuple", e))?; |
|
74 let fileno = tup.get_item(py, 0).extract(py).map_err(|e| { |
|
75 vfs_error("vfs did not return a valid fileno", e) |
|
76 })?; |
|
77 let temp_name = tup.get_item(py, 1); |
|
78 // Safety: this must be a valid owned file descriptor, and |
|
79 // Python has just given it to us, it will only exist here now |
|
80 let file = unsafe { File::from_raw_fd(fileno) }; |
|
81 let temp_name = if atomic_temp { |
|
82 Some( |
|
83 get_path_from_bytes( |
|
84 temp_name |
|
85 .extract::<PyBytes>(py) |
|
86 .map_err(|e| vfs_error("invalid tempname", e))? |
|
87 .data(py), |
|
88 ) |
|
89 .to_owned(), |
|
90 ) |
|
91 } else { |
|
92 None |
|
93 }; |
|
94 Ok((file, temp_name)) |
|
95 } |
|
96 Err(mut e) => { |
|
97 // TODO surely there is a better way of comparing |
|
98 if e.instance(py).get_type(py).name(py) == "FileNotFoundError" |
|
99 { |
|
100 return Err(HgError::IoError { |
|
101 error: Error::new( |
|
102 std::io::ErrorKind::NotFound, |
|
103 e.instance(py).to_string(), |
|
104 ), |
|
105 context: hg::errors::IoErrorContext::ReadingFile( |
|
106 filename.to_owned(), |
|
107 ), |
|
108 }); |
|
109 } |
|
110 Err(vfs_error("failed to call opener", e)) |
|
111 } |
|
112 } |
|
113 } |
|
114 } |
|
115 |
|
116 fn vfs_error(reason: impl Into<String>, mut error: PyErr) -> HgError { |
|
117 let gil = &Python::acquire_gil(); |
|
118 let py = gil.python(); |
|
119 HgError::abort( |
|
120 format!("{}: {}", reason.into(), error.instance(py)), |
|
121 exit_codes::ABORT, |
|
122 None, |
|
123 ) |
|
124 } |
|
125 |
|
126 py_class!(pub class PyFile |py| { |
|
127 data number: Cell<i32>; |
|
128 |
|
129 def fileno(&self) -> PyResult<PyInt> { |
|
130 Ok(self.number(py).get().to_py_object(py)) |
|
131 } |
|
132 }); |
|
133 |
|
134 impl Vfs for PyVfs { |
|
135 fn open(&self, filename: &Path) -> Result<File, HgError> { |
|
136 self.inner_open(filename, false, false, false, true) |
|
137 .map(|(f, _)| f) |
|
138 } |
|
139 fn open_read(&self, filename: &Path) -> Result<File, HgError> { |
|
140 self.inner_open(filename, false, false, false, false) |
|
141 .map(|(f, _)| f) |
|
142 } |
|
143 |
|
144 fn open_check_ambig( |
|
145 &self, |
|
146 filename: &Path, |
|
147 ) -> Result<std::fs::File, HgError> { |
|
148 self.inner_open(filename, false, true, false, true) |
|
149 .map(|(f, _)| f) |
|
150 } |
|
151 |
|
152 fn create(&self, filename: &Path) -> Result<std::fs::File, HgError> { |
|
153 self.inner_open(filename, true, false, false, true) |
|
154 .map(|(f, _)| f) |
|
155 } |
|
156 |
|
157 fn create_atomic( |
|
158 &self, |
|
159 filename: &Path, |
|
160 check_ambig: bool, |
|
161 ) -> Result<hg::vfs::AtomicFile, HgError> { |
|
162 self.inner_open(filename, true, false, true, true).map( |
|
163 |(fp, temp_name)| { |
|
164 hg::vfs::AtomicFile::new( |
|
165 fp, |
|
166 check_ambig, |
|
167 temp_name.expect("temp name should exist"), |
|
168 filename.to_owned(), |
|
169 ) |
|
170 }, |
|
171 ) |
|
172 } |
|
173 |
|
174 fn file_size(&self, file: &File) -> Result<u64, HgError> { |
|
175 let gil = &Python::acquire_gil(); |
|
176 let py = gil.python(); |
|
177 let raw_fd = file.as_raw_fd(); |
|
178 let py_fd = PyFile::create_instance(py, Cell::new(raw_fd)) |
|
179 .expect("create_instance cannot fail"); |
|
180 let fstat = self |
|
181 .inner |
|
182 .call_method(py, "fstat", (py_fd,), None) |
|
183 .map_err(|e| { |
|
184 vfs_error(format!("failed to fstat fd '{}'", raw_fd), e) |
|
185 })?; |
|
186 fstat |
|
187 .getattr(py, "st_size") |
|
188 .map(|v| { |
|
189 v.extract(py).map_err(|e| { |
|
190 vfs_error(format!("invalid size for fd '{}'", raw_fd), e) |
|
191 }) |
|
192 }) |
|
193 .map_err(|e| { |
|
194 vfs_error(format!("failed to get size of fd '{}'", raw_fd), e) |
|
195 })? |
|
196 } |
|
197 |
|
198 fn exists(&self, filename: &Path) -> bool { |
|
199 let gil = &Python::acquire_gil(); |
|
200 let py = gil.python(); |
|
201 self.inner |
|
202 .call_method( |
|
203 py, |
|
204 "exists", |
|
205 (PyBytes::new(py, &get_bytes_from_path(filename)),), |
|
206 None, |
|
207 ) |
|
208 .unwrap_or_else(|_| false.into_py_object(py).into_object()) |
|
209 .extract(py) |
|
210 .unwrap() |
|
211 } |
|
212 |
|
213 fn unlink(&self, filename: &Path) -> Result<(), HgError> { |
|
214 let gil = &Python::acquire_gil(); |
|
215 let py = gil.python(); |
|
216 if let Err(e) = self.inner.call_method( |
|
217 py, |
|
218 "unlink", |
|
219 (PyBytes::new(py, &get_bytes_from_path(filename)),), |
|
220 None, |
|
221 ) { |
|
222 return Err(vfs_error( |
|
223 format!("failed to unlink '{}'", filename.display()), |
|
224 e, |
|
225 )); |
|
226 } |
|
227 Ok(()) |
|
228 } |
|
229 |
|
230 fn rename( |
|
231 &self, |
|
232 from: &Path, |
|
233 to: &Path, |
|
234 check_ambig: bool, |
|
235 ) -> Result<(), HgError> { |
|
236 let gil = &Python::acquire_gil(); |
|
237 let py = gil.python(); |
|
238 let kwargs = PyDict::new(py); |
|
239 kwargs |
|
240 .set_item(py, "checkambig", check_ambig) |
|
241 .map_err(|e| vfs_error("dict setitem failed", e))?; |
|
242 if let Err(e) = self.inner.call_method( |
|
243 py, |
|
244 "rename", |
|
245 ( |
|
246 PyBytes::new(py, &get_bytes_from_path(from)), |
|
247 PyBytes::new(py, &get_bytes_from_path(to)), |
|
248 ), |
|
249 Some(&kwargs), |
|
250 ) { |
|
251 let msg = format!( |
|
252 "failed to rename '{}' to '{}'", |
|
253 from.display(), |
|
254 to.display() |
|
255 ); |
|
256 return Err(vfs_error(msg, e)); |
|
257 } |
|
258 Ok(()) |
|
259 } |
|
260 |
|
261 fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> { |
|
262 let gil = &Python::acquire_gil(); |
|
263 let py = gil.python(); |
|
264 let from = self |
|
265 .inner |
|
266 .call_method( |
|
267 py, |
|
268 "join", |
|
269 (PyBytes::new(py, &get_bytes_from_path(from)),), |
|
270 None, |
|
271 ) |
|
272 .unwrap(); |
|
273 let from = from.extract::<PyBytes>(py).unwrap(); |
|
274 let from = get_path_from_bytes(from.data(py)); |
|
275 let to = self |
|
276 .inner |
|
277 .call_method( |
|
278 py, |
|
279 "join", |
|
280 (PyBytes::new(py, &get_bytes_from_path(to)),), |
|
281 None, |
|
282 ) |
|
283 .unwrap(); |
|
284 let to = to.extract::<PyBytes>(py).unwrap(); |
|
285 let to = get_path_from_bytes(to.data(py)); |
|
286 std::fs::copy(from, to).when_writing_file(to)?; |
|
287 Ok(()) |
|
288 } |
|
289 } |