Skip to main content

pharos_native/platform/
mod.rs

1#[cfg(feature = "wasi-host-bridge")]
2use std::collections::HashSet;
3#[cfg(feature = "wasi-host-bridge")]
4use std::env;
5#[cfg(feature = "wasi-host-bridge")]
6use std::io::{self, Read};
7
8use std::path::Path;
9#[cfg(feature = "wasi-host-bridge")]
10use std::process::{Child, Command, Stdio};
11#[cfg(feature = "wasi-host-bridge")]
12use std::sync::{
13    Arc,
14    atomic::{AtomicBool, Ordering},
15};
16#[cfg(feature = "wasi-host-bridge")]
17use std::thread;
18#[cfg(feature = "wasi-host-bridge")]
19use std::thread::JoinHandle;
20#[cfg(feature = "wasi-host-bridge")]
21use std::time::{Duration, Instant};
22
23/// Public constant `MODULE_STATUS` exposed by this module.
24///
25/// # Examples
26///
27/// ```rust,ignore
28/// let _ = pharos_native::platform::MODULE_STATUS;
29/// ```
30///
31/// # Related
32///
33/// - [`crate::platform`] for the surrounding module surface.
34pub const MODULE_STATUS: &str = "PENDING";
35
36#[derive(Debug, Clone, Copy, Eq, PartialEq)]
37/// Public enum `HostTarget` used by this module.
38///
39/// # Variants
40///
41/// - `MacosDarwinX86_64`: Supported `HostTarget` variant.
42/// - `Wasm32Wasip1`: Supported `HostTarget` variant.
43/// - `Wasm32UnknownUnknown`: Supported `HostTarget` variant.
44///
45/// # Examples
46///
47/// ```rust,ignore
48/// let _ = pharos_native::platform::HostTarget::MacosDarwinX86_64;
49/// ```
50///
51/// # Related
52///
53/// - [`crate::platform`] for constructors, helpers, and neighboring runtime types.
54pub enum HostTarget {
55    MacosDarwinX86_64,
56    Wasm32Wasip1,
57    Wasm32UnknownUnknown,
58}
59
60#[derive(Debug, Clone, Copy, Eq, PartialEq)]
61/// Public struct `TargetConstraint` used by this module.
62///
63/// # Fields
64///
65/// - `product_target`: Stored field on `TargetConstraint`.
66/// - `rust_target`: Stored field on `TargetConstraint`.
67/// - `constraints`: Stored field on `TargetConstraint`.
68///
69/// # Examples
70///
71/// ```rust,ignore
72/// let _ = std::mem::size_of::<pharos_native::platform::TargetConstraint>();
73/// ```
74///
75/// # Related
76///
77/// - [`crate::platform`] for constructors, helpers, and neighboring runtime types.
78pub struct TargetConstraint {
79    pub product_target: &'static str,
80    pub rust_target: HostTarget,
81    pub constraints: &'static [&'static str],
82}
83
84const MACOS_CONSTRAINTS: &[&str] = &[
85    "authoritative local target for Phase 1 first build, lockfile generation, and SBOM generation",
86    "native runtime, PostgreSQL rustls TLS, SMTP rustls TLS, and SQLite feasibility remain enabled",
87];
88
89const WASI_P1_CONSTRAINTS: &[&str] = &[
90    "uses the wasm32-wasip1 target",
91    "must fail closed for raw sockets, process spawning, SMTP, PostgreSQL, and unsupported async filesystem behavior",
92];
93
94const WASM_WEB_CONSTRAINTS: &[&str] = &[
95    "reserved for the host-mediated wasm entrypoint target",
96    "no networking, process, SMTP, or PostgreSQL capability may be compiled into this target",
97];
98
99/// Public constant `TARGET_CONSTRAINTS` exposed by this module.
100///
101/// # Examples
102///
103/// ```rust,ignore
104/// let _ = pharos_native::platform::TARGET_CONSTRAINTS;
105/// ```
106///
107/// # Related
108///
109/// - [`crate::platform`] for the surrounding module surface.
110pub const TARGET_CONSTRAINTS: &[TargetConstraint] = &[
111    TargetConstraint {
112        product_target: "native host runtime",
113        rust_target: HostTarget::MacosDarwinX86_64,
114        constraints: MACOS_CONSTRAINTS,
115    },
116    TargetConstraint {
117        product_target: "wasi guest",
118        rust_target: HostTarget::Wasm32Wasip1,
119        constraints: WASI_P1_CONSTRAINTS,
120    },
121    TargetConstraint {
122        product_target: "wasm entrypoint",
123        rust_target: HostTarget::Wasm32UnknownUnknown,
124        constraints: WASM_WEB_CONSTRAINTS,
125    },
126];
127
128/// Performs target constraints.
129///
130/// # Returns
131///
132/// Returns the computed value produced by [`crate::platform::target_constraints`].
133///
134/// # Examples
135///
136/// ```rust,ignore
137/// let _result = pharos_native::platform::target_constraints();
138/// ```
139///
140/// # Related
141///
142/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
143pub fn target_constraints() -> &'static [TargetConstraint] {
144    TARGET_CONSTRAINTS
145}
146
147#[allow(non_snake_case)]
148/// Performs acrt initialize.
149///
150/// # Returns
151///
152/// Returns the computed value produced by [`crate::platform::__acrt_initialize`].
153///
154/// # Examples
155///
156/// ```rust,ignore
157/// let _result = pharos_native::platform::__acrt_initialize();
158/// ```
159///
160/// # Related
161///
162/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
163pub fn __acrt_initialize() -> i32 {
164    1
165}
166
167#[allow(non_snake_case)]
168/// Performs acrt uninitialize.
169///
170/// # Parameters
171///
172/// - `_terminating`: Input consumed by [`crate::platform::__acrt_uninitialize`].
173///
174/// # Returns
175///
176/// Returns the computed value produced by [`crate::platform::__acrt_uninitialize`].
177///
178/// # Examples
179///
180/// ```rust,ignore
181/// let _result = pharos_native::platform::__acrt_uninitialize(todo!());
182/// ```
183///
184/// # Related
185///
186/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
187pub fn __acrt_uninitialize(_terminating: bool) -> i32 {
188    1
189}
190
191#[allow(non_snake_case)]
192/// Performs acrt uninitialize critical.
193///
194/// # Parameters
195///
196/// - `_terminating`: Input consumed by [`crate::platform::__acrt_uninitialize_critical`].
197///
198/// # Returns
199///
200/// Returns the computed value produced by [`crate::platform::__acrt_uninitialize_critical`].
201///
202/// # Examples
203///
204/// ```rust,ignore
205/// let _result = pharos_native::platform::__acrt_uninitialize_critical(todo!());
206/// ```
207///
208/// # Related
209///
210/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
211pub fn __acrt_uninitialize_critical(_terminating: bool) -> i32 {
212    1
213}
214
215#[allow(non_snake_case)]
216/// Performs acrt thread attach.
217///
218/// # Returns
219///
220/// Returns the computed value produced by [`crate::platform::__acrt_thread_attach`].
221///
222/// # Examples
223///
224/// ```rust,ignore
225/// let _result = pharos_native::platform::__acrt_thread_attach();
226/// ```
227///
228/// # Related
229///
230/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
231pub fn __acrt_thread_attach() -> i32 {
232    1
233}
234
235#[allow(non_snake_case)]
236/// Performs acrt thread detach.
237///
238/// # Returns
239///
240/// Returns the computed value produced by [`crate::platform::__acrt_thread_detach`].
241///
242/// # Examples
243///
244/// ```rust,ignore
245/// let _result = pharos_native::platform::__acrt_thread_detach();
246/// ```
247///
248/// # Related
249///
250/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
251pub fn __acrt_thread_detach() -> i32 {
252    1
253}
254
255/// Performs is c termination complete.
256///
257/// # Returns
258///
259/// Returns the computed value produced by [`crate::platform::_is_c_termination_complete`].
260///
261/// # Examples
262///
263/// ```rust,ignore
264/// let _result = pharos_native::platform::_is_c_termination_complete();
265/// ```
266///
267/// # Related
268///
269/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
270pub fn _is_c_termination_complete() -> i32 {
271    0
272}
273
274#[allow(non_snake_case)]
275/// Performs vcrt uninitialize critical.
276///
277/// # Returns
278///
279/// Returns the computed value produced by [`crate::platform::__vcrt_uninitialize_critical`].
280///
281/// # Examples
282///
283/// ```rust,ignore
284/// let _result = pharos_native::platform::__vcrt_uninitialize_critical();
285/// ```
286///
287/// # Related
288///
289/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
290pub fn __vcrt_uninitialize_critical() -> i32 {
291    1
292}
293
294#[allow(non_snake_case)]
295/// Performs vcrt initialize.
296///
297/// # Returns
298///
299/// Returns the computed value produced by [`crate::platform::__vcrt_initialize`].
300///
301/// # Examples
302///
303/// ```rust,ignore
304/// let _result = pharos_native::platform::__vcrt_initialize();
305/// ```
306///
307/// # Related
308///
309/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
310pub fn __vcrt_initialize() -> i32 {
311    1
312}
313
314#[allow(non_snake_case)]
315/// Performs vcrt uninitialize.
316///
317/// # Parameters
318///
319/// - `_terminating`: Input consumed by [`crate::platform::__vcrt_uninitialize`].
320///
321/// # Returns
322///
323/// Returns the computed value produced by [`crate::platform::__vcrt_uninitialize`].
324///
325/// # Examples
326///
327/// ```rust,ignore
328/// let _result = pharos_native::platform::__vcrt_uninitialize(todo!());
329/// ```
330///
331/// # Related
332///
333/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
334pub fn __vcrt_uninitialize(_terminating: bool) -> i32 {
335    1
336}
337
338#[allow(non_snake_case)]
339/// Performs vcrt thread attach.
340///
341/// # Returns
342///
343/// Returns the computed value produced by [`crate::platform::__vcrt_thread_attach`].
344///
345/// # Examples
346///
347/// ```rust,ignore
348/// let _result = pharos_native::platform::__vcrt_thread_attach();
349/// ```
350///
351/// # Related
352///
353/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
354pub fn __vcrt_thread_attach() -> i32 {
355    1
356}
357
358#[allow(non_snake_case)]
359/// Performs vcrt thread detach.
360///
361/// # Returns
362///
363/// Returns the computed value produced by [`crate::platform::__vcrt_thread_detach`].
364///
365/// # Examples
366///
367/// ```rust,ignore
368/// let _result = pharos_native::platform::__vcrt_thread_detach();
369/// ```
370///
371/// # Related
372///
373/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
374pub fn __vcrt_thread_detach() -> i32 {
375    1
376}
377
378#[cfg(feature = "wasi-host-bridge")]
379const MAX_CAPTURE_OUTPUT_BYTES: usize = 8 * 1024 * 1024;
380#[cfg(feature = "wasi-host-bridge")]
381const CAPTURE_READ_CHUNK_BYTES: usize = 4096;
382#[cfg(feature = "wasi-host-bridge")]
383const MAX_CAPTURE_WALL_CLOCK: Duration = Duration::from_secs(30);
384#[cfg(feature = "wasi-host-bridge")]
385/// Public constant `CHILD_PROCESS_TIMEOUT_CODE` exposed by this module.
386///
387/// # Examples
388///
389/// ```rust,ignore
390/// let _ = pharos_native::platform::CHILD_PROCESS_TIMEOUT_CODE;
391/// ```
392///
393/// # Related
394///
395/// - [`crate::platform`] for the surrounding module surface.
396pub const CHILD_PROCESS_TIMEOUT_CODE: i32 = 70;
397#[cfg(feature = "wasi-host-bridge")]
398const CAPTURE_POLL_INTERVAL: Duration = Duration::from_millis(5);
399#[cfg(feature = "wasi-host-bridge")]
400const MAX_CAPTURE_ARGUMENTS: usize = 1024;
401#[cfg(feature = "wasi-host-bridge")]
402const MAX_CAPTURE_ARGUMENT_BYTES: usize = 64 * 1024;
403#[cfg(feature = "wasi-host-bridge")]
404const MAX_ENV_COMPONENT_BYTES: usize = 64 * 1024;
405
406#[cfg(feature = "wasi-host-bridge")]
407/// Reads one child-process pipe chunk into a bounded output buffer.
408///
409/// # Parameters
410///
411/// - `reader`: Reader supplying child-process output bytes.
412/// - `buffer`: Input consumed by [`crate::platform::append_pipe_to_buffer`].
413/// - `open`: Input consumed by [`crate::platform::append_pipe_to_buffer`].
414///
415/// # Returns
416///
417/// Returns the computed value produced by [`crate::platform::append_pipe_to_buffer`].
418///
419/// # Examples
420///
421/// ```rust,ignore
422/// let _result = pharos_native::platform::append_pipe_to_buffer(todo!(), &mut [], todo!(), todo!());
423/// ```
424///
425/// # Related
426///
427/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
428pub(crate) fn append_pipe_to_buffer<R: Read>(
429    reader: &mut R,
430    buffer: &mut Vec<u8>,
431    open: &mut bool,
432) -> io::Result<()> {
433    if !*open {
434        return Ok(());
435    }
436
437    let remaining = MAX_CAPTURE_OUTPUT_BYTES.saturating_sub(buffer.len());
438    let read_limit = remaining.saturating_add(1).min(CAPTURE_READ_CHUNK_BYTES);
439    let mut chunk = [0_u8; CAPTURE_READ_CHUNK_BYTES];
440    let bytes_read = reader.read(&mut chunk[..read_limit])?;
441    if bytes_read == 0 {
442        *open = false;
443        return Ok(());
444    }
445    if bytes_read > remaining {
446        return Err(io::Error::new(
447            io::ErrorKind::InvalidData,
448            "child-process output exceeds the capture limit",
449        ));
450    }
451    buffer.extend_from_slice(&chunk[..bytes_read]);
452    Ok(())
453}
454
455#[cfg(feature = "wasi-host-bridge")]
456/// Builds one validated `KEY=VALUE` environment assignment for a child.
457///
458/// # Parameters
459///
460/// - `key`: Environment variable name to validate and encode.
461/// - `value`: Input consumed by [`crate::platform::build_child_env_assignment`].
462///
463/// # Returns
464///
465/// Returns [`Some`] when [`crate::platform::build_child_env_assignment`] can produce a value and [`None`] otherwise.
466///
467/// # Examples
468///
469/// ```rust,ignore
470/// let _result = pharos_native::platform::build_child_env_assignment(todo!(), "value");
471/// ```
472///
473/// # Related
474///
475/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
476pub(crate) fn build_child_env_assignment(key: Option<&str>, value: Option<&str>) -> Option<String> {
477    let key = key.filter(|value| !value.is_empty())?;
478    let value = value.filter(|value| !value.is_empty())?;
479    if key.len() > MAX_ENV_COMPONENT_BYTES
480        || value.len() > MAX_ENV_COMPONENT_BYTES
481        || key.contains('=')
482        || key.contains('\0')
483        || value.contains('\0')
484    {
485        return None;
486    }
487    Some(format!("{key}={value}"))
488}
489
490#[cfg(feature = "wasi-host-bridge")]
491/// Builds the inherited environment with valid requested keys applied once.
492///
493/// # Parameters
494///
495/// - `env_keys`: Keys to override or append in the child environment.
496/// - `env_values`: Input consumed by [`crate::platform::build_child_env`].
497///
498/// # Returns
499///
500/// Returns the computed value produced by [`crate::platform::build_child_env`].
501///
502/// # Performance
503///
504/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::platform::build_child_env`].
505///
506/// # Examples
507///
508/// ```rust,ignore
509/// let _result = pharos_native::platform::build_child_env(todo!(), todo!(), todo!(), todo!());
510/// ```
511///
512/// # Related
513///
514/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
515pub(crate) fn build_child_env<K, V>(env_keys: &[K], env_values: &[V]) -> Vec<(String, String)>
516where
517    K: AsRef<str>,
518    V: AsRef<str>,
519{
520    let assignments: Vec<(String, String)> = env_keys
521        .iter()
522        .zip(env_values.iter())
523        .filter_map(|(key, value)| {
524            let assignment = build_child_env_assignment(Some(key.as_ref()), Some(value.as_ref()))?;
525            let (key, value) = assignment.split_once('=')?;
526            Some((key.to_owned(), value.to_owned()))
527        })
528        .collect();
529    let overridden: HashSet<&str> = assignments.iter().map(|(key, _)| key.as_str()).collect();
530    let mut child_env: Vec<(String, String)> = env::vars()
531        .filter(|(key, _)| !overridden.contains(key.as_str()))
532        .collect();
533    child_env.extend(assignments);
534    child_env
535}
536
537#[cfg(feature = "wasi-host-bridge")]
538/// Releases an owned child environment using Rust's normal drop semantics.
539///
540/// # Parameters
541///
542/// - `child_env`: Environment vector being consumed and dropped.
543///
544/// # Returns
545///
546/// Returns after [`crate::platform::free_child_env`] applies its in-place effect.
547///
548/// # Examples
549///
550/// ```rust,ignore
551/// let _result = pharos_native::platform::free_child_env(todo!(), todo!());
552/// ```
553///
554/// # Related
555///
556/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
557pub(crate) fn free_child_env<K, V>(child_env: &mut Vec<(K, V)>) {
558    child_env.clear();
559}
560
561/// Returns whether a path currently identifies a directory.
562///
563/// # Parameters
564///
565/// - `path`: Input consumed by [`crate::platform::path_is_directory`].
566///
567/// # Returns
568///
569/// Returns `true` when the condition checked by [`crate::platform::path_is_directory`] holds and `false` otherwise.
570///
571/// # Examples
572///
573/// ```rust,ignore
574/// let _result = pharos_native::platform::path_is_directory(std::path::Path::new("fixture.json"));
575/// ```
576///
577/// # Related
578///
579/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
580pub fn path_is_directory(path: &Path) -> bool {
581    !path.as_os_str().is_empty() && path.is_dir()
582}
583
584/// Tests whether one string begins with another string.
585///
586/// # Parameters
587///
588/// - `value`: Input consumed by [`crate::platform::starts_with`].
589/// - `prefix`: Input consumed by [`crate::platform::starts_with`].
590///
591/// # Returns
592///
593/// Returns `true` when the condition checked by [`crate::platform::starts_with`] holds and `false` otherwise.
594///
595/// # Examples
596///
597/// ```rust,ignore
598/// let _result = pharos_native::platform::starts_with("value", todo!());
599/// ```
600///
601/// # Related
602///
603/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
604pub fn starts_with(value: &str, prefix: &str) -> bool {
605    value.starts_with(prefix)
606}
607
608#[cfg(feature = "wasi-host-bridge")]
609#[derive(Debug)]
610struct CapturePipeResult {
611    bytes: Result<Vec<u8>, i32>,
612}
613
614#[cfg(feature = "wasi-host-bridge")]
615/// Captures pipe.
616fn capture_pipe<R: Read + Send + 'static>(
617    mut reader: R,
618    failed: Arc<AtomicBool>,
619) -> JoinHandle<CapturePipeResult> {
620    thread::spawn(move || {
621        let mut buffer = Vec::new();
622        let mut open = true;
623        while open {
624            if append_pipe_to_buffer(&mut reader, &mut buffer, &mut open).is_err() {
625                failed.store(true, Ordering::Release);
626                return CapturePipeResult { bytes: Err(69) };
627            }
628        }
629        CapturePipeResult { bytes: Ok(buffer) }
630    })
631}
632
633#[cfg(feature = "wasi-host-bridge")]
634/// Captures argv.
635fn capture_argv<A: AsRef<str>>(argv: &[A]) -> Result<Vec<String>, i32> {
636    if argv.is_empty() || argv.len() > MAX_CAPTURE_ARGUMENTS {
637        return Err(69);
638    }
639    argv.iter()
640        .map(|argument| {
641            let argument = argument.as_ref();
642            if argument.is_empty()
643                || argument.len() > MAX_CAPTURE_ARGUMENT_BYTES
644                || argument.contains('\0')
645            {
646                Err(69)
647            } else {
648                Ok(argument.to_owned())
649            }
650        })
651        .collect()
652}
653
654#[cfg(feature = "wasi-host-bridge")]
655/// Terminates a supervised child and its process group where supported, then reaps it.
656///
657/// # Parameters
658///
659/// - `child`: Input consumed by [`crate::platform::terminate_child_process_tree`].
660///
661/// # Returns
662///
663/// Returns after [`crate::platform::terminate_child_process_tree`] applies its in-place effect.
664///
665/// # Examples
666///
667/// ```rust,ignore
668/// let _result = pharos_native::platform::terminate_child_process_tree(todo!());
669/// ```
670///
671/// # Related
672///
673/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
674pub fn terminate_child_process_tree(child: &mut Child) {
675    #[cfg(unix)]
676    {
677        let group = format!("-{}", child.id());
678        let _ = Command::new("/bin/kill")
679            .args(["-TERM", "--", group.as_str()])
680            .stdout(Stdio::null())
681            .stderr(Stdio::null())
682            .status();
683        thread::sleep(Duration::from_millis(20));
684        let _ = Command::new("/bin/kill")
685            .args(["-KILL", "--", group.as_str()])
686            .stdout(Stdio::null())
687            .stderr(Stdio::null())
688            .status();
689    }
690    #[cfg(windows)]
691    {
692        let pid = child.id().to_string();
693        let taskkill = env::var_os("SystemRoot")
694            .map(|root| Path::new(&root).join("System32").join("taskkill.exe"))
695            .unwrap_or_else(|| Path::new("taskkill.exe").to_path_buf());
696        let _ = Command::new(taskkill)
697            .args(["/PID", pid.as_str(), "/T", "/F"])
698            .stdout(Stdio::null())
699            .stderr(Stdio::null())
700            .status();
701    }
702    let _ = child.kill();
703    let _ = child.wait();
704}
705
706/// Executes an argv vector directly with bounded output and wall-clock lifetime.
707///
708/// No shell is involved. Each stream is drained concurrently and is capped at
709/// `MAX_CAPTURE_OUTPUT_BYTES`. A timeout terminates the child process tree where
710/// supported and always reaps the direct child before returning.
711#[cfg(feature = "wasi-host-bridge")]
712fn capture_execv_output_split_env_with_timeout<A, K, V>(
713    argv: &[A],
714    env_keys: &[K],
715    env_values: &[V],
716    timeout: Duration,
717) -> Result<(i32, Vec<u8>, Vec<u8>), i32>
718where
719    A: AsRef<str>,
720    K: AsRef<str>,
721    V: AsRef<str>,
722{
723    let argv = capture_argv(argv)?;
724    let mut child_env = build_child_env(env_keys, env_values);
725    let mut command = Command::new(&argv[0]);
726    command
727        .args(&argv[1..])
728        .env_clear()
729        .stdout(Stdio::piped())
730        .stderr(Stdio::piped());
731    #[cfg(unix)]
732    {
733        use std::os::unix::process::CommandExt;
734        command.process_group(0);
735    }
736    for (key, value) in &child_env {
737        command.env(key, value);
738    }
739    free_child_env(&mut child_env);
740
741    let mut child = command.spawn().map_err(|_| 69)?;
742    let stdout = child.stdout.take().ok_or(69)?;
743    let stderr = child.stderr.take().ok_or(69)?;
744    let failed = Arc::new(AtomicBool::new(false));
745    let stdout_thread = capture_pipe(stdout, Arc::clone(&failed));
746    let stderr_thread = capture_pipe(stderr, Arc::clone(&failed));
747    let deadline = Instant::now().checked_add(timeout);
748
749    let status = loop {
750        if failed.load(Ordering::Acquire) {
751            terminate_child_process_tree(&mut child);
752            break child.try_wait().map_err(|_| 69)?.ok_or(69)?;
753        }
754        if deadline.is_some_and(|value| Instant::now() >= value) {
755            terminate_child_process_tree(&mut child);
756            let _ = stdout_thread.join();
757            let _ = stderr_thread.join();
758            return Err(CHILD_PROCESS_TIMEOUT_CODE);
759        }
760        match child.try_wait() {
761            Ok(Some(status)) => break status,
762            Ok(None) => thread::sleep(CAPTURE_POLL_INTERVAL),
763            Err(_) => {
764                terminate_child_process_tree(&mut child);
765                return Err(69);
766            }
767        }
768    };
769
770    let stdout = stdout_thread.join().map_err(|_| 69)?.bytes?;
771    let stderr = stderr_thread.join().map_err(|_| 69)?.bytes?;
772    Ok((status.code().unwrap_or(69), stdout, stderr))
773}
774
775#[cfg(feature = "wasi-host-bridge")]
776/// Executes an argv vector directly and captures stdout and stderr separately.
777///
778/// # Parameters
779///
780/// - `argv`: Input consumed by [`crate::platform::capture_execv_output_split_env`].
781/// - `env_keys`: Input consumed by [`crate::platform::capture_execv_output_split_env`].
782/// - `env_values`: Input consumed by [`crate::platform::capture_execv_output_split_env`].
783///
784/// # Returns
785///
786/// Returns the successful value produced by [`crate::platform::capture_execv_output_split_env`] or an error when the operation fails.
787///
788/// # Errors
789///
790/// Returns an error value when [`crate::platform::capture_execv_output_split_env`] cannot complete successfully.
791///
792/// # Examples
793///
794/// ```rust,ignore
795/// let _result = pharos_native::platform::capture_execv_output_split_env(todo!(), todo!(), todo!(), todo!(), todo!(), todo!());
796/// ```
797///
798/// # Related
799///
800/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
801pub fn capture_execv_output_split_env<A, K, V>(
802    argv: &[A],
803    env_keys: &[K],
804    env_values: &[V],
805) -> Result<(i32, Vec<u8>, Vec<u8>), i32>
806where
807    A: AsRef<str>,
808    K: AsRef<str>,
809    V: AsRef<str>,
810{
811    capture_execv_output_split_env_with_timeout(argv, env_keys, env_values, MAX_CAPTURE_WALL_CLOCK)
812}
813
814#[cfg(feature = "wasi-host-bridge")]
815/// Executes argv with validated environment overrides and merges both streams.
816///
817/// # Parameters
818///
819/// - `argv`: Input consumed by [`crate::platform::capture_execv_output_env`].
820/// - `env_keys`: Input consumed by [`crate::platform::capture_execv_output_env`].
821/// - `env_values`: Input consumed by [`crate::platform::capture_execv_output_env`].
822///
823/// # Returns
824///
825/// Returns the successful value produced by [`crate::platform::capture_execv_output_env`] or an error when the operation fails.
826///
827/// # Errors
828///
829/// Returns an error value when [`crate::platform::capture_execv_output_env`] cannot complete successfully.
830///
831/// # Examples
832///
833/// ```rust,ignore
834/// let _result = pharos_native::platform::capture_execv_output_env(todo!(), todo!(), todo!(), todo!(), todo!());
835/// ```
836///
837/// # Related
838///
839/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
840pub fn capture_execv_output_env<A, K, V>(
841    argv: &[A],
842    env_keys: &[K],
843    env_values: &[V],
844) -> Result<(i32, Vec<u8>), i32>
845where
846    A: AsRef<str>,
847    K: AsRef<str>,
848    V: AsRef<str>,
849{
850    let (status, stdout, stderr) = capture_execv_output_split_env(argv, env_keys, env_values)?;
851    if stdout
852        .len()
853        .checked_add(stderr.len())
854        .is_none_or(|length| length > MAX_CAPTURE_OUTPUT_BYTES)
855    {
856        return Err(69);
857    }
858    let mut output = Vec::with_capacity(stdout.len() + stderr.len());
859    output.extend_from_slice(&stdout);
860    output.extend_from_slice(&stderr);
861    Ok((status, output))
862}
863
864#[cfg(feature = "wasi-host-bridge")]
865/// Executes argv directly and captures bounded merged output.
866///
867/// # Parameters
868///
869/// - `argv`: Input consumed by [`crate::platform::capture_execv_output`].
870/// - `Vec<u8>`: Input consumed by [`crate::platform::capture_execv_output`].
871///
872/// # Returns
873///
874/// Returns the successful value produced by [`crate::platform::capture_execv_output`] or an error when the operation fails.
875///
876/// # Errors
877///
878/// Returns an error value when [`crate::platform::capture_execv_output`] cannot complete successfully.
879///
880/// # Examples
881///
882/// ```rust,ignore
883/// let _result = pharos_native::platform::capture_execv_output(todo!(), todo!());
884/// ```
885///
886/// # Related
887///
888/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
889pub fn capture_execv_output<A: AsRef<str>>(argv: &[A]) -> Result<(i32, Vec<u8>), i32> {
890    let empty: [&str; 0] = [];
891    capture_execv_output_env(argv, &empty, &empty)
892}
893
894/// Checks writability without creating files or invoking external commands.
895///
896/// # Parameters
897///
898/// - `path`: Input consumed by [`crate::platform::path_is_writable`].
899///
900/// # Returns
901///
902/// Returns `true` when the condition checked by [`crate::platform::path_is_writable`] holds and `false` otherwise.
903///
904/// # Examples
905///
906/// ```rust,ignore
907/// let _result = pharos_native::platform::path_is_writable(std::path::Path::new("fixture.json"));
908/// ```
909///
910/// # Related
911///
912/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
913pub fn path_is_writable(path: &Path) -> bool {
914    if path.as_os_str().is_empty() {
915        return false;
916    }
917    let directory = if path.is_dir() {
918        path.to_path_buf()
919    } else {
920        path.parent()
921            .unwrap_or_else(|| Path::new("."))
922            .to_path_buf()
923    };
924    if !directory.is_dir() {
925        return false;
926    }
927    #[cfg(unix)]
928    {
929        use std::os::unix::fs::PermissionsExt;
930        directory
931            .metadata()
932            .map(|metadata| metadata.permissions().mode() & 0o222 != 0)
933            .unwrap_or(false)
934    }
935    #[cfg(not(unix))]
936    {
937        true
938    }
939}
940
941const PLATFORM_BUFFER_LIMIT: usize = 16 * 1024 * 1024;
942
943#[derive(Debug, Clone, Default, Eq, PartialEq)]
944/// Public struct `PlatformBuffer` used by this module.
945///
946/// # Fields
947///
948/// - `bytes`: Stored field on `PlatformBuffer`.
949///
950/// # Examples
951///
952/// ```rust,ignore
953/// let _ = std::mem::size_of::<pharos_native::platform::PlatformBuffer>();
954/// ```
955///
956/// # Related
957///
958/// - [`crate::platform`] for constructors, helpers, and neighboring runtime types.
959pub struct PlatformBuffer {
960    pub bytes: Vec<u8>,
961}
962
963/// Initializes a bounded platform buffer.
964///
965/// # Parameters
966///
967/// - `buffer`: Input consumed by [`crate::platform::buffer_init`].
968///
969/// # Returns
970///
971/// Returns after [`crate::platform::buffer_init`] applies its in-place effect.
972///
973/// # Examples
974///
975/// ```rust,ignore
976/// let _result = pharos_native::platform::buffer_init(&mut []);
977/// ```
978///
979/// # Related
980///
981/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
982pub fn buffer_init(buffer: &mut PlatformBuffer) {
983    buffer.bytes.clear();
984}
985
986/// Releases a platform buffer.
987///
988/// # Parameters
989///
990/// - `buffer`: Input consumed by [`crate::platform::buffer_free`].
991///
992/// # Returns
993///
994/// Returns after [`crate::platform::buffer_free`] applies its in-place effect.
995///
996/// # Examples
997///
998/// ```rust,ignore
999/// let _result = pharos_native::platform::buffer_free(&mut []);
1000/// ```
1001///
1002/// # Related
1003///
1004/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1005pub fn buffer_free(buffer: &mut PlatformBuffer) {
1006    buffer.bytes.clear();
1007    buffer.bytes.shrink_to_fit();
1008}
1009
1010/// Reserves bounded capacity for a platform buffer.
1011///
1012/// # Parameters
1013///
1014/// - `buffer`: Input consumed by [`crate::platform::buffer_reserve`].
1015/// - `needed`: Input consumed by [`crate::platform::buffer_reserve`].
1016///
1017/// # Returns
1018///
1019/// Returns `true` when the condition checked by [`crate::platform::buffer_reserve`] holds and `false` otherwise.
1020///
1021/// # Examples
1022///
1023/// ```rust,ignore
1024/// let _result = pharos_native::platform::buffer_reserve(&mut [], todo!());
1025/// ```
1026///
1027/// # Related
1028///
1029/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1030pub fn buffer_reserve(buffer: &mut PlatformBuffer, needed: usize) -> bool {
1031    needed <= PLATFORM_BUFFER_LIMIT
1032        && buffer
1033            .bytes
1034            .try_reserve(needed.saturating_sub(buffer.bytes.len()))
1035            .is_ok()
1036}
1037
1038/// Appends bytes to a platform buffer.
1039///
1040/// # Parameters
1041///
1042/// - `buffer`: Input consumed by [`crate::platform::buffer_append`].
1043/// - `data`: Input consumed by [`crate::platform::buffer_append`].
1044///
1045/// # Returns
1046///
1047/// Returns `true` when the condition checked by [`crate::platform::buffer_append`] holds and `false` otherwise.
1048///
1049/// # Examples
1050///
1051/// ```rust,ignore
1052/// let _result = pharos_native::platform::buffer_append(&mut [], b"");
1053/// ```
1054///
1055/// # Related
1056///
1057/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1058pub fn buffer_append(buffer: &mut PlatformBuffer, data: &[u8]) -> bool {
1059    if !buffer_reserve(buffer, buffer.bytes.len().saturating_add(data.len())) {
1060        return false;
1061    }
1062    buffer.bytes.extend_from_slice(data);
1063    true
1064}
1065
1066/// Duplicates a bounded byte range as UTF-8 text.
1067///
1068/// # Parameters
1069///
1070/// - `text`: Input consumed by [`crate::platform::dup_range`].
1071/// - `length`: Input consumed by [`crate::platform::dup_range`].
1072///
1073/// # Returns
1074///
1075/// Returns [`Some`] when [`crate::platform::dup_range`] can produce a value and [`None`] otherwise.
1076///
1077/// # Examples
1078///
1079/// ```rust,ignore
1080/// let _result = pharos_native::platform::dup_range("{}", todo!());
1081/// ```
1082///
1083/// # Related
1084///
1085/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1086pub fn dup_range(text: &str, length: usize) -> Option<String> {
1087    text.get(..length.min(text.len())).map(ToOwned::to_owned)
1088}
1089
1090/// Trims ASCII whitespace in place.
1091///
1092/// # Parameters
1093///
1094/// - `text`: Input consumed by [`crate::platform::trim_in_place`].
1095///
1096/// # Returns
1097///
1098/// Returns after [`crate::platform::trim_in_place`] applies its in-place effect.
1099///
1100/// # Examples
1101///
1102/// ```rust,ignore
1103/// let _result = pharos_native::platform::trim_in_place("{}");
1104/// ```
1105///
1106/// # Related
1107///
1108/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1109pub fn trim_in_place(text: &mut String) {
1110    *text = text.trim().to_owned();
1111}
1112
1113/// Joins two paths without allowing an absolute right-hand replacement.
1114///
1115/// # Parameters
1116///
1117/// - `left`: Input consumed by [`crate::platform::path_join`].
1118/// - `right`: Input consumed by [`crate::platform::path_join`].
1119///
1120/// # Returns
1121///
1122/// Returns [`Some`] when [`crate::platform::path_join`] can produce a value and [`None`] otherwise.
1123///
1124/// # Examples
1125///
1126/// ```rust,ignore
1127/// let _result = pharos_native::platform::path_join(todo!(), todo!());
1128/// ```
1129///
1130/// # Related
1131///
1132/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1133pub fn path_join(left: &str, right: &str) -> Option<String> {
1134    if left.is_empty() || right.is_empty() || Path::new(right).is_absolute() || right.contains('\0')
1135    {
1136        return None;
1137    }
1138    Some(Path::new(left).join(right).to_string_lossy().into_owned())
1139}
1140
1141/// Returns a path's parent component as owned text.
1142///
1143/// # Parameters
1144///
1145/// - `path`: Input consumed by [`crate::platform::path_dirname`].
1146///
1147/// # Returns
1148///
1149/// Returns [`Some`] when [`crate::platform::path_dirname`] can produce a value and [`None`] otherwise.
1150///
1151/// # Examples
1152///
1153/// ```rust,ignore
1154/// let _result = pharos_native::platform::path_dirname(std::path::Path::new("fixture.json"));
1155/// ```
1156///
1157/// # Related
1158///
1159/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1160pub fn path_dirname(path: &str) -> Option<String> {
1161    Path::new(path)
1162        .parent()
1163        .map(|value| value.to_string_lossy().into_owned())
1164}
1165
1166/// Checks whether a path exists without following user-controlled commands.
1167///
1168/// # Parameters
1169///
1170/// - `path`: Input consumed by [`crate::platform::path_exists`].
1171///
1172/// # Returns
1173///
1174/// Returns `true` when the condition checked by [`crate::platform::path_exists`] holds and `false` otherwise.
1175///
1176/// # Examples
1177///
1178/// ```rust,ignore
1179/// let _result = pharos_native::platform::path_exists(std::path::Path::new("fixture.json"));
1180/// ```
1181///
1182/// # Related
1183///
1184/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1185pub fn path_exists(path: &str) -> bool {
1186    !path.is_empty() && Path::new(path).exists()
1187}
1188
1189/// Reads a bounded UTF-8 file.
1190///
1191/// # Parameters
1192///
1193/// - `path`: Input consumed by [`crate::platform::read_file_text`].
1194///
1195/// # Returns
1196///
1197/// Returns [`Some`] when [`crate::platform::read_file_text`] can produce a value and [`None`] otherwise.
1198///
1199/// # Performance
1200///
1201/// Performance is proportional to the size of the supplied inputs and any parsed or loaded data consumed by [`crate::platform::read_file_text`].
1202///
1203/// # Examples
1204///
1205/// ```rust,ignore
1206/// let _result = pharos_native::platform::read_file_text(std::path::Path::new("fixture.json"));
1207/// ```
1208///
1209/// # Related
1210///
1211/// - [`crate::platform`] for adjacent helpers that share the same runtime surface.
1212pub fn read_file_text(path: &Path) -> Option<String> {
1213    let metadata = std::fs::metadata(path).ok()?;
1214    (metadata.len() <= PLATFORM_BUFFER_LIMIT as u64)
1215        .then(|| std::fs::read_to_string(path).ok())
1216        .flatten()
1217}
1218
1219#[cfg(all(test, feature = "wasi-host-bridge"))]
1220mod tests {
1221    use super::{CHILD_PROCESS_TIMEOUT_CODE, capture_execv_output_split_env_with_timeout};
1222    use std::time::{Duration, Instant};
1223
1224    #[cfg(unix)]
1225    #[test]
1226    /// Captures timeout terminates a child process group.
1227    fn capture_timeout_terminates_a_child_process_group() {
1228        let started = Instant::now();
1229        let result = capture_execv_output_split_env_with_timeout(
1230            &["/bin/sh", "-c", "sleep 5 & wait"],
1231            &[] as &[&str],
1232            &[] as &[&str],
1233            Duration::from_millis(100),
1234        );
1235        assert_eq!(result, Err(CHILD_PROCESS_TIMEOUT_CODE));
1236        assert!(started.elapsed() < Duration::from_secs(2));
1237    }
1238}