vfs.rs
304 lines
| 9.0 KiB
| application/rls-services+xml
|
RustLexer
Raphaël Gomès
|
r53060 | use std::{ | ||
cell::Cell, | ||||
fs::File, | ||||
io::Error, | ||||
os::fd::{AsRawFd, FromRawFd}, | ||||
path::{Path, PathBuf}, | ||||
}; | ||||
use cpython::{ | ||||
ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyObject, | ||||
PyResult, PyTuple, Python, PythonObject, ToPyObject, | ||||
}; | ||||
use hg::{ | ||||
errors::{HgError, IoResultExt}, | ||||
exit_codes, | ||||
utils::files::{get_bytes_from_path, get_path_from_bytes}, | ||||
Raphaël Gomès
|
r53078 | vfs::{Vfs, VfsFile}, | ||
Raphaël Gomès
|
r53060 | }; | ||
/// Wrapper around a Python VFS object to call back into Python from `hg-core`. | ||||
pub struct PyVfs { | ||||
inner: PyObject, | ||||
Raphaël Gomès
|
r53069 | base: PathBuf, | ||
Raphaël Gomès
|
r53060 | } | ||
impl Clone for PyVfs { | ||||
fn clone(&self) -> Self { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
Self { | ||||
inner: self.inner.clone_ref(py), | ||||
Raphaël Gomès
|
r53069 | base: self.base.clone(), | ||
Raphaël Gomès
|
r53060 | } | ||
} | ||||
} | ||||
impl PyVfs { | ||||
Raphaël Gomès
|
r53069 | pub fn new( | ||
_py: Python, | ||||
py_vfs: PyObject, | ||||
base: PathBuf, | ||||
) -> PyResult<Self> { | ||||
Ok(Self { | ||||
inner: py_vfs, | ||||
base, | ||||
}) | ||||
Raphaël Gomès
|
r53060 | } | ||
fn inner_open( | ||||
&self, | ||||
filename: &Path, | ||||
create: bool, | ||||
check_ambig: bool, | ||||
atomic_temp: bool, | ||||
write: bool, | ||||
) -> Result<(File, Option<PathBuf>), HgError> { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
let mode = if atomic_temp { | ||||
PyBytes::new(py, b"w") | ||||
} else if create { | ||||
PyBytes::new(py, b"w+") | ||||
} else if write { | ||||
PyBytes::new(py, b"r+") | ||||
} else { | ||||
PyBytes::new(py, b"rb") | ||||
}; | ||||
let res = self.inner.call( | ||||
py, | ||||
( | ||||
PyBytes::new(py, &get_bytes_from_path(filename)), | ||||
mode, | ||||
atomic_temp, | ||||
check_ambig, | ||||
), | ||||
None, | ||||
); | ||||
match res { | ||||
Ok(tup) => { | ||||
let tup = tup | ||||
.extract::<PyTuple>(py) | ||||
.map_err(|e| vfs_error("vfs did not return a tuple", e))?; | ||||
let fileno = tup.get_item(py, 0).extract(py).map_err(|e| { | ||||
vfs_error("vfs did not return a valid fileno", e) | ||||
})?; | ||||
let temp_name = tup.get_item(py, 1); | ||||
// Safety: this must be a valid owned file descriptor, and | ||||
// Python has just given it to us, it will only exist here now | ||||
let file = unsafe { File::from_raw_fd(fileno) }; | ||||
let temp_name = if atomic_temp { | ||||
Some( | ||||
get_path_from_bytes( | ||||
temp_name | ||||
.extract::<PyBytes>(py) | ||||
.map_err(|e| vfs_error("invalid tempname", e))? | ||||
.data(py), | ||||
) | ||||
.to_owned(), | ||||
) | ||||
} else { | ||||
None | ||||
}; | ||||
Ok((file, temp_name)) | ||||
} | ||||
Err(mut e) => { | ||||
// TODO surely there is a better way of comparing | ||||
if e.instance(py).get_type(py).name(py) == "FileNotFoundError" | ||||
{ | ||||
return Err(HgError::IoError { | ||||
error: Error::new( | ||||
std::io::ErrorKind::NotFound, | ||||
e.instance(py).to_string(), | ||||
), | ||||
context: hg::errors::IoErrorContext::ReadingFile( | ||||
filename.to_owned(), | ||||
), | ||||
}); | ||||
} | ||||
Err(vfs_error("failed to call opener", e)) | ||||
} | ||||
} | ||||
} | ||||
} | ||||
fn vfs_error(reason: impl Into<String>, mut error: PyErr) -> HgError { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
HgError::abort( | ||||
format!("{}: {}", reason.into(), error.instance(py)), | ||||
exit_codes::ABORT, | ||||
None, | ||||
) | ||||
} | ||||
py_class!(pub class PyFile |py| { | ||||
data number: Cell<i32>; | ||||
def fileno(&self) -> PyResult<PyInt> { | ||||
Ok(self.number(py).get().to_py_object(py)) | ||||
} | ||||
}); | ||||
impl Vfs for PyVfs { | ||||
Raphaël Gomès
|
r53078 | fn open(&self, filename: &Path) -> Result<VfsFile, HgError> { | ||
Raphaël Gomès
|
r53191 | self.inner_open(filename, false, false, false, false) | ||
Raphaël Gomès
|
r53078 | .map(|(f, _)| VfsFile::normal(f, filename.to_owned())) | ||
Raphaël Gomès
|
r53060 | } | ||
Raphaël Gomès
|
r53191 | |||
fn open_write(&self, filename: &Path) -> Result<VfsFile, HgError> { | ||||
self.inner_open(filename, false, false, false, true) | ||||
Raphaël Gomès
|
r53078 | .map(|(f, _)| VfsFile::normal(f, filename.to_owned())) | ||
Raphaël Gomès
|
r53060 | } | ||
Raphaël Gomès
|
r53078 | fn open_check_ambig(&self, filename: &Path) -> Result<VfsFile, HgError> { | ||
self.inner_open(filename, false, true, false, true) | ||||
.map(|(f, _)| VfsFile::normal(f, filename.to_owned())) | ||||
} | ||||
fn create( | ||||
Raphaël Gomès
|
r53060 | &self, | ||
filename: &Path, | ||||
Raphaël Gomès
|
r53078 | check_ambig: bool, | ||
) -> Result<VfsFile, HgError> { | ||||
self.inner_open(filename, true, check_ambig, false, true) | ||||
.map(|(f, _)| VfsFile::normal(f, filename.to_owned())) | ||||
Raphaël Gomès
|
r53060 | } | ||
fn create_atomic( | ||||
&self, | ||||
filename: &Path, | ||||
check_ambig: bool, | ||||
Raphaël Gomès
|
r53078 | ) -> Result<VfsFile, HgError> { | ||
Raphaël Gomès
|
r53060 | self.inner_open(filename, true, false, true, true).map( | ||
|(fp, temp_name)| { | ||||
Raphaël Gomès
|
r53078 | VfsFile::Atomic(hg::vfs::AtomicFile::from_file( | ||
Raphaël Gomès
|
r53060 | fp, | ||
check_ambig, | ||||
temp_name.expect("temp name should exist"), | ||||
filename.to_owned(), | ||||
Raphaël Gomès
|
r53078 | )) | ||
Raphaël Gomès
|
r53060 | }, | ||
) | ||||
} | ||||
Raphaël Gomès
|
r53078 | fn file_size(&self, file: &VfsFile) -> Result<u64, HgError> { | ||
Raphaël Gomès
|
r53060 | let gil = &Python::acquire_gil(); | ||
let py = gil.python(); | ||||
let raw_fd = file.as_raw_fd(); | ||||
let py_fd = PyFile::create_instance(py, Cell::new(raw_fd)) | ||||
.expect("create_instance cannot fail"); | ||||
let fstat = self | ||||
.inner | ||||
.call_method(py, "fstat", (py_fd,), None) | ||||
.map_err(|e| { | ||||
vfs_error(format!("failed to fstat fd '{}'", raw_fd), e) | ||||
})?; | ||||
fstat | ||||
.getattr(py, "st_size") | ||||
.map(|v| { | ||||
v.extract(py).map_err(|e| { | ||||
vfs_error(format!("invalid size for fd '{}'", raw_fd), e) | ||||
}) | ||||
}) | ||||
.map_err(|e| { | ||||
vfs_error(format!("failed to get size of fd '{}'", raw_fd), e) | ||||
})? | ||||
} | ||||
fn exists(&self, filename: &Path) -> bool { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
self.inner | ||||
.call_method( | ||||
py, | ||||
"exists", | ||||
(PyBytes::new(py, &get_bytes_from_path(filename)),), | ||||
None, | ||||
) | ||||
.unwrap_or_else(|_| false.into_py_object(py).into_object()) | ||||
.extract(py) | ||||
.unwrap() | ||||
} | ||||
fn unlink(&self, filename: &Path) -> Result<(), HgError> { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
if let Err(e) = self.inner.call_method( | ||||
py, | ||||
"unlink", | ||||
(PyBytes::new(py, &get_bytes_from_path(filename)),), | ||||
None, | ||||
) { | ||||
return Err(vfs_error( | ||||
format!("failed to unlink '{}'", filename.display()), | ||||
e, | ||||
)); | ||||
} | ||||
Ok(()) | ||||
} | ||||
fn rename( | ||||
&self, | ||||
from: &Path, | ||||
to: &Path, | ||||
check_ambig: bool, | ||||
) -> Result<(), HgError> { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
let kwargs = PyDict::new(py); | ||||
kwargs | ||||
.set_item(py, "checkambig", check_ambig) | ||||
.map_err(|e| vfs_error("dict setitem failed", e))?; | ||||
if let Err(e) = self.inner.call_method( | ||||
py, | ||||
"rename", | ||||
( | ||||
PyBytes::new(py, &get_bytes_from_path(from)), | ||||
PyBytes::new(py, &get_bytes_from_path(to)), | ||||
), | ||||
Some(&kwargs), | ||||
) { | ||||
let msg = format!( | ||||
"failed to rename '{}' to '{}'", | ||||
from.display(), | ||||
to.display() | ||||
); | ||||
return Err(vfs_error(msg, e)); | ||||
} | ||||
Ok(()) | ||||
} | ||||
fn copy(&self, from: &Path, to: &Path) -> Result<(), HgError> { | ||||
let gil = &Python::acquire_gil(); | ||||
let py = gil.python(); | ||||
let from = self | ||||
.inner | ||||
.call_method( | ||||
py, | ||||
"join", | ||||
(PyBytes::new(py, &get_bytes_from_path(from)),), | ||||
None, | ||||
) | ||||
.unwrap(); | ||||
let from = from.extract::<PyBytes>(py).unwrap(); | ||||
let from = get_path_from_bytes(from.data(py)); | ||||
let to = self | ||||
.inner | ||||
.call_method( | ||||
py, | ||||
"join", | ||||
(PyBytes::new(py, &get_bytes_from_path(to)),), | ||||
None, | ||||
) | ||||
.unwrap(); | ||||
let to = to.extract::<PyBytes>(py).unwrap(); | ||||
let to = get_path_from_bytes(to.data(py)); | ||||
std::fs::copy(from, to).when_writing_file(to)?; | ||||
Ok(()) | ||||
} | ||||
Raphaël Gomès
|
r53064 | |||
fn base(&self) -> &Path { | ||||
Raphaël Gomès
|
r53069 | &self.base | ||
Raphaël Gomès
|
r53064 | } | ||
Raphaël Gomès
|
r53060 | } | ||