Merge pull request #1970 from youknowone/sub-1962

non-subprocess stuff from #1962
This commit is contained in:
Jeong YunWon
2020-06-22 03:57:47 +09:00
committed by GitHub
5 changed files with 356 additions and 17 deletions

View File

@@ -122,7 +122,7 @@ schannel = "0.1"
[target.'cfg(windows)'.dependencies.winapi]
version = "0.3"
features = ["winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt", "fileapi"]
features = ["winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt", "fileapi", "wincon"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2"

View File

@@ -274,6 +274,11 @@ impl<T> From<HashMap<String, T>> for KwArgs<T> {
KwArgs(map)
}
}
impl<T> Default for KwArgs<T> {
fn default() -> Self {
KwArgs(HashMap::new())
}
}
impl<T> FromArgs for KwArgs<T>
where

View File

@@ -10,7 +10,7 @@ use crate::exceptions::PyBaseExceptionRef;
use crate::function::{KwArgs, OptionalArg, PyFuncArgs};
use crate::pyobject::{
IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyClassImpl, PyContext, PyIterable,
PyObjectRef, PyRef, PyResult, PyValue,
PyObjectRef, PyRef, PyResult, PyValue, TryFromObject,
};
use crate::vm::{ReprGuard, VirtualMachine};
@@ -39,6 +39,7 @@ impl PyValue for PyDict {
}
// Python dict methods:
#[allow(clippy::len_without_is_empty)]
#[pyimpl(flags(BASETYPE))]
impl PyDictRef {
#[pyslot]
@@ -179,7 +180,7 @@ impl PyDictRef {
}
#[pymethod(magic)]
fn len(self) -> usize {
pub fn len(self) -> usize {
self.entries.len()
}
@@ -531,6 +532,11 @@ impl Iterator for DictIter {
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let l = self.dict.entries.len_from_entry_index(self.position);
(l, Some(l))
}
}
macro_rules! dict_iterator {
@@ -675,3 +681,26 @@ pub(crate) fn init(context: &PyContext) {
PyDictItems::extend_class(context, &context.types.dictitems_type);
PyDictItemIterator::extend_class(context, &context.types.dictitemiterator_type);
}
pub struct PyMapping {
dict: PyDictRef,
}
impl TryFromObject for PyMapping {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let dict = vm.ctx.new_dict();
PyDictRef::merge(
&dict.entries,
OptionalArg::Present(obj),
KwArgs::default(),
vm,
)?;
Ok(PyMapping { dict })
}
}
impl PyMapping {
pub fn into_dict(self) -> PyDictRef {
self.dict
}
}

View File

@@ -980,9 +980,12 @@ impl<T> PyIterable<T> {
},
)?;
let lenhint = objiter::length_hint(vm, iter_obj.clone())?;
Ok(PyIterator {
vm,
obj: iter_obj,
lenhint,
_item: std::marker::PhantomData,
})
}
@@ -991,6 +994,7 @@ impl<T> PyIterable<T> {
pub struct PyIterator<'a, T> {
vm: &'a VirtualMachine,
obj: PyObjectRef,
lenhint: Option<usize>,
_item: std::marker::PhantomData<T>,
}
@@ -1012,6 +1016,10 @@ where
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.lenhint.unwrap_or(0), self.lenhint)
}
}
impl<T> TryFromObject for PyIterable<T>

View File

