Merge nucleic/sleek-thistle-egret-fyej into dev

This commit is contained in:
2026-07-18 05:19:31 -07:00
commit b6be87b72d
677 changed files with 102939 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
//! Async pipe reading utilities for non-Unix platforms.
//!
//! Uses `spawn_blocking` internally for the I/O operation only,
//! not for the entire subshell execution.
use std::io::{self, Read};
pub(crate) struct AsyncPipeReader {
inner: Option<std::io::PipeReader>,
}
impl AsyncPipeReader {
pub(crate) fn new(fd: std::io::PipeReader) -> io::Result<Self> {
Ok(Self { inner: Some(fd) })
}
pub(crate) async fn read_to_string(&mut self) -> io::Result<String> {
let Some(reader) = self.inner.take() else {
return Ok(String::new());
};
tokio::task::spawn_blocking(move || {
let mut s = String::new();
{ reader }.read_to_string(&mut s)?;
Ok(s)
})
.await
.map_err(io::Error::other)?
}
}
+95
View File
@@ -0,0 +1,95 @@
//! Command execution utilities.
use std::ffi::OsStr;
use crate::{ShellFd, error, openfiles};
/// Extension trait for Unix-like command extensions.
pub trait CommandExt {
/// Sets the zeroth argument (argv[0]) of the command.
///
/// # Arguments
///
/// * `arg` - The argument to set as argv[0].
fn arg0<S>(&mut self, arg: S) -> &mut Self
where
S: AsRef<OsStr>;
/// Sets the process group ID of the command.
///
/// # Arguments
///
/// * `pgroup` - The process group ID to set.
fn process_group(&mut self, pgroup: i32) -> &mut Self;
}
impl CommandExt for std::process::Command {
fn arg0<S>(&mut self, _arg: S) -> &mut Self
where
S: AsRef<OsStr>,
{
// NOTE: no-op.
self
}
fn process_group(&mut self, _pgroup: i32) -> &mut Self {
// NOTE: no-op.
self
}
}
/// Extension trait for Unix-like exit status extensions.
pub trait ExitStatusExt {
/// Returns the signal that terminated the process, if any.
fn signal(&self) -> Option<i32>;
}
impl ExitStatusExt for std::process::ExitStatus {
fn signal(&self) -> Option<i32> {
None
}
}
/// Extension trait for injecting file descriptors into commands.
pub trait CommandFdInjectionExt {
/// Injects the given open files as file descriptors into the command.
///
/// # Arguments
///
/// * `open_files` - A mapping of child file descriptors to open files.
fn inject_fds(
&mut self,
open_files: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
) -> Result<(), error::Error>;
}
impl CommandFdInjectionExt for std::process::Command {
fn inject_fds(
&mut self,
mut open_files: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
) -> Result<(), error::Error> {
if open_files.next().is_some() {
return Err(error::ErrorKind::NotSupportedOnThisPlatform("fd redirections").into());
}
Ok(())
}
}
/// Extension trait for arranging for commands to take the foreground.
pub trait CommandFgControlExt {
/// Arranges for the command to take the foreground when it is executed.
fn take_foreground(&mut self);
/// Arranges for the command to become a session leader when it is executed.
fn lead_session(&mut self);
}
impl CommandFgControlExt for std::process::Command {
fn take_foreground(&mut self) {
// NOTE: This is a no-op.
}
fn lead_session(&mut self) {
// NOTE: This is a no-op.
}
}
+8
View File
@@ -0,0 +1,8 @@
//! Environment variable retrieval (stub implementation).
/// Retrieves environment variables from the host process.
///
/// Stub implementation that returns no variables.
pub(crate) fn get_host_env_vars() -> impl Iterator<Item = (String, String)> {
std::iter::empty()
}
+13
View File
@@ -0,0 +1,13 @@
//! File descriptor utilities.
use crate::{ShellFd, error, openfiles};
/// Stub implementation for platforms that do not support enumerating file descriptors.
pub fn try_iter_open_fds() -> impl Iterator<Item = (ShellFd, openfiles::OpenFile)> {
std::iter::empty()
}
/// Stub implementation for platforms that do not support opening file descriptors.
pub fn try_get_file_for_open_fd(_fd: ShellFd) -> Option<openfiles::OpenFile> {
None
}
+136
View File
@@ -0,0 +1,136 @@
//! Filesystem utilities (stubs).
use crate::error;
pub(crate) trait MetadataExt {
fn gid(&self) -> u32 {
0
}
fn uid(&self) -> u32 {
0
}
}
impl MetadataExt for std::fs::Metadata {}
pub(crate) fn get_default_executable_search_paths() -> Vec<std::path::PathBuf> {
vec![]
}
/// Returns the default paths where standard Unix utilities are typically installed.
/// This is a stub implementation that returns an empty vector.
pub fn get_default_standard_utils_paths() -> Vec<std::path::PathBuf> {
vec![]
}
/// Opens a null file that will discard all I/O.
///
/// This is a stub implementation that returns an error.
pub fn open_null_file() -> Result<std::fs::File, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("opening null file").into())
}
/// Gives the platform an opportunity to handle a special file path (e.g. `/dev/null`).
//
// This is a stub implementation that returns no result.
pub fn try_open_special_file(
_path: &std::path::Path,
) -> Option<Result<std::fs::File, std::io::Error>> {
None
}
/// Returns the path to the system-wide shell profile script.
///
/// Stub implementation that returns `None`.
pub fn get_system_profile_path() -> Option<&'static std::path::Path> {
None
}
/// Returns the path to the system-wide shell rc script.
///
/// Stub implementation that returns `None`.
pub fn get_system_rc_path() -> Option<&'static std::path::Path> {
None
}
/// Returns the platform default for case-insensitive pathname expansion.
///
/// In the stub implementation, this returns `false`.
pub const fn default_case_insensitive_path_expansion() -> bool {
false
}
/// Returns true if the string contains a path separator character.
///
/// In the stub implementation, only `/` is considered a path separator.
pub fn contains_path_separator(s: &str) -> bool {
s.contains('/')
}
/// Returns true if the string ends with a path separator character.
///
/// In the stub implementation, only `/` is considered a path separator.
pub fn ends_with_path_separator(s: &str) -> bool {
s.ends_with('/')
}
/// Returns the string with a trailing path separator removed, if present.
///
/// In the stub implementation, only `/` is considered a path separator.
pub fn strip_path_separator_suffix(s: &str) -> &str {
s.strip_suffix('/').unwrap_or(s)
}
/// Finds the byte index of the last path separator in the string.
///
/// In the stub implementation, only `/` is considered a path separator.
pub fn rfind_path_separator(s: &str) -> Option<usize> {
s.rfind('/')
}
/// Splits a string on path separator characters, returning an iterator of components.
///
/// In the stub implementation, only `/` is used as a separator.
pub fn split_path_for_pattern(s: &str) -> impl Iterator<Item = &str> {
s.split('/')
}
/// Returns the root path for an absolute pattern, if the first component indicates one.
///
/// In the stub implementation, an empty first component indicates an absolute path.
pub fn pattern_path_root(first_component: &str) -> Option<std::path::PathBuf> {
if first_component.is_empty() {
Some(std::path::PathBuf::from("/"))
} else {
None
}
}
/// Pushes a component onto a path for pattern expansion.
///
/// In the stub implementation, this delegates directly to `PathBuf::push`.
pub fn push_path_for_pattern(path: &mut std::path::PathBuf, component: &str) {
path.push(component);
}
/// Normalizes path separators for shell output.
///
/// In the stub implementation, this is a no-op.
pub const fn normalize_path_separators(s: &str) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Borrowed(s)
}
/// Resolves an owned path to the actual on-disk executable file, if any.
///
/// In the stub implementation, returns the path unchanged if it is
/// executable (per the stub `PathExt`, which considers every path
/// executable).
pub fn resolve_executable(path: std::path::PathBuf) -> Option<std::path::PathBuf> {
use crate::sys::fs::PathExt;
if path.as_path().executable() {
Some(path)
} else {
None
}
}
+16
View File
@@ -0,0 +1,16 @@
//! Terminal input utilities
use crate::{error, interfaces};
/// Translates a key code (byte sequence) into a `Key` enum value. Returns `None`
/// if the key code is not recognized.
///
/// This is a stub implementation that recognizes single-byte non-control
/// characters but does not support terminal-specific key sequences.
pub fn try_get_key_from_key_code(key_code: &[u8]) -> Option<interfaces::Key> {
if key_code.len() == 1 && !key_code[0].is_ascii_control() {
Some(interfaces::Key::Character(key_code[0] as char))
} else {
None
}
}
+3
View File
@@ -0,0 +1,3 @@
pub(crate) fn get_hostname() -> std::io::Result<std::ffi::OsString> {
Ok("".into())
}
+53
View File
@@ -0,0 +1,53 @@
/// Stub implementation of a pipe reader.
#[derive(Clone)]
pub(crate) struct PipeReader {}
impl PipeReader {
/// Tries to clone the reader.
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok((*self).clone())
}
}
impl From<PipeReader> for std::process::Stdio {
fn from(_reader: PipeReader) -> Self {
Self::null()
}
}
impl std::io::Read for PipeReader {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Ok(0)
}
}
/// Stub implementation o a pipe writer.
#[derive(Clone)]
pub(crate) struct PipeWriter {}
impl PipeWriter {
/// Tries to clone the writer.
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok((*self).clone())
}
}
impl From<PipeWriter> for std::process::Stdio {
fn from(_writer: PipeWriter) -> Self {
Self::null()
}
}
impl std::io::Write for PipeWriter {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
Ok(0)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
pub(crate) fn pipe() -> std::io::Result<(PipeReader, PipeWriter)> {
Ok((PipeReader {}, PipeWriter {}))
}
+15
View File
@@ -0,0 +1,15 @@
//! Stub file descriptor polling utilities for platforms without poll support.
use std::time::Duration;
use crate::openfiles::OpenFile;
/// Stub implementation that always returns an unsupported error.
///
/// Timeout-based reading is not supported on this platform.
pub fn poll_for_input(_file: &OpenFile, _timeout: Duration) -> std::io::Result<bool> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"poll-based timeout is not supported on this platform",
))
}
+34
View File
@@ -0,0 +1,34 @@
//! Process management utilities
pub(crate) type ProcessId = i32;
/// Provides access to a child process.
pub struct Child {
inner: std::process::Child,
}
pub(crate) use std::process::ExitStatus;
pub(crate) use std::process::Output;
impl Child {
/// Returns the process ID of the child process, if available.
pub fn id(&self) -> Option<u32> {
None
}
/// Asynchronously waits for the child process to exit.
pub async fn wait(&mut self) -> std::io::Result<ExitStatus> {
self.inner.wait()
}
/// Asynchronously waits for the child process to exit and collects its
/// output.
pub async fn wait_with_output(self) -> std::io::Result<Output> {
self.inner.wait_with_output()
}
}
pub(crate) fn spawn(mut command: std::process::Command) -> std::io::Result<Child> {
let child = command.spawn()?;
Ok(Child { inner: child })
}
+19
View File
@@ -0,0 +1,19 @@
//! Signal processing utilities
use crate::error;
/// Returns the user and system CPU time used by the current process.
///
/// This is a stub implementation that returns zero durations.
pub fn get_self_user_and_system_time()
-> Result<(std::time::Duration, std::time::Duration), error::Error> {
Ok((std::time::Duration::ZERO, std::time::Duration::ZERO))
}
/// Returns the user and system CPU time used by child processes.
///
/// This is a stub implementation that returns zero durations.
pub fn get_children_user_and_system_time()
-> Result<(std::time::Duration, std::time::Duration), error::Error> {
Ok((std::time::Duration::ZERO, std::time::Duration::ZERO))
}
+84
View File
@@ -0,0 +1,84 @@
//! Signal processing utilities
use crate::{error, sys, traps};
/// A stub enum representing system signals on unsupported platforms.
#[allow(unnameable_types)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Signal {}
impl Signal {
/// Returns an iterator over all possible signals.
pub fn iterator() -> impl Iterator<Item = Self> {
std::iter::empty()
}
/// Converts the signal into its corresponding name as a `&'static str`.
pub const fn as_str(self) -> &'static str {
""
}
/// Creates a `Signal` from a string representation.
pub fn from_str(s: &str) -> Result<Self, error::Error> {
Err(error::ErrorKind::InvalidSignal(s.into()).into())
}
}
impl TryFrom<i32> for Signal {
type Error = error::Error;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Err(error::ErrorKind::InvalidSignal(std::format!("{value}")).into())
}
}
pub(crate) fn continue_process(_pid: sys::process::ProcessId) -> Result<(), error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("continuing process").into())
}
/// Sends a signal to a specific process.
///
/// This is a stub implementation that returns an error.
pub fn kill_process(
_pid: sys::process::ProcessId,
_signal: traps::TrapSignal,
) -> Result<(), error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("killing process").into())
}
pub(crate) fn lead_new_process_group() -> Result<(), error::Error> {
Ok(())
}
pub(crate) struct FakeSignal {}
impl FakeSignal {
fn new() -> Self {
Self {}
}
pub async fn recv(&self) {
futures::future::pending::<()>().await;
}
}
pub(crate) fn tstp_signal_listener() -> Result<FakeSignal, error::Error> {
Ok(FakeSignal::new())
}
pub(crate) fn chld_signal_listener() -> Result<FakeSignal, error::Error> {
Ok(FakeSignal::new())
}
pub(crate) async fn await_ctrl_c() -> std::io::Result<()> {
FakeSignal::new().recv().await;
Ok(())
}
pub(crate) fn mask_sigttou() -> Result<(), error::Error> {
Ok(())
}
pub(crate) fn poll_for_stopped_children() -> Result<bool, error::Error> {
Ok(false)
}
+79
View File
@@ -0,0 +1,79 @@
//! Terminal utilities.
use crate::{error, openfiles, sys, terminal};
/// Terminal configuration.
#[derive(Clone, Debug)]
pub struct Config;
#[allow(clippy::unused_self)]
impl Config {
/// Creates a new `Config` from the actual terminal attributes of the terminal associated
/// with the given file descriptor.
///
/// # Arguments
///
/// * `_file` - A reference to the open terminal.
pub fn from_term(_file: &openfiles::OpenFile) -> Result<Self, error::Error> {
Ok(Self)
}
/// Applies the terminal settings to the terminal associated with the given file descriptor.
///
/// # Arguments
///
/// * `_file` - A reference to the open terminal.
pub fn apply_to_term(&self, _file: &openfiles::OpenFile) -> Result<(), error::Error> {
Ok(())
}
/// Applies the given high-level terminal settings to this configuration. Does not modify any
/// terminal itself.
///
/// # Arguments
///
/// * `_settings` - The high-level terminal settings to apply to this configuration.
pub fn update(&mut self, _settings: &terminal::Settings) {}
}
/// Get the process ID of this process's parent.
///
/// This is a stub implementation that returns `None`.
pub fn get_parent_process_id() -> Option<sys::process::ProcessId> {
None
}
/// Get the process group ID for this process's process group.
///
/// This is a stub implementation that returns `None`.
pub fn get_process_group_id() -> Option<sys::process::ProcessId> {
None
}
/// Get the foreground process ID of the attached terminal.
///
/// This is a stub implementation that returns `None`.
pub fn get_foreground_pid() -> Option<sys::process::ProcessId> {
None
}
/// Move the specified process to the foreground of the attached terminal.
///
/// This is a stub implementation that takes no action.
pub fn move_to_foreground(_pid: sys::process::ProcessId) -> Result<(), error::Error> {
Ok(())
}
/// Moves the current process to the foreground of the attached terminal.
///
/// This is a stub implementation that returns `None`.
pub fn move_self_to_foreground() -> Result<(), std::io::Error> {
Ok(())
}
/// Tries to get the path of the terminal device associated with the attached terminal.
///
/// This is a stub implementation that always returns `None`.
pub fn try_get_terminal_device_path() -> Option<std::path::PathBuf> {
None
}
+50
View File
@@ -0,0 +1,50 @@
use crate::error;
use std::path::PathBuf;
pub(crate) fn get_user_home_dir(_username: &str) -> Option<PathBuf> {
None
}
pub(crate) fn get_current_user_home_dir() -> Option<PathBuf> {
std::env::home_dir()
}
pub(crate) fn get_current_user_default_shell() -> Option<PathBuf> {
None
}
pub(crate) fn is_root() -> bool {
false
}
pub(crate) fn get_current_uid() -> Result<u32, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("getting current uid").into())
}
pub(crate) fn get_current_gid() -> Result<u32, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("getting current gid").into())
}
pub(crate) fn get_effective_uid() -> Result<u32, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("getting effective uid").into())
}
pub(crate) fn get_effective_gid() -> Result<u32, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("getting effective gid").into())
}
pub(crate) fn get_current_username() -> Result<String, error::Error> {
Err(error::ErrorKind::NotSupportedOnThisPlatform("getting current username").into())
}
pub(crate) fn get_user_group_ids() -> Result<Vec<u32>, error::Error> {
Ok(vec![])
}
pub(crate) fn get_all_users() -> Result<Vec<String>, error::Error> {
Ok(vec![])
}
pub(crate) fn get_all_groups() -> Result<Vec<String>, error::Error> {
Ok(vec![])
}