diff --git a/common/src/os.rs b/common/src/os.rs index 40f403971..c583647ce 100644 --- a/common/src/os.rs +++ b/common/src/os.rs @@ -32,3 +32,8 @@ pub fn bytes_as_osstr(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { pub fn bytes_as_osstr(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> { Ok(std::str::from_utf8(b)?.as_ref()) } + +#[cfg(unix)] +pub use std::os::unix::ffi; +#[cfg(target_os = "wasi")] +pub use std::os::wasi::ffi; diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index 715f8476e..08ca8496f 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -14,7 +14,7 @@ mod _socket { use crate::vm::{ builtins::{PyBaseExceptionRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef}, convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject}, - function::{ArgBytesLike, ArgMemoryBuffer, Either, OptionalArg, OptionalOption}, + function::{ArgBytesLike, ArgMemoryBuffer, Either, FsPath, OptionalArg, OptionalOption}, types::{DefaultConstructor, Initializer}, utils::ToCString, AsObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, @@ -1977,12 +1977,10 @@ mod _socket { #[cfg(not(target_os = "redox"))] #[pyfunction] - fn if_nametoindex(name: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let name = crate::vm::stdlib::os::FsPath::try_from(name, true, vm)?; - let name = ffi::CString::new(name.as_bytes()).map_err(|err| err.into_pyexception(vm))?; + fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult { + let name = name.to_cstring(vm)?; let ret = unsafe { c::if_nametoindex(name.as_ptr()) }; - if ret == 0 { Err(vm.new_os_error("no interface with this name".to_owned())) } else { diff --git a/stdlib/src/sqlite.rs b/stdlib/src/sqlite.rs index 6643c97e7..57ce3dc35 100644 --- a/stdlib/src/sqlite.rs +++ b/stdlib/src/sqlite.rs @@ -58,10 +58,9 @@ mod _sqlite { PyInt, PyIntRef, PySlice, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, }, convert::IntoObject, - function::{ArgCallable, ArgIterable, FuncArgs, OptionalArg, PyComparisonValue}, + function::{ArgCallable, ArgIterable, FsPath, FuncArgs, OptionalArg, PyComparisonValue}, protocol::{PyBuffer, PyIterReturn, PyMappingMethods, PySequence, PySequenceMethods}, sliceable::{SaturatedSliceIter, SliceableSequenceOp}, - stdlib::os::PyPathLike, types::{ AsMapping, AsSequence, Callable, Comparable, Constructor, Hashable, IterNext, IterNextIterable, Iterable, PyComparisonOp, @@ -293,7 +292,7 @@ mod _sqlite { #[derive(FromArgs)] struct ConnectArgs { #[pyarg(any)] - database: PyPathLike, + database: FsPath, #[pyarg(any, default = "5.0")] timeout: f64, #[pyarg(any, default = "0")] @@ -828,7 +827,7 @@ mod _sqlite { #[pyclass(with(Constructor, Callable), flags(BASETYPE))] impl Connection { fn new(args: ConnectArgs, vm: &VirtualMachine) -> PyResult { - let path = args.database.into_cstring(vm)?; + let path = args.database.to_cstring(vm)?; let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?); let timeout = (args.timeout * 1000.0) as c_int; db.busy_timeout(timeout); diff --git a/stdlib/src/ssl.rs b/stdlib/src/ssl.rs index 10f35a595..e9ccde6a6 100644 --- a/stdlib/src/ssl.rs +++ b/stdlib/src/ssl.rs @@ -36,9 +36,9 @@ mod _ssl { convert::{ToPyException, ToPyObject}, exceptions, function::{ - ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, OptionalArg, + ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath, + OptionalArg, }, - stdlib::os::PyPathLike, types::Constructor, utils::ToCString, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, @@ -726,10 +726,12 @@ mod _ssl { ); } let mut ctx = self.builder(); - ctx.set_certificate_chain_file(&certfile) + let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?; + let cert_path = certfile.to_path_buf(vm)?; + ctx.set_certificate_chain_file(&cert_path) .and_then(|()| { ctx.set_private_key_file( - keyfile.as_ref().unwrap_or(&certfile), + key_path.as_ref().unwrap_or(&cert_path), ssl::SslFiletype::PEM, ) }) @@ -819,9 +821,9 @@ mod _ssl { #[derive(FromArgs)] struct LoadCertChainArgs { - certfile: PyPathLike, + certfile: FsPath, #[pyarg(any, optional)] - keyfile: Option, + keyfile: Option, #[pyarg(any, optional)] password: Option>, } @@ -1308,7 +1310,8 @@ mod _ssl { } #[pyfunction] - fn _test_decode_cert(path: PyPathLike, vm: &VirtualMachine) -> PyResult { + fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult { + let path = path.to_path_buf(vm)?; let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?; let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?; cert_to_py(vm, &x509, false) diff --git a/vm/src/function/fspath.rs b/vm/src/function/fspath.rs new file mode 100644 index 000000000..41e99b054 --- /dev/null +++ b/vm/src/function/fspath.rs @@ -0,0 +1,130 @@ +use crate::{ + builtins::{PyBytes, PyBytesRef, PyStrRef}, + convert::{IntoPyException, ToPyObject}, + function::PyStr, + protocol::PyBuffer, + PyObjectRef, PyResult, TryFromObject, VirtualMachine, +}; +use std::{ffi::OsStr, path::PathBuf}; + +#[derive(Clone)] +pub enum FsPath { + Str(PyStrRef), + Bytes(PyBytesRef), +} + +impl FsPath { + // PyOS_FSPath in CPython + pub fn try_from(obj: PyObjectRef, check_for_nul: bool, vm: &VirtualMachine) -> PyResult { + let check_nul = |b: &[u8]| { + if !check_for_nul || memchr::memchr(b'\0', b).is_none() { + Ok(()) + } else { + Err(crate::exceptions::cstring_error(vm)) + } + }; + let match1 = |obj: PyObjectRef| { + let pathlike = match_class!(match obj { + s @ PyStr => { + check_nul(s.as_str().as_bytes())?; + FsPath::Str(s) + } + b @ PyBytes => { + check_nul(&b)?; + FsPath::Bytes(b) + } + obj => return Ok(Err(obj)), + }); + Ok(Ok(pathlike)) + }; + let obj = match match1(obj)? { + Ok(pathlike) => return Ok(pathlike), + Err(obj) => obj, + }; + let method = + vm.get_method_or_type_error(obj.clone(), identifier!(vm, __fspath__), || { + format!( + "should be string, bytes, os.PathLike or integer, not {}", + obj.class().name() + ) + })?; + let result = method.call((), vm)?; + match1(result)?.map_err(|result| { + vm.new_type_error(format!( + "expected {}.__fspath__() to return str or bytes, not {}", + obj.class().name(), + result.class().name(), + )) + }) + } + + pub fn as_os_str(&self, vm: &VirtualMachine) -> PyResult<&OsStr> { + // TODO: FS encodings + match self { + FsPath::Str(s) => Ok(s.as_str().as_ref()), + FsPath::Bytes(b) => Self::bytes_as_osstr(b.as_bytes(), vm), + } + } + + pub fn as_bytes(&self) -> &[u8] { + // TODO: FS encodings + match self { + FsPath::Str(s) => s.as_str().as_bytes(), + FsPath::Bytes(b) => b.as_bytes(), + } + } + + pub fn as_str(&self) -> &str { + match self { + FsPath::Bytes(b) => std::str::from_utf8(b).unwrap(), + FsPath::Str(s) => s.as_str(), + } + } + + pub fn to_path_buf(&self, vm: &VirtualMachine) -> PyResult { + let path = match self { + FsPath::Str(s) => PathBuf::from(s.as_str()), + FsPath::Bytes(b) => PathBuf::from(Self::bytes_as_osstr(b, vm)?), + }; + Ok(path) + } + + pub fn to_cstring(&self, vm: &VirtualMachine) -> PyResult { + std::ffi::CString::new(self.as_bytes()).map_err(|e| e.into_pyexception(vm)) + } + + #[cfg(windows)] + pub fn to_widecstring(&self, vm: &VirtualMachine) -> PyResult { + widestring::WideCString::from_os_str(self.as_os_str(vm)?) + .map_err(|err| err.into_pyexception(vm)) + } + + pub fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> { + rustpython_common::os::bytes_as_osstr(b) + .map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned())) + } +} + +impl ToPyObject for FsPath { + fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { + match self { + Self::Str(s) => s.into(), + Self::Bytes(b) => b.into(), + } + } +} + +impl TryFromObject for FsPath { + // PyUnicode_FSDecoder in CPython + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + let obj = match obj.try_to_value::(vm) { + Ok(buffer) => { + let mut bytes = vec![]; + buffer.append_to(&mut bytes); + vm.ctx.new_bytes(bytes).into() + } + Err(_) => obj, + }; + Self::try_from(obj, true, vm) + } +} diff --git a/vm/src/function/mod.rs b/vm/src/function/mod.rs index 3a097acf8..0521c06e4 100644 --- a/vm/src/function/mod.rs +++ b/vm/src/function/mod.rs @@ -3,6 +3,7 @@ mod arithmetic; mod buffer; mod builtin; mod either; +mod fspath; mod getset; mod number; mod protocol; @@ -16,6 +17,7 @@ pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLik pub(self) use builtin::{BorrowedParam, OwnedParam, RefParam}; pub use builtin::{IntoPyNativeFunc, PyNativeFunc}; pub use either::Either; +pub use fspath::FsPath; pub use getset::PySetterValue; pub(super) use getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc}; pub use number::{ArgIndex, ArgIntoBool, ArgIntoComplex, ArgIntoFloat, ArgPrimitiveIndex, ArgSize}; diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 9826c2b1b..b5d9f5e8c 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -3703,7 +3703,7 @@ mod fileio { builtins::{PyStr, PyStrRef}, common::crt_fd::Fd, convert::ToPyException, - function::{ArgBytesLike, ArgMemoryBuffer, OptionalArg, OptionalOption}, + function::{ArgBytesLike, ArgMemoryBuffer, FsPath, OptionalArg, OptionalOption}, stdlib::os, types::{DefaultConstructor, Initializer}, AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, @@ -3870,13 +3870,19 @@ mod fileio { } else if let Some(i) = name.payload::() { i.try_to_primitive(vm)? } else { - let path = os::PyPathLike::try_from_object(vm, name.clone())?; + let path = FsPath::try_from_object(vm, name.clone())?; if !args.closefd { return Err( vm.new_value_error("Cannot use closefd=False with file name".to_owned()) ); } - os::open(path, flags as _, None, Default::default(), vm)? + os::open( + path.to_pathlike(vm)?, + flags as _, + None, + Default::default(), + vm, + )? }; if mode.contains(Mode::APPENDING) { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index c0c8af32d..43564b9e4 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,12 +1,9 @@ use crate::{ - builtins::{PyBaseExceptionRef, PyBytes, PyBytesRef, PyInt, PySet, PyStr, PyStrRef}, + builtins::{PyBaseExceptionRef, PyInt, PySet}, common::crt_fd::Fd, - convert::{IntoPyException, ToPyObject}, - function::{ArgumentError, FromArgs, FuncArgs}, - identifier, - protocol::PyBuffer, - AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromBorrowedObject, TryFromObject, - VirtualMachine, + convert::IntoPyException, + function::{ArgumentError, FromArgs, FsPath, FuncArgs}, + AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, }; use std::{ ffi, fs, io, @@ -110,65 +107,8 @@ impl AsRef for PyPathLike { } } -pub enum FsPath { - Str(PyStrRef), - Bytes(PyBytesRef), -} - impl FsPath { - pub fn try_from(obj: PyObjectRef, check_for_nul: bool, vm: &VirtualMachine) -> PyResult { - // PyOS_FSPath in CPython - let check_nul = |b: &[u8]| { - if !check_for_nul || memchr::memchr(b'\0', b).is_none() { - Ok(()) - } else { - Err(crate::exceptions::cstring_error(vm)) - } - }; - let match1 = |obj: PyObjectRef| { - let pathlike = match_class!(match obj { - s @ PyStr => { - check_nul(s.as_str().as_bytes())?; - FsPath::Str(s) - } - b @ PyBytes => { - check_nul(&b)?; - FsPath::Bytes(b) - } - obj => return Ok(Err(obj)), - }); - Ok(Ok(pathlike)) - }; - let obj = match match1(obj)? { - Ok(pathlike) => return Ok(pathlike), - Err(obj) => obj, - }; - let method = - vm.get_method_or_type_error(obj.clone(), identifier!(vm, __fspath__), || { - format!( - "should be string, bytes, os.PathLike or integer, not {}", - obj.class().name() - ) - })?; - let result = method.call((), vm)?; - match1(result)?.map_err(|result| { - vm.new_type_error(format!( - "expected {}.__fspath__() to return str or bytes, not {}", - obj.class().name(), - result.class().name(), - )) - }) - } - - pub fn as_os_str(&self, vm: &VirtualMachine) -> PyResult<&ffi::OsStr> { - // TODO: FS encodings - match self { - FsPath::Str(s) => Ok(s.as_str().as_ref()), - FsPath::Bytes(b) => bytes_as_osstr(b.as_bytes(), vm), - } - } - - fn to_pathlike(&self, vm: &VirtualMachine) -> PyResult { + pub(crate) fn to_pathlike(&self, vm: &VirtualMachine) -> PyResult { let path = self.as_os_str(vm)?.to_owned().into(); let mode = match self { Self::Str(_) => OutputMode::String, @@ -176,36 +116,11 @@ impl FsPath { }; Ok(PyPathLike { path, mode }) } - - pub fn as_bytes(&self) -> &[u8] { - // TODO: FS encodings - match self { - FsPath::Str(s) => s.as_str().as_bytes(), - FsPath::Bytes(b) => b.as_bytes(), - } - } -} - -impl ToPyObject for FsPath { - fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef { - match self { - Self::Str(s) => s.into(), - Self::Bytes(b) => b.into(), - } - } } impl TryFromObject for PyPathLike { + // TODO: path_converter in CPython fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - // path_converter in CPython - let obj = match PyBuffer::try_from_borrowed_object(vm, &obj) { - Ok(buffer) => { - let mut bytes = vec![]; - buffer.append_to(&mut bytes); - PyBytes::from(bytes).to_pyobject(vm) - } - Err(_) => obj, - }; let fs_path = FsPath::try_from(obj, true, vm)?; fs_path.to_pathlike(vm) } @@ -386,7 +301,7 @@ fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a ffi::OsS #[pymodule(name = "_os")] pub(super) mod _os { use super::{ - errno_err, DirFd, FollowSymlinks, FsPath, IOErrorBuilder, OutputMode, PathOrFd, PyPathLike, + errno_err, DirFd, FollowSymlinks, IOErrorBuilder, OutputMode, PathOrFd, PyPathLike, SupportFunc, }; use crate::common::lock::{OnceCell, PyRwLock}; @@ -394,11 +309,12 @@ pub(super) mod _os { builtins::{ PyBytesRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple, PyTupleRef, PyTypeRef, }, - common::crt_fd::{Fd, Offset}, - common::suppress_iph, + common::{ + crt_fd::{Fd, Offset}, + suppress_iph, + }, convert::{IntoPyException, ToPyObject}, - function::Either, - function::{ArgBytesLike, FuncArgs, OptionalArg}, + function::{ArgBytesLike, Either, FsPath, FuncArgs, OptionalArg}, protocol::PyIterReturn, recursion::ReprGuard, types::{IterNext, IterNextIterable, PyStructSequence}, @@ -1224,7 +1140,7 @@ pub(super) mod _os { #[pyfunction] fn fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult { - super::FsPath::try_from(path, false, vm) + FsPath::try_from(path, false, vm) } #[pyfunction]