From 4ceea4fb6904923a3ec66c4ad156466fa09a21d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Mon, 29 Jun 2026 14:43:12 +0200 Subject: [PATCH 1/3] Handle broken pipes more gracefully. --- jaq/src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jaq/src/main.rs b/jaq/src/main.rs index 8f3694374..36a613213 100644 --- a/jaq/src/main.rs +++ b/jaq/src/main.rs @@ -26,6 +26,13 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; extern crate alloc; fn main() -> io::Result { + match main_pipe() { + Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(ExitCode::from(141)), + r => r, + } +} + +fn main_pipe() -> io::Result { use env_logger::Env; env_logger::Builder::from_env(Env::default().filter_or("LOG", "jaq=debug")) .format(|buf, record| match record.level() { @@ -261,6 +268,7 @@ impl fmt::Display for ErrorColor<'_> { let Self(error, color) = self; match error { Error::FalseOrNull | Error::NoOutput | Error::Halt(_) => Ok(()), + Error::Io(_, e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(()), Error::Io(prefix, e) => { write!(f, "Error: ")?; if let Some(p) = prefix { @@ -284,6 +292,7 @@ impl Termination for Error { fn report(self) -> ExitCode { ExitCode::from(match self { Self::FalseOrNull => 1, + Self::Io(_, e) if e.kind() == io::ErrorKind::BrokenPipe => 141, Self::Io(_, _) => 2, Self::Report(_) => 3, Self::NoOutput => 4, From 9f8fdb6f228396f3439400f3d1358dd05dda43db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Fri, 17 Jul 2026 09:05:13 +0200 Subject: [PATCH 2/3] More explicit I/O handling. --- jaq-fmts/src/write/mod.rs | 3 +-- jaq/src/funs.rs | 4 +++- jaq/src/main.rs | 36 +++++++++++++++++++----------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/jaq-fmts/src/write/mod.rs b/jaq-fmts/src/write/mod.rs index d2e38614b..36f20eaa5 100644 --- a/jaq-fmts/src/write/mod.rs +++ b/jaq-fmts/src/write/mod.rs @@ -33,8 +33,7 @@ pub struct Writer { } /// Buffer writes if stdout is terminal, else just lock stdout. -pub fn with_stdout(f: impl FnOnce(&mut dyn Write) -> T) -> T { - let stdout = io::stdout(); +pub fn with_stdout(stdout: &mut io::Stdout, f: impl FnOnce(&mut dyn Write) -> T) -> T { if stdout.is_terminal() { f(&mut stdout.lock()) } else { diff --git a/jaq/src/funs.rs b/jaq/src/funs.rs index d094c770d..77803a7c6 100644 --- a/jaq/src/funs.rs +++ b/jaq/src/funs.rs @@ -36,7 +36,9 @@ fn eval(runner: &Runner, code: String, input: Val) -> Result<(), Error> { let ctx = Vars::new(ctx); let inputs = core::iter::once(Ok(input)); let writer = &runner.writer; - with_stdout(|out| run(runner, &filter, ctx, inputs, |v| write(out, writer, &v)))?; + with_stdout(&mut std::io::stdout(), |out| { + run(runner, &filter, ctx, inputs, |v| write(out, writer, &v)) + })?; Ok(()) } diff --git a/jaq/src/main.rs b/jaq/src/main.rs index 36a613213..2cadb71c4 100644 --- a/jaq/src/main.rs +++ b/jaq/src/main.rs @@ -25,14 +25,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; extern crate alloc; -fn main() -> io::Result { - match main_pipe() { - Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Ok(ExitCode::from(141)), - r => r, - } -} - -fn main_pipe() -> io::Result { +fn main() -> ExitCode { use env_logger::Env; env_logger::Builder::from_env(Env::default().filter_or("LOG", "jaq=debug")) .format(|buf, record| match record.level() { @@ -43,9 +36,16 @@ fn main_pipe() -> io::Result { }) .init(); - let mut out = io::stdout(); - let mut err = io::stderr(); + // catch broken pipes + main_io(&mut io::stdin(), &mut io::stdout(), &mut io::stderr()) + .unwrap_or_else(|e: io::Error| Error::from(e).report()) +} +fn main_io( + inp: &mut io::Stdin, + out: &mut io::Stdout, + err: &mut io::Stderr, +) -> io::Result { let cli = match Cli::parse() { Ok(cli) => cli, Err(e) => { @@ -65,10 +65,10 @@ fn main_pipe() -> io::Result { } else if let Some(test_files) = &cli.run_tests { match test_files.last() { Some(file) => tests::run(io::BufReader::new(std::fs::File::open(file)?)), - None => tests::run(io::stdin().lock()), + None => tests::run(inp.lock()), } } else { - real_main(&cli).or_else(|e| { + real_main(&cli, inp, out).or_else(|e| { write!(err, "{}", ErrorColor::new(&e, cli.color_errors()))?; Ok(e.report()) }) @@ -112,7 +112,7 @@ impl Cli { } } -fn real_main(cli: &Cli) -> Result { +fn real_main(cli: &Cli, inp: &mut io::Stdin, out: &mut io::Stdout) -> Result { let mut var_val = binds(cli)?; let input_filename_idx = var_val.len(); var_val.push(("!input_filename".to_string(), Val::Null)); @@ -141,9 +141,11 @@ fn real_main(cli: &Cli) -> Result { let vars = Vars::new(vars); let format = unwrap_or_json(cli.from); - let s = read::read_string(format, io::stdin().lock())?; - let inputs = read::read(format, io::stdin().lock(), &s, cli.slurp); - with_stdout(|out| run(runner, &filter, vars, inputs, |v| write(out, writer, &v)))? + let s = read::read_string(format, inp.lock())?; + let inputs = read::read(format, inp.lock(), &s, cli.slurp); + with_stdout(out, |out| { + run(runner, &filter, vars, inputs, |v| write(out, writer, &v)) + })? } else { let mut last = None; for file in &cli.files { @@ -175,7 +177,7 @@ fn real_main(cli: &Cli) -> Result { tmp.persist(path).map_err(|e| Error::Io(None, e.into()))?; std::fs::set_permissions(path, perms)?; } else { - last = with_stdout(|out| { + last = with_stdout(out, |out| { run(runner, &filter, vars.clone(), inputs, |v| { write(out, writer, &v) }) From fc907af8b6fb3226e22bcd199a1afa50d0043728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20F=C3=A4rber?= <01mf02@gmail.com> Date: Fri, 17 Jul 2026 09:24:49 +0200 Subject: [PATCH 3/3] Document. --- jaq/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jaq/src/main.rs b/jaq/src/main.rs index 2cadb71c4..5d85e15f9 100644 --- a/jaq/src/main.rs +++ b/jaq/src/main.rs @@ -36,7 +36,8 @@ fn main() -> ExitCode { }) .init(); - // catch broken pipes + // last line of defense against I/O errors that happen during printing, + // e.g. catching broken pipes during error printing main_io(&mut io::stdin(), &mut io::stdout(), &mut io::stderr()) .unwrap_or_else(|e: io::Error| Error::from(e).report()) }