mirror of
https://github.com/openai/codex.git
synced 2026-02-04 07:53:43 +00:00
Compare commits
7 Commits
remove/doc
...
damn-tty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75a7fbed70 | ||
|
|
63e0e6025d | ||
|
|
297c9a6a6a | ||
|
|
ca61481f9b | ||
|
|
68b1e1a93d | ||
|
|
dec738534d | ||
|
|
0226fa6df9 |
@@ -512,17 +512,22 @@ pub struct ConfigEdit {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum CommandExecutionApprovalDecision {
|
||||
/// User approved the command.
|
||||
#[serde(alias = "approved")]
|
||||
Accept,
|
||||
/// User approved the command and future identical commands should run without prompting.
|
||||
#[serde(alias = "approved_for_session")]
|
||||
AcceptForSession,
|
||||
/// User approved the command, and wants to apply the proposed execpolicy amendment so future
|
||||
/// matching commands can run without prompting.
|
||||
#[serde(alias = "approved_with_amendment")]
|
||||
AcceptWithExecpolicyAmendment {
|
||||
execpolicy_amendment: ExecPolicyAmendment,
|
||||
},
|
||||
/// User denied the command. The agent will continue the turn.
|
||||
#[serde(alias = "denied")]
|
||||
Decline,
|
||||
/// User denied the command. The turn will also be immediately interrupted.
|
||||
#[serde(alias = "abort")]
|
||||
Cancel,
|
||||
}
|
||||
|
||||
@@ -531,12 +536,16 @@ pub enum CommandExecutionApprovalDecision {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum FileChangeApprovalDecision {
|
||||
/// User approved the file changes.
|
||||
#[serde(alias = "approved")]
|
||||
Accept,
|
||||
/// User approved the file changes and future changes to the same files should run without prompting.
|
||||
#[serde(alias = "approved_for_session")]
|
||||
AcceptForSession,
|
||||
/// User denied the file changes. The agent will continue the turn.
|
||||
#[serde(alias = "denied")]
|
||||
Decline,
|
||||
/// User denied the file changes. The turn will also be immediately interrupted.
|
||||
#[serde(alias = "abort")]
|
||||
Cancel,
|
||||
}
|
||||
|
||||
|
||||
@@ -1215,6 +1215,7 @@ impl CodexMessageProcessor {
|
||||
let timeout_ms = params
|
||||
.timeout_ms
|
||||
.and_then(|timeout_ms| u64::try_from(timeout_ms).ok());
|
||||
let detach_from_tty = self.config.features.enabled(Feature::DetachNonTty);
|
||||
let exec_params = ExecParams {
|
||||
command: params.command,
|
||||
cwd,
|
||||
@@ -1223,6 +1224,7 @@ impl CodexMessageProcessor {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty,
|
||||
};
|
||||
|
||||
let requested_policy = params.sandbox_policy.map(|policy| policy.to_core());
|
||||
|
||||
@@ -75,9 +75,15 @@
|
||||
"apply_patch_freeform": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"detach_non_tty": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"elevated_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -99,9 +105,6 @@
|
||||
"experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -543,9 +546,15 @@
|
||||
"apply_patch_freeform": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"detach_non_tty": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"elevated_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -567,9 +576,6 @@
|
||||
"experimental_windows_sandbox": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"child_agents_md": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"include_apply_patch_tool": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -4325,6 +4325,7 @@ mod tests {
|
||||
sandbox_permissions,
|
||||
justification: Some("test".to_string()),
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let params2 = ExecParams {
|
||||
@@ -4335,6 +4336,7 @@ mod tests {
|
||||
env: HashMap::new(),
|
||||
justification: params.justification.clone(),
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
|
||||
|
||||
@@ -60,6 +60,7 @@ pub struct ExecParams {
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub justification: Option<String>,
|
||||
pub arg0: Option<String>,
|
||||
pub detach_from_tty: bool,
|
||||
}
|
||||
|
||||
/// Mechanism to terminate an exec invocation before it finishes naturally.
|
||||
@@ -151,6 +152,7 @@ pub async fn process_exec_tool_call(
|
||||
sandbox_permissions,
|
||||
justification,
|
||||
arg0: _,
|
||||
detach_from_tty,
|
||||
} = params;
|
||||
|
||||
let (program, args) = command.split_first().ok_or_else(|| {
|
||||
@@ -168,6 +170,7 @@ pub async fn process_exec_tool_call(
|
||||
expiration,
|
||||
sandbox_permissions,
|
||||
justification,
|
||||
detach_from_tty,
|
||||
};
|
||||
|
||||
let manager = SandboxManager::new();
|
||||
@@ -199,6 +202,7 @@ pub(crate) async fn execute_exec_env(
|
||||
sandbox_permissions,
|
||||
justification,
|
||||
arg0,
|
||||
detach_from_tty,
|
||||
} = env;
|
||||
|
||||
let params = ExecParams {
|
||||
@@ -209,6 +213,7 @@ pub(crate) async fn execute_exec_env(
|
||||
sandbox_permissions,
|
||||
justification,
|
||||
arg0,
|
||||
detach_from_tty,
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -539,6 +544,7 @@ async fn exec(
|
||||
env,
|
||||
arg0,
|
||||
expiration,
|
||||
detach_from_tty,
|
||||
..
|
||||
} = params;
|
||||
|
||||
@@ -549,6 +555,7 @@ async fn exec(
|
||||
))
|
||||
})?;
|
||||
let arg0_ref = arg0.as_deref();
|
||||
let detach_from_tty = detach_from_tty && matches!(sandbox, SandboxType::None);
|
||||
let child = spawn_child_async(
|
||||
PathBuf::from(program),
|
||||
args.into(),
|
||||
@@ -557,6 +564,7 @@ async fn exec(
|
||||
sandbox_policy,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
env,
|
||||
detach_from_tty,
|
||||
)
|
||||
.await?;
|
||||
consume_truncated_output(child, expiration, stdout_stream).await
|
||||
@@ -850,6 +858,7 @@ mod tests {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let output = exec(params, SandboxType::None, &SandboxPolicy::ReadOnly, None).await?;
|
||||
@@ -895,6 +904,7 @@ mod tests {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(1_000)).await;
|
||||
|
||||
@@ -88,6 +88,8 @@ pub enum Feature {
|
||||
RemoteModels,
|
||||
/// Experimental shell snapshotting.
|
||||
ShellSnapshot,
|
||||
/// Detach non-interactive child processes from the controlling TTY.
|
||||
DetachNonTty,
|
||||
/// Append additional AGENTS.md guidance to user instructions.
|
||||
ChildAgentsMd,
|
||||
/// Experimental TUI v2 (viewport) implementation.
|
||||
@@ -358,6 +360,16 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::DetachNonTty,
|
||||
key: "detach_non_tty",
|
||||
stage: Stage::Beta {
|
||||
name: "Detach non-PTY commands",
|
||||
menu_description: "Run non-interactive commands without inheriting the controlling TTY.",
|
||||
announcement: "NEW! Try detaching non-PTY commands to avoid TTY issues. Enable in /experimental!",
|
||||
},
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ChildAgentsMd,
|
||||
key: "child_agents_md",
|
||||
|
||||
@@ -35,6 +35,7 @@ where
|
||||
sandbox_policy,
|
||||
stdio_policy,
|
||||
env,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ pub struct CommandSpec {
|
||||
pub expiration: ExecExpiration,
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub justification: Option<String>,
|
||||
pub detach_from_tty: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -47,6 +48,7 @@ pub struct ExecEnv {
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub justification: Option<String>,
|
||||
pub arg0: Option<String>,
|
||||
pub detach_from_tty: bool,
|
||||
}
|
||||
|
||||
pub enum SandboxPreference {
|
||||
@@ -163,6 +165,7 @@ impl SandboxManager {
|
||||
sandbox_permissions: spec.sandbox_permissions,
|
||||
justification: spec.justification,
|
||||
arg0: arg0_override,
|
||||
detach_from_tty: spec.detach_from_tty,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ pub async fn spawn_command_under_seatbelt(
|
||||
sandbox_policy,
|
||||
stdio_policy,
|
||||
env,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ pub enum StdioPolicy {
|
||||
/// For now, we take `SandboxPolicy` as a parameter to spawn_child() because
|
||||
/// we need to determine whether to set the
|
||||
/// `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` environment variable.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn spawn_child_async(
|
||||
program: PathBuf,
|
||||
args: Vec<String>,
|
||||
@@ -43,6 +44,7 @@ pub(crate) async fn spawn_child_async(
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
stdio_policy: StdioPolicy,
|
||||
env: HashMap<String, String>,
|
||||
#[cfg_attr(not(unix), allow(unused_variables))] detach_from_tty: bool,
|
||||
) -> std::io::Result<Child> {
|
||||
trace!(
|
||||
"spawn_child_async: {program:?} {args:?} {arg0:?} {cwd:?} {sandbox_policy:?} {stdio_policy:?} {env:?}"
|
||||
@@ -66,12 +68,16 @@ pub(crate) async fn spawn_child_async(
|
||||
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
let set_process_group = matches!(stdio_policy, StdioPolicy::RedirectForShellTool);
|
||||
let isolate_process_group = matches!(stdio_policy, StdioPolicy::RedirectForShellTool);
|
||||
#[cfg(target_os = "linux")]
|
||||
let parent_pid = libc::getpid();
|
||||
cmd.pre_exec(move || {
|
||||
if set_process_group {
|
||||
codex_utils_pty::process_group::set_process_group()?;
|
||||
if isolate_process_group {
|
||||
if detach_from_tty {
|
||||
codex_utils_pty::process_group::detach_from_tty()?;
|
||||
} else {
|
||||
codex_utils_pty::process_group::set_process_group()?;
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on prctl(2), so it only works on Linux.
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::exec::StdoutStream;
|
||||
use crate::exec::StreamOutput;
|
||||
use crate::exec::execute_exec_env;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::features::Feature;
|
||||
use crate::parse_command::parse_command;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::ExecCommandBeginEvent;
|
||||
@@ -111,6 +112,7 @@ impl SessionTask for UserShellCommandTask {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: session.features().enabled(Feature::DetachNonTty),
|
||||
};
|
||||
|
||||
let stdout_stream = Some(StdoutStream {
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::exec::ExecParams;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::is_safe_command::is_known_safe_command;
|
||||
use crate::protocol::ExecCommandSource;
|
||||
@@ -29,7 +30,11 @@ pub struct ShellHandler;
|
||||
pub struct ShellCommandHandler;
|
||||
|
||||
impl ShellHandler {
|
||||
fn to_exec_params(params: ShellToolCallParams, turn_context: &TurnContext) -> ExecParams {
|
||||
fn to_exec_params(
|
||||
params: ShellToolCallParams,
|
||||
session: &crate::codex::Session,
|
||||
turn_context: &TurnContext,
|
||||
) -> ExecParams {
|
||||
ExecParams {
|
||||
command: params.command,
|
||||
cwd: turn_context.resolve_path(params.workdir.clone()),
|
||||
@@ -38,6 +43,7 @@ impl ShellHandler {
|
||||
sandbox_permissions: params.sandbox_permissions.unwrap_or_default(),
|
||||
justification: params.justification,
|
||||
arg0: None,
|
||||
detach_from_tty: session.features().enabled(Feature::DetachNonTty),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,6 +70,7 @@ impl ShellCommandHandler {
|
||||
sandbox_permissions: params.sandbox_permissions.unwrap_or_default(),
|
||||
justification: params.justification,
|
||||
arg0: None,
|
||||
detach_from_tty: session.features().enabled(Feature::DetachNonTty),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +113,7 @@ impl ToolHandler for ShellHandler {
|
||||
match payload {
|
||||
ToolPayload::Function { arguments } => {
|
||||
let params: ShellToolCallParams = parse_arguments(&arguments)?;
|
||||
let exec_params = Self::to_exec_params(params, turn.as_ref());
|
||||
let exec_params = Self::to_exec_params(params, session.as_ref(), turn.as_ref());
|
||||
Self::run_exec_like(
|
||||
tool_name.as_str(),
|
||||
exec_params,
|
||||
@@ -119,7 +126,7 @@ impl ToolHandler for ShellHandler {
|
||||
.await
|
||||
}
|
||||
ToolPayload::LocalShell { params } => {
|
||||
let exec_params = Self::to_exec_params(params, turn.as_ref());
|
||||
let exec_params = Self::to_exec_params(params, session.as_ref(), turn.as_ref());
|
||||
Self::run_exec_like(
|
||||
tool_name.as_str(),
|
||||
exec_params,
|
||||
@@ -297,6 +304,7 @@ mod tests {
|
||||
|
||||
use crate::codex::make_session_and_context;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::features::Feature;
|
||||
use crate::is_safe_command::is_known_safe_command;
|
||||
use crate::powershell::try_find_powershell_executable_blocking;
|
||||
use crate::powershell::try_find_pwsh_executable_blocking;
|
||||
@@ -387,6 +395,10 @@ mod tests {
|
||||
assert_eq!(exec_params.sandbox_permissions, sandbox_permissions);
|
||||
assert_eq!(exec_params.justification, justification);
|
||||
assert_eq!(exec_params.arg0, None);
|
||||
assert_eq!(
|
||||
exec_params.detach_from_tty,
|
||||
session.features().enabled(Feature::DetachNonTty)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -64,6 +64,7 @@ impl ApplyPatchRuntime {
|
||||
env: HashMap::new(),
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
detach_from_tty: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(crate) fn build_command_spec(
|
||||
expiration: ExecExpiration,
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
justification: Option<String>,
|
||||
detach_from_tty: bool,
|
||||
) -> Result<CommandSpec, ToolError> {
|
||||
let (program, args) = command
|
||||
.split_first()
|
||||
@@ -37,6 +38,7 @@ pub(crate) fn build_command_spec(
|
||||
expiration,
|
||||
sandbox_permissions,
|
||||
justification,
|
||||
detach_from_tty,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
|
||||
req.timeout_ms.into(),
|
||||
req.sandbox_permissions,
|
||||
req.justification.clone(),
|
||||
ctx.session.features().enabled(Feature::DetachNonTty),
|
||||
)?;
|
||||
let env = attempt
|
||||
.env_for(spec)
|
||||
|
||||
@@ -181,6 +181,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
command
|
||||
};
|
||||
|
||||
let detach_from_tty = ctx.session.features().enabled(Feature::DetachNonTty) && !req.tty;
|
||||
let spec = build_command_spec(
|
||||
&command,
|
||||
&req.cwd,
|
||||
@@ -188,6 +189,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
ExecExpiration::DefaultTimeout,
|
||||
req.sandbox_permissions,
|
||||
req.justification.clone(),
|
||||
detach_from_tty,
|
||||
)
|
||||
.map_err(|_| ToolError::Rejected("missing command line for PTY".to_string()))?;
|
||||
let exec_env = attempt
|
||||
|
||||
@@ -10,6 +10,7 @@ use tokio::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::exec::SandboxType;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::protocol::ExecCommandSource;
|
||||
use crate::sandboxing::ExecEnv;
|
||||
@@ -470,14 +471,26 @@ impl UnifiedExecProcessManager {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
env.cwd.as_path(),
|
||||
&env.env,
|
||||
&env.arg0,
|
||||
)
|
||||
.await
|
||||
let detach_from_tty = env.detach_from_tty && matches!(env.sandbox, SandboxType::None);
|
||||
if detach_from_tty {
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin_detached(
|
||||
program,
|
||||
args,
|
||||
env.cwd.as_path(),
|
||||
&env.env,
|
||||
&env.arg0,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
codex_utils_pty::pipe::spawn_process_no_stdin(
|
||||
program,
|
||||
args,
|
||||
env.cwd.as_path(),
|
||||
&env.env,
|
||||
&env.arg0,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
let spawned =
|
||||
spawn_result.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
|
||||
|
||||
@@ -38,6 +38,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
@@ -89,6 +89,7 @@ impl EscalateServer {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
},
|
||||
&sandbox_state.sandbox_policy,
|
||||
&sandbox_state.sandbox_cwd,
|
||||
|
||||
@@ -62,6 +62,7 @@ async fn run_cmd_output(
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
@@ -179,6 +180,7 @@ async fn assert_network_blocked(cmd: &[&str]) {
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
justification: None,
|
||||
arg0: None,
|
||||
detach_from_tty: false,
|
||||
};
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
@@ -103,6 +103,7 @@ async fn spawn_process_with_stdin_mode(
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
stdin_mode: PipeStdinMode,
|
||||
#[cfg_attr(not(unix), allow(unused_variables))] detach_from_tty: bool,
|
||||
) -> Result<SpawnedProcess> {
|
||||
if program.is_empty() {
|
||||
anyhow::bail!("missing program for pipe spawn");
|
||||
@@ -118,7 +119,11 @@ async fn spawn_process_with_stdin_mode(
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
command.pre_exec(move || {
|
||||
crate::process_group::set_process_group()?;
|
||||
if detach_from_tty {
|
||||
crate::process_group::detach_from_tty()?;
|
||||
} else {
|
||||
crate::process_group::set_process_group()?;
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::process_group::set_parent_death_signal(parent_pid)?;
|
||||
Ok(())
|
||||
@@ -253,7 +258,7 @@ pub async fn spawn_process(
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Piped).await
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Piped, false).await
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes, but close stdin immediately.
|
||||
@@ -264,5 +269,27 @@ pub async fn spawn_process_no_stdin(
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Null).await
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Null, false).await
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes, but detach it from the controlling TTY.
|
||||
pub async fn spawn_process_detached(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Piped, true).await
|
||||
}
|
||||
|
||||
/// Spawn a process using regular pipes, close stdin immediately, and detach it from the TTY.
|
||||
pub async fn spawn_process_no_stdin_detached(
|
||||
program: &str,
|
||||
args: &[String],
|
||||
cwd: &Path,
|
||||
env: &HashMap<String, String>,
|
||||
arg0: &Option<String>,
|
||||
) -> Result<SpawnedProcess> {
|
||||
spawn_process_with_stdin_mode(program, args, cwd, env, arg0, PipeStdinMode::Null, true).await
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//! command can be cleaned up reliably:
|
||||
//! - `set_process_group` is called in `pre_exec` so the child starts its own
|
||||
//! process group.
|
||||
//! - `detach_from_tty` starts a new session so non-interactive children do not
|
||||
//! inherit the controlling TTY.
|
||||
//! - `kill_process_group_by_pid` targets the whole group (children/grandchildren)
|
||||
//! - `kill_process_group` targets a known process group ID directly
|
||||
//! instead of a single PID.
|
||||
@@ -42,6 +44,26 @@ pub fn set_parent_death_signal(_parent_pid: i32) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
/// Detach from the controlling TTY by starting a new session.
|
||||
pub fn detach_from_tty() -> io::Result<()> {
|
||||
let result = unsafe { libc::setsid() };
|
||||
if result == -1 {
|
||||
let err = io::Error::last_os_error();
|
||||
if err.raw_os_error() == Some(libc::EPERM) {
|
||||
return set_process_group();
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
/// No-op on non-Unix platforms.
|
||||
pub fn detach_from_tty() -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
/// Put the calling process into its own process group.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user