Move all functions of main.rs into modules (#7803)

The affected modules are:
- `command.rs`
- `config_files.rs`
- `terminal.rs`
This commit is contained in:
Hofer-Julian 2023-01-20 22:20:38 +01:00 committed by GitHub
parent 24aa1f312a
commit 9e4a2ab824
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 462 additions and 453 deletions

300
src/command.rs Normal file
View file

@ -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<NushellCliArgs, ShellError> {
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<Expression> = call.get_flag_expr("commands");
let testbin: Option<Expression> = call.get_flag_expr("testbin");
#[cfg(feature = "plugin")]
let plugin_file: Option<Expression> = call.get_flag_expr("plugin-config");
let config_file: Option<Expression> = call.get_flag_expr("config");
let env_file: Option<Expression> = call.get_flag_expr("env-config");
let log_level: Option<Expression> = call.get_flag_expr("log-level");
let log_target: Option<Expression> = call.get_flag_expr("log-target");
let execute: Option<Expression> = call.get_flag_expr("execute");
let threads: Option<Value> = call.get_flag(engine_state, &mut stack, "threads")?;
let table_mode: Option<Value> =
call.get_flag(engine_state, &mut stack, "table-mode")?;
fn extract_contents(
expression: Option<Expression>,
) -> Result<Option<Spanned<String>>, 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<Spanned<String>>,
pub(crate) login_shell: Option<Spanned<String>>,
pub(crate) interactive_shell: Option<Spanned<String>>,
pub(crate) commands: Option<Spanned<String>>,
pub(crate) testbin: Option<Spanned<String>>,
#[cfg(feature = "plugin")]
pub(crate) plugin_file: Option<Spanned<String>>,
pub(crate) config_file: Option<Spanned<String>>,
pub(crate) env_file: Option<Spanned<String>>,
pub(crate) log_level: Option<Spanned<String>>,
pub(crate) log_target: Option<Spanned<String>>,
pub(crate) execute: Option<Spanned<String>>,
pub(crate) threads: Option<Value>,
pub(crate) table_mode: Option<Value>,
}
#[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<nu_protocol::PipelineData, nu_protocol::ShellError> {
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<nu_protocol::Example> {
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,
},
]
}
}

View file

@ -1,4 +1,6 @@
use log::info; use log::info;
#[cfg(feature = "plugin")]
use nu_cli::read_plugin_file;
use nu_cli::{eval_config_contents, eval_source, report_error}; use nu_cli::{eval_config_contents, eval_source, report_error};
use nu_parser::ParseError; use nu_parser::ParseError;
use nu_path::canonicalize_with; use nu_path::canonicalize_with;
@ -7,6 +9,7 @@ use nu_protocol::{PipelineData, Spanned};
use nu_utils::{get_default_config, get_default_env}; use nu_utils::{get_default_config, get_default_env};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path;
pub(crate) const NUSHELL_FOLDER: &str = "nushell"; pub(crate) const NUSHELL_FOLDER: &str = "nushell";
const CONFIG_FILE: &str = "config.nu"; 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<Spanned<String>>,
config_file: Option<Spanned<String>>,
env_file: Option<Spanned<String>>,
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<Spanned<String>>,
) {
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);
}
}

View file

@ -1,138 +1,37 @@
mod command;
mod config_files; mod config_files;
mod logger; mod logger;
mod signals; mod signals;
mod terminal;
mod test_bins; mod test_bins;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use crate::config_files::NUSHELL_FOLDER; 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 log::{info, Level};
use miette::Result; use miette::Result;
#[cfg(feature = "plugin")] #[cfg(feature = "plugin")]
use nu_cli::read_plugin_file; use nu_cli::read_plugin_file;
use nu_cli::{ use nu_cli::{
evaluate_commands, evaluate_file, evaluate_repl, gather_parent_env_vars, get_init_cwd, 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_command::create_default_context;
use nu_engine::{get_full_help, CallExt}; use nu_parser::{escape_for_script_arg, escape_quote_string};
use nu_parser::{escape_for_script_arg, escape_quote_string, parse}; use nu_protocol::{util::BufferedReader, PipelineData, RawStream};
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 signals::{ctrlc_protection, sigquit_protection}; use signals::{ctrlc_protection, sigquit_protection};
use std::str::FromStr;
use std::{ use std::{
io::BufReader, io::BufReader,
sync::{atomic::AtomicBool, Arc}, 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<()> { fn main() -> Result<()> {
let miette_hook = std::panic::take_hook(); let miette_hook = std::panic::take_hook();
@ -423,343 +322,3 @@ fn main() -> Result<()> {
ret_val ret_val
} }
} }
fn setup_config(
engine_state: &mut EngineState,
stack: &mut Stack,
#[cfg(feature = "plugin")] plugin_file: Option<Spanned<String>>,
config_file: Option<Spanned<String>>,
env_file: Option<Spanned<String>>,
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<NushellCliArgs, ShellError> {
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<Expression> = call.get_flag_expr("commands");
let testbin: Option<Expression> = call.get_flag_expr("testbin");
#[cfg(feature = "plugin")]
let plugin_file: Option<Expression> = call.get_flag_expr("plugin-config");
let config_file: Option<Expression> = call.get_flag_expr("config");
let env_file: Option<Expression> = call.get_flag_expr("env-config");
let log_level: Option<Expression> = call.get_flag_expr("log-level");
let log_target: Option<Expression> = call.get_flag_expr("log-target");
let execute: Option<Expression> = call.get_flag_expr("execute");
let threads: Option<Value> = call.get_flag(engine_state, &mut stack, "threads")?;
let table_mode: Option<Value> =
call.get_flag(engine_state, &mut stack, "table-mode")?;
fn extract_contents(
expression: Option<Expression>,
) -> Result<Option<Spanned<String>>, 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<Spanned<String>>,
login_shell: Option<Spanned<String>>,
interactive_shell: Option<Spanned<String>>,
commands: Option<Spanned<String>>,
testbin: Option<Spanned<String>>,
#[cfg(feature = "plugin")]
plugin_file: Option<Spanned<String>>,
config_file: Option<Spanned<String>>,
env_file: Option<Spanned<String>>,
log_level: Option<Spanned<String>>,
log_target: Option<Spanned<String>>,
execute: Option<Spanned<String>>,
threads: Option<Value>,
table_mode: Option<Value>,
}
#[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<nu_protocol::PipelineData, nu_protocol::ShellError> {
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<nu_protocol::Example> {
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<Spanned<String>>,
) {
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);
}
}

97
src/terminal.rs Normal file
View file

@ -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);
}
}