### Get Shell Executable Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Retrieves the shell executable path, defaulting to cmd.exe on Windows if ComSpec is not set. ```rust pub fn get_shell(&self) -> String { let exe: OsString = self .get_env("ComSpec") .unwrap_or(OsStr::new("cmd.exe")) .into(); exe.into_string() .unwrap_or_else(|_| "%CompSpec%".to_string()) } ``` -------------------------------- ### Get Current Directory as Wide String Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Retrieves the current working directory or home directory as a null-terminated wide string for Windows API compatibility. ```rust pub(crate) fn current_directory(&self) -> Option> { let home: Option<&OsStr> = self .get_env("USERPROFILE") .filter(|path| Path::new(path).is_dir()); let cwd: Option<&OsStr> = self.cwd.as_deref().filter(|path| Path::new(path).is_dir()); let dir: Option<&OsStr> = cwd.or(home); dir.map(|dir| { let mut wide = vec![]; if Path::new(dir).is_relative() { if let Ok(ccwd) = std::env::current_dir() { wide.extend(ccwd.join(dir).as_os_str().encode_wide()); } else { wide.extend(dir.encode_wide()); } } else { wide.extend(dir.encode_wide()); } wide.push(0); wide }) } ``` -------------------------------- ### Unix Path Relative Check Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Checks if a given path starts with current directory or parent directory components on Unix systems. ```rust #[cfg(unix)] /// Returns true if the path begins with `./` or `../` fn is_cwd_relative_path>(p: P) -> bool { matches!( p.as_ref().components().next(), Some(Component::CurDir | Component::ParentDir) ) } ``` -------------------------------- ### Controlling Terminal Configuration Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods to get and set whether the pty should act as the controlling terminal, useful for containerized environments. ```rust pub fn set_controlling_tty(&mut self, controlling_tty: bool) { self.controlling_tty = controlling_tty; } pub fn get_controlling_tty(&self) -> bool { self.controlling_tty } ``` -------------------------------- ### UnixPtySystem::openpty Source: https://docs.rs/portable-pty/latest/portable_pty/unix/struct.UnixPtySystem.html Creates a new PTY instance with the specified window size, returning a master and slave pair. ```APIDOC ## fn openpty(&self, size: PtySize) -> Result ### Description Creates a new PTY instance with the window size set to the specified dimensions. Returns a (master, slave) Pty pair. The master side is used to drive the slave side. ### Parameters - **size** (PtySize) - Required - The initial window dimensions for the PTY. ### Returns - **Result** - A result containing the PTY pair on success, or an error if the operation fails. ``` -------------------------------- ### CommandBuilder Initialization Methods Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods for creating new instances of CommandBuilder with different initial states. ```rust impl CommandBuilder { /// Create a new builder instance with argv[0] set to the specified /// program. pub fn new>(program: S) -> Self { Self { args: vec![program.as_ref().to_owned()], envs: get_base_env(), cwd: None, #[cfg(unix)] umask: None, controlling_tty: true, } } /// Create a new builder instance from a pre-built argument vector pub fn from_argv(args: Vec) -> Self { Self { args, envs: get_base_env(), cwd: None, #[cfg(unix)] umask: None, controlling_tty: true, } } /// Create a new builder instance that will run some idea of a default /// program. Such a builder will panic if `arg` is called on it. pub fn new_default_prog() -> Self { Self { args: vec![], envs: get_base_env(), cwd: None, #[cfg(unix)] umask: None, controlling_tty: true, } } ``` -------------------------------- ### Spawn a shell in a pseudo-terminal Source: https://docs.rs/portable-pty/latest/index.html Demonstrates initializing a native pty system, opening a pty pair, spawning a bash shell, and interacting with the master end. ```rust use portable_pty::{CommandBuilder, PtySize, native_pty_system, PtySystem}; use anyhow::Error; // Use the native pty implementation for the system let pty_system = native_pty_system(); // Create a new pty let mut pair = pty_system.openpty(PtySize { rows: 24, cols: 80, // Not all systems support pixel_width, pixel_height, // but it is good practice to set it to something // that matches the size of the selected font. That // is more complex than can be shown here in this // brief example though! pixel_width: 0, pixel_height: 0, })?; // Spawn a shell into the pty let cmd = CommandBuilder::new("bash"); let child = pair.slave.spawn_command(cmd)?; // Read and parse output from the pty with reader let mut reader = pair.master.try_clone_reader()?; // Send data to the pty by writing to the master writeln!(pair.master.take_writer()?, "ls -l\r\n")?; ``` -------------------------------- ### NativePtySystem::openpty Source: https://docs.rs/portable-pty/latest/portable_pty/type.NativePtySystem.html Creates a new PTY instance with the specified window dimensions, returning a master and slave pair. ```APIDOC ## fn openpty(&self, size: PtySize) -> Result ### Description Creates a new PTY instance with the window size set to the specified dimensions. The master side is used to drive the slave side. ### Parameters - **size** (PtySize) - Required - The initial dimensions for the PTY window. ### Returns - **Result** - A Result containing a (master, slave) PtyPair on success, or an error if the PTY could not be created. ``` -------------------------------- ### Initialize base environment map Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Collects current environment variables into a BTreeMap, ensuring platform-specific defaults like SHELL are populated on Unix. ```rust fn get_base_env() -> BTreeMap { let mut env: BTreeMap = std::env::vars_os() .map(|(key, value)| { ( EnvEntry::map_key(key.clone()), EnvEntry { is_from_base_env: true, preferred_key: key, value, }, ) }) .collect(); #[cfg(unix)] { let key = EnvEntry::map_key("SHELL".into()); // Only set the value of SHELL if it isn't already set if !env.contains_key(&key) { env.insert( EnvEntry::map_key("SHELL".into()), EnvEntry { is_from_base_env: true, preferred_key: "SHELL".into(), value: get_shell().into(), }, ); } } #[cfg(windows)] { use std::os::windows::ffi::OsStringExt; use winapi::um::processenv::ExpandEnvironmentStringsW; use winreg::enums::{RegType, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; use winreg::types::FromRegValue; use winreg::{RegKey, RegValue}; fn reg_value_to_string(value: &RegValue) -> anyhow::Result { match value.vtype { RegType::REG_EXPAND_SZ => { let src = unsafe { std::slice::from_raw_parts( value.bytes.as_ptr() as *const u16, value.bytes.len() / 2, ) }; let size = unsafe { ExpandEnvironmentStringsW(src.as_ptr(), std::ptr::null_mut(), 0) }; let mut buf = vec![0u16; size as usize + 1]; unsafe { ExpandEnvironmentStringsW(src.as_ptr(), buf.as_mut_ptr(), buf.len() as u32) }; let mut buf = buf.as_slice(); while let Some(0) = buf.last() { buf = &buf[0..buf.len() - 1]; } Ok(OsString::from_wide(buf)) } _ => Ok(OsString::from_reg_value(value)?), } } if let Ok(sys_env) = RegKey::predef(HKEY_LOCAL_MACHINE) ``` -------------------------------- ### Manage working directory Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods to set, clear, or retrieve the current working directory. ```rust pub fn cwd(&mut self, dir: D) where D: AsRef, { self.cwd = Some(dir.as_ref().to_owned()); } pub fn clear_cwd(&mut self) { self.cwd.take(); } pub fn get_cwd(&self) -> Option<&OsString> { self.cwd.as_ref() } ``` -------------------------------- ### Format as Unix command line Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Converts the configured command and arguments into a single string using Unix shell quoting conventions. ```rust /// Return the configured command and arguments as a single string, /// quoted per the unix shell conventions. pub fn as_unix_command_line(&self) -> anyhow::Result { let mut strs = vec![]; for arg in &self.args { let s = arg .to_str() .ok_or_else(|| anyhow::anyhow!("argument cannot be represented as utf8"))?; strs.push(s); } Ok(shell_words::join(strs)) } ``` -------------------------------- ### SerialTty::new Source: https://docs.rs/portable-pty/latest/portable_pty/serial/struct.SerialTty.html Creates a new instance of SerialTty for the specified port. ```APIDOC ## pub fn new + ?Sized>(port: &T) -> Self ### Description Creates a new SerialTty instance associated with the provided serial port path. ### Parameters - **port** (&T) - Required - The path to the serial port device. ``` -------------------------------- ### Open Pseudo-Terminal on Unix Source: https://docs.rs/portable-pty/latest/src/portable_pty/unix.rs.html Opens a master and slave pseudo-terminal pair using the libc openpty function. ```rust fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> { let mut master: RawFd = -1; let mut slave: RawFd = -1; let mut size = winsize { ws_row: size.rows, ws_col: size.cols, ws_xpixel: size.pixel_width, ws_ypixel: size.pixel_height, }; let result = unsafe { // BSDish systems may require mut pointers to some args #[allow(clippy::unnecessary_mut_passed)] libc::openpty( &mut master, &mut slave, ptr::null_mut(), ptr::null_mut(), &mut size, ) }; if result != 0 { bail!("failed to openpty: {:?}", io::Error::last_os_error()); } let tty_name = tty_name(slave); let master = UnixMasterPty { fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }), took_writer: RefCell::new(false), tty_name, }; let slave = UnixSlavePty { fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }), }; // Ensure that these descriptors will get closed when we execute // the child process. This is done after constructing the Pty // instances so that we ensure that the Ptys get drop()'d if // the cloexec() functions fail (unlikely!). cloexec(master.fd.as_raw_fd())?; cloexec(slave.fd.as_raw_fd())?; Ok((master, slave)) } ``` -------------------------------- ### SerialTty Configuration Methods Source: https://docs.rs/portable-pty/latest/portable_pty/serial/struct.SerialTty.html Methods to configure the serial port settings. ```APIDOC ## Configuration Methods ### set_baud_rate(baud: u32) Sets the baud rate for the serial connection. ### set_char_size(char_size: CharSize) Sets the character size for the serial connection. ### set_parity(parity: Parity) Sets the parity for the serial connection. ### set_stop_bits(stop_bits: StopBits) Sets the stop bits for the serial connection. ### set_flow_control(flow_control: FlowControl) Sets the flow control for the serial connection. ``` -------------------------------- ### Search Executable Path on Windows Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Locates an executable in the system PATH, optionally appending extensions defined in PATHEXT. ```rust fn search_path(&self, exe: &OsStr) -> OsString { if let Some(path) = self.get_env("PATH") { let extensions = self.get_env("PATHEXT").unwrap_or(OsStr::new(".EXE")); for path in std::env::split_paths(&path) { // Check for exactly the user's string in this path dir let candidate = path.join(&exe); if candidate.exists() { return candidate.into_os_string(); } // otherwise try tacking on some extensions. // Note that this really replaces the extension in the // user specified path, so this is potentially wrong. for ext in std::env::split_paths(&extensions) { // PATHEXT includes the leading `.`, but `with_extension` // doesn't want that let ext = ext.to_str().expect("PATHEXT entries must be utf8"); let path = path.join(&exe).with_extension(&ext[1..]); if path.exists() { return path.into_os_string(); } } } } exe.to_owned() } ``` -------------------------------- ### Construct Windows Environment Block Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Encodes environment variables into a null-terminated wide character block suitable for CreateProcessW. ```rust pub(crate) fn environment_block(&self) -> Vec { // encode the environment as wide characters let mut block = vec![]; for EnvEntry { is_from_base_env: _, preferred_key, value, } in self.envs.values() { block.extend(preferred_key.encode_wide()); block.push(b'=' as u16); block.extend(value.encode_wide()); block.push(0); } // and a final terminator for CreateProcessW block.push(0); block } ``` -------------------------------- ### Resize and Query PTY Window Size Source: https://docs.rs/portable-pty/latest/src/portable_pty/unix.rs.html Uses ioctl with TIOCSWINSZ and TIOCGWINSZ to set or retrieve the terminal window dimensions. ```rust fn resize(&self, size: PtySize) -> Result<(), Error> { let ws_size = winsize { ws_row: size.rows, ws_col: size.cols, ws_xpixel: size.pixel_width, ws_ypixel: size.pixel_height, }; if unsafe { libc::ioctl( self.0.as_raw_fd(), libc::TIOCSWINSZ as _, &ws_size as *const _, ) } != 0 { bail!( "failed to ioctl(TIOCSWINSZ): {:?}", io::Error::last_os_error() ); } Ok(()) } ``` ```rust fn get_size(&self) -> Result { let mut size: winsize = unsafe { mem::zeroed() }; if unsafe { libc::ioctl( self.0.as_raw_fd(), libc::TIOCGWINSZ as _, &mut size as *mut _, ) } != 0 { bail!( "failed to ioctl(TIOCGWINSZ): {:?}", io::Error::last_os_error() ); } Ok(PtySize { rows: size.ws_row, cols: size.ws_col, pixel_width: size.ws_xpixel, pixel_height: size.ws_ypixel, }) } ``` -------------------------------- ### PtySystem::openpty Source: https://docs.rs/portable-pty/latest/portable_pty Opens a new pseudo-terminal pair with the specified dimensions. ```APIDOC ## PtySystem::openpty(size: PtySize) ### Description Creates a new pseudo-terminal pair (master and slave) with the provided `PtySize` dimensions. ### Parameters - **size** (PtySize) - The initial dimensions of the pty (rows, cols, pixel_width, pixel_height). ``` -------------------------------- ### Initialize Native PTY System Source: https://docs.rs/portable-pty/latest/src/portable_pty/lib.rs.html Provides a factory function to retrieve the platform-specific PTY system implementation. ```rust pub fn native_pty_system() -> Box { Box::new(NativePtySystem::default()) } #[cfg(unix)] pub type NativePtySystem = unix::UnixPtySystem; #[cfg(windows)] pub type NativePtySystem = win::conpty::ConPtySystem; ``` -------------------------------- ### Unix-specific configuration Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Unix-specific methods for setting umask and resolving executable paths. ```rust #[cfg(unix)] impl CommandBuilder { pub fn umask(&mut self, mask: Option) { self.umask = mask; } fn resolve_path(&self) -> Option<&OsStr> { self.get_env("PATH") } fn search_path(&self, exe: &OsStr, cwd: &OsStr) -> anyhow::Result { use nix::unistd::{access, AccessFlags}; let exe_path: &Path = exe.as_ref(); if exe_path.is_relative() { let cwd: &Path = cwd.as_ref(); let mut errors = vec![]; // If the requested executable is explicitly relative to cwd, // then check only there. ``` -------------------------------- ### fn spawn_command Source: https://docs.rs/portable-pty/latest/portable_pty/trait.SlavePty.html Spawns a command into the pseudo-terminal using the provided CommandBuilder. ```APIDOC ### fn spawn_command(&self, cmd: CommandBuilder) -> Result, Error> #### Description Spawns the command specified by the provided `CommandBuilder` into the pty. #### Parameters - **cmd** (CommandBuilder) - Required - The command configuration to be executed within the pty. #### Returns - **Result, Error>** - Returns a handle to the spawned child process on success, or an Error if the command could not be spawned. ``` -------------------------------- ### Manage environment variables Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods to set, remove, clear, or retrieve environment variables. ```rust /// Override the value of an environmental variable pub fn env(&mut self, key: K, value: V) where K: AsRef, V: AsRef, { let key: OsString = key.as_ref().into(); let value: OsString = value.as_ref().into(); self.envs.insert( EnvEntry::map_key(key.clone()), EnvEntry { is_from_base_env: false, preferred_key: key, value: value, }, ); } pub fn env_remove(&mut self, key: K) where K: AsRef, { let key = key.as_ref().into(); self.envs.remove(&EnvEntry::map_key(key)); } pub fn env_clear(&mut self) { self.envs.clear(); } pub fn get_env(&self, key: K) -> Option<&OsStr> where K: AsRef, { let key = key.as_ref().into(); self.envs.get(&EnvEntry::map_key(key)).map( |EnvEntry { is_from_base_env: _, preferred_key: _, value, }| value.as_os_str(), ) } ``` -------------------------------- ### Manage command arguments Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods for appending single or multiple arguments to the command line. Note that arg() will panic if the builder was created via new_default_prog. ```rust /// Append an argument to the current command line. /// Will panic if called on a builder created via `new_default_prog`. pub fn arg>(&mut self, arg: S) { if self.is_default_prog() { panic!("attempted to add args to a default_prog builder"); } self.args.push(arg.as_ref().to_owned()); } /// Append a sequence of arguments to the current command line pub fn args(&mut self, args: I) where I: IntoIterator, S: AsRef, { for arg in args { self.arg(arg); } } ``` -------------------------------- ### CommandBuilder Environment Variable Tests Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Unit tests verifying environment variable setting, removal, clearing, and case-insensitive overrides on Windows. ```rust #[cfg(test)] mod tests { use super::*; #[cfg(unix)] #[test] fn test_cwd_relative() { assert!(is_cwd_relative_path(".")); assert!(is_cwd_relative_path("./foo")); assert!(is_cwd_relative_path("../foo")); assert!(!is_cwd_relative_path("foo")); assert!(!is_cwd_relative_path("/foo")); } #[test] fn test_env() { let mut cmd = CommandBuilder::new("dummy"); let package_authors = cmd.get_env("CARGO_PKG_AUTHORS"); println!("package_authors: {:?}", package_authors); assert!(package_authors == Some(OsStr::new("Wez Furlong"))); cmd.env("foo key", "foo value"); cmd.env("bar key", "bar value"); let iterated_envs = cmd.iter_extra_env_as_str().collect::>(); println!("iterated_envs: {:?}", iterated_envs); assert!(iterated_envs == vec![("bar key", "bar value"), ("foo key", "foo value")]); { let mut cmd = cmd.clone(); cmd.env_remove("foo key"); let iterated_envs = cmd.iter_extra_env_as_str().collect::>(); println!("iterated_envs: {:?}", iterated_envs); assert!(iterated_envs == vec![("bar key", "bar value")]); } { let mut cmd = cmd.clone(); cmd.env_remove("bar key"); let iterated_envs = cmd.iter_extra_env_as_str().collect::>(); println!("iterated_envs: {:?}", iterated_envs); assert!(iterated_envs == vec![("foo key", "foo value")]); } { let mut cmd = cmd.clone(); cmd.env_clear(); let iterated_envs = cmd.iter_extra_env_as_str().collect::>(); println!("iterated_envs: {:?}", iterated_envs); assert!(iterated_envs.is_empty()); } } #[cfg(windows)] #[test] fn test_env_case_insensitive_override() { let mut cmd = CommandBuilder::new("dummy"); cmd.env("Cargo_Pkg_Authors", "Not Wez"); assert!(cmd.get_env("cargo_pkg_authors") == Some(OsStr::new("Not Wez"))); cmd.env_remove("cARGO_pKG_aUTHORS"); assert!(cmd.get_env("CARGO_PKG_AUTHORS").is_none()); } } ``` -------------------------------- ### SlavePty::spawn_command Source: https://docs.rs/portable-pty/latest/portable_pty Spawns a command into the slave side of the pseudo-terminal. ```APIDOC ## SlavePty::spawn_command(cmd: CommandBuilder) ### Description Spawns a process into the pty using the provided `CommandBuilder` configuration. ### Parameters - **cmd** (CommandBuilder) - The command configuration to execute. ``` -------------------------------- ### fn openpty(&self, size: PtySize) -> Result Source: https://docs.rs/portable-pty/latest/portable_pty/trait.PtySystem.html Creates a new Pty instance with the specified dimensions, returning a master and slave Pty pair. ```APIDOC ### fn openpty(&self, size: PtySize) -> Result #### Description Create a new Pty instance with the window size set to the specified dimensions. Returns a (master, slave) Pty pair. The master side is used to drive the slave side. #### Parameters - **size** (PtySize) - Required - The initial dimensions for the Pty instance. #### Returns - **Result** - A Result containing the PtyPair on success, or an error if the operation fails. ``` -------------------------------- ### Check if default program Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Returns true if the builder was initialized via new_default_prog. ```rust /// Returns true if this builder was created via `new_default_prog` pub fn is_default_prog(&self) -> bool { self.args.is_empty() } ``` -------------------------------- ### Map environment keys for case-insensitive platforms Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Normalizes environment variable keys to lowercase on Windows to handle case-insensitivity. ```rust fn map_key(k: OsString) -> OsString { if cfg!(windows) { // Best-effort lowercase transformation of an os string match k.to_str() { Some(s) => s.to_lowercase().into(), None => k, } } else { k } } ``` -------------------------------- ### resize Source: https://docs.rs/portable-pty/latest/portable_pty/trait.MasterPty.html Informs the kernel and the child process that the window has been resized, updating the winsize information and generating a signal. ```APIDOC ## fn resize(&self, size: PtySize) -> Result<(), Error> ### Description Updates the kernel's winsize information for the pty and signals the child process to update its state accordingly. ### Parameters - **size** (PtySize) - Required - The new dimensions for the pty. ``` -------------------------------- ### UnixPtySystem Definition Source: https://docs.rs/portable-pty/latest/portable_pty/unix/struct.UnixPtySystem.html The structure definition for UnixPtySystem. ```rust pub struct UnixPtySystem {} ``` -------------------------------- ### Retrieve Shell Configuration Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Determines the shell to execute by checking the SHELL environment variable or falling back to system defaults. ```rust pub fn get_shell(&self) -> String { use nix::unistd::{access, AccessFlags}; if let Some(shell) = self.get_env("SHELL").and_then(OsStr::to_str) { ``` -------------------------------- ### PtySystem::openpty Source: https://docs.rs/portable-pty/latest/portable_pty/serial/struct.SerialTty.html Creates a new Pty instance with the specified dimensions. ```APIDOC ## fn openpty(&self, _size: PtySize) -> Result ### Description Creates a new Pty instance with the window size set to the specified dimensions. Returns a (master, slave) Pty pair where the master side is used to drive the slave side. ### Parameters - **_size** (PtySize) - Required - The dimensions for the new Pty instance. ``` -------------------------------- ### Convert to std::process::Command Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Converts the internal CommandBuilder state into a standard library Command, handling shell login prefixes and environment variables. ```rust pub(crate) fn as_command(&self) -> anyhow::Result { use std::os::unix::process::CommandExt; let home = self.get_home_dir()?; let dir: &OsStr = self .cwd .as_ref() .map(|dir| dir.as_os_str()) .filter(|dir| std::path::Path::new(dir).is_dir()) .unwrap_or(home.as_ref()); let shell = self.get_shell(); let mut cmd = if self.is_default_prog() { let mut cmd = std::process::Command::new(&shell); // Run the shell as a login shell by prefixing the shell's // basename with `-` and setting that as argv0 let basename = shell.rsplit('/').next().unwrap_or(&shell); cmd.arg0(&format!("-", basename)); cmd } else { let resolved = self.search_path(&self.args[0], dir)?; let mut cmd = std::process::Command::new(&resolved); cmd.arg0(&self.args[0]); cmd.args(&self.args[1..]); cmd }; cmd.current_dir(dir); cmd.env_clear(); cmd.env("SHELL", shell); cmd.envs(self.envs.values().map( |EnvEntry { is_from_base_env: _, preferred_key, value, }| (preferred_key.as_os_str(), value.as_os_str()), )); Ok(cmd) } ``` -------------------------------- ### ExitStatus Methods Source: https://docs.rs/portable-pty/latest/portable_pty/struct.ExitStatus.html Methods for constructing and querying the status of a child process. ```APIDOC ## pub fn with_exit_code(code: u32) -> Self ### Description Constructs an ExitStatus from a process return code. ## pub fn with_signal(signal: &str) -> Self ### Description Constructs an ExitStatus from a signal name. ## pub fn success(&self) -> bool ### Description Returns true if the status indicates successful completion. ## pub fn exit_code(&self) -> u32 ### Description Returns the exit code that this ExitStatus was constructed with. ## pub fn signal(&self) -> Option<&str> ### Description Returns the signal if present that this ExitStatus was constructed with. ``` -------------------------------- ### Resolve Home Directory Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Attempts to resolve the user's home directory using environment variables or the system password database. ```rust fn get_home_dir(&self) -> anyhow::Result { if let Some(home_dir) = self.get_env("HOME").and_then(OsStr::to_str) { return Ok(home_dir.into()); } let ent = unsafe { libc::getpwuid(libc::getuid()) }; if ent.is_null() { Ok("/".into()) } else { use std::ffi::CStr; use std::str; let home = unsafe { CStr::from_ptr((*ent).pw_dir) }; home.to_str() .map(str::to_owned) .context("failed to resolve home dir") } } ``` -------------------------------- ### Iterate environment variables Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Methods to iterate over extra or full environment variables as string slices. ```rust /// Iterate over the configured environment. Only includes environment /// variables set by the caller via `env`, not variables set in the base /// environment. pub fn iter_extra_env_as_str(&self) -> impl Iterator { self.envs.values().filter_map( |EnvEntry { is_from_base_env, preferred_key, value, }| { if *is_from_base_env { None } else { let key = preferred_key.to_str()?; let value = value.to_str()?; Some((key, value)) } }, ) } pub fn iter_full_env_as_str(&self) -> impl Iterator { self.envs.values().filter_map( |EnvEntry { preferred_key, value, .. }| { let key = preferred_key.to_str()?; let value = value.to_str()?; Some((key, value)) }, ) } ``` -------------------------------- ### Registry Environment Variable Merging Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Logic for reading and merging system and user environment variables from the Windows registry. ```rust .open_subkey("System\\CurrentControlSet\\Control\\Session Manager\\Environment") { for res in sys_env.enum_values() { if let Ok((name, value)) = res { if name.to_ascii_lowercase() == "username" { continue; } if let Ok(value) = reg_value_to_string(&value) { log::trace!("adding SYS env: {:?} {:?}", name, value); env.insert( EnvEntry::map_key(name.clone().into()), EnvEntry { is_from_base_env: true, preferred_key: name.into(), value, }, ); } } } } if let Ok(sys_env) = RegKey::predef(HKEY_CURRENT_USER).open_subkey("Environment") { for res in sys_env.enum_values() { if let Ok((name, value)) = res { if let Ok(value) = reg_value_to_string(&value) { // Merge the system and user paths together let value = if name.to_ascii_lowercase() == "path" { match env.get(&EnvEntry::map_key(name.clone().into())) { Some(entry) => { let mut result = OsString::new(); result.push(&entry.value); result.push(";"); result.push(&value); result } None => value, } } else { value }; log::trace!("adding USER env: {:?} {:?}", name, value); env.insert( EnvEntry::map_key(name.clone().into()), EnvEntry { is_from_base_env: true, preferred_key: name.into(), value, }, ); } } } } } env } ``` -------------------------------- ### CommandBuilder Struct Definition Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html The core structure for managing command configuration, including arguments, environment, and terminal settings. ```rust #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub struct CommandBuilder { args: Vec, envs: BTreeMap, cwd: Option, #[cfg(unix)] pub(crate) umask: Option, controlling_tty: bool, } ``` -------------------------------- ### Spawn Process with PTY Configuration Source: https://docs.rs/portable-pty/latest/src/portable_pty/unix.rs.html Configures a child process to use the PTY as its controlling terminal, resets signal dispositions, and cleans up file descriptors. ```rust fn spawn_command(&self, builder: CommandBuilder) -> anyhow::Result { let configured_umask = builder.umask; let mut cmd = builder.as_command()?; let controlling_tty = builder.get_controlling_tty(); unsafe { cmd.stdin(self.as_stdio()?) .stdout(self.as_stdio()?) .stderr(self.as_stdio()?) .pre_exec(move || { // Clean up a few things before we exec the program // Clear out any potentially problematic signal // dispositions that we might have inherited for signo in &[ libc::SIGCHLD, libc::SIGHUP, libc::SIGINT, libc::SIGQUIT, libc::SIGTERM, libc::SIGALRM, ] { libc::signal(*signo, libc::SIG_DFL); } let empty_set: libc::sigset_t = std::mem::zeroed(); libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut()); // Establish ourselves as a session leader. if libc::setsid() == -1 { return Err(io::Error::last_os_error()); } // Clippy wants us to explicitly cast TIOCSCTTY using // type::from(), but the size and potentially signedness // are system dependent, which is why we're using `as _`. // Suppress this lint for this section of code. #[allow(clippy::cast_lossless)] if controlling_tty { // Set the pty as the controlling terminal. // Failure to do this means that delivery of // SIGWINCH won't happen when we resize the // terminal, among other undesirable effects. if libc::ioctl(0, libc::TIOCSCTTY as _, 0) == -1 { return Err(io::Error::last_os_error()); } } close_random_fds(); ``` -------------------------------- ### Resolve and Validate Executable Path Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Logic for checking if an executable exists, is a directory, or has appropriate execution permissions within the current working directory or system PATH. ```rust if is_cwd_relative_path(exe_path) { let abs_path = cwd.join(exe_path); if abs_path.is_dir() { anyhow::bail!( "Unable to spawn {} because it is a directory", abs_path.display() ); } else if access(&abs_path, AccessFlags::X_OK).is_ok() { return Ok(abs_path.into_os_string()); } else if access(&abs_path, AccessFlags::F_OK).is_ok() { anyhow::bail!( "Unable to spawn {} because it is not executable", abs_path.display() ); } anyhow::bail!( "Unable to spawn {} because it does not exist", abs_path.display() ); } if let Some(path) = self.resolve_path() { for path in std::env::split_paths(&path) { let candidate = cwd.join(&path).join(&exe); if candidate.is_dir() { errors.push(format!("{} exists but is a directory", candidate.display())); } else if access(&candidate, AccessFlags::X_OK).is_ok() { return Ok(candidate.into_os_string()); } else if access(&candidate, AccessFlags::F_OK).is_ok() { errors.push(format!( "{} exists but is not executable", candidate.display() )); } } errors.push(format!("No viable candidates found in PATH {path:?}")); } else { errors.push("Unable to resolve the PATH".to_string()); } anyhow::bail!( "Unable to spawn {} because:\n{}", exe_path.display(), errors.join(".\n") ); } else if exe_path.is_dir() { anyhow::bail!( "Unable to spawn {} because it is a directory", exe_path.display() ); } else { if let Err(err) = access(exe_path, AccessFlags::X_OK) { if access(exe_path, AccessFlags::F_OK).is_ok() { anyhow::bail!( "Unable to spawn {} because it is not executable ({err:#})", exe_path.display() ); } else { anyhow::bail!( "Unable to spawn {} because it doesn't exist on the filesystem ({err:#})", exe_path.display() ); } } Ok(exe.to_owned()) } } ``` -------------------------------- ### NativePtySystem Type Definition Source: https://docs.rs/portable-pty/latest/portable_pty/type.NativePtySystem.html The type alias definition for NativePtySystem. ```rust pub type NativePtySystem = UnixPtySystem; ``` -------------------------------- ### native_pty_system() Source: https://docs.rs/portable-pty/latest/portable_pty/fn.native_pty_system.html Returns a boxed instance of the platform-specific PtySystem trait implementation. ```APIDOC ## Function: native_pty_system ### Description Returns a platform-specific implementation of the `PtySystem` trait. This function is used to initialize the PTY system for the current operating system. ### Signature `pub fn native_pty_system() -> Box` ### Returns - **Box** - A heap-allocated object implementing the `PtySystem` trait, safe to send across threads. ``` -------------------------------- ### take_writer Source: https://docs.rs/portable-pty/latest/portable_pty/trait.MasterPty.html Obtains a writable handle to send data to the slave end of the pty. ```APIDOC ## fn take_writer(&self) -> Result, Error> ### Description Returns a boxed writer stream. Writing to this stream sends data to the slave. Dropping the writer sends an EOF to the slave. Note: It is invalid to call this method more than once. ``` -------------------------------- ### Resolve default shell on Unix Source: https://docs.rs/portable-pty/latest/src/portable_pty/cmdbuilder.rs.html Retrieves the user's default shell from the passwd database, falling back to /bin/sh if unavailable or non-executable. ```rust fn get_shell() -> String { use nix::unistd::{access, AccessFlags}; use std::ffi::CStr; use std::str; let ent = unsafe { libc::getpwuid(libc::getuid()) }; if !ent.is_null() { let shell = unsafe { CStr::from_ptr((*ent).pw_shell) }; match shell.to_str().map(str::to_owned) { Err(err) => { log::warn!( "passwd database shell could not be \ represented as utf-8: {err:#}, \ falling back to /bin/sh" ); } Ok(shell) => { if let Err(err) = access(Path::new(&shell), AccessFlags::X_OK) { log::warn!( "passwd database shell={shell:?} which is \ not executable ({err:#}), falling back to /bin/sh" ); } else { return shell; } } } } "/bin/sh".into() } ``` -------------------------------- ### Implement Child trait for std::process::Child Source: https://docs.rs/portable-pty/latest/src/portable_pty/lib.rs.html Provides a wrapper for standard process child management, including platform-specific handle access on Windows. ```rust impl Child for std::process::Child { fn try_wait(&mut self) -> IoResult> { std::process::Child::try_wait(self).map(|s| match s { Some(s) => Some(s.into()), None => None, }) } fn wait(&mut self) -> IoResult { std::process::Child::wait(self).map(Into::into) } fn process_id(&self) -> Option { Some(self.id()) } #[cfg(windows)] fn as_raw_handle(&self) -> Option { Some(std::os::windows::io::AsRawHandle::as_raw_handle(self)) } } ``` -------------------------------- ### SerialTty Implementation Source: https://docs.rs/portable-pty/latest/src/portable_pty/serial.rs.html The core implementation of the SerialTty struct and its PtySystem trait, including configuration methods and the openpty logic. ```rust //! This module implements a serial port based tty. //! This is a bit different from the other implementations in that //! we cannot explicitly spawn a process into the serial connection, //! so we can only use a `CommandBuilder::new_default_prog` with the //! `openpty` method. //! On most (all?) systems, attempting to open multiple instances of //! the same serial port will fail. use crate::{ Child, ChildKiller, CommandBuilder, ExitStatus, MasterPty, PtyPair, PtySize, PtySystem, SlavePty, }; use anyhow::{ensure, Context}; use filedescriptor::FileDescriptor; use serial2::{CharSize, FlowControl, Parity, SerialPort, StopBits}; use std::cell::RefCell; use std::ffi::{OsStr, OsString}; use std::io::{Read, Result as IoResult, Write}; #[cfg(unix)] use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; type Handle = Arc; pub struct SerialTty { port: OsString, baud: u32, char_size: CharSize, parity: Parity, stop_bits: StopBits, flow_control: FlowControl, } impl SerialTty { pub fn new + ?Sized>(port: &T) -> Self { Self { port: port.as_ref().to_owned(), baud: 9600, char_size: CharSize::Bits8, parity: Parity::None, stop_bits: StopBits::One, flow_control: FlowControl::XonXoff, } } pub fn set_baud_rate(&mut self, baud: u32) { self.baud = baud; } pub fn set_char_size(&mut self, char_size: CharSize) { self.char_size = char_size; } pub fn set_parity(&mut self, parity: Parity) { self.parity = parity; } pub fn set_stop_bits(&mut self, stop_bits: StopBits) { self.stop_bits = stop_bits; } pub fn set_flow_control(&mut self, flow_control: FlowControl) { self.flow_control = flow_control; } } impl PtySystem for SerialTty { fn openpty(&self, _size: PtySize) -> anyhow::Result { let mut port = SerialPort::open(&self.port, self.baud) .with_context(|| format!("openpty on serial port {:?}", self.port))?; let mut settings = port.get_configuration()?; settings.set_raw(); settings.set_baud_rate(self.baud)?; settings.set_char_size(self.char_size); settings.set_flow_control(self.flow_control); settings.set_parity(self.parity); settings.set_stop_bits(self.stop_bits); log::debug!("serial settings: {:#?}", port.get_configuration()); port.set_configuration(&settings)?; // The timeout needs to be rather short because, at least on Windows, // a read with a long timeout will block a concurrent write from // happening. In wezterm we tend to have a thread looping on read // while writes happen occasionally from the gui thread, and if we // make this timeout too long we can block the gui thread. port.set_read_timeout(Duration::from_millis(50))?; port.set_write_timeout(Duration::from_millis(50))?; let port: Handle = Arc::new(port); Ok(PtyPair { slave: Box::new(Slave { port: Arc::clone(&port), }), master: Box::new(Master { port, took_writer: RefCell::new(false), }), }) } } struct Slave { port: Handle, } impl SlavePty for Slave { fn spawn_command(&self, cmd: CommandBuilder) -> anyhow::Result> { ensure!( cmd.is_default_prog(), "can only use default prog commands with serial tty implementations" ); Ok(Box::new(SerialChild { port: Arc::clone(&self.port), })) } } /// There isn't really a child process on the end of the serial connection, /// so all of the Child trait impls are NOP struct SerialChild { port: Handle, } // An anemic impl of Debug to satisfy some indirect trait bounds impl std::fmt::Debug for SerialChild { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { fmt.debug_struct("SerialChild").finish() } } impl Child for SerialChild { fn try_wait(&mut self) -> IoResult> { Ok(None) } fn wait(&mut self) -> IoResult { // There isn't really a child process to wait for, // as the serial connection never really "dies", // however, for something like a USB serial port, ```