Skip to content

Commit

Permalink
feat: implement command -p
Browse files Browse the repository at this point in the history
  • Loading branch information
39555 committed Oct 11, 2024
1 parent 118dc9e commit 66ad76d
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 6 deletions.
59 changes: 55 additions & 4 deletions brush-core/src/builtins/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ use std::{fmt::Display, io::Write, path::Path};

use crate::{builtins, commands, error, shell, sys::fs::PathExt, ExecutionResult};

/// The value for PATH when invoking `command -p`. This is only used when
/// the Posix.2 `confstr()` returns nothing
/// The value of this variable is taken from the BASH source code.
const STANDARD_UTILS_PATH: &[&str] = &["/bin", "/usr/bin", "/sbin", "/usr/sbin", "/etc:/usr/etc"];

/// Directly invokes an external command, without going through typical search order.
#[derive(Parser)]
pub(crate) struct CommandCommand {
Expand Down Expand Up @@ -32,10 +37,6 @@ impl builtins::Command for CommandCommand {
&self,
context: commands::ExecutionContext<'_>,
) -> Result<builtins::ExitCode, error::Error> {
if self.use_default_path {
return error::unimp("command -p");
}

if self.print_description || self.print_verbose_description {
if let Some(found_cmd) = self.try_find_command(context.shell) {
if self.print_description {
Expand Down Expand Up @@ -105,6 +106,21 @@ impl CommandCommand {
}
}

if self.use_default_path {
let path = confstr_path();
// Without an allocation if possible.
let path = path.as_ref().map(|p| String::from_utf8_lossy(p));
let path = path.as_ref().map_or(
itertools::Either::Right(STANDARD_UTILS_PATH.iter().copied()),
|p| itertools::Either::Left(p.split(':')),
);

return shell
.find_executables_in(path, self.command_name.as_str())
.first()
.map(|path| FoundCommand::External(path.to_string_lossy().to_string()));
}

shell
.find_executables_in_path(self.command_name.as_str())
.first()
Expand Down Expand Up @@ -148,3 +164,38 @@ impl CommandCommand {
}
}
}

/// A wrapper for [`nix::libc::confstr`]. Returns a value for the default PATH variable which
/// indicates where all the POSIX.2 standard utilities can be found.
fn confstr_path() -> Option<Vec<u8>> {
#[cfg(unix)]
{
let required_size =
unsafe { nix::libc::confstr(nix::libc::_CS_PATH, std::ptr::null_mut(), 0) };
if required_size == 0 {
return None;
}
// NOTE: Writing `c_char` (i8 or u8 depending on the platform) into `Vec<u8>` is fine,
// as i8 and u8 have compatible representations,
// and Rust does not support platforms where `c_char` is not 8-bit wide.
let mut buffer = Vec::<u8>::with_capacity(required_size);
let final_size = unsafe {
nix::libc::confstr(
nix::libc::_CS_PATH,
buffer.as_mut_ptr().cast(),
required_size,
)
};
if final_size == 0 {
return None;
}
// ERANGE
if final_size > required_size {
return None;
}
unsafe { buffer.set_len(final_size - 1) }; // The last byte is a null terminator.
return Some(buffer);
}
#[allow(unreachable_code)]
None
}
22 changes: 20 additions & 2 deletions brush-core/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,12 +879,30 @@ impl Shell {
/// # Arguments
///
/// * `required_glob_pattern` - The glob pattern to match against.
#[allow(clippy::manual_flatten)]
pub fn find_executables_in_path(&self, required_glob_pattern: &str) -> Vec<PathBuf> {
self.find_executables_in(
self.env.get_str("PATH").unwrap_or_default().split(':'),
required_glob_pattern,
)
}

/// Finds executables in the given paths, matching the given glob pattern.
///
/// # Arguments
///
/// * `paths` - The paths to search in
/// * `required_glob_pattern` - The glob pattern to match against.
#[allow(clippy::manual_flatten)]
pub fn find_executables_in<T: AsRef<str>>(
&self,
paths: impl Iterator<Item = T>,
required_glob_pattern: &str,
) -> Vec<PathBuf> {
let is_executable = |path: &Path| path.executable();

let mut executables = vec![];
for dir_str in self.env.get_str("PATH").unwrap_or_default().split(':') {
for dir_str in paths {
let dir_str = dir_str.as_ref();
let pattern = std::format!("{dir_str}/{required_glob_pattern}");
// TODO: Pass through quoting.
if let Ok(entries) = patterns::Pattern::from(pattern).expand(
Expand Down

0 comments on commit 66ad76d

Please sign in to comment.