Skip to content

Commit d887948

Browse files
authored
Merge pull request RustPython#4782 from youknowone/bytes-as-osstr
relocate FsPath from stdlib::os and rename PyPathLike to OsPath
2 parents e429270 + 69280ee commit d887948

File tree

12 files changed

+311
-264
lines changed

12 files changed

+311
-264
lines changed

common/src/os.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// TODO: we can move more os-specific bindings/interfaces from stdlib::{os, posix, nt} to here
22

3-
use std::io;
3+
use std::{io, str::Utf8Error};
44

55
#[cfg(windows)]
66
pub fn errno() -> io::Error {
@@ -21,3 +21,19 @@ pub fn errno() -> io::Error {
2121
pub fn errno() -> io::Error {
2222
io::Error::last_os_error()
2323
}
24+
25+
#[cfg(unix)]
26+
pub fn bytes_as_osstr(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> {
27+
use std::os::unix::ffi::OsStrExt;
28+
Ok(std::ffi::OsStr::from_bytes(b))
29+
}
30+
31+
#[cfg(not(unix))]
32+
pub fn bytes_as_osstr(b: &[u8]) -> Result<&std::ffi::OsStr, Utf8Error> {
33+
Ok(std::str::from_utf8(b)?.as_ref())
34+
}
35+
36+
#[cfg(unix)]
37+
pub use std::os::unix::ffi;
38+
#[cfg(target_os = "wasi")]
39+
pub use std::os::wasi::ffi;

stdlib/src/posixsubprocess.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::vm::{
22
builtins::PyListRef,
33
function::ArgSequence,
4-
stdlib::{os::PyPathLike, posix},
4+
stdlib::{os::OsPath, posix},
55
{PyObjectRef, PyResult, TryFromObject, VirtualMachine},
66
};
77
use nix::{errno::Errno, unistd};
@@ -60,7 +60,7 @@ struct CStrPathLike {
6060
}
6161
impl TryFromObject for CStrPathLike {
6262
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
63-
let s = PyPathLike::try_from_object(vm, obj)?.into_cstring(vm)?;
63+
let s = OsPath::try_from_object(vm, obj)?.into_cstring(vm)?;
6464
Ok(CStrPathLike { s })
6565
}
6666
}

stdlib/src/socket.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ mod _socket {
1414
use crate::vm::{
1515
builtins::{PyBaseExceptionRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef},
1616
convert::{IntoPyException, ToPyObject, TryFromBorrowedObject, TryFromObject},
17-
function::{ArgBytesLike, ArgMemoryBuffer, Either, OptionalArg, OptionalOption},
17+
function::{ArgBytesLike, ArgMemoryBuffer, Either, FsPath, OptionalArg, OptionalOption},
1818
types::{DefaultConstructor, Initializer},
1919
utils::ToCString,
2020
AsObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
@@ -1977,12 +1977,10 @@ mod _socket {
19771977

19781978
#[cfg(not(target_os = "redox"))]
19791979
#[pyfunction]
1980-
fn if_nametoindex(name: PyObjectRef, vm: &VirtualMachine) -> PyResult<IfIndex> {
1981-
let name = crate::vm::stdlib::os::FsPath::try_from(name, true, vm)?;
1982-
let name = ffi::CString::new(name.as_bytes()).map_err(|err| err.into_pyexception(vm))?;
1980+
fn if_nametoindex(name: FsPath, vm: &VirtualMachine) -> PyResult<IfIndex> {
1981+
let name = name.to_cstring(vm)?;
19831982

19841983
let ret = unsafe { c::if_nametoindex(name.as_ptr()) };
1985-
19861984
if ret == 0 {
19871985
Err(vm.new_os_error("no interface with this name".to_owned()))
19881986
} else {

stdlib/src/sqlite.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,9 @@ mod _sqlite {
5858
PyInt, PyIntRef, PySlice, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef,
5959
},
6060
convert::IntoObject,
61-
function::{ArgCallable, ArgIterable, FuncArgs, OptionalArg, PyComparisonValue},
61+
function::{ArgCallable, ArgIterable, FsPath, FuncArgs, OptionalArg, PyComparisonValue},
6262
protocol::{PyBuffer, PyIterReturn, PyMappingMethods, PySequence, PySequenceMethods},
6363
sliceable::{SaturatedSliceIter, SliceableSequenceOp},
64-
stdlib::os::PyPathLike,
6564
types::{
6665
AsMapping, AsSequence, Callable, Comparable, Constructor, Hashable, IterNext,
6766
IterNextIterable, Iterable, PyComparisonOp,
@@ -293,7 +292,7 @@ mod _sqlite {
293292
#[derive(FromArgs)]
294293
struct ConnectArgs {
295294
#[pyarg(any)]
296-
database: PyPathLike,
295+
database: FsPath,
297296
#[pyarg(any, default = "5.0")]
298297
timeout: f64,
299298
#[pyarg(any, default = "0")]
@@ -828,7 +827,7 @@ mod _sqlite {
828827
#[pyclass(with(Constructor, Callable), flags(BASETYPE))]
829828
impl Connection {
830829
fn new(args: ConnectArgs, vm: &VirtualMachine) -> PyResult<Self> {
831-
let path = args.database.into_cstring(vm)?;
830+
let path = args.database.to_cstring(vm)?;
832831
let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?);
833832
let timeout = (args.timeout * 1000.0) as c_int;
834833
db.busy_timeout(timeout);

stdlib/src/ssl.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ mod _ssl {
3636
convert::{ToPyException, ToPyObject},
3737
exceptions,
3838
function::{
39-
ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, OptionalArg,
39+
ArgBytesLike, ArgCallable, ArgMemoryBuffer, ArgStrOrBytesLike, Either, FsPath,
40+
OptionalArg,
4041
},
41-
stdlib::os::PyPathLike,
4242
types::Constructor,
4343
utils::ToCString,
4444
PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
@@ -726,10 +726,12 @@ mod _ssl {
726726
);
727727
}
728728
let mut ctx = self.builder();
729-
ctx.set_certificate_chain_file(&certfile)
729+
let key_path = keyfile.map(|path| path.to_path_buf(vm)).transpose()?;
730+
let cert_path = certfile.to_path_buf(vm)?;
731+
ctx.set_certificate_chain_file(&cert_path)
730732
.and_then(|()| {
731733
ctx.set_private_key_file(
732-
keyfile.as_ref().unwrap_or(&certfile),
734+
key_path.as_ref().unwrap_or(&cert_path),
733735
ssl::SslFiletype::PEM,
734736
)
735737
})
@@ -819,9 +821,9 @@ mod _ssl {
819821

820822
#[derive(FromArgs)]
821823
struct LoadCertChainArgs {
822-
certfile: PyPathLike,
824+
certfile: FsPath,
823825
#[pyarg(any, optional)]
824-
keyfile: Option<PyPathLike>,
826+
keyfile: Option<FsPath>,
825827
#[pyarg(any, optional)]
826828
password: Option<Either<PyStrRef, ArgCallable>>,
827829
}
@@ -1308,7 +1310,8 @@ mod _ssl {
13081310
}
13091311

13101312
#[pyfunction]
1311-
fn _test_decode_cert(path: PyPathLike, vm: &VirtualMachine) -> PyResult {
1313+
fn _test_decode_cert(path: FsPath, vm: &VirtualMachine) -> PyResult {
1314+
let path = path.to_path_buf(vm)?;
13121315
let pem = std::fs::read(path).map_err(|e| e.to_pyexception(vm))?;
13131316
let x509 = X509::from_pem(&pem).map_err(|e| convert_openssl_error(vm, e))?;
13141317
cert_to_py(vm, &x509, false)

vm/src/function/fspath.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use crate::{
2+
builtins::{PyBytes, PyBytesRef, PyStrRef},
3+
convert::{IntoPyException, ToPyObject},
4+
function::PyStr,
5+
protocol::PyBuffer,
6+
PyObjectRef, PyResult, TryFromObject, VirtualMachine,
7+
};
8+
use std::{ffi::OsStr, path::PathBuf};
9+
10+
#[derive(Clone)]
11+
pub enum FsPath {
12+
Str(PyStrRef),
13+
Bytes(PyBytesRef),
14+
}
15+
16+
impl FsPath {
17+
// PyOS_FSPath in CPython
18+
pub fn try_from(obj: PyObjectRef, check_for_nul: bool, vm: &VirtualMachine) -> PyResult<Self> {
19+
let check_nul = |b: &[u8]| {
20+
if !check_for_nul || memchr::memchr(b'\0', b).is_none() {
21+
Ok(())
22+
} else {
23+
Err(crate::exceptions::cstring_error(vm))
24+
}
25+
};
26+
let match1 = |obj: PyObjectRef| {
27+
let pathlike = match_class!(match obj {
28+
s @ PyStr => {
29+
check_nul(s.as_str().as_bytes())?;
30+
FsPath::Str(s)
31+
}
32+
b @ PyBytes => {
33+
check_nul(&b)?;
34+
FsPath::Bytes(b)
35+
}
36+
obj => return Ok(Err(obj)),
37+
});
38+
Ok(Ok(pathlike))
39+
};
40+
let obj = match match1(obj)? {
41+
Ok(pathlike) => return Ok(pathlike),
42+
Err(obj) => obj,
43+
};
44+
let method =
45+
vm.get_method_or_type_error(obj.clone(), identifier!(vm, __fspath__), || {
46+
format!(
47+
"should be string, bytes, os.PathLike or integer, not {}",
48+
obj.class().name()
49+
)
50+
})?;
51+
let result = method.call((), vm)?;
52+
match1(result)?.map_err(|result| {
53+
vm.new_type_error(format!(
54+
"expected {}.__fspath__() to return str or bytes, not {}",
55+
obj.class().name(),
56+
result.class().name(),
57+
))
58+
})
59+
}
60+
61+
pub fn as_os_str(&self, vm: &VirtualMachine) -> PyResult<&OsStr> {
62+
// TODO: FS encodings
63+
match self {
64+
FsPath::Str(s) => Ok(s.as_str().as_ref()),
65+
FsPath::Bytes(b) => Self::bytes_as_osstr(b.as_bytes(), vm),
66+
}
67+
}
68+
69+
pub fn as_bytes(&self) -> &[u8] {
70+
// TODO: FS encodings
71+
match self {
72+
FsPath::Str(s) => s.as_str().as_bytes(),
73+
FsPath::Bytes(b) => b.as_bytes(),
74+
}
75+
}
76+
77+
pub fn as_str(&self) -> &str {
78+
match self {
79+
FsPath::Bytes(b) => std::str::from_utf8(b).unwrap(),
80+
FsPath::Str(s) => s.as_str(),
81+
}
82+
}
83+
84+
pub fn to_path_buf(&self, vm: &VirtualMachine) -> PyResult<PathBuf> {
85+
let path = match self {
86+
FsPath::Str(s) => PathBuf::from(s.as_str()),
87+
FsPath::Bytes(b) => PathBuf::from(Self::bytes_as_osstr(b, vm)?),
88+
};
89+
Ok(path)
90+
}
91+
92+
pub fn to_cstring(&self, vm: &VirtualMachine) -> PyResult<std::ffi::CString> {
93+
std::ffi::CString::new(self.as_bytes()).map_err(|e| e.into_pyexception(vm))
94+
}
95+
96+
#[cfg(windows)]
97+
pub fn to_widecstring(&self, vm: &VirtualMachine) -> PyResult<widestring::WideCString> {
98+
widestring::WideCString::from_os_str(self.as_os_str(vm)?)
99+
.map_err(|err| err.into_pyexception(vm))
100+
}
101+
102+
pub fn bytes_as_osstr<'a>(b: &'a [u8], vm: &VirtualMachine) -> PyResult<&'a std::ffi::OsStr> {
103+
rustpython_common::os::bytes_as_osstr(b)
104+
.map_err(|_| vm.new_unicode_decode_error("can't decode path for utf-8".to_owned()))
105+
}
106+
}
107+
108+
impl ToPyObject for FsPath {
109+
fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
110+
match self {
111+
Self::Str(s) => s.into(),
112+
Self::Bytes(b) => b.into(),
113+
}
114+
}
115+
}
116+
117+
impl TryFromObject for FsPath {
118+
// PyUnicode_FSDecoder in CPython
119+
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
120+
let obj = match obj.try_to_value::<PyBuffer>(vm) {
121+
Ok(buffer) => {
122+
let mut bytes = vec![];
123+
buffer.append_to(&mut bytes);
124+
vm.ctx.new_bytes(bytes).into()
125+
}
126+
Err(_) => obj,
127+
};
128+
Self::try_from(obj, true, vm)
129+
}
130+
}

vm/src/function/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod arithmetic;
33
mod buffer;
44
mod builtin;
55
mod either;
6+
mod fspath;
67
mod getset;
78
mod number;
89
mod protocol;
@@ -16,6 +17,7 @@ pub use buffer::{ArgAsciiBuffer, ArgBytesLike, ArgMemoryBuffer, ArgStrOrBytesLik
1617
pub(self) use builtin::{BorrowedParam, OwnedParam, RefParam};
1718
pub use builtin::{IntoPyNativeFunc, PyNativeFunc};
1819
pub use either::Either;
20+
pub use fspath::FsPath;
1921
pub use getset::PySetterValue;
2022
pub(super) use getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySetterFunc};
2123
pub use number::{ArgIndex, ArgIntoBool, ArgIntoComplex, ArgIntoFloat, ArgPrimitiveIndex, ArgSize};

vm/src/stdlib/io.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3545,7 +3545,7 @@ mod _io {
35453545

35463546
// check file descriptor validity
35473547
#[cfg(unix)]
3548-
if let Ok(crate::stdlib::os::PathOrFd::Fd(fd)) = file.clone().try_into_value(vm) {
3548+
if let Ok(crate::stdlib::os::OsPathOrFd::Fd(fd)) = file.clone().try_into_value(vm) {
35493549
nix::fcntl::fcntl(fd, nix::fcntl::F_GETFD)
35503550
.map_err(|_| crate::stdlib::os::errno_err(vm))?;
35513551
}
@@ -3874,7 +3874,7 @@ mod fileio {
38743874
} else if let Some(i) = name.payload::<crate::builtins::PyInt>() {
38753875
i.try_to_primitive(vm)?
38763876
} else {
3877-
let path = os::PyPathLike::try_from_object(vm, name.clone())?;
3877+
let path = os::OsPath::try_from_object(vm, name.clone())?;
38783878
if !args.closefd {
38793879
return Err(
38803880
vm.new_value_error("Cannot use closefd=False with file name".to_owned())

0 commit comments

Comments
 (0)