Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
+9
-2
@@ -18,6 +18,10 @@ Every fork change is marked with a `// nash:` comment. Current patches:
|
|||||||
| `brush-core/src/commands.rs` | `SimpleCommand::execute` split into gate wrapper + `execute_inner`: `on_exec` before dispatch (Deny short-circuits), completion correlated through all three spawn-result variants |
|
| `brush-core/src/commands.rs` | `SimpleCommand::execute` split into gate wrapper + `execute_inner`: `on_exec` before dispatch (Deny short-circuits), completion correlated through all three spawn-result variants |
|
||||||
| `brush-core/src/processes.rs` | `ChildProcess.gate_token` + `on_exit` reporting from `wait()`/`poll()` (128+signal for signal deaths) |
|
| `brush-core/src/processes.rs` | `ChildProcess.gate_token` + `on_exit` reporting from `wait()`/`poll()` (128+signal for signal deaths) |
|
||||||
| `brush-shell/src/entry.rs` | `set_exit_hook` seam so nash can flush its event buffer before `process::exit` |
|
| `brush-shell/src/entry.rs` | `set_exit_hook` seam so nash can flush its event buffer before `process::exit` |
|
||||||
|
| `brush-core/src/gate.rs` | M2 (docs/NASH.md §5.2–5.3): `RedirectRecord`/`RedirectReadback` + `ExecStart.redirects` + `Gate::on_cmdsub` default hook |
|
||||||
|
| `brush-core/src/interp.rs` | `ExecutionParameters.nash_redirects` accumulator; `setup_redirect` records file (`>`,`>>`,`<`,`2>`,`&>`,`<>`), heredoc, and here-string redirects |
|
||||||
|
| `brush-core/src/commands.rs` | gate wrapper drains `nash_redirects` into `ExecStart` for post-run read-back |
|
||||||
|
| `brush-core/src/expansion.rs` | command-substitution arm calls `gate().on_cmdsub` with the trimmed output |
|
||||||
|
|
||||||
Verification (this container): brush compat suite `2067 ran: 1684 succeeded, 6
|
Verification (this container): brush compat suite `2067 ran: 1684 succeeded, 6
|
||||||
failed, 377 known to fail, 38 skipped` — the 6 failures are identical to a
|
failed, 377 known to fail, 38 skipped` — the 6 failures are identical to a
|
||||||
@@ -26,8 +30,11 @@ pristine `brush-shell-v0.4.0` baseline run (environmental: ANSI-C quote and
|
|||||||
zero fork-caused regressions; +3 successes vs baseline are the repaired IFS
|
zero fork-caused regressions; +3 successes vs baseline are the repaired IFS
|
||||||
tests. nash corpus: 94/94.
|
tests. nash corpus: 94/94.
|
||||||
|
|
||||||
Planned (M2, per docs/NASH.md §5.2–5.3): redirect/pipe tap hooks on the `Gate`
|
Planned (M2 remainder, per docs/NASH.md §5.3): inline tees on the pipe wiring
|
||||||
trait (`on_redirect`/`on_pipe`) in `interp.rs`/`openfiles.rs`.
|
in `interp.rs::spawn_pipeline_processes` (the `std::io::pipe()` links between
|
||||||
|
`a | b` stages) — the one data-flow channel not yet tapped. Deferred as the most
|
||||||
|
invasive change (async copier + backpressure + SIGPIPE); regular-file
|
||||||
|
redirections, heredoc/here-string, and command substitution are all captured.
|
||||||
|
|
||||||
Updating: diff against the upstream tag, re-vendor, re-apply `// nash:` patches
|
Updating: diff against the upstream tag, re-vendor, re-apply `// nash:` patches
|
||||||
(grep for the marker), then re-run `shell/corpus/replay.py` and the brush compat
|
(grep for the marker), then re-run `shell/corpus/replay.py` and the brush compat
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> {
|
|||||||
/// command passes through the process-global [`crate::gate::Gate`] before
|
/// command passes through the process-global [`crate::gate::Gate`] before
|
||||||
/// dispatch; completion is reported back with the issued token, however the
|
/// dispatch; completion is reported back with the issued token, however the
|
||||||
/// command runs (inline, spawned process, or spawned task).
|
/// command runs (inline, spawned process, or spawned task).
|
||||||
pub async fn execute(self) -> Result<ExecutionSpawnResult, error::Error> {
|
pub async fn execute(mut self) -> Result<ExecutionSpawnResult, error::Error> {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
let gate = crate::gate::gate();
|
let gate = crate::gate::gate();
|
||||||
@@ -362,9 +362,12 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> {
|
|||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
argv.push(self.command_name.clone());
|
argv.push(self.command_name.clone());
|
||||||
}
|
}
|
||||||
|
// nash: hand this command's redirects to the observer for post-run read-back.
|
||||||
|
let redirects = std::mem::take(&mut self.params.nash_redirects);
|
||||||
let start_event = crate::gate::ExecStart {
|
let start_event = crate::gate::ExecStart {
|
||||||
argv,
|
argv,
|
||||||
cwd: self.shell.working_dir().to_path_buf(),
|
cwd: self.shell.working_dir().to_path_buf(),
|
||||||
|
redirects,
|
||||||
};
|
};
|
||||||
let token = match gate.on_exec(start_event) {
|
let token = match gate.on_exec(start_event) {
|
||||||
crate::gate::Verdict::Allow(token) => token,
|
crate::gate::Verdict::Allow(token) => token,
|
||||||
|
|||||||
@@ -999,6 +999,12 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
|
|||||||
let trimmed_len = cmd_output.trim_end_matches('\n').len();
|
let trimmed_len = cmd_output.trim_end_matches('\n').len();
|
||||||
cmd_output.truncate(trimmed_len);
|
cmd_output.truncate(trimmed_len);
|
||||||
|
|
||||||
|
// nash: capture command-substitution output (docs/NASH.md §5.3) —
|
||||||
|
// invisible to the transcript, often carries decisions/secrets.
|
||||||
|
if !self.disable_command_substitutions {
|
||||||
|
crate::gate::gate().on_cmdsub(cmd_output.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
Expansion::from(ExpansionPiece::Splittable(cmd_output))
|
Expansion::from(ExpansionPiece::Splittable(cmd_output))
|
||||||
}
|
}
|
||||||
brush_parser::word::WordPiece::EscapeSequence(s) => {
|
brush_parser::word::WordPiece::EscapeSequence(s) => {
|
||||||
|
|||||||
@@ -18,6 +18,41 @@ pub struct ExecStart {
|
|||||||
pub argv: Vec<String>,
|
pub argv: Vec<String>,
|
||||||
/// The shell's working directory at execution time.
|
/// The shell's working directory at execution time.
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
|
/// nash: the redirections applied to this command (docs/NASH.md §5.2). The
|
||||||
|
/// observer reads back the affected byte range after the command completes.
|
||||||
|
pub redirects: Vec<RedirectRecord>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How a redirection's data is captured after the command runs (docs/NASH.md §5.2).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RedirectReadback {
|
||||||
|
/// A truncating write (`>`, `>|`, `&>`): read the head of the final file.
|
||||||
|
Truncate,
|
||||||
|
/// An append (`>>`): read the bytes added after `size_before`.
|
||||||
|
Append,
|
||||||
|
/// An input (`<`): read the head of the source file.
|
||||||
|
Input,
|
||||||
|
/// Inline data known at setup (heredoc / here-string): no file read-back.
|
||||||
|
Inline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One redirection observed on a command. For regular files nash hands the child
|
||||||
|
/// the *real* fd (semantics untouched) and reads back a bounded range afterward;
|
||||||
|
/// for heredoc/here-string the body is captured inline at setup.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RedirectRecord {
|
||||||
|
/// The operator as written (`>`, `>>`, `<`, `2>`, `&>`, `<<`, `<<<`).
|
||||||
|
pub op: String,
|
||||||
|
/// The affected file descriptor number.
|
||||||
|
pub fd: u32,
|
||||||
|
/// The regular-file target, if any (`None` for heredoc/here-string).
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
/// How to read the data back.
|
||||||
|
pub readback: RedirectReadback,
|
||||||
|
/// The file's size at setup time (for append ranges); 0 otherwise.
|
||||||
|
pub size_before: u64,
|
||||||
|
/// Inline body for heredoc/here-string (already known at setup).
|
||||||
|
pub inline: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about a completed simple command.
|
/// Details about a completed simple command.
|
||||||
@@ -49,6 +84,9 @@ pub trait Gate: Send + Sync {
|
|||||||
/// Called when a simple command completes, with the token issued by
|
/// Called when a simple command completes, with the token issued by
|
||||||
/// `on_exec`.
|
/// `on_exec`.
|
||||||
fn on_exit(&self, token: u64, ev: ExecEnd);
|
fn on_exit(&self, token: u64, ev: ExecEnd);
|
||||||
|
/// nash: called with the (trimmed) output of a command substitution
|
||||||
|
/// `$(…)` / backticks (docs/NASH.md §5.3). Default no-op.
|
||||||
|
fn on_cmdsub(&self, _output: &str) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AllowAll;
|
struct AllowAll;
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub struct ExecutionParameters {
|
|||||||
/// Whether `errexit` (exit on error) behavior should be
|
/// Whether `errexit` (exit on error) behavior should be
|
||||||
/// suppressed in this execution context. Defaults to `false`.
|
/// suppressed in this execution context. Defaults to `false`.
|
||||||
pub suppress_errexit: bool,
|
pub suppress_errexit: bool,
|
||||||
|
// nash: redirections applied to the command being built, drained into its
|
||||||
|
// `gate::ExecStart` for post-run read-back (docs/NASH.md §5.2). Empty unless
|
||||||
|
// observation is active and the command has redirects.
|
||||||
|
pub(crate) nash_redirects: Vec<crate::gate::RedirectRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionParameters {
|
impl ExecutionParameters {
|
||||||
@@ -1641,6 +1645,22 @@ pub(crate) async fn setup_redirect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let expanded_file_path = expanded_fields.remove(0);
|
let expanded_file_path = expanded_fields.remove(0);
|
||||||
|
// nash: `&>` / `&>>` writes stdout+stderr to one file — record fd 1
|
||||||
|
// for read-back (docs/NASH.md §5.2).
|
||||||
|
let abs = shell.absolute_path(Path::new(expanded_file_path.as_str()));
|
||||||
|
let size_before = std::fs::metadata(&abs).map(|m| m.len()).unwrap_or(0);
|
||||||
|
params.nash_redirects.push(crate::gate::RedirectRecord {
|
||||||
|
op: if *append { "&>>".to_string() } else { "&>".to_string() },
|
||||||
|
fd: 1,
|
||||||
|
path: Some(abs),
|
||||||
|
readback: if *append {
|
||||||
|
crate::gate::RedirectReadback::Append
|
||||||
|
} else {
|
||||||
|
crate::gate::RedirectReadback::Truncate
|
||||||
|
},
|
||||||
|
size_before,
|
||||||
|
inline: None,
|
||||||
|
});
|
||||||
setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
|
setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1717,6 +1737,10 @@ pub(crate) async fn setup_redirect(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// nash: record the redirect so the observer can read back the
|
||||||
|
// affected byte range after the command runs (docs/NASH.md §5.2).
|
||||||
|
nash_record_file_redirect(params, fd_num, kind, &expanded_file_path);
|
||||||
|
|
||||||
params.open_files.set_fd(fd_num, opened_file);
|
params.open_files.set_fd(fd_num, opened_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1839,6 +1863,9 @@ pub(crate) async fn setup_redirect(
|
|||||||
|
|
||||||
let f = setup_open_file_with_contents(io_here_doc.as_str())?;
|
let f = setup_open_file_with_contents(io_here_doc.as_str())?;
|
||||||
|
|
||||||
|
// nash: heredoc body is known at setup — capture inline (docs/NASH.md §5.2b).
|
||||||
|
nash_record_inline_redirect(params, fd_num, "<<", &io_here_doc);
|
||||||
|
|
||||||
params.open_files.set_fd(fd_num, f);
|
params.open_files.set_fd(fd_num, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1851,6 +1878,9 @@ pub(crate) async fn setup_redirect(
|
|||||||
|
|
||||||
let f = setup_open_file_with_contents(expanded_word.as_str())?;
|
let f = setup_open_file_with_contents(expanded_word.as_str())?;
|
||||||
|
|
||||||
|
// nash: here-string body is known at setup — capture inline.
|
||||||
|
nash_record_inline_redirect(params, fd_num, "<<<", &expanded_word);
|
||||||
|
|
||||||
params.open_files.set_fd(fd_num, f);
|
params.open_files.set_fd(fd_num, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1858,6 +1888,52 @@ pub(crate) async fn setup_redirect(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nash: record a regular-file redirect for post-run read-back (docs/NASH.md §5.2a).
|
||||||
|
// The child still gets the real fd; this only notes what to read back afterward.
|
||||||
|
fn nash_record_file_redirect(
|
||||||
|
params: &mut ExecutionParameters,
|
||||||
|
fd: ShellFd,
|
||||||
|
kind: &ast::IoFileRedirectKind,
|
||||||
|
path: &Path,
|
||||||
|
) {
|
||||||
|
use crate::gate::RedirectReadback;
|
||||||
|
let (op, readback) = match kind {
|
||||||
|
ast::IoFileRedirectKind::Read | ast::IoFileRedirectKind::DuplicateInput => ("<", RedirectReadback::Input),
|
||||||
|
ast::IoFileRedirectKind::Write | ast::IoFileRedirectKind::DuplicateOutput => (">", RedirectReadback::Truncate),
|
||||||
|
ast::IoFileRedirectKind::Clobber => (">|", RedirectReadback::Truncate),
|
||||||
|
ast::IoFileRedirectKind::Append => (">>", RedirectReadback::Append),
|
||||||
|
ast::IoFileRedirectKind::ReadAndWrite => ("<>", RedirectReadback::Truncate),
|
||||||
|
};
|
||||||
|
// Size at setup: append ranges start here; only regular files are read back.
|
||||||
|
let size_before = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
|
||||||
|
let op = if fd == 2 && op == ">" { "2>".to_string() } else { op.to_string() };
|
||||||
|
params.nash_redirects.push(crate::gate::RedirectRecord {
|
||||||
|
op,
|
||||||
|
fd: u32::try_from(fd).unwrap_or(0),
|
||||||
|
path: Some(path.to_path_buf()),
|
||||||
|
readback,
|
||||||
|
size_before,
|
||||||
|
inline: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// nash: record an inline (heredoc / here-string) redirect — body known at setup.
|
||||||
|
fn nash_record_inline_redirect(
|
||||||
|
params: &mut ExecutionParameters,
|
||||||
|
fd: ShellFd,
|
||||||
|
op: &str,
|
||||||
|
body: &str,
|
||||||
|
) {
|
||||||
|
params.nash_redirects.push(crate::gate::RedirectRecord {
|
||||||
|
op: op.to_string(),
|
||||||
|
fd: u32::try_from(fd).unwrap_or(0),
|
||||||
|
path: None,
|
||||||
|
readback: crate::gate::RedirectReadback::Inline,
|
||||||
|
size_before: 0,
|
||||||
|
inline: Some(body.to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets up redirection of both stdout and stderr to the same file, given by `file_path`.
|
/// Sets up redirection of both stdout and stderr to the same file, given by `file_path`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
Reference in New Issue
Block a user