diff --git a/.gitignore b/.gitignore index 38a694b..e839429 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ Cargo.lock .idea .vscode *.exe +.cargo +venv diff --git a/Cargo.toml b/Cargo.toml index 762181e..860a29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,30 +3,11 @@ name = "river" version = "0.1.0" edition = "2021" -[dependencies] -libc = "0.2.153" -clap = { version = "4.5.1", features = ["derive"] } -clap-verbosity-flag = "2.2.0" -log = "0.4.20" -env_logger = "0.10.2" -tempfile = "3.10.0" -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "river" +crate-type = ["cdylib"] -[target.'cfg(windows)'.dependencies.windows] -version = "0.53.0" -features = [ - "Win32_Storage", - "Win32_Storage_FileSystem", - "Win32_System_Ioctl", - "Win32_System_IO", - "Win32_Foundation", - "Win32_Security", - "Win32_System_JobObjects", - "Win32_System_Threading", - "Win32_UI_WindowsAndMessaging", - "Win32_System_Diagnostics", - "Win32_System_Diagnostics_Debug", - "Win32_System_ProcessStatus", - "Win32_System_WindowsProgramming" -] \ No newline at end of file +[dependencies] +pyo3 = "0.22.0" +libc = "0.2.155" diff --git a/README.md b/README.md index d57aa3d..170def1 100644 --- a/README.md +++ b/README.md @@ -1,84 +1 @@ # river - -## 用法 - -```bash -$ river -h -example: `river -vvv -- /usr/bin/echo hello world` - -Usage: river.exe [OPTIONS] -- ... - -Arguments: - ... Program to run and command line arguments - -Options: - -i, --input - Input stream. The default value is STDIN(0) - -o, --output - Output stream. The default value is STDOUT(1) - -e, --error - Error stream. The default value is STDERR(2) - -r, --result - Output location of the running result. The default value is STDOUT(1) - -t, --time-limit - Time limit, in ms. The default value is unlimited - -c, --cpu-time-limit - CPU Time limit, in ms. The default value is unlimited - -m, --memory-limit - Memory limit, in kib. The default value is unlimited - -v, --verbose... - Increase logging verbosity - -q, --quiet... - Decrease logging verbosity - -h, --help - Print help - -V, --version - Print version -``` - -## 结果 - -结果的格式为 JSON - -| 字段 | 含义 | -|-----------------|--------------------| -| `time_used` | 程序运行用时 | -| `cpu_time_used` | 程序运行使用 CPU 时间 | -| `memory_used` | 程序运行使用内存 | -| `exit_code` | 程序退出 code,正常情况下为 0 | -| `status` | 正常情况下为 0 | -| `signal` | 正常情况下为 0 | - -## 系统支持 - -`~` 代表开发中的功能 - -| 特性 | Linux | Windows | macOS | -|-------------|-------|---------|-------| -| 执行指定命令 | ~ | √ | ~ | -| 流重定向 | ~ | √ | ~ | -| 运行时间统计 | ~ | √ | ~ | -| 运行 CPU 时间统计 | ~ | √ | ~ | -| 运行内存统计 | ~ | √ | ~ | -| 运行时间限制 | ~ | √ | ~ | -| 运行 CPU 时间限制 | ~ | ~ | ~ | -| 运行内存限制 | ~ | ~ | ~ | -| 获取进程退出状态 | ~ | ~ | ~ | -| 切换工作空间 | ~ | ~ | ~ | -| 传递环境变量 | ~ | ~ | ~ | -| 网络限制 | ~ | ~ | ~ | -| 写入文件大小限制 | ~ | ~ | ~ | -| 进程/线程数量限制 | ~ | ~ | ~ | -| 危险系统调用限制 | ~ | ~ | ~ | -| 执行用户权限限制 | ~ | ~ | ~ | -| 平滑退出 | ~ | ~ | ~ | - -**注意:** Windows 平台下运行 CPU 时间限制与运行内存限制不能保证精确,请不要以此为基准进行判断。 - -## 测试 - -```bash -cargo test -- --test-threads=1 -``` - -测试涉及文件操作,建议顺序执行测试用例(并发限制为 1) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0fdf696 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "river" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/resources/runit.s b/resources/runit.s new file mode 100644 index 0000000..d345e71 --- /dev/null +++ b/resources/runit.s @@ -0,0 +1,27 @@ + .text + .globl main +main: + endbr64 + pushq %rbp + movq %rsp, %rbp + subq $48, %rsp + movl %edi, -20(%rbp) + movq %rsi, -32(%rbp) + movq %rdx, -40(%rbp) + call fork@PLT + movl %eax, -4(%rbp) + cmpl $0, -4(%rbp) + jne .L2 + movq -32(%rbp), %rax + leaq 8(%rax), %rcx + movq -32(%rbp), %rax + addq $8, %rax + movq (%rax), %rax + movq -40(%rbp), %rdx + movq %rcx, %rsi + movq %rax, %rdi + call execve@PLT +.L2: + movl $0, %eax + leave + ret diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 0803aaf..0000000 --- a/src/error.rs +++ /dev/null @@ -1,45 +0,0 @@ -use serde_json::Error as SerdeJsonError; -use std::fmt::Formatter; -use std::io::Error as IOError; -use std::{fmt, result}; - -#[cfg(target_os = "windows")] -use windows::core::Error as WIN_ERROR; - -#[derive(Debug)] -pub enum Error { - E(String, u32, String), - IOError(IOError), - SerdeJsonError(SerdeJsonError), - /// Windows 平台下的 LastError - #[cfg(target_os = "windows")] - WinError(String, u32, WIN_ERROR), -} - -pub type Result = result::Result; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Error::E(ref filename, ref line, ref e) => { - write!(f, "{}:{}: Error: {}", filename, line, e) - } - Error::IOError(ref e) => { - write!(f, "{}", e) - } - Error::SerdeJsonError(ref e) => { - write!(f, "{}", e) - } - #[cfg(target_os = "windows")] - Error::WinError(ref filename, ref line, ref e) => { - write!( - f, - "{}:{}: Windows API Error: {}", - filename, - line, - e.message() - ) - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d1042b4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,29 @@ +use pyo3::prelude::*; + +#[pyclass] +struct River { + file: String, +} + +#[pymethods] +impl River { + #[new] + fn new(file: String) -> Self { + Self { file } + } + + #[getter] + fn val(&self) -> String { + self.file.to_string() + } + + fn __str__(&self) -> String { + self.file.to_string() + } +} + +#[pymodule] +fn river(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 1fc7a91..0000000 --- a/src/main.rs +++ /dev/null @@ -1,123 +0,0 @@ -use clap::Parser; -use clap_verbosity_flag::Verbosity; -use env_logger::Builder; -use log::{error, trace}; - -#[cfg(target_os = "linux")] -use sys::linux::Sandbox; -#[cfg(target_os = "macos")] -use sys::macos::Sandbox; -#[cfg(target_os = "windows")] -use sys::windows::Sandbox; - -use crate::sys::SandboxImpl; - -mod error; -mod status; -mod sys; - -/// example: `river -vvv -- /usr/bin/echo hello world` -#[derive(Parser, Debug)] -#[clap(version = "1.0.0", author = "MeiK ")] -pub struct Opts { - /// Input stream. The default value is STDIN(0) - #[clap(short, long)] - input: Option, - - /// Output stream. The default value is STDOUT(1) - #[clap(short, long)] - output: Option, - - /// Error stream. The default value is STDERR(2) - #[clap(short, long)] - error: Option, - - #[cfg(any(target_os = "linux", target_os = "macos"))] - /// Working directory. The default value is the current directory. - #[clap(short, long, default_value = "./")] - workdir: String, - - /// Output location of the running result. The default value is STDOUT(1) - #[clap(short, long)] - result: Option, - - /// Time limit, in ms. The default value is unlimited. - #[clap(short, long)] - time_limit: Option, - - /// CPU Time limit, in ms. The default value is unlimited. - #[clap(short, long)] - cpu_time_limit: Option, - - /// Memory limit, in kib. The default value is unlimited. - #[clap(short, long)] - memory_limit: Option, - - #[cfg(any(target_os = "linux", target_os = "macos"))] - /// Maximum number of files that can be written. The unit is bit. The default value is unlimited. - #[clap(short, long, default_value = "0")] - file_size_limit: i32, - - #[cfg(any(target_os = "linux", target_os = "macos"))] - /// Cgroup version, 1 or 2 - #[clap(short, long, default_value = "1")] - cgroup: i32, - - #[cfg(any(target_os = "linux", target_os = "macos"))] - /// Number of processes that can be created. The default value is unlimited. - #[clap(short, long, default_value = "0")] - pids: i32, - - /// Program to run and command line arguments - #[clap(last(true), required = true)] - command: Vec, - - /// A level of verbosity, and can be used multiple times - #[command(flatten)] - verbose: Verbosity, - - #[cfg(any(target_os = "linux", target_os = "macos"))] - /// Network enable - #[clap(long, default_value = "false")] - network: bool, -} - -impl Default for Opts { - fn default() -> Self { - Opts { - input: None, - output: None, - error: None, - result: None, - time_limit: None, - cpu_time_limit: None, - memory_limit: None, - command: vec![], - verbose: Default::default(), - } - } -} - -fn main() { - let opts: Opts = Opts::parse(); - - Builder::new() - .filter_level(opts.verbose.log_level_filter()) - .init(); - - trace!("{:?}", opts); - let result = opts.result.clone(); - let status = unsafe { Sandbox::with_opts(opts).run() }; - match status { - Ok(val) => { - if let Err(e) = val.write(result) { - error!("{}", e); - std::process::exit(1); - } - } - Err(e) => { - error!("{}", e); - std::process::exit(1); - } - } -} diff --git a/src/status.rs b/src/status.rs deleted file mode 100644 index 8b24b17..0000000 --- a/src/status.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::fmt; -use std::fmt::Formatter; -use std::fs; - -use serde::{Deserialize, Serialize}; - -use crate::error::Error::{IOError, SerdeJsonError}; -use crate::error::Result; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Status { - pub time_used: u64, - pub cpu_time_used: u64, - pub memory_used: u64, - pub exit_code: i32, - pub status: i32, - pub signal: i32, -} - -impl Status { - pub fn write(&self, file: Option) -> Result<()> { - let json_str = serde_json::to_string_pretty(&self).map_err(|e| SerdeJsonError(e))?; - if let Some(f) = file { - fs::write(f, format!("{}", json_str)).map_err(|e| IOError(e))?; - } else { - println!("{}", json_str); - }; - Ok(()) - } -} - -impl fmt::Display for Status { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl Default for Status { - fn default() -> Self { - Status { - time_used: 0, - cpu_time_used: 0, - memory_used: 0, - exit_code: 0, - status: 0, - signal: 0, - } - } -} diff --git a/src/sys/linux/mod.rs b/src/sys/linux/mod.rs deleted file mode 100644 index d76cf71..0000000 --- a/src/sys/linux/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::sys::SandboxImpl; - -pub struct Sandbox {} - -impl SandboxImpl for Sandbox { - fn run() -> () { - println!("Linux") - } -} \ No newline at end of file diff --git a/src/sys/macos/mod.rs b/src/sys/macos/mod.rs deleted file mode 100644 index 500fe18..0000000 --- a/src/sys/macos/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::sys::SandboxImpl; - -pub struct Sandbox {} - -impl SandboxImpl for Sandbox { - fn run() -> () { - println!("macOS") - } -} \ No newline at end of file diff --git a/src/sys/mod.rs b/src/sys/mod.rs deleted file mode 100644 index e90ac5f..0000000 --- a/src/sys/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::Opts; -use crate::status::Status; - -use super::error::Result; - -#[cfg(target_os = "linux")] -pub(crate) mod linux; -#[cfg(target_os = "macos")] -pub(crate) mod macos; -#[cfg(target_os = "windows")] -pub(crate) mod windows; - -pub trait SandboxImpl { - fn with_opts(opts: Opts) -> Self; - - /** - * run - */ - unsafe fn run(&mut self) -> Result; -} diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs deleted file mode 100644 index a2ef9fe..0000000 --- a/src/sys/windows/mod.rs +++ /dev/null @@ -1,341 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use windows::core::PSTR; -use windows::Win32::Foundation::{CloseHandle, FILETIME, WAIT_FAILED, WAIT_TIMEOUT}; -use windows::Win32::System::JobObjects::{ - AssignProcessToJobObject, CreateJobObjectA, JobObjectBasicLimitInformation, - SetInformationJobObject, JOBOBJECT_BASIC_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_PRIORITY_CLASS, - JOB_OBJECT_LIMIT_PROCESS_TIME, -}; -use windows::Win32::System::ProcessStatus::{GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS}; -use windows::Win32::System::Threading::{ - CreateProcessA, GetProcessTimes, ResumeThread, SetProcessWorkingSetSize, TerminateProcess, - WaitForSingleObject, CREATE_SUSPENDED, IDLE_PRIORITY_CLASS, PROCESS_INFORMATION, - STARTF_USESTDHANDLES, STARTUPINFOA, -}; - -use utils::utils::string_to_pcstr; - -use crate::error::Error::{WinError, E}; -use crate::error::Result; -use crate::status::Status; -use crate::sys::windows::utils::utils::{handle_from_file, string_to_pstr}; -use crate::sys::SandboxImpl; -use crate::Opts; - -mod utils; - -#[macro_export] -macro_rules! winapi { - ($expression:expr) => { - if let Err(e) = $expression { - return Err(WinError(String::from(file!()), line!(), e)); - } - }; -} - -#[derive(Debug)] -pub struct Sandbox { - inner_args: Vec, - time_limit: Option, - cpu_time_limit: Option, - memory_limit: Option, - input: Option, - output: Option, - error: Option, -} - -impl Sandbox { - unsafe fn set_limit(&mut self, information: &PROCESS_INFORMATION) -> Result<()> { - // 创建 JOB - let job = match CreateJobObjectA(None, None) { - Ok(j) => j, - Err(e) => return Err(WinError(file!().to_string(), line!(), e)), - }; - - let mut limit: JOBOBJECT_BASIC_LIMIT_INFORMATION = Default::default(); - limit.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS; - limit.PriorityClass = IDLE_PRIORITY_CLASS.0; - - // 内存限制 - if let Some(l) = self.memory_limit { - // 与 cpu 时间限制类似,此限制并不能保证可用性 - winapi!(SetProcessWorkingSetSize( - information.hProcess, - 1, - l as usize * 1024 - )); - } - - // 系统定期检查以确定与作业关联的每个进程是否累积了比设置限制更多的用户模式时间。 如果已终止,则终止进程。 - // cpu 时间限制,此限制不会实时结束进程(需要等到下次检查?) - if let Some(l) = self.cpu_time_limit { - limit.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_TIME; - limit.PerProcessUserTimeLimit = l as i64 * 10000; - limit.PerJobUserTimeLimit = l as i64 * 10000; - } - - // 设置 job 限制 - winapi!(SetInformationJobObject( - job, - JobObjectBasicLimitInformation, - &limit as *const _ as *const c_void, - size_of::() as u32, - )); - // 将 job 附加到进程 - winapi!(AssignProcessToJobObject(job, information.hProcess)); - Ok(()) - } - - unsafe fn redirect_fd(&mut self, info: &mut STARTUPINFOA) -> Result<()> { - // 重定向 stdin - if let Some(file) = &self.input { - info.hStdInput = handle_from_file(file, 'r')?; - } - // 重定向 stdout - if let Some(file) = &self.output { - info.hStdOutput = handle_from_file(file, 'w')?; - } - // 重定向 stderr - if let Some(file) = &self.error { - info.hStdError = handle_from_file(file, 'w')?; - } - - Ok(()) - } - - unsafe fn wait_it( - &mut self, - info: &STARTUPINFOA, - information: &PROCESS_INFORMATION, - ) -> Result { - let mut status: Status = Default::default(); - let timeout = if let Some(t) = self.time_limit { - t - } else { - // 如果 dwMilliseconds 为 INFINITE,则仅当发出对象信号时,该函数才会返回 - 0xFFFFFFFF - }; - let wait_ret = WaitForSingleObject(information.hProcess, timeout); - if wait_ret == WAIT_TIMEOUT { - // 超时中断进程 - winapi!(TerminateProcess(information.hProcess, 0)); - // 此处不检查返回值 - WaitForSingleObject(information.hProcess, 0xFFFFFFFF); - } else if wait_ret == WAIT_FAILED { - return Err(E(file!().to_string(), line!(), "WAIT_FAILED".to_string())); - } - - // 关闭文件流 - if !info.hStdInput.is_invalid() { - winapi!(CloseHandle(info.hStdInput)); - } - if !info.hStdOutput.is_invalid() { - winapi!(CloseHandle(info.hStdOutput)); - } - if !info.hStdError.is_invalid() { - winapi!(CloseHandle(info.hStdError)); - } - - let mut pmc: PROCESS_MEMORY_COUNTERS = Default::default(); - - // 获取内存使用情况 - winapi!(GetProcessMemoryInfo( - information.hProcess, - &mut pmc, - size_of::() as u32, - )); - - status.memory_used = (pmc.PeakWorkingSetSize / 1024) as u64; - - // 获取时间使用情况 - let mut lp_creation_time: FILETIME = Default::default(); - let mut lp_exit_time: FILETIME = Default::default(); - let mut lp_kernel_time: FILETIME = Default::default(); - let mut lp_user_time: FILETIME = Default::default(); - winapi!(GetProcessTimes( - information.hProcess, - &mut lp_creation_time, - &mut lp_exit_time, - &mut lp_kernel_time, - &mut lp_user_time, - )); - - status.time_used = - (lp_exit_time.dwLowDateTime - lp_creation_time.dwLowDateTime) as u64 / 10000; - status.cpu_time_used = - (lp_kernel_time.dwLowDateTime + lp_user_time.dwLowDateTime) as u64 / 10000; - - Ok(status) - } -} - -impl SandboxImpl for Sandbox { - fn with_opts(opts: Opts) -> Self { - Sandbox { - inner_args: opts.command, - time_limit: opts.time_limit, - cpu_time_limit: opts.cpu_time_limit, - memory_limit: opts.memory_limit, - input: opts.input, - output: opts.output, - error: opts.error, - } - } - - unsafe fn run(&mut self) -> Result { - // 执行的目标 app,前置的命令行解析保证 inner_args 至少有一项 - let app = string_to_pcstr(&mut self.inner_args[0]); - // 执行的文件参数 - let command_line_pstr = if self.inner_args.len() > 1 { - let mut command_line = &mut self.inner_args[1..].join(" "); - string_to_pstr(&mut command_line) - } else { - PSTR::null() - }; - - let mut info: STARTUPINFOA = Default::default(); - let mut information: PROCESS_INFORMATION = Default::default(); - - let mut binherithandles = false; - // 设置 stdin/stdout/stderr 的重定向 - if self.input != None || self.output != None || self.error != None { - binherithandles = true; - info.dwFlags |= STARTF_USESTDHANDLES; - self.redirect_fd(&mut info)?; - } - - // 创建进程 - winapi!(CreateProcessA( - app, - command_line_pstr, - None, - None, - binherithandles, - // CREATE_SUSPENDED: 创建一个暂停的进程,需要 ResumeThread 之后才可以正常运行 - CREATE_SUSPENDED, - None, - None, - &mut info, - &mut information, - )); - - self.set_limit(&information)?; - - let resume = ResumeThread(information.hThread); - - // 唤醒被暂停的进程 - if resume != 1 { - return Err(E( - String::from(file!()), - line!(), - format!("唤醒进程失败,resume = {}", resume), - )); - } - - self.wait_it(&info, &information) - } -} - -#[cfg(test)] -mod tests { - use std::fs; - - use crate::sys::windows::Sandbox; - use crate::sys::SandboxImpl; - use crate::Opts; - - /** - * 执行不存在的可执行文件 - */ - #[test] - #[should_panic] - fn not_found() { - let mut opts: Opts = Opts::default(); - opts.command - .push("./tests/windows/not-found.exe".to_string()); - unsafe { - Sandbox::with_opts(opts).run().unwrap(); - } - } - - /** - * 测试时间限制 - */ - #[test] - fn time_limit() { - let mut opts: Opts = Opts::default(); - opts.command - .push("./tests/windows/sleep/sleep.exe".to_string()); - opts.time_limit = Some(1000); - let status = unsafe { Sandbox::with_opts(opts).run().unwrap() }; - assert!(status.time_used >= 1000); - assert!(status.time_used < 2000); - } - - /** - * 测试 stdout - */ - #[test] - fn output() { - let filename = "./output.txt"; - let mut opts: Opts = Opts::default(); - opts.command - .push("./tests/windows/output/output.exe".to_string()); - opts.output = Option::from(filename.to_string()); - unsafe { Sandbox::with_opts(opts).run().unwrap() }; - - if let Ok(content) = fs::read_to_string(filename) { - assert_eq!(content.trim(), "Hello World!"); - fs::remove_file(filename).unwrap() - } else { - assert!(false) - } - } - - /** - * 测试 stderr - */ - #[test] - fn stderr() { - let filename = "./stderr.txt"; - let mut opts: Opts = Opts::default(); - opts.command - .push("./tests/windows/stderr/stderr.exe".to_string()); - opts.error = Option::from(filename.to_string()); - unsafe { Sandbox::with_opts(opts).run().unwrap() }; - - if let Ok(content) = fs::read_to_string(filename) { - assert_eq!("Hello World!", content.trim()); - fs::remove_file(filename).unwrap() - } else { - assert!(false) - } - } - - /** - * 测试 stdin - */ - #[test] - fn stdin() { - let filename = "./stdin.txt"; - let out_filename = "./stdin-stdout.txt"; - let content = "Hello Stdin!"; - fs::write(filename, content).unwrap(); - let mut opts: Opts = Opts::default(); - opts.command - .push("./tests/windows/stdin/stdin.exe".to_string()); - opts.input = Option::from(filename.to_string()); - opts.output = Option::from(out_filename.to_string()); - unsafe { Sandbox::with_opts(opts).run().unwrap() }; - - if let Ok(c) = fs::read_to_string(out_filename) { - assert_eq!(c.trim(), content.trim()); - fs::remove_file(filename).unwrap(); - fs::remove_file(out_filename).unwrap() - } else { - assert!(false) - } - } -} diff --git a/src/sys/windows/utils.rs b/src/sys/windows/utils.rs deleted file mode 100644 index ce59759..0000000 --- a/src/sys/windows/utils.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub(crate) mod utils { - use std::mem::size_of; - use std::ptr; - - use windows::core::{PCSTR, PSTR}; - use windows::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE, TRUE}; - use windows::Win32::Security::SECURITY_ATTRIBUTES; - use windows::Win32::Storage::FileSystem::{ - CreateFileA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, OPEN_EXISTING, - }; - - use crate::error::Error::WinError; - use crate::error::Result; - - pub unsafe fn string_to_pcstr(string: &mut String) -> PCSTR { - string.push('\0'); - PCSTR(string.as_ptr()) - } - - pub unsafe fn string_to_pstr(string: &mut String) -> PSTR { - string.push('\0'); - PSTR(string.as_mut_ptr()) - } - - pub unsafe fn handle_from_file(string: &String, wr: char) -> Result { - let mut string = string.clone(); - let sa = SECURITY_ATTRIBUTES { - nLength: size_of::() as u32, - lpSecurityDescriptor: ptr::null_mut(), - bInheritHandle: TRUE, // 指明这个 handle 需要被子进程继承 - }; - let mode = if wr == 'w' { - GENERIC_WRITE - } else { - GENERIC_READ - }; - let exist = if wr == 'w' { - CREATE_ALWAYS - } else { - OPEN_EXISTING - }; - return match CreateFileA( - string_to_pcstr(&mut string), - mode.0, - Default::default(), - Some(&sa), - exist, - FILE_ATTRIBUTE_NORMAL, - HANDLE::default(), - ) { - Ok(h_file) => Ok(h_file), - Err(e) => Err(WinError(String::from(string), line!(), e)), - }; - } -} diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..d1f1cf2 --- /dev/null +++ b/tests/test.py @@ -0,0 +1,7 @@ +from river import River + +def main(): + r = River("echo") + print(r) + +main() diff --git a/tests/windows/output/output.rs b/tests/windows/output/output.rs deleted file mode 100644 index 47ad8c6..0000000 --- a/tests/windows/output/output.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello World!"); -} diff --git a/tests/windows/sleep/sleep.rs b/tests/windows/sleep/sleep.rs deleted file mode 100644 index e6da1e8..0000000 --- a/tests/windows/sleep/sleep.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::thread; -use std::time::Duration; - -fn main() { - let sleep_duration = Duration::from_secs(30); - thread::sleep(sleep_duration); -} diff --git a/tests/windows/stderr/stderr.rs b/tests/windows/stderr/stderr.rs deleted file mode 100644 index 724666e..0000000 --- a/tests/windows/stderr/stderr.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - eprintln!("Hello World!"); -} diff --git a/tests/windows/stdin/stdin.rs b/tests/windows/stdin/stdin.rs deleted file mode 100644 index d3c4bb6..0000000 --- a/tests/windows/stdin/stdin.rs +++ /dev/null @@ -1,7 +0,0 @@ -use std::io; - -fn main() { - let mut input = String::new(); - io::stdin().read_line(&mut input).unwrap(); - println!("{}", input); -}