rust/hg-cpython/src/vfs.rs
changeset 52163 7346f93be7a4
child 52167 7be39c5110c9
equal deleted inserted replaced
52162:13815c9decd4 52163:7346f93be7a4
       
     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 }