Skip to content

Fix #194: Allow --version/--help to run without TTY access #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const SCRATCH_ARENA_CAPACITY: usize = 128 * MEBI;
#[cfg(target_pointer_width = "64")]
const SCRATCH_ARENA_CAPACITY: usize = 512 * MEBI;

#[derive(PartialEq, Eq, Debug)]
enum EarlyAction {
PrintHelp,
PrintVersion,
}

fn main() -> process::ExitCode {
if cfg!(debug_assertions) {
let hook = std::panic::take_hook();
Expand All @@ -62,9 +68,37 @@ fn main() -> process::ExitCode {
}
}

fn check_for_early_exit_args() -> Option<EarlyAction> {
let args_os: Vec<std::ffi::OsString> = env::args_os().skip(1).collect();
for arg_os in args_os {
if arg_os == "-v" || arg_os == "--version" {
return Some(EarlyAction::PrintVersion);
}
if arg_os == "-h" || arg_os == "--help" {
return Some(EarlyAction::PrintHelp);
}
if cfg!(windows) && arg_os == "/?" {
// For Windows-specific help argument
return Some(EarlyAction::PrintHelp);
}
}
None
}

fn run() -> apperr::Result<()> {
// Init `sys` first, as everything else may depend on its functionality (IO, function pointers, etc.).
let _sys_deinit = sys::init()?;
if let Some(action) = check_for_early_exit_args() {
// arena::init() and localization::init() are NOT needed for this path
// as print_help() and print_version() don't use loc!().
match action {
EarlyAction::PrintHelp => print_help(), // Uses sys::write_stdout()
EarlyAction::PrintVersion => print_version(), // Uses sys::write_stdout()
}
return Ok(()); // Exit early after printing.
}
sys::ensure_interactive_tty()?;

// Next init `arena`, so that `scratch_arena` works. `loc` depends on it.
arena::init(SCRATCH_ARENA_CAPACITY)?;
// Init the `loc` module, so that error messages are localized.
Expand Down
44 changes: 39 additions & 5 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,51 @@ extern "C" fn sigwinch_handler(_: libc::c_int) {
}
}

pub fn init() -> apperr::Result<Deinit> {
// In src/sys/unix.rs
pub fn ensure_interactive_tty() -> apperr::Result<()> {
unsafe {
// Reopen stdin if it's redirected (= piped input).
// If the current STATE.stdin (which after sys::init() is libc::STDIN_FILENO)
// is not a TTY, it means input was redirected. For interactive mode,
// we want to use /dev/tty instead for actual user input.
if libc::isatty(STATE.stdin) == 0 {
STATE.stdin = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_RDONLY))?;
let tty_fd = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_RDWR))?;
// Now STATE.stdin points to the actual TTY for interactive input.
STATE.stdin = tty_fd;
// Update stdin_flags for the new TTY file descriptor.
STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?;
}

// Store the stdin flags so we can more easily toggle `O_NONBLOCK` later on.
// Similar logic could be applied to STATE.stdout if it also needs to be
// explicitly a TTY for some interactive features, though output redirection
// is often desired. For now, focusing on stdin as per the original issue.
// If STATE.stdout is also redirected and needs to be /dev/tty for output:
// if libc::isatty(STATE.stdout) == 0 {
// let tty_out_fd = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_WRONLY))?;
// STATE.stdout = tty_out_fd;
// }

Ok(())
}
}

pub fn init() -> apperr::Result<Deinit> {
unsafe {
// STATE.stdin is already libc::STDIN_FILENO by default.
// STATE.stdout is already libc::STDOUT_FILENO by default.

// DO NOT include the logic:
// // if libc::isatty(STATE.stdin) == 0 {
// // STATE.stdin = check_int_return(libc::open(c"/dev/tty".as_ptr(), libc::O_RDONLY))?;
// // }

// Initialize stdin_flags based on the default STATE.stdin (libc::STDIN_FILENO).
// This is needed because sys::write_stdout -> set_tty_nonblocking uses these.
STATE.stdin_flags = check_int_return(libc::fcntl(STATE.stdin, libc::F_GETFL))?;

Ok(Deinit)
// Any other truly minimal, safe initializations for default stdio handles can go here.
// For example, ensuring STATE.stdout is usable for sys::write_stdout.

Ok(Deinit) // Deinit's drop only cares about stdout_initial_termios, which is None at this stage.
}
}

Expand Down