From 720690031353ab25b2a64cba2657f9b6c5ef9fe3 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sun, 2 Aug 2026 15:42:16 +0800 Subject: [PATCH] fix(agent): recover native executable permissions --- crates/dbx-core/src/db/agent_driver.rs | 128 ++++++++++++++++++++----- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/crates/dbx-core/src/db/agent_driver.rs b/crates/dbx-core/src/db/agent_driver.rs index 0ee71a7ee..ea46f777b 100644 --- a/crates/dbx-core/src/db/agent_driver.rs +++ b/crates/dbx-core/src/db/agent_driver.rs @@ -35,15 +35,7 @@ pub struct AgentRuntimeClient { impl AgentRuntimeClient { pub async fn spawn(launch: AgentLaunchSpec, app_version: &str) -> Result, String> { - let mut command = crate::process::new_std_command(&launch.program); - command.args(&launch.args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); - if let Some(working_dir) = &launch.working_dir { - command.current_dir(working_dir); - } - remove_agent_proxy_env(&mut command); - - let mut child = - command.spawn().map_err(|e| format!("Failed to spawn agent process {}: {e}", launch_display(&launch)))?; + let mut child = spawn_agent_process(&launch)?; let child_stdin = child.stdin.take().ok_or("Failed to capture agent stdin")?; let child_stdout = child.stdout.take().ok_or("Failed to capture agent stdout")?; let child_stderr = child.stderr.take().ok_or("Failed to capture agent stderr")?; @@ -937,15 +929,7 @@ impl AgentDriverClient { /// they speak the DBX stdin/stdout JSON-RPC protocol. /// Blocks (async) until the agent writes `{"ready":true}` to stdout. pub async fn spawn(launch: AgentLaunchSpec) -> Result { - let mut command = crate::process::new_std_command(&launch.program); - command.args(&launch.args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); - if let Some(working_dir) = &launch.working_dir { - command.current_dir(working_dir); - } - remove_agent_proxy_env(&mut command); - - let mut child = - command.spawn().map_err(|e| format!("Failed to spawn agent process {}: {e}", launch_display(&launch)))?; + let mut child = spawn_agent_process(&launch)?; let child_stdin = child.stdin.take().ok_or("Failed to capture agent stdin")?; let child_stdout = child.stdout.take().ok_or("Failed to capture agent stdout")?; @@ -2136,6 +2120,68 @@ fn launch_display(launch: &AgentLaunchSpec) -> String { parts.join(" ") } +fn spawn_agent_process(launch: &AgentLaunchSpec) -> Result { + match agent_command(launch).spawn() { + Ok(child) => Ok(child), + Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => { + let repaired = repair_native_agent_execute_permission(&launch.program).map_err(|repair_error| { + format!( + "Failed to spawn agent process {}: {error}; failed to restore executable permission: {repair_error}", + launch_display(launch) + ) + })?; + if !repaired { + return Err(format!("Failed to spawn agent process {}: {error}", launch_display(launch))); + } + agent_command(launch).spawn().map_err(|retry_error| { + format!("Failed to spawn agent process {}: {retry_error}", launch_display(launch)) + }) + } + Err(error) => Err(format!("Failed to spawn agent process {}: {error}", launch_display(launch))), + } +} + +fn agent_command(launch: &AgentLaunchSpec) -> Command { + let mut command = crate::process::new_std_command(&launch.program); + command.args(&launch.args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); + if let Some(working_dir) = &launch.working_dir { + command.current_dir(working_dir); + } + remove_agent_proxy_env(&mut command); + command +} + +fn repair_native_agent_execute_permission(program: &Path) -> Result { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + if !program.is_absolute() || program.file_name().and_then(|name| name.to_str()) != Some("agent") { + return Ok(false); + } + let metadata = program + .metadata() + .map_err(|error| format!("Failed to inspect native agent {}: {error}", program.display()))?; + if !metadata.is_file() { + return Ok(false); + } + let mut permissions = metadata.permissions(); + let mode = permissions.mode(); + if mode & 0o100 != 0 { + return Ok(false); + } + permissions.set_mode(mode | 0o100); + std::fs::set_permissions(program, permissions) + .map_err(|error| format!("Failed to make native agent executable {}: {error}", program.display()))?; + Ok(true) + } + #[cfg(not(unix))] + { + let _ = program; + Ok(false) + } +} + fn remove_agent_proxy_env(command: &mut Command) { for key in agent_proxy_env_vars() { command.env_remove(key); @@ -2308,10 +2354,10 @@ mod tests { agent_supports_capability, agent_transaction_params, decode_agent_response, format_agent_process_error, format_agent_startup_error, is_agent_rpc_response_error, is_unsupported_handshake_error, mongo_collection_params, mongo_database_params, mongo_document_id_params, parse_agent_java_opts, - read_agent_line, start_stderr_collector, validate_dameng_java_system_properties, AgentCapability, - AgentDriverClient, AgentHandshake, AgentKvMethod, AgentLaunchSpec, AgentMethod, AgentRuntimeClient, - AgentSessionDisposition, AgentTableReadCloseParams, AgentTableReadPageParams, AgentTableReadStartParams, - MongoAgentMethod, StderrTail, AGENT_PROTOCOL_VERSION, + read_agent_line, spawn_agent_process, start_stderr_collector, validate_dameng_java_system_properties, + AgentCapability, AgentDriverClient, AgentHandshake, AgentKvMethod, AgentLaunchSpec, AgentMethod, + AgentRuntimeClient, AgentSessionDisposition, AgentTableReadCloseParams, AgentTableReadPageParams, + AgentTableReadStartParams, MongoAgentMethod, StderrTail, AGENT_PROTOCOL_VERSION, }; use std::io::Cursor; use std::io::Write; @@ -2320,6 +2366,44 @@ mod tests { use std::time::{Duration, Instant}; use tokio_util::sync::CancellationToken; + #[cfg(unix)] + #[test] + fn spawn_repairs_missing_native_execute_permission_after_eacces() { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let dir = std::env::temp_dir().join(format!("dbx-agent-permission-{}", uuid::Uuid::new_v4())); + fs::create_dir_all(&dir).unwrap(); + let program = dir.join("agent"); + fs::write(&program, b"#!/bin/sh\nexit 0\n").unwrap(); + fs::set_permissions(&program, fs::Permissions::from_mode(0o600)).unwrap(); + + let mut child = spawn_agent_process(&AgentLaunchSpec::new(&program)).unwrap(); + assert!(child.wait().unwrap().success()); + assert_ne!(fs::metadata(&program).unwrap().permissions().mode() & 0o100, 0); + + fs::remove_dir_all(dir).unwrap(); + } + + #[cfg(unix)] + #[test] + fn spawn_keeps_existing_native_execute_permissions() { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let dir = std::env::temp_dir().join(format!("dbx-agent-permission-{}", uuid::Uuid::new_v4())); + fs::create_dir_all(&dir).unwrap(); + let program = dir.join("agent"); + fs::write(&program, b"#!/bin/sh\nexit 0\n").unwrap(); + fs::set_permissions(&program, fs::Permissions::from_mode(0o700)).unwrap(); + + let mut child = spawn_agent_process(&AgentLaunchSpec::new(&program)).unwrap(); + assert!(child.wait().unwrap().success()); + assert_eq!(fs::metadata(&program).unwrap().permissions().mode() & 0o777, 0o700); + + fs::remove_dir_all(dir).unwrap(); + } + #[test] fn structured_agent_error_data_survives_legacy_string_boundary() { let response = serde_json::json!({