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/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-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
|
||||
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
|
||||
tests. nash corpus: 94/94.
|
||||
|
||||
Planned (M2, per docs/NASH.md §5.2–5.3): redirect/pipe tap hooks on the `Gate`
|
||||
trait (`on_redirect`/`on_pipe`) in `interp.rs`/`openfiles.rs`.
|
||||
Planned (M2 remainder, per docs/NASH.md §5.3): inline tees on the pipe wiring
|
||||
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
|
||||
(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
|
||||
/// dispatch; completion is reported back with the issued token, however the
|
||||
/// 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;
|
||||
|
||||
let gate = crate::gate::gate();
|
||||
@@ -362,9 +362,12 @@ impl<'a, SE: extensions::ShellExtensions> SimpleCommand<'a, SE> {
|
||||
if argv.is_empty() {
|
||||
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 {
|
||||
argv,
|
||||
cwd: self.shell.working_dir().to_path_buf(),
|
||||
redirects,
|
||||
};
|
||||
let token = match gate.on_exec(start_event) {
|
||||
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();
|
||||
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))
|
||||
}
|
||||
brush_parser::word::WordPiece::EscapeSequence(s) => {
|
||||
|
||||
@@ -18,6 +18,41 @@ pub struct ExecStart {
|
||||
pub argv: Vec<String>,
|
||||
/// The shell's working directory at execution time.
|
||||
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.
|
||||
@@ -49,6 +84,9 @@ pub trait Gate: Send + Sync {
|
||||
/// Called when a simple command completes, with the token issued by
|
||||
/// `on_exec`.
|
||||
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;
|
||||
|
||||
@@ -37,6 +37,10 @@ pub struct ExecutionParameters {
|
||||
/// Whether `errexit` (exit on error) behavior should be
|
||||
/// suppressed in this execution context. Defaults to `false`.
|
||||
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 {
|
||||
@@ -1641,6 +1645,22 @@ pub(crate) async fn setup_redirect(
|
||||
}
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1839,6 +1863,9 @@ pub(crate) async fn setup_redirect(
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1851,6 +1878,9 @@ pub(crate) async fn setup_redirect(
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1858,6 +1888,52 @@ pub(crate) async fn setup_redirect(
|
||||
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`.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
Reference in New Issue
Block a user