From a4d08e9998ec9d1a707a22d13b2a489f85184a09 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Mon, 27 Jul 2026 22:33:13 +0100 Subject: [PATCH] fix(base64-rs): print usage instead of blocking on console stdin Running base64 with no FILE fell into the stdin branch and sat in a read loop. That is exactly what GNU does, but on Windows it presents as a dead terminal: the console echoes keystrokes so it looks like a prompt, output buffers into 32K chunks so nothing comes back as you type, and there is no hint that it is waiting for Ctrl+Z. Guard the implicit case only. With no FILE operand and stdin attached to a console, print the help text to stderr and exit 1. Pipes and '<' redirects are untouched, so pipelines and the parity harness see no change -- the harness always hands the child a stdin pipe via input=. An explicit '-' still reads the console for anyone who does want to type input by hand. Uses std::io::IsTerminal, so the crate stays dependency-free. --- base64-rs/README.md | 1 + base64-rs/src/main.rs | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/base64-rs/README.md b/base64-rs/README.md index 9a40278..1a1419c 100644 --- a/base64-rs/README.md +++ b/base64-rs/README.md @@ -61,6 +61,7 @@ Verified against GNU coreutils 9.7 with a 154-case differential test ([tests/par | | Why | |---|---| | `\r` is skipped when decoding, as `\n` already is | GNU rejects CRLF input as invalid. On Windows a base64 file will routinely have CRLF line endings, and erroring on those would make the tool useless for its main job. Everything else is still rejected unless you pass `-i`. | +| Running it with no FILE at an interactive console prints the usage text and exits `1` | GNU would read stdin, which looks exactly like a hung terminal — no prompt, no output, no hint that it wants Ctrl+Z. Only applies when stdin is a console: pipes and `<` redirects read stdin as usual, and `base64 -` still reads the console if you actually want to type input. | | `--help` omits GNU's support URLs | This isn't GNU coreutils; pointing at their bug tracker would be wrong. | | Error messages quote with `'ASCII'` | GNU uses `'…'` in a UTF-8 locale. Windows consoles are frequently cp437/cp1252, where those bytes render as mojibake. | diff --git a/base64-rs/src/main.rs b/base64-rs/src/main.rs index 8878a71..eaab7df 100644 --- a/base64-rs/src/main.rs +++ b/base64-rs/src/main.rs @@ -5,7 +5,7 @@ use std::env; use std::fs::File; -use std::io::{self, BufWriter, ErrorKind, Read, Write}; +use std::io::{self, BufWriter, ErrorKind, IsTerminal, Read, Write}; use std::process::ExitCode; const PROGRAM: &str = "base64"; @@ -56,6 +56,10 @@ fn main() -> ExitCode { eprintln!("Try '{PROGRAM} --help' for more information."); ExitCode::FAILURE } + Err(Fail::NoInput) => { + eprintln!("{HELP}"); + ExitCode::FAILURE + } Err(Fail::Error(msg)) => { eprintln!("{PROGRAM}: {msg}"); ExitCode::FAILURE @@ -68,6 +72,9 @@ enum Fail { Usage(String), /// Runtime problem: message only. Error(String), + /// Bare invocation at a console: show the usage text rather than sit on + /// stdin looking hung. + NoInput, /// Downstream closed the pipe (`base64 f | head`); nothing to report. Quiet, } @@ -88,6 +95,13 @@ fn run() -> Result<(), Fail> { } }; + // GNU reads stdin when given no FILE, which at an interactive prompt is + // indistinguishable from a hang. Only the implicit case is caught: an + // explicit `-` is someone asking for stdin on purpose. + if opts.file.is_none() && io::stdin().is_terminal() { + return Err(Fail::NoInput); + } + // Reading is buffered by the encode/decode loops, which pull in large // chunks; stdout is wrapped so small writes do not hit the OS each time. let stdout = io::stdout();