@@ -28,7 +28,7 @@ use crate::exceptions::PyBaseExceptionRef;
use crate::function::{IntoPyNativeFunc, OptionalArg, PyFuncArgs};
use crate::obj::objbyteinner::PyBytesLike;
use crate::obj::objbytes::{PyBytes, PyBytesRef};
use crate::obj::objdict::PyDictRef;
use crate::obj::objdict::{PyDictRef, PyMapping};
use crate::obj::objint::PyIntRef;
use crate::obj::objiter;
use crate::obj::objset::PySet;
@@ -41,14 +41,11 @@ use crate::pyobject::{
};
use crate::vm::VirtualMachine;
// cfg from nix
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "linux",
target_os = "openbsd"
))]
// just to avoid unused import warnings
#[cfg(unix)]
use crate::pyobject::PyIterable;
#[cfg(unix)]
use std::convert::TryFrom;
#[cfg(windows)]
pub const MODULE_NAME: &str = "nt";
@@ -138,6 +135,11 @@ impl PyPathLike {
mode: OutputMode::String,
}
}
#[cfg(windows)]
pub fn wide(&self) -> Vec<u16> {
use std::os::windows::ffi::OsStrExt;
self.path.encode_wide().chain(std::iter::once(0)).collect()
}
}
impl TryFromObject for PyPathLike {
@@ -412,11 +414,9 @@ fn getgroups() -> nix::Result<Vec<Gid>> {
}
#[cfg(unix)]
fn os_access(path: PyStringRef, mode: u8, vm: &VirtualMachine) -> PyResult<bool> {
fn os_access(path: PyPathLike, mode: u8, vm: &VirtualMachine) -> PyResult<bool> {
use std::os::unix::fs::MetadataExt;
let path = path.as_str();
let flags = AccessFlags::from_bits(mode).ok_or_else(|| {
vm.new_value_error(
"One of the flags is wrong, there are only 4 possibilities F_OK, R_OK, W_OK and X_OK"
@@ -424,7 +424,7 @@ fn os_access(path: PyStringRef, mode: u8, vm: &VirtualMachine) -> PyResult<bool>
)
})?;
let metadata = fs::metadata(path);
let metadata = fs::metadata(&path.path);
// if it's only checking for F_OK
if flags == AccessFlags::F_OK {
@@ -446,6 +446,19 @@ fn os_access(path: PyStringRef, mode: u8, vm: &VirtualMachine) -> PyResult<bool>
Ok(r_ok && w_ok && x_ok)
}
#[cfg(windows)]
fn os_access(path: PyPathLike, mode: u8) -> bool {
use winapi::um::{fileapi, winnt};
let attr = unsafe { fileapi::GetFileAttributesW(path.wide().as_ptr()) };
attr != fileapi::INVALID_FILE_ATTRIBUTES
&& (mode & 2 == 0
|| attr & winnt::FILE_ATTRIBUTE_READONLY == 0
|| attr & winnt::FILE_ATTRIBUTE_DIRECTORY != 0)
}
#[cfg(not(any(unix, windows)))]
fn os_access(path: PyStringRef, mode: u8, vm: &VirtualMachine) -> PyResult<bool> {
unimplemented!()
}
fn os_error(message: OptionalArg<PyStringRef>, vm: &VirtualMachine) -> PyResult {
let msg = message.map_or("".to_owned(), |msg| msg.as_str().to_owned());
@@ -1581,6 +1594,272 @@ fn os_setgroups(group_ids: PyIterable<u32>, vm: &VirtualMachine) -> PyResult<()>
ret.map_err(|err| convert_nix_error(vm, err))
}
#[cfg(unix)]
fn envp_from_dict(dict: PyDictRef, vm: &VirtualMachine) -> PyResult<Vec<ffi::CString>> {
use std::os::unix::ffi::OsStringExt;
dict.into_iter()
.map(|(k, v)| {
let k = PyPathLike::try_from_object(vm, k)?.path.into_vec();
let v = PyPathLike::try_from_object(vm, v)?.path.into_vec();
if k.contains(&0) {
return Err(
vm.new_value_error("envp dict key cannot contain a nul byte".to_owned())
);
}
if k.contains(&b'=') {
return Err(
vm.new_value_error("envp dict key cannot contain a '=' character".to_owned())
);
}
if v.contains(&0) {
return Err(
vm.new_value_error("envp dict value cannot contain a nul byte".to_owned())
);
}
let mut env = k;
env.push(b'=');
env.extend(v);
Ok(unsafe { ffi::CString::from_vec_unchecked(env) })
})
.collect()
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
#[derive(FromArgs)]
struct PosixSpawnArgs {
#[pyarg(positional_only)]
path: PyPathLike,
#[pyarg(positional_only)]
args: PyIterable<PyPathLike>,
#[pyarg(positional_only)]
env: PyMapping,
#[pyarg(keyword_only, default = "None")]
file_actions: Option<PyIterable<PyTupleRef>>,
#[pyarg(keyword_only, default = "None")]
setsigdef: Option<PyIterable<i32>>,
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
#[repr(i32)]
enum PosixSpawnFileActionIdentifier {
Open,
Close,
Dup2,
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
impl PosixSpawnArgs {
fn spawn(self, spawnp: bool, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
use std::os::unix::ffi::OsStringExt;
let path = ffi::CString::new(self.path.path.into_vec())
.map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))?;
let mut file_actions = unsafe {
let mut fa = std::mem::MaybeUninit::uninit();
assert!(libc::posix_spawn_file_actions_init(fa.as_mut_ptr()) == 0);
fa.assume_init()
};
if let Some(it) = self.file_actions {
for action in it.iter(vm)? {
let action = action?;
let (id, args) = action.as_slice().split_first().ok_or_else(|| {
vm.new_type_error(
"Each file_actions element must be a non-empty tuple".to_owned(),
)
})?;
let id = i32::try_from_object(vm, id.clone())?;
let id = PosixSpawnFileActionIdentifier::try_from(id)
.map_err(|_| vm.new_type_error("Unknown file_actions identifier".to_owned()))?;
let args = PyFuncArgs::from(args.to_vec());
let ret = match id {
PosixSpawnFileActionIdentifier::Open => {
let (fd, path, oflag, mode): (_, PyPathLike, _, _) = args.bind(vm)?;
let path = ffi::CString::new(path.path.into_vec()).map_err(|_| {
vm.new_value_error(
"POSIX_SPAWN_OPEN path should not have nul bytes".to_owned(),
)
})?;
unsafe {
libc::posix_spawn_file_actions_addopen(
&mut file_actions,
fd,
path.as_ptr(),
oflag,
mode,
)
}
}
PosixSpawnFileActionIdentifier::Close => {
let (fd,) = args.bind(vm)?;
unsafe { libc::posix_spawn_file_actions_addclose(&mut file_actions, fd) }
}
PosixSpawnFileActionIdentifier::Dup2 => {
let (fd, newfd) = args.bind(vm)?;
unsafe {
libc::posix_spawn_file_actions_adddup2(&mut file_actions, fd, newfd)
}
}
};
if ret != 0 {
return Err(errno_err(vm));
}
}
}
let mut attrp = unsafe {
let mut sa = std::mem::MaybeUninit::uninit();
assert!(libc::posix_spawnattr_init(sa.as_mut_ptr()) == 0);
sa.assume_init()
};
if let Some(sigs) = self.setsigdef {
use nix::sys::signal;
let mut set = signal::SigSet::empty();
for sig in sigs.iter(vm)? {
let sig = sig?;
let sig = signal::Signal::try_from(sig).map_err(|_| {
vm.new_value_error(format!("signal number {} out of range", sig))
})?;
set.add(sig);
}
assert!(unsafe { libc::posix_spawnattr_setsigdefault(&mut attrp, set.as_ref()) } == 0);
}
let mut args: Vec<ffi::CString> = self
.args
.iter(vm)?
.map(|res| {
ffi::CString::new(res?.path.into_vec())
.map_err(|_| vm.new_value_error("path should not have nul bytes".to_owned()))
})
.collect::<Result<_, _>>()?;
let argv: Vec<*mut libc::c_char> = args
.iter_mut()
.map(|s| s.as_ptr() as _)
.chain(std::iter::once(std::ptr::null_mut()))
.collect();
let mut env = envp_from_dict(self.env.into_dict(), vm)?;
let envp: Vec<*mut libc::c_char> = env
.iter_mut()
.map(|s| s.as_ptr() as _)
.chain(std::iter::once(std::ptr::null_mut()))
.collect();
let mut pid = 0;
let ret = unsafe {
if spawnp {
libc::posix_spawnp(
&mut pid,
path.as_ptr(),
&file_actions,
&attrp,
argv.as_ptr(),
envp.as_ptr(),
)
} else {
libc::posix_spawn(
&mut pid,
path.as_ptr(),
&file_actions,
&attrp,
argv.as_ptr(),
envp.as_ptr(),
)
}
};
if ret == 0 {
Ok(pid)
} else {
Err(errno_err(vm))
}
}
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
fn os_posix_spawn(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
args.spawn(false, vm)
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
fn os_posix_spawnp(args: PosixSpawnArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
args.spawn(true, vm)
}
#[cfg(unix)]
fn os_wifsignaled(status: i32) -> bool {
unsafe { libc::WIFSIGNALED(status) }
}
#[cfg(unix)]
fn os_wifstopped(status: i32) -> bool {
unsafe { libc::WIFSTOPPED(status) }
}
#[cfg(unix)]
fn os_wifexited(status: i32) -> bool {
unsafe { libc::WIFEXITED(status) }
}
#[cfg(unix)]
fn os_wtermsig(status: i32) -> i32 {
unsafe { libc::WTERMSIG(status) }
}
#[cfg(unix)]
fn os_wstopsig(status: i32) -> i32 {
unsafe { libc::WSTOPSIG(status) }
}
#[cfg(unix)]
fn os_wexitstatus(status: i32) -> i32 {
unsafe { libc::WEXITSTATUS(status) }
}
// TODO: os.wait[pid] for windows
#[cfg(unix)]
fn os_waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> {
let mut status = 0;
let pid = unsafe { libc::waitpid(pid, &mut status, opt) };
let pid = Errno::result(pid).map_err(|e| convert_nix_error(vm, e))?;
Ok((pid, status))
}
#[cfg(unix)]
fn os_wait(vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> {
os_waitpid(-1, 0, vm)
}
fn os_kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> {
#[cfg(unix)]
{
let ret = unsafe { libc::kill(pid, sig as i32) };
if ret == -1 {
Err(errno_err(vm))
} else {
Ok(())
}
}
#[cfg(windows)]
{
use winapi::um::{handleapi, processthreadsapi, wincon, winnt};
let sig = sig as u32;
let pid = pid as u32;
if sig == wincon::CTRL_C_EVENT || sig == wincon::CTRL_BREAK_EVENT {
let ret = unsafe { wincon::GenerateConsoleCtrlEvent(sig, pid) };
let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) };
return res;
}
let h = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_ALL_ACCESS, 0, pid) };
if h.is_null() {
return Err(errno_err(vm));
}
let ret = unsafe { processthreadsapi::TerminateProcess(h, sig) };
let res = if ret == 0 { Err(errno_err(vm)) } else { Ok(()) };
unsafe { handleapi::CloseHandle(h) };
res
}
#[cfg(not(any(unix, windows)))]
{
unimplemented!()
}
}
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
let ctx = &vm.ctx;
@@ -1632,7 +1911,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
#[allow(unused_mut)]
let mut support_funcs = vec![
SupportFunc::new(vm, "open", os_open, None, Some(false), None),
// access Some Some None
SupportFunc::new(vm, "access", os_access, Some(false), Some(false), None),
SupportFunc::new(vm, "chdir", os_chdir, Some(false), None, None),
// chflags Some, None Some
// chown Some Some Some
@@ -1690,6 +1969,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
"lseek" => ctx.new_function(os_lseek),
"set_inheritable" => ctx.new_function(os_set_inheritable),
"link" => ctx.new_function(os_link),
"kill" => ctx.new_function(os_kill),
"O_RDONLY" => ctx.new_int(libc::O_RDONLY),
"O_WRONLY" => ctx.new_int(libc::O_WRONLY),
@@ -1748,7 +2028,6 @@ fn extend_module_platform_specific(vm: &VirtualMachine, module: &PyObjectRef) {
let uname_result = UnameResult::make_class(ctx);
extend_module!(vm, module, {
"access" => ctx.new_function(os_access),
"chmod" => ctx.new_function(os_chmod),
"get_inheritable" => ctx.new_function(os_get_inheritable), // TODO: windows
"get_blocking" => ctx.new_function(os_get_blocking),
@@ -1769,6 +2048,15 @@ fn extend_module_platform_specific(vm: &VirtualMachine, module: &PyObjectRef) {
"ttyname" => ctx.new_function(os_ttyname),
"uname" => ctx.new_function(os_uname),
"uname_result" => uname_result,
"wait" => ctx.new_function(os_wait),
"waitpid" => ctx.new_function(os_waitpid),
"WIFSIGNALED" => ctx.new_function(os_wifsignaled),
"WIFSTOPPED" => ctx.new_function(os_wifstopped),
"WIFEXITED" => ctx.new_function(os_wifexited),
"WTERMSIG" => ctx.new_function(os_wtermsig),
"WSTOPSIG" => ctx.new_function(os_wstopsig),
"WEXITSTATUS" => ctx.new_function(os_wexitstatus),
"WNOHANG" => ctx.new_int(libc::WNOHANG),
"EX_OK" => ctx.new_int(exitcode::OK as i8),
"EX_USAGE" => ctx.new_int(exitcode::USAGE as i8),
"EX_DATAERR" => ctx.new_int(exitcode::DATAERR as i8),
@@ -1846,6 +2134,15 @@ fn extend_module_platform_specific(vm: &VirtualMachine, module: &PyObjectRef) {
extend_module!(vm, module, {
"pipe2" => ctx.new_function(os_pipe2),
});
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))]
extend_module!(vm, module, {
"posix_spawn" => ctx.new_function(os_posix_spawn),
"posix_spawnp" => ctx.new_function(os_posix_spawnp),
"POSIX_SPAWN_OPEN" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Open)),
"POSIX_SPAWN_CLOSE" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Close)),
"POSIX_SPAWN_DUP2" => ctx.new_int(i32::from(PosixSpawnFileActionIdentifier::Dup2)),
});
}
#[cfg(windows)]