From c5fc34a106a706ac12925c4df273a926811d260b Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 24 Jun 2026 03:09:17 +0900 Subject: feat(process): redesign proc_* on PhpResource process handles proc_open/proc_close/proc_get_status/proc_terminate represented the process handle as a PhpMixed, which cannot hold a live child process or its pipes, so they were stubs or todo!(). Model the handle as a new PhpResource::Process variant and child pipes as a StreamBacking::Pipe, with a native Descriptor enum for descriptorspec; proc_open now returns io::Result and fills pipes as IndexMap. Rewire the Symfony Process pipes and the Console terminal/cursor onto the new types, removing the "PhpMixed cannot carry a PhpResource" todo!()s. The remaining todo!()s are genuine syscall leaves (proc_terminate signal delivery, stream_select, stream_set_blocking, posix_kill, pty, fd>=3) left unimplemented since no syscall crate is introduced. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/symfony/process/pipes/abstract_pipes.rs | 63 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) (limited to 'crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs') diff --git a/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs b/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs index d8ced43..5b85813 100644 --- a/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs +++ b/crates/shirabe-external-packages/src/symfony/process/pipes/abstract_pipes.rs @@ -1,11 +1,11 @@ //! ref: composer/vendor/symfony/process/Pipes/AbstractPipes.php use indexmap::IndexMap; -use shirabe_php_shim::{self as php, PhpMixed}; +use shirabe_php_shim::{self as php, PhpMixed, PhpResource}; #[derive(Debug)] pub struct AbstractPipes { - pub pipes: PhpMixed, + pub pipes: IndexMap, input_buffer: String, input: PhpMixed, @@ -17,6 +17,8 @@ impl AbstractPipes { pub fn new(input: PhpMixed) -> Self { let mut input_buffer = String::new(); let stored_input; + // TODO(plugin): `$input instanceof \Iterator` is not modeled. `is_resource` on a PhpMixed is + // always false, so the resource branch never stores input here. if php::is_resource(&input) { stored_input = input; } else if let PhpMixed::String(s) = &input { @@ -28,7 +30,7 @@ impl AbstractPipes { } Self { - pipes: PhpMixed::List(Vec::new()), + pipes: IndexMap::new(), input_buffer, input: stored_input, blocked: true, @@ -37,9 +39,10 @@ impl AbstractPipes { } pub fn close(&mut self) { - // TODO(phase-d): each pipe is a PHP stream resource that should be fclose()d, but the pipe - // list is a PhpMixed that cannot hold a PhpResource; the handles are dropped instead. - self.pipes = PhpMixed::List(Vec::new()); + for (_, pipe) in &self.pipes { + php::fclose(pipe); + } + self.pipes = IndexMap::new(); } /// Returns true if a system call has been interrupted. @@ -54,14 +57,52 @@ impl AbstractPipes { /// Unblocks streams. pub(crate) fn unblock(&mut self) { - let _ = &self.input_buffer; - let _ = &self.blocked; - todo!() + if !self.blocked { + return; + } + + for (_, pipe) in &self.pipes { + php::stream_set_blocking(pipe, false); + } + // The `is_resource($this->input)` branch does not apply: `input` is never a resource in this + // port (is_resource on a PhpMixed is always false). + + self.blocked = false; } /// Writes input to stdin. - pub(crate) fn write(&mut self) -> Option> { - todo!() + pub(crate) fn write(&mut self) -> Option> { + let stdin = self.pipes.get(&0)?.clone(); + + // TODO(plugin): the `$input instanceof \Iterator` branch is not modeled. `input` is never a + // resource here, so the fread($input)/stream_set_blocking($input) paths do not apply and + // only the input buffer is written to stdin. + + let mut r: Vec = Vec::new(); + let mut e: Vec = Vec::new(); + let mut w: Vec = vec![stdin.clone()]; + + // let's have a look if something changed in streams + if php::stream_select(&mut r, &mut w, &mut e, 0, Some(0)).is_none() { + return None; + } + + if !self.input_buffer.is_empty() { + let written = php::fwrite(&stdin, &self.input_buffer, None).unwrap_or(0) as usize; + self.input_buffer = self.input_buffer.get(written..).unwrap_or("").to_string(); + if !self.input_buffer.is_empty() { + return Some(vec![stdin]); + } + } + + // no input to read on resource, buffer is empty + if self.input_buffer.is_empty() && !php::php_truthy(&self.input) { + self.input = PhpMixed::Null; + php::fclose(&stdin); + self.pipes.shift_remove(&0); + } + + None } pub fn handle_error(&mut self, _type: i64, msg: String) { -- cgit v1.3.1