aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-php-shim/src/lib.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-24 03:09:17 +0900
committernsfisis <nsfisis@gmail.com>2026-06-24 03:09:17 +0900
commitc5fc34a106a706ac12925c4df273a926811d260b (patch)
tree24e798c4a00b32aa1a1f155b705d02fba9895875 /crates/shirabe-php-shim/src/lib.rs
parentb2b321a26f6a628ee8e6757eb8577613e2024e70 (diff)
downloadphp-shirabe-c5fc34a106a706ac12925c4df273a926811d260b.tar.gz
php-shirabe-c5fc34a106a706ac12925c4df273a926811d260b.tar.zst
php-shirabe-c5fc34a106a706ac12925c4df273a926811d260b.zip
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<i64, PhpResource>. 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) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim/src/lib.rs')
-rw-r--r--crates/shirabe-php-shim/src/lib.rs48
1 files changed, 48 insertions, 0 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index 8eb8f2f..113c9d7 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -346,6 +346,7 @@ pub enum PhpResource {
Stdout,
Stderr,
Stream(std::rc::Rc<std::cell::RefCell<StreamState>>),
+ Process(std::rc::Rc<std::cell::RefCell<process::ProcessState>>),
}
/// Combined capability of every seekable byte stream backing. Both `std::fs::File`
@@ -361,6 +362,8 @@ pub enum StreamBacking {
/// TODO(phase-d): `php://temp/maxmemory:N` spills to a temp file past N bytes;
/// the threshold is ignored here and everything stays in memory.
Memory(std::io::Cursor<Vec<u8>>),
+ /// A child process pipe created by `proc_open`. Half-duplex and not seekable.
+ Pipe(ChildPipe),
}
impl StreamBacking {
@@ -368,10 +371,55 @@ impl StreamBacking {
match self {
StreamBacking::File(f) => f,
StreamBacking::Memory(c) => c,
+ StreamBacking::Pipe(p) => p,
}
}
}
+/// One end of a child process pipe. Each variant supports only the direction PHP
+/// allows for it; the unsupported operations return `ErrorKind::Unsupported` so the
+/// `ReadWriteSeek` contract is satisfied without pretending pipes are seekable.
+#[derive(Debug)]
+pub enum ChildPipe {
+ In(std::process::ChildStdin),
+ Out(std::process::ChildStdout),
+ Err(std::process::ChildStderr),
+}
+
+impl std::io::Read for ChildPipe {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ match self {
+ ChildPipe::Out(o) => o.read(buf),
+ ChildPipe::Err(e) => e.read(buf),
+ ChildPipe::In(_) => Err(std::io::Error::from(std::io::ErrorKind::Unsupported)),
+ }
+ }
+}
+
+impl std::io::Write for ChildPipe {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ match self {
+ ChildPipe::In(i) => i.write(buf),
+ ChildPipe::Out(_) | ChildPipe::Err(_) => {
+ Err(std::io::Error::from(std::io::ErrorKind::Unsupported))
+ }
+ }
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ match self {
+ ChildPipe::In(i) => i.flush(),
+ ChildPipe::Out(_) | ChildPipe::Err(_) => Ok(()),
+ }
+ }
+}
+
+impl std::io::Seek for ChildPipe {
+ fn seek(&mut self, _pos: std::io::SeekFrom) -> std::io::Result<u64> {
+ Err(std::io::Error::from(std::io::ErrorKind::Unsupported))
+ }
+}
+
#[derive(Debug)]
pub struct StreamState {
pub(crate) backing: StreamBacking,