Skip to content

Commit

Permalink
fix: allow source to be used with process substitution (#175)
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno authored Sep 29, 2024
1 parent 663964a commit e472e16
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 16 deletions.
2 changes: 1 addition & 1 deletion brush-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub enum Error {

/// An error occurred while sourcing the indicated script file.
#[error("failed to source file: {0}; {1}")]
FailedSourcingFile(PathBuf, std::io::Error),
FailedSourcingFile(PathBuf, Box<Error>),

/// The shell failed to send a signal to a process.
#[error("failed to send signal to process")]
Expand Down
14 changes: 14 additions & 0 deletions brush-core/src/openfiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ impl OpenFile {
}
}

pub(crate) fn is_dir(&self) -> bool {
match self {
OpenFile::Stdin | OpenFile::Stdout | OpenFile::Stderr | OpenFile::Null => false,
OpenFile::File(file) => file.metadata().map(|m| m.is_dir()).unwrap_or(false),
OpenFile::PipeReader(_) | OpenFile::PipeWriter(_) => false,
}
}

pub(crate) fn is_term(&self) -> bool {
match self {
OpenFile::Stdin => std::io::stdin().is_terminal(),
Expand Down Expand Up @@ -122,6 +130,12 @@ impl OpenFile {
}
}

impl From<std::fs::File> for OpenFile {
fn from(file: std::fs::File) -> Self {
OpenFile::File(file)
}
}

impl From<OpenFile> for Stdio {
fn from(open_file: OpenFile) -> Self {
match open_file {
Expand Down
55 changes: 40 additions & 15 deletions brush-core/src/shell.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::collections::{HashMap, VecDeque};
use std::fmt::Write as _;
use std::io::Write;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;

Expand Down Expand Up @@ -382,28 +382,22 @@ impl Shell {
params: &ExecutionParameters,
) -> Result<ExecutionResult, error::Error> {
tracing::debug!("sourcing: {}", path.display());
let opened_file: openfiles::OpenFile = self
.open_file(path, params)
.map_err(|e| error::Error::FailedSourcingFile(path.to_owned(), e.into()))?;

let path_to_open = self.get_absolute_path(path);

let opened_file = std::fs::File::open(path_to_open)
.map_err(|e| error::Error::FailedSourcingFile(path.to_owned(), e))?;

let file_metadata = opened_file
.metadata()
.map_err(|e| error::Error::FailedSourcingFile(path.to_owned(), e))?;

if file_metadata.is_dir() {
if opened_file.is_dir() {
return Err(error::Error::FailedSourcingFile(
path.to_owned(),
std::io::Error::new(std::io::ErrorKind::Other, error::Error::IsADirectory),
error::Error::IsADirectory.into(),
));
}

let source_info = brush_parser::SourceInfo {
source: path.to_string_lossy().to_string(),
};

self.source_file(&opened_file, &source_info, args, params)
self.source_file(opened_file, &source_info, args, params)
.await
}

Expand All @@ -415,9 +409,9 @@ impl Shell {
/// * `source_info` - Information about the source of the script.
/// * `args` - The arguments to pass to the script as positional parameters.
/// * `params` - Execution parameters.
pub async fn source_file<S: AsRef<str>>(
pub async fn source_file<F: Read, S: AsRef<str>>(
&mut self,
file: &std::fs::File,
file: F,
source_info: &brush_parser::SourceInfo,
args: &[S],
params: &ExecutionParameters,
Expand Down Expand Up @@ -908,6 +902,37 @@ impl Shell {
}
}

/// Opens the given file.
///
/// # Arguments
///
/// * `path` - The path to the file to open; may be relative to the shell's working directory.
/// * `params` - Execution parameters.
pub(crate) fn open_file(
&self,
path: &Path,
params: &ExecutionParameters,
) -> Result<openfiles::OpenFile, error::Error> {
let path_to_open = self.get_absolute_path(path);

// See if this is a reference to a file descriptor, in which case the actual
// /dev/fd* file path for this process may not match with what's in the execution
// parameters.
if let Some(parent) = path_to_open.parent() {
if parent == Path::new("/dev/fd") {
if let Some(filename) = path_to_open.file_name() {
if let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<u32>() {
if let Some(open_file) = params.open_files.files.get(&fd_num) {
return open_file.try_dup();
}
}
}
}
}

Ok(std::fs::File::open(path_to_open)?.into())
}

/// Sets the shell's current working directory to the given path.
///
/// # Arguments
Expand Down
5 changes: 5 additions & 0 deletions brush-shell/tests/cases/redirection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ cases:
shopt -u -o posix
for f in <(echo hi); do echo $f; done
- name: "Process substitution: builtins"
stdin: |
source <(echo VAR=100)
echo "var: ${VAR}"
- name: "Process substitution: input redirection"
stdin: |
shopt -u -o posix
Expand Down

0 comments on commit e472e16

Please sign in to comment.