Files
RedBear-OS/local/recipes/shells/brush/patches/brush-redox-runtime-and-input.patch
T
vasilito 37c2ecece4 brush: route rb_dbg diagnostics to /scheme/debug instead of stderr
Writing startup diagnostics to stderr interleaves with the interactive
console on the live image; /scheme/debug reaches the serial log without
disturbing the shell's tty.
2026-07-20 09:05:44 +09:00

191 lines
8.3 KiB
Diff

diff --git a/brush-interactive/src/minimal/input_backend.rs b/brush-interactive/src/minimal/input_backend.rs
index 48732fb..70b4049 100644
--- a/brush-interactive/src/minimal/input_backend.rs
+++ b/brush-interactive/src/minimal/input_backend.rs
@@ -48,15 +48,53 @@ impl MinimalInputBackend {
}
fn read_input_line() -> Result<ReadResult, ShellError> {
- let mut input = String::new();
- let bytes_read = std::io::stdin()
- .read_line(&mut input)
- .map_err(ShellError::InputError)?;
-
- if bytes_read == 0 {
- Ok(ReadResult::Eof)
- } else {
- Ok(ReadResult::Input(input))
+ // On Redox the interactive console is a pty slave whose reads can
+ // return Ok(0) with no bytes even though the master side is still open
+ // (e.g. a non-blocking read with no data currently queued). std's
+ // `read_line` reports that as end-of-input; if the shell treated it as
+ // EOF it would exit immediately and getty would respawn login in a
+ // tight loop. Distinguish a *spurious* empty read (retry, briefly
+ // yielding to avoid a busy spin) from a real terminal by only treating
+ // 0 bytes as EOF when stdin is not a tty.
+ use std::io::ErrorKind;
+ let mut dbg_iters: u32 = 0;
+ loop {
+ let mut input = String::new();
+ let dbg_res = std::io::stdin().read_line(&mut input);
+ if dbg_iters < 6 {
+ use std::io::Write;
+ let _ = std::io::stderr().write_all(
+ format!(
+ "BRUSH-DBG: read iter={} tty={} res={:?}\n",
+ dbg_iters,
+ std::io::stdin().is_terminal(),
+ dbg_res.as_ref().map(|n| *n).map_err(|e| e.kind()),
+ )
+ .as_bytes(),
+ );
+ }
+ dbg_iters = dbg_iters.wrapping_add(1);
+ match dbg_res {
+ // Got a line.
+ Ok(n) if n != 0 => return Ok(ReadResult::Input(input)),
+ // Zero bytes: real EOF only on a non-interactive input. On an
+ // interactive Redox pty a non-blocking read with no data queued
+ // can also surface as Ok(0); retry rather than exiting (which
+ // would drop back to a getty login loop).
+ Ok(_) => {
+ if !std::io::stdin().is_terminal() {
+ return Ok(ReadResult::Eof);
+ }
+ }
+ // A non-blocking read with no data currently available (or an
+ // interrupted syscall) is not fatal on an interactive terminal;
+ // keep polling for input.
+ Err(e)
+ if std::io::stdin().is_terminal()
+ && matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {}
+ Err(e) => return Err(ShellError::InputError(e)),
+ }
+ std::thread::sleep(std::time::Duration::from_millis(20));
}
}
}
diff --git a/brush-shell/src/entry.rs b/brush-shell/src/entry.rs
index 15340ad..46774c3 100644
--- a/brush-shell/src/entry.rs
+++ b/brush-shell/src/entry.rs
@@ -117,8 +117,18 @@ impl CommandLineArgs {
}
}
+/// Temporary mini-diagnostic: write to fd 2 (console) so brush startup is
+/// visible on serial.
+#[doc(hidden)]
+fn rb_dbg(m: &str) {
+ use std::io::Write;
+ let df = std::fs::OpenOptions::new().write(true).open("/scheme/debug");
+ if let Ok(mut df) = df { let _ = df.write_all(m.as_bytes()); }
+}
+
/// Main entry point for the `brush` shell.
pub fn run() {
+ rb_dbg("BRUSH-DBG: run() entered\n");
//
// Install the bundled-command registry so it's available both for
// bundled dispatch (handled next) and for builtin shim registration
@@ -173,17 +183,30 @@ pub fn run() {
//
// Run.
//
- #[cfg(any(unix, windows))]
+ // Redox's tokio multi-threaded runtime fails to build (EINVAL on worker
+ // thread setup); the current-thread runtime is the correct model there and
+ // is all an interactive shell needs.
+ #[cfg(all(any(unix, windows), not(target_os = "redox")))]
let mut builder = tokio::runtime::Builder::new_multi_thread();
- #[cfg(not(any(unix, windows)))]
+ #[cfg(any(not(any(unix, windows)), target_os = "redox"))]
let mut builder = tokio::runtime::Builder::new_current_thread();
- let Ok(runtime) = builder.enable_all().build() else {
- tracing::error!("error: failed to create Tokio runtime");
- std::process::exit(1);
+ rb_dbg("BRUSH-DBG: building tokio runtime (enable_all)\n");
+ let runtime = match builder.enable_all().build() {
+ Ok(rt) => {
+ rb_dbg("BRUSH-DBG: tokio runtime built OK\n");
+ rt
+ }
+ Err(e) => {
+ rb_dbg(&format!("BRUSH-DBG: tokio enable_all build FAILED: {e}\n"));
+ tracing::error!("error: failed to create Tokio runtime");
+ std::process::exit(1);
+ }
};
+ rb_dbg("BRUSH-DBG: entering block_on(run_async)\n");
let result = runtime.block_on(run_async(&args, parsed_args));
+ rb_dbg("BRUSH-DBG: block_on returned (shell exiting)\n");
let exit_code = match result {
Ok(code) => code,
@@ -242,6 +265,7 @@ async fn run_async(
cli_args: &[String],
args: CommandLineArgs,
) -> Result<u8, brush_interactive::ShellError> {
+ rb_dbg("BRUSH-DBG: run_async: entered\n");
// Initializing tracing.
let mut event_config = TRACE_EVENT_CONFIG.lock().await;
*event_config = Some(events::TraceEventConfig::init(
@@ -249,15 +273,18 @@ async fn run_async(
&args.disabled_events,
));
drop(event_config);
+ rb_dbg("BRUSH-DBG: run_async: tracing init done, loading config\n");
// Load configuration file.
let file_config = config::load_config(args.no_config, args.config_file.as_deref())
.into_config_or_log()
.map_err(|e| brush_interactive::ShellError::IoError(std::io::Error::other(e)))?;
+ rb_dbg("BRUSH-DBG: run_async: config loaded\n");
// Instantiate an appropriately configured shell and wrap it in an `Arc`. Note that we do
// *not* run any code in the shell yet. We'll delay loading profiles and such until after
// we've set up everything else (in `run_in_shell`).
+ rb_dbg("BRUSH-DBG: run_async: instantiating shell\n");
let shell: BrushShell = instantiate_shell(&args, cli_args).await?;
let shell = Arc::new(Mutex::new(shell));
@@ -265,6 +292,16 @@ async fn run_async(
// backend type and calls `run_in_shell`, preserving static dispatch.
let default_backend = get_default_input_backend_type(&args);
let selected_backend = args.input_backend.unwrap_or(default_backend);
+ rb_dbg(&format!(
+ "BRUSH-DBG: backend={} stdin_tty={} interactive={}\n",
+ match selected_backend {
+ InputBackendType::Reedline => "reedline",
+ InputBackendType::Basic => "basic",
+ InputBackendType::Minimal => "minimal",
+ },
+ std::io::IsTerminal::is_terminal(&std::io::stdin()),
+ will_run_interactively(&args),
+ ));
// Build UI options by merging config file with CLI args.
#[allow(unused_variables, reason = "not used when no backend features enabled")]
@@ -623,7 +660,18 @@ const fn new_error_behavior(args: &CommandLineArgs) -> error_formatter::Formatte
}
fn get_default_input_backend_type(args: &CommandLineArgs) -> InputBackendType {
- #[cfg(any(unix, windows))]
+ // On Redox, reedline's crossterm raw-mode terminal handling does not yet
+ // work over the fbcon console scheme: crossterm's raw-mode event poll never
+ // receives key events, so the interactive shell hangs without reading input.
+ // Plain line-based stdin reads DO work over fbcon (getty/login read the
+ // username that way), so use the minimal backend, which reads stdin
+ // directly. Remove this once reedline/crossterm raw mode is supported.
+ #[cfg(target_os = "redox")]
+ {
+ let _args = args;
+ InputBackendType::Minimal
+ }
+ #[cfg(all(any(unix, windows), not(target_os = "redox")))]
{
// If stdin isn't a terminal, then `reedline` doesn't do the right thing
// (reference: https://github.com/nushell/reedline/issues/509). Switch to