mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Merge pull request #1962 from RustPython/coolreader18/subprocess-py
Implement subprocess using _posixsubprocess and _winapi
This commit is contained in:
19 files changed
+2290
-470
No files matched your search
Generated
+1
-11
@@ -1633,6 +1633,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"byteorder 1.3.4",
|
||||
"caseless",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"crc",
|
||||
"crc32fast",
|
||||
@@ -1694,7 +1695,6 @@ dependencies = [
|
||||
"smallbox",
|
||||
"socket2",
|
||||
"statrs",
|
||||
"subprocess",
|
||||
"thread_local",
|
||||
"uname",
|
||||
"unic-bidi",
|
||||
@@ -1962,16 +1962,6 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "subprocess"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5109e3c3215ce5339ca9766a6db7f39144ccc7d9bad53b96d65cfb14ae6c2985"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -9,8 +9,7 @@ if sys.platform != 'win32': # pragma: no cover
|
||||
|
||||
import _winapi
|
||||
import itertools
|
||||
# XXX RustPython TODO: msvcrt
|
||||
# import msvcrt
|
||||
import msvcrt
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
Vendored
+3
-4
@@ -281,10 +281,9 @@ def _syscmd_ver(system='', release='', version='',
|
||||
for cmd in ('ver', 'command /c ver', 'cmd /c ver'):
|
||||
try:
|
||||
info = subprocess.check_output(cmd,
|
||||
stderr=subprocess.DEVNULL,)
|
||||
# XXX RustPython TODO: more Popen args
|
||||
# text=True,
|
||||
# shell=True)
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
shell=True)
|
||||
except (OSError, subprocess.CalledProcessError) as why:
|
||||
#print('Command %s failed: %s' % (cmd, why))
|
||||
continue
|
||||
|
||||
Vendored
+1540
-99
File diff suppressed because it is too large.
Load diff
@@ -2442,7 +2442,8 @@ def strip_python_stderr(stderr):
|
||||
This will typically be run on the result of the communicate() method
|
||||
of a subprocess.Popen object.
|
||||
"""
|
||||
stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?", b"", stderr).strip()
|
||||
# XXX RustPython TODO: bytes regexes
|
||||
# stderr = re.sub(br"\[\d+ refs, \d+ blocks\]\r?\n?", b"", stderr).strip()
|
||||
return stderr
|
||||
|
||||
requires_type_collecting = unittest.skipIf(hasattr(sys, 'getcounts'),
|
||||
|
||||
@@ -7,7 +7,7 @@ from test import support
|
||||
from test.support.script_helper import assert_python_ok
|
||||
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON") # Need to fix subprocess to take env
|
||||
@unittest.skip("TODO: RUSTPYTHON, rustpython -X faulthandler")
|
||||
class TestTool(unittest.TestCase):
|
||||
data = """
|
||||
|
||||
|
||||
@@ -5,7 +5,20 @@ import signal
|
||||
|
||||
from testutils import assert_raises
|
||||
|
||||
p = subprocess.Popen(["echo", "test"])
|
||||
is_unix = not sys.platform.startswith("win")
|
||||
if is_unix:
|
||||
def echo(text):
|
||||
return ["echo", text]
|
||||
def sleep(secs):
|
||||
return ["sleep", str(secs)]
|
||||
else:
|
||||
def echo(text):
|
||||
return ["cmd", "/C", f"echo {text}"]
|
||||
def sleep(secs):
|
||||
# TODO: make work in a non-unixy environment (something with timeout.exe?)
|
||||
return ["sleep", str(secs)]
|
||||
|
||||
p = subprocess.Popen(echo("test"))
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
@@ -14,7 +27,7 @@ assert p.returncode is None
|
||||
assert p.poll() == 0
|
||||
assert p.returncode == 0
|
||||
|
||||
p = subprocess.Popen(["sleep", "2"])
|
||||
p = subprocess.Popen(sleep(2))
|
||||
|
||||
assert p.poll() is None
|
||||
|
||||
@@ -25,14 +38,13 @@ p.wait()
|
||||
|
||||
assert p.returncode == 0
|
||||
|
||||
p = subprocess.Popen(["echo", "test"], stdout=subprocess.PIPE)
|
||||
p = subprocess.Popen(echo("test"), stdout=subprocess.PIPE)
|
||||
p.wait()
|
||||
|
||||
is_unix = "win" not in sys.platform or "darwin" in sys.platform
|
||||
|
||||
assert p.stdout.read().strip() == b"test"
|
||||
|
||||
p = subprocess.Popen(["sleep", "2"])
|
||||
p = subprocess.Popen(sleep(2))
|
||||
p.terminate()
|
||||
p.wait()
|
||||
if is_unix:
|
||||
@@ -40,7 +52,7 @@ if is_unix:
|
||||
else:
|
||||
assert p.returncode == 1
|
||||
|
||||
p = subprocess.Popen(["sleep", "2"])
|
||||
p = subprocess.Popen(sleep(2))
|
||||
p.kill()
|
||||
p.wait()
|
||||
if is_unix:
|
||||
@@ -48,10 +60,10 @@ if is_unix:
|
||||
else:
|
||||
assert p.returncode == 1
|
||||
|
||||
p = subprocess.Popen(["echo", "test"], stdout=subprocess.PIPE)
|
||||
p = subprocess.Popen(echo("test"), stdout=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
assert stdout.strip() == b"test"
|
||||
|
||||
p = subprocess.Popen(["sleep", "5"], stdout=subprocess.PIPE)
|
||||
p = subprocess.Popen(sleep(5), stdout=subprocess.PIPE)
|
||||
with assert_raises(subprocess.TimeoutExpired):
|
||||
p.communicate(timeout=1)
|
||||
+8
-2
@@ -73,6 +73,7 @@ crossbeam-utils = "0.7"
|
||||
generational-arena = "0.2"
|
||||
parking_lot = "0.11"
|
||||
thread_local = "1.0"
|
||||
cfg-if = "0.1.10"
|
||||
|
||||
## unicode stuff
|
||||
unicode_names2 = "0.4"
|
||||
@@ -101,7 +102,6 @@ uname = "0.1.1"
|
||||
crc32fast = "1.2.0"
|
||||
adler32 = "1.0.3"
|
||||
gethostname = "0.2.0"
|
||||
subprocess = "0.2.2"
|
||||
socket2 = "0.3"
|
||||
rustyline = "6.0"
|
||||
openssl = { version = "0.10", features = ["vendored"], optional = true }
|
||||
@@ -122,7 +122,13 @@ schannel = "0.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.winapi]
|
||||
version = "0.3"
|
||||
features = ["winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt", "fileapi", "wincon"]
|
||||
features = [
|
||||
"winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt", "fileapi", "processenv",
|
||||
"namedpipeapi", "winnt", "processthreadsapi", "errhandlingapi", "winuser", "synchapi",
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
itertools = "0.8"
|
||||
+19
@@ -1,3 +1,7 @@
|
||||
use itertools::Itertools;
|
||||
use std::env;
|
||||
use std::io::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
@@ -8,6 +12,21 @@ fn main() {
|
||||
);
|
||||
println!("cargo:rustc-env=RUSTPYTHON_GIT_TAG={}", git_tag());
|
||||
println!("cargo:rustc-env=RUSTPYTHON_GIT_BRANCH={}", git_branch());
|
||||
|
||||
println!(
|
||||
"cargo:rustc-env=RUSTPYTHON_TARGET_TRIPLE={}",
|
||||
env::var("TARGET").unwrap()
|
||||
);
|
||||
|
||||
let mut env_path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
env_path.push("env_vars.rs");
|
||||
let mut f = std::fs::File::create(env_path).unwrap();
|
||||
write!(
|
||||
f,
|
||||
"hashmap! {{ {} }}",
|
||||
std::env::vars_os().format_with(", ", |(k, v), f| f(&format_args!("{:?} => {:?}", k, v)))
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn git_hash() -> String {
|
||||
|
||||
@@ -183,6 +183,9 @@ impl PyDictRef {
|
||||
pub fn len(self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.len() == 0
|
||||
}
|
||||
|
||||
#[pymethod(magic)]
|
||||
fn sizeof(self) -> usize {
|
||||
|
||||
@@ -1383,6 +1383,23 @@ where
|
||||
|
||||
pub type PyComparisonValue = PyArithmaticValue<bool>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PySequence<T = PyObjectRef>(Vec<T>);
|
||||
|
||||
impl<T> PySequence<T> {
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
self.0
|
||||
}
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<T: TryFromObject> TryFromObject for PySequence<T> {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
vm.extract_elements(&obj).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+12
-3
@@ -30,6 +30,7 @@ pub mod socket;
|
||||
mod string;
|
||||
#[cfg(feature = "rustpython-compiler")]
|
||||
mod symtable;
|
||||
mod sysconfigdata;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod thread;
|
||||
mod time_module;
|
||||
@@ -49,6 +50,8 @@ mod faulthandler;
|
||||
mod msvcrt;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod multiprocessing;
|
||||
#[cfg(unix)]
|
||||
mod posixsubprocess;
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
|
||||
mod pwd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -57,8 +60,6 @@ mod select;
|
||||
pub mod signal;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "ssl"))]
|
||||
mod ssl;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod subprocess;
|
||||
#[cfg(windows)]
|
||||
mod winapi;
|
||||
#[cfg(windows)]
|
||||
@@ -95,6 +96,7 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
|
||||
"_imp".to_owned() => Box::new(imp::make_module),
|
||||
"unicodedata".to_owned() => Box::new(unicodedata::make_module),
|
||||
"_warnings".to_owned() => Box::new(warnings::make_module),
|
||||
crate::sysmodule::sysconfigdata_name() => Box::new(sysconfigdata::make_module),
|
||||
};
|
||||
|
||||
// Insert parser related modules:
|
||||
@@ -129,7 +131,6 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
|
||||
modules.insert("select".to_owned(), Box::new(select::make_module));
|
||||
#[cfg(feature = "ssl")]
|
||||
modules.insert("_ssl".to_owned(), Box::new(ssl::make_module));
|
||||
modules.insert("_subprocess".to_owned(), Box::new(subprocess::make_module));
|
||||
modules.insert("_thread".to_owned(), Box::new(thread::make_module));
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
modules.insert("zlib".to_owned(), Box::new(zlib::make_module));
|
||||
@@ -145,6 +146,14 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
|
||||
modules.insert("pwd".to_owned(), Box::new(pwd::make_module));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
modules.insert(
|
||||
"_posixsubprocess".to_owned(),
|
||||
Box::new(posixsubprocess::make_module),
|
||||
);
|
||||
}
|
||||
|
||||
// Windows-only
|
||||
#[cfg(windows)]
|
||||
{
|
||||
|
||||
+15
-1
@@ -59,9 +59,22 @@ fn msvcrt_setmode(fd: i32, flags: i32, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn _open_osfhandle(osfhandle: isize, flags: i32) -> i32;
|
||||
}
|
||||
|
||||
fn msvcrt_open_osfhandle(handle: isize, flags: i32, vm: &VirtualMachine) -> PyResult<i32> {
|
||||
let ret = unsafe { suppress_iph!(_open_osfhandle(handle, flags)) };
|
||||
if ret == -1 {
|
||||
Err(errno_err(vm))
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
py_module!(vm, "_msvcrt", {
|
||||
py_module!(vm, "msvcrt", {
|
||||
"getch" => ctx.new_function(msvcrt_getch),
|
||||
"getwch" => ctx.new_function(msvcrt_getwch),
|
||||
"getche" => ctx.new_function(msvcrt_getche),
|
||||
@@ -69,5 +82,6 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
"putch" => ctx.new_function(msvcrt_putch),
|
||||
"putwch" => ctx.new_function(msvcrt_putwch),
|
||||
"setmode" => ctx.new_function(msvcrt_setmode),
|
||||
"open_osfhandle" => ctx.new_function(msvcrt_open_osfhandle),
|
||||
})
|
||||
}
|
||||
+26
-18
@@ -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, PyMapping};
|
||||
use crate::obj::objdict::PyDictRef;
|
||||
use crate::obj::objint::PyIntRef;
|
||||
use crate::obj::objiter;
|
||||
use crate::obj::objset::PySet;
|
||||
@@ -43,6 +43,8 @@ use crate::vm::VirtualMachine;
|
||||
|
||||
// just to avoid unused import warnings
|
||||
#[cfg(unix)]
|
||||
use crate::obj::objdict::PyMapping;
|
||||
#[cfg(unix)]
|
||||
use crate::pyobject::PyIterable;
|
||||
#[cfg(unix)]
|
||||
use std::convert::TryFrom;
|
||||
@@ -119,12 +121,12 @@ enum OutputMode {
|
||||
fn output_by_mode(val: String, mode: OutputMode, vm: &VirtualMachine) -> PyObjectRef {
|
||||
match mode {
|
||||
OutputMode::String => vm.ctx.new_str(val),
|
||||
OutputMode::Bytes => vm.ctx.new_bytes(val.as_bytes().to_vec()),
|
||||
OutputMode::Bytes => vm.ctx.new_bytes(val.into_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PyPathLike {
|
||||
path: ffi::OsString,
|
||||
pub path: ffi::OsString,
|
||||
mode: OutputMode,
|
||||
}
|
||||
|
||||
@@ -1036,6 +1038,19 @@ fn os_get_inheritable(fd: RawFd, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn set_inheritable(fd: RawFd, inheritable: bool) -> nix::Result<()> {
|
||||
use nix::fcntl;
|
||||
|
||||
let flags = fcntl::FdFlag::from_bits_truncate(fcntl::fcntl(fd, fcntl::FcntlArg::F_GETFD)?);
|
||||
let mut new_flags = flags;
|
||||
new_flags.set(fcntl::FdFlag::FD_CLOEXEC, !inheritable);
|
||||
if flags != new_flags {
|
||||
fcntl::fcntl(fd, fcntl::FcntlArg::F_SETFD(new_flags))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn os_set_inheritable(fd: i64, inheritable: bool, vm: &VirtualMachine) -> PyResult<()> {
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
{
|
||||
@@ -1043,21 +1058,7 @@ fn os_set_inheritable(fd: i64, inheritable: bool, vm: &VirtualMachine) -> PyResu
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let fd = fd as RawFd;
|
||||
let _set_flag = || {
|
||||
use nix::fcntl::fcntl;
|
||||
use nix::fcntl::FcntlArg;
|
||||
use nix::fcntl::FdFlag;
|
||||
|
||||
let flags = FdFlag::from_bits_truncate(fcntl(fd, FcntlArg::F_GETFD)?);
|
||||
let mut new_flags = flags;
|
||||
new_flags.set(FdFlag::from_bits_truncate(libc::FD_CLOEXEC), !inheritable);
|
||||
if flags != new_flags {
|
||||
fcntl(fd, FcntlArg::F_SETFD(new_flags))?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
_set_flag().or_else(|err| Err(convert_nix_error(vm, err)))
|
||||
set_inheritable(fd as RawFd, inheritable).map_err(|err| convert_nix_error(vm, err))
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -1860,6 +1861,12 @@ fn os_kill(pid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn os_strerror(e: i32) -> String {
|
||||
unsafe { ffi::CStr::from_ptr(libc::strerror(e)) }
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
|
||||
@@ -1970,6 +1977,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
"set_inheritable" => ctx.new_function(os_set_inheritable),
|
||||
"link" => ctx.new_function(os_link),
|
||||
"kill" => ctx.new_function(os_kill),
|
||||
"strerror" => ctx.new_function(os_strerror),
|
||||
|
||||
"O_RDONLY" => ctx.new_int(libc::O_RDONLY),
|
||||
"O_WRONLY" => ctx.new_int(libc::O_WRONLY),
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
use nix::{dir, errno::Errno, fcntl, unistd};
|
||||
use std::convert::Infallible as Never;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::{self, prelude::*};
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
use crate::pyobject::{PyObjectRef, PyResult, PySequence, TryFromObject};
|
||||
use crate::VirtualMachine;
|
||||
|
||||
use super::os::{convert_nix_error, set_inheritable, PyPathLike};
|
||||
|
||||
macro_rules! gen_args {
|
||||
($($field:ident: $t:ty),*$(,)?) => {
|
||||
#[derive(FromArgs)]
|
||||
struct ForkExecArgs {
|
||||
$(#[pyarg(positional_only)] $field: $t,)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct CStrPathLike {
|
||||
s: CString,
|
||||
}
|
||||
impl TryFromObject for CStrPathLike {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
let s = PyPathLike::try_from_object(vm, obj)?.path.into_vec();
|
||||
let s = CString::new(s)
|
||||
.map_err(|_| vm.new_value_error("embedded null character".to_owned()))?;
|
||||
Ok(CStrPathLike { s })
|
||||
}
|
||||
}
|
||||
|
||||
gen_args! {
|
||||
args: PySequence<CStrPathLike> /* list */, exec_list: PySequence<CStrPathLike> /* list */,
|
||||
close_fds: bool, fds_to_keep: PySequence<i32>,
|
||||
cwd: Option<CStrPathLike>, env_list: Option<PySequence<CStrPathLike>>,
|
||||
p2cread: i32, p2cwrite: i32, c2pread: i32, c2pwrite: i32,
|
||||
errread: i32, errwrite: i32, errpipe_read: i32, errpipe_write: i32,
|
||||
restore_signals: bool, call_setsid: bool, preexec_fn: Option<PyObjectRef>,
|
||||
}
|
||||
|
||||
fn _posixsubprocess_fork_exec(args: ForkExecArgs, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
|
||||
if args.preexec_fn.is_some() {
|
||||
return Err(vm.new_not_implemented_error("preexec_fn not supported yet".to_owned()));
|
||||
}
|
||||
let cstrs_to_ptrs = |cstrs: &[CStrPathLike]| {
|
||||
cstrs
|
||||
.iter()
|
||||
.map(|s| s.s.as_ptr())
|
||||
.chain(std::iter::once(std::ptr::null()))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let argv = cstrs_to_ptrs(args.args.as_slice());
|
||||
let argv = &argv;
|
||||
let envp = args.env_list.as_ref().map(|s| cstrs_to_ptrs(s.as_slice()));
|
||||
let envp = envp.as_deref();
|
||||
match nix::unistd::fork().map_err(|e| convert_nix_error(vm, e))? {
|
||||
nix::unistd::ForkResult::Child => exec(&args, ProcArgs { argv, envp }),
|
||||
nix::unistd::ForkResult::Parent { child } => Ok(child.as_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
// can't reallocate inside of exec(), so we reallocate prior to fork() and pass this along
|
||||
struct ProcArgs<'a> {
|
||||
argv: &'a [*const libc::c_char],
|
||||
envp: Option<&'a [*const libc::c_char]>,
|
||||
}
|
||||
|
||||
fn exec(args: &ForkExecArgs, procargs: ProcArgs) -> ! {
|
||||
match exec_inner(args, procargs) {
|
||||
Ok(x) => match x {},
|
||||
Err(e) => {
|
||||
let e = e.as_errno().expect("got a non-errno nix error");
|
||||
let buf: &mut [u8] = &mut [0; 256];
|
||||
let mut cur = io::Cursor::new(&mut *buf);
|
||||
// TODO: check if reached preexec, if not then have "noexec" after
|
||||
let _ = write!(cur, "OSError:{}:", e as i32);
|
||||
let pos = cur.position();
|
||||
let _ = unistd::write(args.errpipe_write, &buf[..pos as usize]);
|
||||
std::process::exit(255)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_inner(args: &ForkExecArgs, procargs: ProcArgs) -> nix::Result<Never> {
|
||||
for &fd in args.fds_to_keep.as_slice() {
|
||||
if fd != args.errpipe_write {
|
||||
set_inheritable(fd, true)?
|
||||
}
|
||||
}
|
||||
|
||||
for &fd in &[args.p2cwrite, args.c2pread, args.errread] {
|
||||
if fd != -1 {
|
||||
unistd::close(fd)?;
|
||||
}
|
||||
}
|
||||
unistd::close(args.errpipe_read)?;
|
||||
|
||||
let c2pwrite = if args.c2pwrite == 0 {
|
||||
let fd = unistd::dup(args.c2pwrite)?;
|
||||
set_inheritable(fd, true)?;
|
||||
fd
|
||||
} else {
|
||||
args.c2pwrite
|
||||
};
|
||||
|
||||
let mut errwrite = args.errwrite;
|
||||
while errwrite == 0 || errwrite == 1 {
|
||||
errwrite = unistd::dup(errwrite)?;
|
||||
set_inheritable(errwrite, true)?;
|
||||
}
|
||||
|
||||
let dup_into_stdio = |fd, io_fd| {
|
||||
if fd == io_fd {
|
||||
set_inheritable(fd, true)
|
||||
} else if fd != -1 {
|
||||
unistd::dup2(fd, io_fd).map(drop)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
dup_into_stdio(args.p2cread, 0)?;
|
||||
dup_into_stdio(c2pwrite, 1)?;
|
||||
dup_into_stdio(errwrite, 2)?;
|
||||
|
||||
if let Some(ref cwd) = args.cwd {
|
||||
unistd::chdir(cwd.s.as_c_str())?
|
||||
}
|
||||
|
||||
if args.restore_signals {
|
||||
// TODO: restore signals SIGPIPE, SIGXFZ, SIGXFSZ to SIG_DFL
|
||||
}
|
||||
|
||||
if args.call_setsid {
|
||||
unistd::setsid()?;
|
||||
}
|
||||
|
||||
if args.close_fds {
|
||||
close_fds(3, args.fds_to_keep.as_slice())?;
|
||||
}
|
||||
|
||||
let mut first_err = None;
|
||||
for exec in args.exec_list.as_slice() {
|
||||
if let Some(envp) = procargs.envp {
|
||||
unsafe { libc::execve(exec.s.as_ptr(), procargs.argv.as_ptr(), envp.as_ptr()) };
|
||||
} else {
|
||||
unsafe { libc::execv(exec.s.as_ptr(), procargs.argv.as_ptr()) };
|
||||
}
|
||||
let e = Errno::last();
|
||||
if e != Errno::ENOENT && e != Errno::ENOTDIR && first_err.is_none() {
|
||||
first_err = Some(e)
|
||||
}
|
||||
}
|
||||
Err(first_err.unwrap_or_else(Errno::last).into())
|
||||
}
|
||||
|
||||
fn close_fds(above: i32, keep: &[i32]) -> nix::Result<()> {
|
||||
// TODO: close fds by brute force if readdir doesn't work:
|
||||
// https://github.com/python/cpython/blob/3.8/Modules/_posixsubprocess.c#L220
|
||||
let path = unsafe { CStr::from_bytes_with_nul_unchecked(FD_DIR_NAME) };
|
||||
let mut dir = dir::Dir::open(
|
||||
path,
|
||||
fcntl::OFlag::O_RDONLY | fcntl::OFlag::O_DIRECTORY,
|
||||
nix::sys::stat::Mode::empty(),
|
||||
)?;
|
||||
for e in dir.iter() {
|
||||
if let Some(fd) = pos_int_from_ascii(e?.file_name()) {
|
||||
if fd > above && !keep.contains(&fd) {
|
||||
unistd::close(fd)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "macos",
|
||||
))]
|
||||
const FD_DIR_NAME: &[u8] = b"/dev/fd\0";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const FD_DIR_NAME: &[u8] = b"/proc/self/fd\0";
|
||||
|
||||
fn pos_int_from_ascii(name: &CStr) -> Option<i32> {
|
||||
let mut num = 0;
|
||||
for c in name.to_bytes() {
|
||||
if !c.is_ascii_digit() {
|
||||
return None;
|
||||
}
|
||||
num = num * 10 + i32::from(c - b'0')
|
||||
}
|
||||
Some(num)
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
py_module!(vm, "_posixsubprocess", {
|
||||
"fork_exec" => named_function!(ctx, _posixsubprocess, fork_exec),
|
||||
})
|
||||
}
|
||||
@@ -1,295 +0,0 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::function::OptionalArg;
|
||||
use crate::obj::objbytes::PyBytesRef;
|
||||
use crate::obj::objlist::PyListRef;
|
||||
use crate::obj::objstr::{self, PyStringRef};
|
||||
use crate::obj::objtype::PyClassRef;
|
||||
use crate::pyobject::{Either, IntoPyObject, PyObjectRef, PyRef, PyResult, PyValue};
|
||||
use crate::stdlib::io::io_open;
|
||||
use crate::stdlib::os::{convert_io_error, raw_file_number, rust_file};
|
||||
use crate::vm::VirtualMachine;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Popen {
|
||||
process: RwLock<subprocess::Popen>,
|
||||
args: PyObjectRef,
|
||||
}
|
||||
|
||||
// Remove once https://github.com/hniksic/rust-subprocess/issues/42 is resolved
|
||||
#[cfg(windows)]
|
||||
unsafe impl Sync for Popen {}
|
||||
|
||||
impl PyValue for Popen {
|
||||
fn class(vm: &VirtualMachine) -> PyClassRef {
|
||||
vm.class("_subprocess", "Popen")
|
||||
}
|
||||
}
|
||||
|
||||
type PopenRef = PyRef<Popen>;
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[allow(dead_code)]
|
||||
struct PopenArgs {
|
||||
#[pyarg(positional_only)]
|
||||
args: Either<PyStringRef, PyListRef>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
stdin: Option<i64>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
stdout: Option<i64>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
stderr: Option<i64>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
close_fds: Option<bool>, // TODO: use these unused options
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
cwd: Option<PyStringRef>,
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
start_new_session: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct PopenWaitArgs {
|
||||
#[pyarg(positional_or_keyword, default = "None")]
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl IntoPyObject for subprocess::ExitStatus {
|
||||
fn into_pyobject(self, vm: &VirtualMachine) -> PyResult {
|
||||
let status: i32 = match self {
|
||||
subprocess::ExitStatus::Exited(status) => status as i32,
|
||||
subprocess::ExitStatus::Signaled(status) => -i32::from(status),
|
||||
subprocess::ExitStatus::Other(status) => status as i32,
|
||||
_ => return Err(vm.new_os_error("Unknown exist status".to_owned())),
|
||||
};
|
||||
Ok(vm.new_int(status))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const NULL_DEVICE: &str = "nul";
|
||||
#[cfg(unix)]
|
||||
const NULL_DEVICE: &str = "/dev/null";
|
||||
|
||||
fn convert_redirection(arg: Option<i64>, vm: &VirtualMachine) -> PyResult<subprocess::Redirection> {
|
||||
match arg {
|
||||
Some(fd) => match fd {
|
||||
-1 => Ok(subprocess::Redirection::Pipe),
|
||||
-2 => Ok(subprocess::Redirection::Merge),
|
||||
-3 => Ok(subprocess::Redirection::File(
|
||||
File::open(NULL_DEVICE).unwrap(),
|
||||
)),
|
||||
fd => {
|
||||
if fd < 0 {
|
||||
Err(vm.new_value_error(format!("Invalid fd: {}", fd)))
|
||||
} else {
|
||||
Ok(subprocess::Redirection::File(rust_file(fd)))
|
||||
}
|
||||
}
|
||||
},
|
||||
None => Ok(subprocess::Redirection::None),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_to_file_io(file: &Option<File>, mode: &str, vm: &VirtualMachine) -> PyResult {
|
||||
match file {
|
||||
Some(ref stdin) => io_open(
|
||||
vm.new_int(raw_file_number(stdin.try_clone().unwrap())),
|
||||
Some(mode),
|
||||
Default::default(),
|
||||
vm,
|
||||
),
|
||||
None => Ok(vm.get_none()),
|
||||
}
|
||||
}
|
||||
|
||||
impl PopenRef {
|
||||
fn borrow_process(&self) -> RwLockReadGuard<'_, subprocess::Popen> {
|
||||
self.process.read().unwrap()
|
||||
}
|
||||
|
||||
fn borrow_process_mut(&self) -> RwLockWriteGuard<'_, subprocess::Popen> {
|
||||
self.process.write().unwrap()
|
||||
}
|
||||
|
||||
fn new(cls: PyClassRef, args: PopenArgs, vm: &VirtualMachine) -> PyResult<PopenRef> {
|
||||
let stdin = convert_redirection(args.stdin, vm)?;
|
||||
let stdout = convert_redirection(args.stdout, vm)?;
|
||||
let stderr = convert_redirection(args.stderr, vm)?;
|
||||
let command_list = match &args.args {
|
||||
Either::A(command) => vec![command.as_str().to_owned()],
|
||||
Either::B(command_list) => command_list
|
||||
.borrow_elements()
|
||||
.iter()
|
||||
.map(|x| objstr::clone_value(x))
|
||||
.collect(),
|
||||
};
|
||||
let cwd = args.cwd.map(|x| OsString::from(x.as_str()));
|
||||
|
||||
let process = subprocess::Popen::create(
|
||||
&command_list,
|
||||
subprocess::PopenConfig {
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
cwd,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.map_err(|s| vm.new_os_error(format!("Could not start program: {}", s)))?;
|
||||
|
||||
Popen {
|
||||
process: RwLock::new(process),
|
||||
args: args.args.into_object(),
|
||||
}
|
||||
.into_ref_with_type(vm, cls)
|
||||
}
|
||||
|
||||
fn poll(self) -> Option<subprocess::ExitStatus> {
|
||||
self.borrow_process_mut().poll()
|
||||
}
|
||||
|
||||
fn return_code(self) -> Option<subprocess::ExitStatus> {
|
||||
self.borrow_process().exit_status()
|
||||
}
|
||||
|
||||
fn wait(self, args: PopenWaitArgs, vm: &VirtualMachine) -> PyResult<i64> {
|
||||
let timeout = match args.timeout {
|
||||
Some(timeout) => self
|
||||
.borrow_process_mut()
|
||||
.wait_timeout(Duration::new(timeout, 0)),
|
||||
None => self.borrow_process_mut().wait().map(Some),
|
||||
}
|
||||
.map_err(|s| vm.new_os_error(format!("Could not start program: {}", s)))?;
|
||||
if let Some(exit) = timeout {
|
||||
use subprocess::ExitStatus::*;
|
||||
Ok(match exit {
|
||||
Exited(i) => i.into(),
|
||||
Signaled(s) => -i64::from(s),
|
||||
_ => unreachable!("should not occur in normal operation"),
|
||||
})
|
||||
} else {
|
||||
let timeout_expired = vm.try_class("_subprocess", "TimeoutExpired")?;
|
||||
Err(vm.new_exception_msg(timeout_expired, "Timeout".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
fn stdin(self, vm: &VirtualMachine) -> PyResult {
|
||||
convert_to_file_io(&self.borrow_process().stdin, "wb", vm)
|
||||
}
|
||||
|
||||
fn stdout(self, vm: &VirtualMachine) -> PyResult {
|
||||
convert_to_file_io(&self.borrow_process().stdout, "rb", vm)
|
||||
}
|
||||
|
||||
fn stderr(self, vm: &VirtualMachine) -> PyResult {
|
||||
convert_to_file_io(&self.borrow_process().stderr, "rb", vm)
|
||||
}
|
||||
|
||||
fn terminate(self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.borrow_process_mut()
|
||||
.terminate()
|
||||
.map_err(|err| convert_io_error(vm, err))
|
||||
}
|
||||
|
||||
fn kill(self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
self.borrow_process_mut()
|
||||
.kill()
|
||||
.map_err(|err| convert_io_error(vm, err))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn communicate(
|
||||
self,
|
||||
args: PopenCommunicateArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(Option<Vec<u8>>, Option<Vec<u8>>)> {
|
||||
let bytes = match args.input {
|
||||
OptionalArg::Present(ref bytes) => Some(bytes.get_value().to_vec()),
|
||||
OptionalArg::Missing => None,
|
||||
};
|
||||
let mut communicator = self.borrow_process_mut().communicate_start(bytes);
|
||||
if let OptionalArg::Present(timeout) = args.timeout {
|
||||
communicator = communicator.limit_time(Duration::new(timeout, 0));
|
||||
}
|
||||
communicator.read().map_err(|err| {
|
||||
if err.error.kind() == ErrorKind::TimedOut {
|
||||
let timeout_expired = vm.try_class("_subprocess", "TimeoutExpired").unwrap();
|
||||
vm.new_exception_msg(timeout_expired, "Timeout".to_owned())
|
||||
} else {
|
||||
convert_io_error(vm, err.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn pid(self) -> Option<u32> {
|
||||
self.borrow_process().pid()
|
||||
}
|
||||
|
||||
fn enter(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
fn exit(
|
||||
self,
|
||||
_exception_type: PyObjectRef,
|
||||
_exception_value: PyObjectRef,
|
||||
_traceback: PyObjectRef,
|
||||
) {
|
||||
let mut process = self.borrow_process_mut();
|
||||
process.stdout.take();
|
||||
process.stdin.take();
|
||||
process.stderr.take();
|
||||
}
|
||||
|
||||
fn args(self) -> PyObjectRef {
|
||||
self.args.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
#[allow(dead_code)]
|
||||
struct PopenCommunicateArgs {
|
||||
#[pyarg(positional_or_keyword, optional = true)]
|
||||
input: OptionalArg<PyBytesRef>,
|
||||
#[pyarg(positional_or_keyword, optional = true)]
|
||||
timeout: OptionalArg<u64>,
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
|
||||
let subprocess_error = ctx.new_class("SubprocessError", ctx.exceptions.exception_type.clone());
|
||||
let timeout_expired = ctx.new_class("TimeoutExpired", subprocess_error.clone());
|
||||
let called_process_error = ctx.new_class("CalledProcessError", subprocess_error.clone());
|
||||
|
||||
let popen = py_class!(ctx, "Popen", ctx.object(), {
|
||||
(slot new) => PopenRef::new,
|
||||
"poll" => ctx.new_method(PopenRef::poll),
|
||||
"returncode" => ctx.new_readonly_getset("returncode", PopenRef::return_code),
|
||||
"wait" => ctx.new_method(PopenRef::wait),
|
||||
"stdin" => ctx.new_readonly_getset("stdin", PopenRef::stdin),
|
||||
"stdout" => ctx.new_readonly_getset("stdout", PopenRef::stdout),
|
||||
"stderr" => ctx.new_readonly_getset("stderr", PopenRef::stderr),
|
||||
"terminate" => ctx.new_method(PopenRef::terminate),
|
||||
"kill" => ctx.new_method(PopenRef::kill),
|
||||
"communicate" => ctx.new_method(PopenRef::communicate),
|
||||
"pid" => ctx.new_readonly_getset("pid", PopenRef::pid),
|
||||
"__enter__" => ctx.new_method(PopenRef::enter),
|
||||
"__exit__" => ctx.new_method(PopenRef::exit),
|
||||
"args" => ctx.new_readonly_getset("args", PopenRef::args),
|
||||
});
|
||||
|
||||
py_module!(vm, "_subprocess", {
|
||||
"Popen" => popen,
|
||||
"SubprocessError" => subprocess_error,
|
||||
"TimeoutExpired" => timeout_expired,
|
||||
"CalledProcessError" => called_process_error,
|
||||
"PIPE" => ctx.new_int(-1),
|
||||
"STDOUT" => ctx.new_int(-2),
|
||||
"DEVNULL" => ctx.new_int(-3),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::pyobject::{ItemProtocol, PyObjectRef};
|
||||
use crate::VirtualMachine;
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let vars = vm.ctx.new_dict();
|
||||
macro_rules! hashmap {
|
||||
($($key:literal => $value:literal),*) => {{
|
||||
$(vars.set_item($key, vm.new_str($value.to_owned()), vm).unwrap();)*
|
||||
}};
|
||||
}
|
||||
include!(concat!(env!("OUT_DIR"), "/env_vars.rs"));
|
||||
|
||||
py_module!(vm, "_sysconfigdata", {
|
||||
"build_time_vars" => vars,
|
||||
})
|
||||
}
|
||||
+373
-10
@@ -1,30 +1,393 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
use winapi::shared::winerror;
|
||||
use winapi::um::winnt::HANDLE;
|
||||
use winapi::um::{handleapi, winbase};
|
||||
use winapi::um::{
|
||||
fileapi, handleapi, namedpipeapi, processenv, processthreadsapi, synchapi, winbase, winnt,
|
||||
winuser,
|
||||
};
|
||||
|
||||
use super::os;
|
||||
use crate::pyobject::{PyObjectRef, PyResult};
|
||||
use super::os::errno_err;
|
||||
use crate::function::OptionalArg;
|
||||
use crate::obj::objdict::{PyDictRef, PyMapping};
|
||||
use crate::obj::objstr::PyStringRef;
|
||||
use crate::pyobject::{PyObjectRef, PyResult, PySequence, TryFromObject};
|
||||
use crate::VirtualMachine;
|
||||
|
||||
fn winapi_CloseHandle(handle: usize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let res = unsafe { handleapi::CloseHandle(handle as HANDLE) };
|
||||
if res == 0 {
|
||||
Err(os::errno_err(vm))
|
||||
} else {
|
||||
Ok(())
|
||||
fn GetLastError() -> u32 {
|
||||
unsafe { winapi::um::errhandlingapi::GetLastError() }
|
||||
}
|
||||
|
||||
fn husize(h: HANDLE) -> usize {
|
||||
h as usize
|
||||
}
|
||||
|
||||
trait Convertable {
|
||||
fn is_err(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Convertable for HANDLE {
|
||||
fn is_err(&self) -> bool {
|
||||
*self == handleapi::INVALID_HANDLE_VALUE
|
||||
}
|
||||
}
|
||||
impl Convertable for i32 {
|
||||
fn is_err(&self) -> bool {
|
||||
*self == 0
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt<T: Convertable>(vm: &VirtualMachine, res: T) -> PyResult<T> {
|
||||
if res.is_err() {
|
||||
Err(errno_err(vm))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
fn _winapi_CloseHandle(handle: usize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
cvt(vm, unsafe { handleapi::CloseHandle(handle as HANDLE) }).map(drop)
|
||||
}
|
||||
|
||||
fn _winapi_GetStdHandle(std_handle: u32, vm: &VirtualMachine) -> PyResult<usize> {
|
||||
cvt(vm, unsafe { processenv::GetStdHandle(std_handle) }).map(husize)
|
||||
}
|
||||
|
||||
fn _winapi_CreatePipe(
|
||||
_pipe_attrs: PyObjectRef,
|
||||
size: u32,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(usize, usize)> {
|
||||
let mut read = null_mut();
|
||||
let mut write = null_mut();
|
||||
cvt(vm, unsafe {
|
||||
namedpipeapi::CreatePipe(&mut read, &mut write, null_mut(), size)
|
||||
})?;
|
||||
Ok((read as usize, write as usize))
|
||||
}
|
||||
|
||||
fn _winapi_DuplicateHandle(
|
||||
(src_process, src): (usize, usize),
|
||||
target_process: usize,
|
||||
access: u32,
|
||||
inherit: i32,
|
||||
options: OptionalArg<u32>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<usize> {
|
||||
let mut target = null_mut();
|
||||
cvt(vm, unsafe {
|
||||
handleapi::DuplicateHandle(
|
||||
src_process as _,
|
||||
src as _,
|
||||
target_process as _,
|
||||
&mut target,
|
||||
access,
|
||||
inherit,
|
||||
options.unwrap_or(0),
|
||||
)
|
||||
})?;
|
||||
Ok(target as usize)
|
||||
}
|
||||
|
||||
fn _winapi_GetCurrentProcess() -> usize {
|
||||
unsafe { processthreadsapi::GetCurrentProcess() as usize }
|
||||
}
|
||||
|
||||
fn _winapi_GetFileType(h: usize, vm: &VirtualMachine) -> PyResult<u32> {
|
||||
let ret = unsafe { fileapi::GetFileType(h as _) };
|
||||
if ret == 0 && GetLastError() != 0 {
|
||||
Err(errno_err(vm))
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct CreateProcessArgs {
|
||||
#[pyarg(positional_only)]
|
||||
name: Option<PyStringRef>,
|
||||
#[pyarg(positional_only)]
|
||||
command_line: Option<PyStringRef>,
|
||||
#[pyarg(positional_only)]
|
||||
_proc_attrs: PyObjectRef,
|
||||
#[pyarg(positional_only)]
|
||||
_thread_attrs: PyObjectRef,
|
||||
#[pyarg(positional_only)]
|
||||
inherit_handles: i32,
|
||||
#[pyarg(positional_only)]
|
||||
creation_flags: u32,
|
||||
#[pyarg(positional_only)]
|
||||
env_mapping: Option<PyMapping>,
|
||||
#[pyarg(positional_only)]
|
||||
current_dir: Option<PyStringRef>,
|
||||
#[pyarg(positional_only)]
|
||||
startup_info: PyObjectRef,
|
||||
}
|
||||
|
||||
fn _winapi_CreateProcess(
|
||||
args: CreateProcessArgs,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<(usize, usize, u32, u32)> {
|
||||
use winbase::STARTUPINFOEXW;
|
||||
let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };
|
||||
si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as _;
|
||||
|
||||
macro_rules! si_attr {
|
||||
($attr:ident, $t:ty) => {{
|
||||
si.StartupInfo.$attr = <Option<$t>>::try_from_object(
|
||||
vm,
|
||||
vm.get_attribute(args.startup_info.clone(), stringify!($attr))?,
|
||||
)?
|
||||
.unwrap_or(0) as _
|
||||
}};
|
||||
($attr:ident) => {{
|
||||
si.StartupInfo.$attr = <Option<_>>::try_from_object(
|
||||
vm,
|
||||
vm.get_attribute(args.startup_info.clone(), stringify!($attr))?,
|
||||
)?
|
||||
.unwrap_or(0)
|
||||
}};
|
||||
}
|
||||
si_attr!(dwFlags);
|
||||
si_attr!(wShowWindow);
|
||||
si_attr!(hStdInput, usize);
|
||||
si_attr!(hStdOutput, usize);
|
||||
si_attr!(hStdError, usize);
|
||||
|
||||
let mut env = args
|
||||
.env_mapping
|
||||
.map(|m| getenvironment(m.into_dict(), vm))
|
||||
.transpose()?;
|
||||
let env = env.as_mut().map_or_else(null_mut, |v| v.as_mut_ptr());
|
||||
|
||||
let mut attrlist = getattributelist(
|
||||
vm.get_attribute(args.startup_info.clone(), "lpAttributeList")?,
|
||||
vm,
|
||||
)?;
|
||||
si.lpAttributeList = attrlist
|
||||
.as_mut()
|
||||
.map_or_else(null_mut, |l| l.attrlist.as_mut_ptr() as _);
|
||||
|
||||
let wstr = |s: PyStringRef| {
|
||||
if s.as_str().contains('\0') {
|
||||
Err(vm.new_value_error("embedded null character".to_owned()))
|
||||
} else {
|
||||
Ok(s.as_str()
|
||||
.encode_utf16()
|
||||
.chain(std::iter::once(0))
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
};
|
||||
|
||||
let app_name = args.name.map(wstr).transpose()?;
|
||||
let app_name = app_name.as_ref().map_or_else(null, |w| w.as_ptr());
|
||||
|
||||
let mut command_line = args.command_line.map(wstr).transpose()?;
|
||||
let command_line = command_line
|
||||
.as_mut()
|
||||
.map_or_else(null_mut, |w| w.as_mut_ptr());
|
||||
|
||||
let mut current_dir = args.current_dir.map(wstr).transpose()?;
|
||||
let current_dir = current_dir
|
||||
.as_mut()
|
||||
.map_or_else(null_mut, |w| w.as_mut_ptr());
|
||||
|
||||
let mut procinfo = unsafe { std::mem::zeroed() };
|
||||
let ret = unsafe {
|
||||
processthreadsapi::CreateProcessW(
|
||||
app_name,
|
||||
command_line,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
args.inherit_handles,
|
||||
args.creation_flags
|
||||
| winbase::EXTENDED_STARTUPINFO_PRESENT
|
||||
| winbase::CREATE_UNICODE_ENVIRONMENT,
|
||||
env as _,
|
||||
current_dir,
|
||||
&mut si as *mut STARTUPINFOEXW as _,
|
||||
&mut procinfo,
|
||||
)
|
||||
};
|
||||
|
||||
if ret == 0 {
|
||||
return Err(errno_err(vm));
|
||||
}
|
||||
|
||||
Ok((
|
||||
procinfo.hProcess as usize,
|
||||
procinfo.hThread as usize,
|
||||
procinfo.dwProcessId,
|
||||
procinfo.dwThreadId,
|
||||
))
|
||||
}
|
||||
|
||||
fn getenvironment(env: PyDictRef, vm: &VirtualMachine) -> PyResult<Vec<u16>> {
|
||||
let mut out = vec![];
|
||||
for (k, v) in env {
|
||||
let k = PyStringRef::try_from_object(vm, k)?;
|
||||
let k = k.as_str();
|
||||
let v = PyStringRef::try_from_object(vm, v)?;
|
||||
let v = v.as_str();
|
||||
if k.contains('\0') || v.contains('\0') {
|
||||
return Err(vm.new_value_error("embedded null character".to_owned()));
|
||||
}
|
||||
if k.len() == 0 || k[1..].contains('=') {
|
||||
return Err(vm.new_value_error("illegal environment variable name".to_owned()));
|
||||
}
|
||||
out.extend(k.encode_utf16());
|
||||
out.push(b'=' as u16);
|
||||
out.extend(v.encode_utf16());
|
||||
out.push(b'\0' as u16);
|
||||
}
|
||||
out.push(b'\0' as u16);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
struct AttrList {
|
||||
handlelist: Option<Vec<usize>>,
|
||||
attrlist: Vec<u8>,
|
||||
}
|
||||
impl Drop for AttrList {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
processthreadsapi::DeleteProcThreadAttributeList(self.attrlist.as_mut_ptr() as _)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn getattributelist(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Option<AttrList>> {
|
||||
<Option<PyMapping>>::try_from_object(vm, obj)?
|
||||
.map(|d| {
|
||||
let d = d.into_dict();
|
||||
let handlelist = d
|
||||
.get_item_option("handle_list", vm)?
|
||||
.and_then(|obj| {
|
||||
<Option<PySequence<usize>>>::try_from_object(vm, obj)
|
||||
.and_then(|s| match s {
|
||||
Some(s) if !s.as_slice().is_empty() => Ok(Some(s.into_vec())),
|
||||
_ => Ok(None),
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.transpose()?;
|
||||
let attr_count = handlelist.is_some() as u32;
|
||||
let mut size = 0;
|
||||
let ret = unsafe {
|
||||
processthreadsapi::InitializeProcThreadAttributeList(
|
||||
null_mut(),
|
||||
attr_count,
|
||||
0,
|
||||
&mut size,
|
||||
)
|
||||
};
|
||||
if ret != 0 || GetLastError() != winerror::ERROR_INSUFFICIENT_BUFFER {
|
||||
return Err(errno_err(vm));
|
||||
}
|
||||
let mut attrlist = vec![0u8; size];
|
||||
let ret = unsafe {
|
||||
processthreadsapi::InitializeProcThreadAttributeList(
|
||||
attrlist.as_mut_ptr() as _,
|
||||
attr_count,
|
||||
0,
|
||||
&mut size,
|
||||
)
|
||||
};
|
||||
if ret == 0 {
|
||||
return Err(errno_err(vm));
|
||||
}
|
||||
let mut attrs = AttrList {
|
||||
handlelist,
|
||||
attrlist,
|
||||
};
|
||||
if let Some(ref mut handlelist) = attrs.handlelist {
|
||||
let ret = unsafe {
|
||||
processthreadsapi::UpdateProcThreadAttribute(
|
||||
attrs.attrlist.as_mut_ptr() as _,
|
||||
0,
|
||||
(2 & 0xffff) | 0x20000, // PROC_THREAD_ATTRIBUTE_HANDLE_LIST
|
||||
handlelist.as_mut_ptr() as _,
|
||||
(handlelist.len() * std::mem::size_of::<HANDLE>()) as _,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
if ret == 0 {
|
||||
return Err(errno_err(vm));
|
||||
}
|
||||
}
|
||||
Ok(attrs)
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn _winapi_WaitForSingleObject(h: usize, ms: u32, vm: &VirtualMachine) -> PyResult<u32> {
|
||||
let ret = unsafe { synchapi::WaitForSingleObject(h as _, ms) };
|
||||
if ret == winbase::WAIT_FAILED {
|
||||
Err(errno_err(vm))
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
fn _winapi_GetExitCodeProcess(h: usize, vm: &VirtualMachine) -> PyResult<u32> {
|
||||
let mut ec = 0;
|
||||
cvt(vm, unsafe {
|
||||
processthreadsapi::GetExitCodeProcess(h as _, &mut ec)
|
||||
})?;
|
||||
Ok(ec)
|
||||
}
|
||||
|
||||
fn _winapi_TerminateProcess(h: usize, exit_code: u32, vm: &VirtualMachine) -> PyResult<()> {
|
||||
cvt(vm, unsafe {
|
||||
processthreadsapi::TerminateProcess(h as _, exit_code)
|
||||
})
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
|
||||
let ctx = &vm.ctx;
|
||||
py_module!(vm, "_winapi", {
|
||||
"CloseHandle" => ctx.new_function(winapi_CloseHandle),
|
||||
"CloseHandle" => named_function!(ctx, _winapi, CloseHandle),
|
||||
"GetStdHandle" => named_function!(ctx, _winapi, GetStdHandle),
|
||||
"CreatePipe" => named_function!(ctx, _winapi, CreatePipe),
|
||||
"DuplicateHandle" => named_function!(ctx, _winapi, DuplicateHandle),
|
||||
"GetCurrentProcess" => named_function!(ctx, _winapi, GetCurrentProcess),
|
||||
"CreateProcess" => named_function!(ctx, _winapi, CreateProcess),
|
||||
"WaitForSingleObject" => named_function!(ctx, _winapi, WaitForSingleObject),
|
||||
"GetExitCodeProcess" => named_function!(ctx, _winapi, GetExitCodeProcess),
|
||||
"TerminateProcess" => named_function!(ctx, _winapi, TerminateProcess),
|
||||
|
||||
"WAIT_OBJECT_0" => ctx.new_int(winbase::WAIT_OBJECT_0),
|
||||
"WAIT_ABANDONED" => ctx.new_int(winbase::WAIT_ABANDONED),
|
||||
"WAIT_ABANDONED_0" => ctx.new_int(winbase::WAIT_ABANDONED_0),
|
||||
"WAIT_TIMEOUT" => ctx.new_int(winerror::WAIT_TIMEOUT),
|
||||
"INFINITE" => ctx.new_int(winbase::INFINITE),
|
||||
"CREATE_NEW_CONSOLE" => ctx.new_int(winbase::CREATE_NEW_CONSOLE),
|
||||
"CREATE_NEW_PROCESS_GROUP" => ctx.new_int(winbase::CREATE_NEW_PROCESS_GROUP),
|
||||
"STD_INPUT_HANDLE" => ctx.new_int(winbase::STD_INPUT_HANDLE),
|
||||
"STD_OUTPUT_HANDLE" => ctx.new_int(winbase::STD_OUTPUT_HANDLE),
|
||||
"STD_ERROR_HANDLE" => ctx.new_int(winbase::STD_ERROR_HANDLE),
|
||||
"SW_HIDE" => ctx.new_int(winuser::SW_HIDE),
|
||||
"STARTF_USESTDHANDLES" => ctx.new_int(winbase::STARTF_USESTDHANDLES),
|
||||
"STARTF_USESHOWWINDOW" => ctx.new_int(winbase::STARTF_USESHOWWINDOW),
|
||||
"ABOVE_NORMAL_PRIORITY_CLASS" => ctx.new_int(winbase::ABOVE_NORMAL_PRIORITY_CLASS),
|
||||
"BELOW_NORMAL_PRIORITY_CLASS" => ctx.new_int(winbase::BELOW_NORMAL_PRIORITY_CLASS),
|
||||
"HIGH_PRIORITY_CLASS" => ctx.new_int(winbase::HIGH_PRIORITY_CLASS),
|
||||
"IDLE_PRIORITY_CLASS" => ctx.new_int(winbase::IDLE_PRIORITY_CLASS),
|
||||
"NORMAL_PRIORITY_CLASS" => ctx.new_int(winbase::NORMAL_PRIORITY_CLASS),
|
||||
"REALTIME_PRIORITY_CLASS" => ctx.new_int(winbase::REALTIME_PRIORITY_CLASS),
|
||||
"CREATE_NO_WINDOW" => ctx.new_int(winbase::CREATE_NO_WINDOW),
|
||||
"DETACHED_PROCESS" => ctx.new_int(winbase::DETACHED_PROCESS),
|
||||
"CREATE_DEFAULT_ERROR_MODE" => ctx.new_int(winbase::CREATE_DEFAULT_ERROR_MODE),
|
||||
"CREATE_BREAKAWAY_FROM_JOB" => ctx.new_int(winbase::CREATE_BREAKAWAY_FROM_JOB),
|
||||
"DUPLICATE_SAME_ACCESS" => ctx.new_int(winnt::DUPLICATE_SAME_ACCESS),
|
||||
"FILE_TYPE_CHAR" => ctx.new_int(winbase::FILE_TYPE_CHAR),
|
||||
"FILE_TYPE_DISK" => ctx.new_int(winbase::FILE_TYPE_DISK),
|
||||
"FILE_TYPE_PIPE" => ctx.new_int(winbase::FILE_TYPE_PIPE),
|
||||
"FILE_TYPE_REMOTE" => ctx.new_int(winbase::FILE_TYPE_REMOTE),
|
||||
"FILE_TYPE_UNKNOWN" => ctx.new_int(winbase::FILE_TYPE_UNKNOWN),
|
||||
})
|
||||
}
|
||||
+29
-15
@@ -222,6 +222,32 @@ fn sys_displayhook(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PLATFORM: &str = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "linux", target_os = "android"))] {
|
||||
// Android is linux as well. see https://bugs.python.org/issue32637
|
||||
"linux"
|
||||
} else if #[cfg(target_os = "macos")] {
|
||||
"darwin"
|
||||
} else if #[cfg(windows)] {
|
||||
"win32"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ABIFLAGS: &str = "";
|
||||
|
||||
// not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu)
|
||||
// but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly,
|
||||
// https://github.com/python/cpython/blob/3.8/configure.ac#L725
|
||||
const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");
|
||||
|
||||
pub fn sysconfigdata_name() -> String {
|
||||
format!("_sysconfigdata_{}_{}_{}", ABIFLAGS, PLATFORM, MULTIARCH)
|
||||
}
|
||||
|
||||
pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) {
|
||||
let ctx = &vm.ctx;
|
||||
|
||||
@@ -244,6 +270,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR
|
||||
let implementation = py_namespace!(vm, {
|
||||
"name" => ctx.new_str("rustpython".to_owned()),
|
||||
"cache_tag" => ctx.new_str("rustpython-01".to_owned()),
|
||||
"_multiarch" => ctx.new_str(MULTIARCH.to_owned()),
|
||||
});
|
||||
|
||||
let path = ctx.new_list(
|
||||
@@ -255,19 +282,6 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let platform = if cfg!(target_os = "linux") {
|
||||
"linux".to_owned()
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin".to_owned()
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"win32".to_owned()
|
||||
} else if cfg!(target_os = "android") {
|
||||
// Linux as well. see https://bugs.python.org/issue32637
|
||||
"linux".to_owned()
|
||||
} else {
|
||||
"unknown".to_owned()
|
||||
};
|
||||
|
||||
let framework = "".to_owned();
|
||||
|
||||
// https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian
|
||||
@@ -395,7 +409,7 @@ settrace() -- set the global debug tracing function
|
||||
"_getframe" => ctx.new_function(getframe),
|
||||
"modules" => modules.clone(),
|
||||
"warnoptions" => ctx.new_list(vec![]),
|
||||
"platform" => ctx.new_str(platform),
|
||||
"platform" => ctx.new_str(PLATFORM.to_owned()),
|
||||
"_framework" => ctx.new_str(framework),
|
||||
"meta_path" => ctx.new_list(vec![]),
|
||||
"path_hooks" => ctx.new_list(vec![]),
|
||||
@@ -414,7 +428,7 @@ settrace() -- set the global debug tracing function
|
||||
"exec_prefix" => ctx.new_str(exec_prefix.to_owned()),
|
||||
"base_exec_prefix" => ctx.new_str(base_exec_prefix.to_owned()),
|
||||
"exit" => ctx.new_function(sys_exit),
|
||||
"abiflags" => ctx.new_str("".to_owned()),
|
||||
"abiflags" => ctx.new_str(ABIFLAGS.to_owned()),
|
||||
"audit" => ctx.new_function(sys_audit),
|
||||
"displayhook" => ctx.new_function(sys_displayhook),
|
||||
"__displayhook__" => ctx.new_function(sys_displayhook),
|
||||
|
||||
Reference in new issue
Block a user