From 9e4a2ab824bd48491f15fb25df55614b9229e9c6 Mon Sep 17 00:00:00 2001 From: Hofer-Julian <30049909+Hofer-Julian@users.noreply.github.com> Date: Fri, 20 Jan 2023 22:20:38 +0100 Subject: [PATCH] Move all functions of main.rs into modules (#7803) The affected modules are: - `command.rs` - `config_files.rs` - `terminal.rs` --- src/command.rs | 300 ++++++++++++++++++++++++++++ src/config_files.rs | 53 +++++ src/main.rs | 465 ++------------------------------------------ src/terminal.rs | 97 +++++++++ 4 files changed, 462 insertions(+), 453 deletions(-) create mode 100644 src/command.rs create mode 100644 src/terminal.rs diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 000000000..777537b02 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,300 @@ +use nu_cli::report_error; +use nu_engine::{get_full_help, CallExt}; +use nu_parser::parse; +use nu_protocol::{ + ast::{Call, Expr, Expression, PipelineElement}, + engine::{Command, EngineState, Stack, StateWorkingSet}, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, + Value, +}; +use nu_utils::stdout_write_all_and_flush; + +pub(crate) fn parse_commandline_args( + commandline_args: &str, + engine_state: &mut EngineState, +) -> Result { + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + working_set.add_decl(Box::new(Nu)); + + let (output, err) = parse( + &mut working_set, + None, + commandline_args.as_bytes(), + false, + &[], + ); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + working_set.hide_decl(b"nu"); + (output, working_set.render()) + }; + + engine_state.merge_delta(delta)?; + + let mut stack = Stack::new(); + + // We should have a successful parse now + if let Some(pipeline) = block.pipelines.get(0) { + if let Some(PipelineElement::Expression( + _, + Expression { + expr: Expr::Call(call), + .. + }, + )) = pipeline.elements.get(0) + { + let redirect_stdin = call.get_named_arg("stdin"); + let login_shell = call.get_named_arg("login"); + let interactive_shell = call.get_named_arg("interactive"); + let commands: Option = call.get_flag_expr("commands"); + let testbin: Option = call.get_flag_expr("testbin"); + #[cfg(feature = "plugin")] + let plugin_file: Option = call.get_flag_expr("plugin-config"); + let config_file: Option = call.get_flag_expr("config"); + let env_file: Option = call.get_flag_expr("env-config"); + let log_level: Option = call.get_flag_expr("log-level"); + let log_target: Option = call.get_flag_expr("log-target"); + let execute: Option = call.get_flag_expr("execute"); + let threads: Option = call.get_flag(engine_state, &mut stack, "threads")?; + let table_mode: Option = + call.get_flag(engine_state, &mut stack, "table-mode")?; + + fn extract_contents( + expression: Option, + ) -> Result>, ShellError> { + if let Some(expr) = expression { + let str = expr.as_string(); + if let Some(str) = str { + Ok(Some(Spanned { + item: str, + span: expr.span, + })) + } else { + Err(ShellError::TypeMismatch("string".into(), expr.span)) + } + } else { + Ok(None) + } + } + + let commands = extract_contents(commands)?; + let testbin = extract_contents(testbin)?; + #[cfg(feature = "plugin")] + let plugin_file = extract_contents(plugin_file)?; + let config_file = extract_contents(config_file)?; + let env_file = extract_contents(env_file)?; + let log_level = extract_contents(log_level)?; + let log_target = extract_contents(log_target)?; + let execute = extract_contents(execute)?; + + let help = call.has_flag("help"); + + if help { + let full_help = get_full_help( + &Nu.signature(), + &Nu.examples(), + engine_state, + &mut stack, + true, + ); + + let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help)); + + std::process::exit(1); + } + + if call.has_flag("version") { + let version = env!("CARGO_PKG_VERSION").to_string(); + let _ = std::panic::catch_unwind(move || { + stdout_write_all_and_flush(format!("{}\n", version)) + }); + + std::process::exit(0); + } + + return Ok(NushellCliArgs { + redirect_stdin, + login_shell, + interactive_shell, + commands, + testbin, + #[cfg(feature = "plugin")] + plugin_file, + config_file, + env_file, + log_level, + log_target, + execute, + threads, + table_mode, + }); + } + } + + // Just give the help and exit if the above fails + let full_help = get_full_help( + &Nu.signature(), + &Nu.examples(), + engine_state, + &mut stack, + true, + ); + print!("{}", full_help); + std::process::exit(1); +} + +pub(crate) struct NushellCliArgs { + pub(crate) redirect_stdin: Option>, + pub(crate) login_shell: Option>, + pub(crate) interactive_shell: Option>, + pub(crate) commands: Option>, + pub(crate) testbin: Option>, + #[cfg(feature = "plugin")] + pub(crate) plugin_file: Option>, + pub(crate) config_file: Option>, + pub(crate) env_file: Option>, + pub(crate) log_level: Option>, + pub(crate) log_target: Option>, + pub(crate) execute: Option>, + pub(crate) threads: Option, + pub(crate) table_mode: Option, +} + +#[derive(Clone)] +struct Nu; + +impl Command for Nu { + fn name(&self) -> &str { + "nu" + } + + fn signature(&self) -> Signature { + let mut signature = Signature::build("nu") + .usage("The nushell language and shell.") + .named( + "commands", + SyntaxShape::String, + "run the given commands and then exit", + Some('c'), + ) + .named( + "execute", + SyntaxShape::String, + "run the given commands and then enter an interactive shell", + Some('e'), + ) + .switch("interactive", "start as an interactive shell", Some('i')) + .switch("login", "start as a login shell", Some('l')) + .named( + "table-mode", + SyntaxShape::String, + "the table mode to use. rounded is default.", + Some('m'), + ) + .named( + "threads", + SyntaxShape::Int, + "threads to use for parallel commands", + Some('t'), + ) + .switch("version", "print the version", Some('v')) + .named( + "config", + SyntaxShape::String, + "start with an alternate config file", + None, + ) + .named( + "env-config", + SyntaxShape::String, + "start with an alternate environment config file", + None, + ); + + #[cfg(feature = "plugin")] + { + signature = signature.named( + "plugin-config", + SyntaxShape::String, + "start with an alternate plugin signature file", + None, + ); + } + + signature = signature + .named( + "log-level", + SyntaxShape::String, + "log level for diagnostic logs (error, warn, info, debug, trace). Off by default", + None, + ) + .named( + "log-target", + SyntaxShape::String, + "set the target for the log to output. stdout, stderr(default), mixed or file", + None, + ) + .switch( + "stdin", + "redirect standard input to a command (with `-c`) or a script file", + None, + ) + .named( + "testbin", + SyntaxShape::String, + "run internal test binary", + None, + ) + .optional( + "script file", + SyntaxShape::Filepath, + "name of the optional script file to run", + ) + .rest( + "script args", + SyntaxShape::String, + "parameters to the script file", + ) + .category(Category::System); + + signature + } + + fn usage(&self) -> &str { + "The nushell language and shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true), + span: call.head, + } + .into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Run a script", + example: "nu myfile.nu", + result: None, + }, + Example { + description: "Run nushell interactively (as a shell or REPL)", + example: "nu", + result: None, + }, + ] + } +} diff --git a/src/config_files.rs b/src/config_files.rs index ae5768a1f..856011420 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -1,4 +1,6 @@ use log::info; +#[cfg(feature = "plugin")] +use nu_cli::read_plugin_file; use nu_cli::{eval_config_contents, eval_source, report_error}; use nu_parser::ParseError; use nu_path::canonicalize_with; @@ -7,6 +9,7 @@ use nu_protocol::{PipelineData, Spanned}; use nu_utils::{get_default_config, get_default_env}; use std::fs::File; use std::io::Write; +use std::path::Path; pub(crate) const NUSHELL_FOLDER: &str = "nushell"; const CONFIG_FILE: &str = "config.nu"; @@ -190,3 +193,53 @@ fn eval_default_config( } } } + +pub(crate) fn setup_config( + engine_state: &mut EngineState, + stack: &mut Stack, + #[cfg(feature = "plugin")] plugin_file: Option>, + config_file: Option>, + env_file: Option>, + is_login_shell: bool, +) { + #[cfg(feature = "plugin")] + read_plugin_file(engine_state, stack, plugin_file, NUSHELL_FOLDER); + + info!("read_config_file {}:{}:{}", file!(), line!(), column!()); + + read_config_file(engine_state, stack, env_file, true); + read_config_file(engine_state, stack, config_file, false); + + if is_login_shell { + read_loginshell_file(engine_state, stack); + } + + // Give a warning if we see `$config` for a few releases + { + let working_set = StateWorkingSet::new(engine_state); + if working_set.find_variable(b"$config").is_some() { + println!("warning: use `let-env config = ...` instead of `let config = ...`"); + } + } +} + +pub(crate) fn set_config_path( + engine_state: &mut EngineState, + cwd: &Path, + default_config_name: &str, + key: &str, + config_file: &Option>, +) { + let config_path = match config_file { + Some(s) => canonicalize_with(&s.item, cwd).ok(), + None => nu_path::config_dir().map(|mut p| { + p.push(NUSHELL_FOLDER); + p.push(default_config_name); + p + }), + }; + + if let Some(path) = config_path { + engine_state.set_config_path(key, path); + } +} diff --git a/src/main.rs b/src/main.rs index ebfe906a8..96f690462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,138 +1,37 @@ +mod command; mod config_files; mod logger; mod signals; +mod terminal; mod test_bins; #[cfg(test)] mod tests; #[cfg(feature = "plugin")] use crate::config_files::NUSHELL_FOLDER; -use crate::logger::{configure, logger}; +use crate::{ + command::parse_commandline_args, + config_files::{set_config_path, setup_config}, + logger::{configure, logger}, + terminal::acquire_terminal, +}; use log::{info, Level}; use miette::Result; #[cfg(feature = "plugin")] use nu_cli::read_plugin_file; use nu_cli::{ evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd, - report_error, report_error_new, + report_error_new, }; use nu_command::create_default_context; -use nu_engine::{get_full_help, CallExt}; -use nu_parser::{escape_for_script_arg, escape_quote_string, parse}; -use nu_path::canonicalize_with; -use nu_protocol::{ - ast::{Call, Expr, Expression, PipelineElement}, - engine::{Command, EngineState, Stack, StateWorkingSet}, - util::BufferedReader, - Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Spanned, - SyntaxShape, Value, -}; -use nu_utils::stdout_write_all_and_flush; +use nu_parser::{escape_for_script_arg, escape_quote_string}; +use nu_protocol::{util::BufferedReader, PipelineData, RawStream}; use signals::{ctrlc_protection, sigquit_protection}; +use std::str::FromStr; use std::{ io::BufReader, sync::{atomic::AtomicBool, Arc}, }; -use std::{path::Path, str::FromStr}; - -// Inspired by fish's acquire_tty_or_exit -#[cfg(unix)] -fn take_control(interactive: bool) { - use nix::{ - errno::Errno, - sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet, Signal}, - unistd::{self, Pid}, - }; - - let shell_pgid = unistd::getpgrp(); - - match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) { - Ok(owner_pgid) if owner_pgid == shell_pgid => { - // Common case, nothing to do - return; - } - Ok(owner_pgid) if owner_pgid == unistd::getpid() => { - // This can apparently happen with sudo: https://github.com/fish-shell/fish-shell/issues/7388 - let _ = unistd::setpgid(owner_pgid, owner_pgid); - return; - } - _ => (), - } - - // Reset all signal handlers to default - for sig in Signal::iterator() { - unsafe { - if let Ok(old_act) = signal::sigaction( - sig, - &SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()), - ) { - // fish preserves ignored SIGHUP, presumably for nohup support, so let's do the same - if sig == Signal::SIGHUP && old_act.handler() == SigHandler::SigIgn { - let _ = signal::sigaction(sig, &old_act); - } - } - } - } - - let mut success = false; - for _ in 0..4096 { - match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) { - Ok(owner_pgid) if owner_pgid == shell_pgid => { - success = true; - break; - } - Ok(owner_pgid) if owner_pgid == Pid::from_raw(0) => { - // Zero basically means something like "not owned" and we can just take it - let _ = unistd::tcsetpgrp(nix::libc::STDIN_FILENO, shell_pgid); - } - Err(Errno::ENOTTY) => { - if !interactive { - // that's fine - return; - } - eprintln!("ERROR: no TTY for interactive shell"); - std::process::exit(1); - } - _ => { - // fish also has other heuristics than "too many attempts" for the orphan check, but they're optional - if signal::killpg(Pid::from_raw(-shell_pgid.as_raw()), Signal::SIGTTIN).is_err() { - if !interactive { - // that's fine - return; - } - eprintln!("ERROR: failed to SIGTTIN ourselves"); - std::process::exit(1); - } - } - } - } - if !success && interactive { - eprintln!("ERROR: failed take control of the terminal, we might be orphaned"); - std::process::exit(1); - } -} - -#[cfg(unix)] -fn acquire_terminal(interactive: bool) { - use nix::sys::signal::{signal, SigHandler, Signal}; - - if !atty::is(atty::Stream::Stdin) { - return; - } - - take_control(interactive); - - unsafe { - // SIGINT and SIGQUIT have special handling above - signal(Signal::SIGTSTP, SigHandler::SigIgn).expect("signal ignore"); - signal(Signal::SIGTTIN, SigHandler::SigIgn).expect("signal ignore"); - signal(Signal::SIGTTOU, SigHandler::SigIgn).expect("signal ignore"); - // signal::signal(Signal::SIGCHLD, SigHandler::SigIgn).expect("signal ignore"); // needed for std::command's waitpid usage - } -} - -#[cfg(not(unix))] -fn acquire_terminal(_: bool) {} fn main() -> Result<()> { let miette_hook = std::panic::take_hook(); @@ -423,343 +322,3 @@ fn main() -> Result<()> { ret_val } } - -fn setup_config( - engine_state: &mut EngineState, - stack: &mut Stack, - #[cfg(feature = "plugin")] plugin_file: Option>, - config_file: Option>, - env_file: Option>, - is_login_shell: bool, -) { - #[cfg(feature = "plugin")] - read_plugin_file(engine_state, stack, plugin_file, NUSHELL_FOLDER); - - info!("read_config_file {}:{}:{}", file!(), line!(), column!()); - - config_files::read_config_file(engine_state, stack, env_file, true); - config_files::read_config_file(engine_state, stack, config_file, false); - - if is_login_shell { - config_files::read_loginshell_file(engine_state, stack); - } - - // Give a warning if we see `$config` for a few releases - { - let working_set = StateWorkingSet::new(engine_state); - if working_set.find_variable(b"$config").is_some() { - println!("warning: use `let-env config = ...` instead of `let config = ...`"); - } - } -} - -fn parse_commandline_args( - commandline_args: &str, - engine_state: &mut EngineState, -) -> Result { - let (block, delta) = { - let mut working_set = StateWorkingSet::new(engine_state); - working_set.add_decl(Box::new(Nu)); - - let (output, err) = parse( - &mut working_set, - None, - commandline_args.as_bytes(), - false, - &[], - ); - if let Some(err) = err { - report_error(&working_set, &err); - - std::process::exit(1); - } - - working_set.hide_decl(b"nu"); - (output, working_set.render()) - }; - - engine_state.merge_delta(delta)?; - - let mut stack = Stack::new(); - - // We should have a successful parse now - if let Some(pipeline) = block.pipelines.get(0) { - if let Some(PipelineElement::Expression( - _, - Expression { - expr: Expr::Call(call), - .. - }, - )) = pipeline.elements.get(0) - { - let redirect_stdin = call.get_named_arg("stdin"); - let login_shell = call.get_named_arg("login"); - let interactive_shell = call.get_named_arg("interactive"); - let commands: Option = call.get_flag_expr("commands"); - let testbin: Option = call.get_flag_expr("testbin"); - #[cfg(feature = "plugin")] - let plugin_file: Option = call.get_flag_expr("plugin-config"); - let config_file: Option = call.get_flag_expr("config"); - let env_file: Option = call.get_flag_expr("env-config"); - let log_level: Option = call.get_flag_expr("log-level"); - let log_target: Option = call.get_flag_expr("log-target"); - let execute: Option = call.get_flag_expr("execute"); - let threads: Option = call.get_flag(engine_state, &mut stack, "threads")?; - let table_mode: Option = - call.get_flag(engine_state, &mut stack, "table-mode")?; - - fn extract_contents( - expression: Option, - ) -> Result>, ShellError> { - if let Some(expr) = expression { - let str = expr.as_string(); - if let Some(str) = str { - Ok(Some(Spanned { - item: str, - span: expr.span, - })) - } else { - Err(ShellError::TypeMismatch("string".into(), expr.span)) - } - } else { - Ok(None) - } - } - - let commands = extract_contents(commands)?; - let testbin = extract_contents(testbin)?; - #[cfg(feature = "plugin")] - let plugin_file = extract_contents(plugin_file)?; - let config_file = extract_contents(config_file)?; - let env_file = extract_contents(env_file)?; - let log_level = extract_contents(log_level)?; - let log_target = extract_contents(log_target)?; - let execute = extract_contents(execute)?; - - let help = call.has_flag("help"); - - if help { - let full_help = get_full_help( - &Nu.signature(), - &Nu.examples(), - engine_state, - &mut stack, - true, - ); - - let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help)); - - std::process::exit(1); - } - - if call.has_flag("version") { - let version = env!("CARGO_PKG_VERSION").to_string(); - let _ = std::panic::catch_unwind(move || { - stdout_write_all_and_flush(format!("{}\n", version)) - }); - - std::process::exit(0); - } - - return Ok(NushellCliArgs { - redirect_stdin, - login_shell, - interactive_shell, - commands, - testbin, - #[cfg(feature = "plugin")] - plugin_file, - config_file, - env_file, - log_level, - log_target, - execute, - threads, - table_mode, - }); - } - } - - // Just give the help and exit if the above fails - let full_help = get_full_help( - &Nu.signature(), - &Nu.examples(), - engine_state, - &mut stack, - true, - ); - print!("{}", full_help); - std::process::exit(1); -} - -struct NushellCliArgs { - redirect_stdin: Option>, - login_shell: Option>, - interactive_shell: Option>, - commands: Option>, - testbin: Option>, - #[cfg(feature = "plugin")] - plugin_file: Option>, - config_file: Option>, - env_file: Option>, - log_level: Option>, - log_target: Option>, - execute: Option>, - threads: Option, - table_mode: Option, -} - -#[derive(Clone)] -struct Nu; - -impl Command for Nu { - fn name(&self) -> &str { - "nu" - } - - fn signature(&self) -> Signature { - let mut signature = Signature::build("nu") - .usage("The nushell language and shell.") - .named( - "commands", - SyntaxShape::String, - "run the given commands and then exit", - Some('c'), - ) - .named( - "execute", - SyntaxShape::String, - "run the given commands and then enter an interactive shell", - Some('e'), - ) - .switch("interactive", "start as an interactive shell", Some('i')) - .switch("login", "start as a login shell", Some('l')) - .named( - "table-mode", - SyntaxShape::String, - "the table mode to use. rounded is default.", - Some('m'), - ) - .named( - "threads", - SyntaxShape::Int, - "threads to use for parallel commands", - Some('t'), - ) - .switch("version", "print the version", Some('v')) - .named( - "config", - SyntaxShape::String, - "start with an alternate config file", - None, - ) - .named( - "env-config", - SyntaxShape::String, - "start with an alternate environment config file", - None, - ); - - #[cfg(feature = "plugin")] - { - signature = signature.named( - "plugin-config", - SyntaxShape::String, - "start with an alternate plugin signature file", - None, - ); - } - - signature = signature - .named( - "log-level", - SyntaxShape::String, - "log level for diagnostic logs (error, warn, info, debug, trace). Off by default", - None, - ) - .named( - "log-target", - SyntaxShape::String, - "set the target for the log to output. stdout, stderr(default), mixed or file", - None, - ) - .switch( - "stdin", - "redirect standard input to a command (with `-c`) or a script file", - None, - ) - .named( - "testbin", - SyntaxShape::String, - "run internal test binary", - None, - ) - .optional( - "script file", - SyntaxShape::Filepath, - "name of the optional script file to run", - ) - .rest( - "script args", - SyntaxShape::String, - "parameters to the script file", - ) - .category(Category::System); - - signature - } - - fn usage(&self) -> &str { - "The nushell language and shell." - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - _input: PipelineData, - ) -> Result { - Ok(Value::String { - val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true), - span: call.head, - } - .into_pipeline_data()) - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "Run a script", - example: "nu myfile.nu", - result: None, - }, - Example { - description: "Run nushell interactively (as a shell or REPL)", - example: "nu", - result: None, - }, - ] - } -} - -fn set_config_path( - engine_state: &mut EngineState, - cwd: &Path, - default_config_name: &str, - key: &str, - config_file: &Option>, -) { - let config_path = match config_file { - Some(s) => canonicalize_with(&s.item, cwd).ok(), - None => nu_path::config_dir().map(|mut p| { - p.push(config_files::NUSHELL_FOLDER); - p.push(default_config_name); - p - }), - }; - - if let Some(path) = config_path { - engine_state.set_config_path(key, path); - } -} diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 000000000..61bfe9d8d --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,97 @@ +#[cfg(unix)] +pub(crate) fn acquire_terminal(interactive: bool) { + use nix::sys::signal::{signal, SigHandler, Signal}; + + if !atty::is(atty::Stream::Stdin) { + return; + } + + take_control(interactive); + + unsafe { + // SIGINT and SIGQUIT have special handling above + signal(Signal::SIGTSTP, SigHandler::SigIgn).expect("signal ignore"); + signal(Signal::SIGTTIN, SigHandler::SigIgn).expect("signal ignore"); + signal(Signal::SIGTTOU, SigHandler::SigIgn).expect("signal ignore"); + } +} + +#[cfg(not(unix))] +pub(crate) fn acquire_terminal(_: bool) {} + +// Inspired by fish's acquire_tty_or_exit +#[cfg(unix)] +fn take_control(interactive: bool) { + use nix::{ + errno::Errno, + sys::signal::{self, SaFlags, SigAction, SigHandler, SigSet, Signal}, + unistd::{self, Pid}, + }; + + let shell_pgid = unistd::getpgrp(); + + match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) { + Ok(owner_pgid) if owner_pgid == shell_pgid => { + // Common case, nothing to do + return; + } + Ok(owner_pgid) if owner_pgid == unistd::getpid() => { + // This can apparently happen with sudo: https://github.com/fish-shell/fish-shell/issues/7388 + let _ = unistd::setpgid(owner_pgid, owner_pgid); + return; + } + _ => (), + } + + // Reset all signal handlers to default + for sig in Signal::iterator() { + unsafe { + if let Ok(old_act) = signal::sigaction( + sig, + &SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()), + ) { + // fish preserves ignored SIGHUP, presumably for nohup support, so let's do the same + if sig == Signal::SIGHUP && old_act.handler() == SigHandler::SigIgn { + let _ = signal::sigaction(sig, &old_act); + } + } + } + } + + let mut success = false; + for _ in 0..4096 { + match unistd::tcgetpgrp(nix::libc::STDIN_FILENO) { + Ok(owner_pgid) if owner_pgid == shell_pgid => { + success = true; + break; + } + Ok(owner_pgid) if owner_pgid == Pid::from_raw(0) => { + // Zero basically means something like "not owned" and we can just take it + let _ = unistd::tcsetpgrp(nix::libc::STDIN_FILENO, shell_pgid); + } + Err(Errno::ENOTTY) => { + if !interactive { + // that's fine + return; + } + eprintln!("ERROR: no TTY for interactive shell"); + std::process::exit(1); + } + _ => { + // fish also has other heuristics than "too many attempts" for the orphan check, but they're optional + if signal::killpg(Pid::from_raw(-shell_pgid.as_raw()), Signal::SIGTTIN).is_err() { + if !interactive { + // that's fine + return; + } + eprintln!("ERROR: failed to SIGTTIN ourselves"); + std::process::exit(1); + } + } + } + } + if !success && interactive { + eprintln!("ERROR: failed take control of the terminal, we might be orphaned"); + std::process::exit(1); + } +}