fcb3f2d258
Project CI / AI game creator shell Rust crates (push) Successful in 1m14s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 5m28s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 4m44s
Project CI / Backend tests (push) Successful in 4m40s
Project CI / Frontend tests (push) Successful in 2m47s
Project CI / Repository checks (push) Successful in 3m22s
Project CI / AI game creator shell web tests (push) Successful in 2m25s
Project CI / Native shell tests (push) Successful in 6m44s
- process_identity:补 use std::io;,dev 构建(no-default-features + editor-execute)下 io::Error 解析不到,挡住本地开发栈启动
207 lines
6.9 KiB
Rust
207 lines
6.9 KiB
Rust
//! 跨平台进程身份与 Windows 文件句柄校验。
|
|
//!
|
|
//! 这两个原语原先寄居在外部 Agent Runner 模块里,但被 Codex app-server 进程树、
|
|
//! 浏览器试玩清理、命令执行与项目写入校验等现役路径共用,因此保留为独立模块。
|
|
|
|
use super::*;
|
|
use std::io;
|
|
|
|
pub(crate) fn external_agent_runner_process_start_identity(
|
|
pid: u32,
|
|
) -> Result<Option<String>, String> {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let stat = fs::read_to_string(format!("/proc/{pid}/stat"))
|
|
.map_err(|error| format!("读取 Agent Runner 进程启动身份失败:{error}"))?;
|
|
let tail = stat
|
|
.rsplit_once(") ")
|
|
.map(|(_, tail)| tail)
|
|
.ok_or_else(|| "解析 Agent Runner 进程启动身份失败".to_string())?;
|
|
let start_time = tail
|
|
.split_whitespace()
|
|
.nth(19)
|
|
.filter(|value| !value.is_empty())
|
|
.ok_or_else(|| "Agent Runner 进程启动身份缺失".to_string())?;
|
|
return Ok(Some(start_time.to_string()));
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
use std::ffi::c_void;
|
|
|
|
#[repr(C)]
|
|
struct FileTime {
|
|
low_date_time: u32,
|
|
high_date_time: u32,
|
|
}
|
|
|
|
#[link(name = "kernel32")]
|
|
unsafe extern "system" {
|
|
fn OpenProcess(access: u32, inherit_handle: i32, process_id: u32) -> *mut c_void;
|
|
fn GetProcessTimes(
|
|
process: *mut c_void,
|
|
creation_time: *mut FileTime,
|
|
exit_time: *mut FileTime,
|
|
kernel_time: *mut FileTime,
|
|
user_time: *mut FileTime,
|
|
) -> i32;
|
|
fn CloseHandle(handle: *mut c_void) -> i32;
|
|
}
|
|
|
|
const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
|
|
// SAFETY: OpenProcess returns an owned kernel handle or null; it is closed below.
|
|
let process = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
|
|
if process.is_null() {
|
|
return Err(format!(
|
|
"打开 Agent Runner 进程启动身份失败:{}",
|
|
io::Error::last_os_error()
|
|
));
|
|
}
|
|
// SAFETY: FileTime is plain data filled by GetProcessTimes.
|
|
let mut creation = unsafe { std::mem::zeroed::<FileTime>() };
|
|
let mut exit = unsafe { std::mem::zeroed::<FileTime>() };
|
|
let mut kernel = unsafe { std::mem::zeroed::<FileTime>() };
|
|
let mut user = unsafe { std::mem::zeroed::<FileTime>() };
|
|
// SAFETY: process is live and all output pointers refer to writable FileTime values.
|
|
let result =
|
|
unsafe { GetProcessTimes(process, &mut creation, &mut exit, &mut kernel, &mut user) };
|
|
// SAFETY: process is an owned non-null handle returned by OpenProcess.
|
|
unsafe { CloseHandle(process) };
|
|
if result == 0 {
|
|
return Err(format!(
|
|
"读取 Agent Runner 进程启动身份失败:{}",
|
|
io::Error::last_os_error()
|
|
));
|
|
}
|
|
return Ok(Some(
|
|
((creation.high_date_time as u64) << 32 | creation.low_date_time as u64).to_string(),
|
|
));
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
#[repr(C)]
|
|
struct ProcBsdInfo {
|
|
pbi_flags: u32,
|
|
pbi_status: u32,
|
|
pbi_xstatus: u32,
|
|
pbi_pid: u32,
|
|
pbi_ppid: u32,
|
|
pbi_uid: u32,
|
|
pbi_gid: u32,
|
|
pbi_ruid: u32,
|
|
pbi_rgid: u32,
|
|
pbi_svuid: u32,
|
|
pbi_svgid: u32,
|
|
rfu_1: u32,
|
|
pbi_comm: [u8; 16],
|
|
pbi_name: [u8; 32],
|
|
pbi_nfiles: u32,
|
|
pbi_pgid: u32,
|
|
pbi_pjobc: u32,
|
|
e_tdev: u32,
|
|
e_tpgid: u32,
|
|
pbi_nice: i32,
|
|
pbi_start_tvsec: u64,
|
|
pbi_start_tvusec: u64,
|
|
}
|
|
|
|
#[link(name = "proc")]
|
|
unsafe extern "C" {
|
|
fn proc_pidinfo(
|
|
pid: i32,
|
|
flavor: i32,
|
|
arg: u64,
|
|
buffer: *mut std::ffi::c_void,
|
|
buffer_size: i32,
|
|
) -> i32;
|
|
}
|
|
|
|
const PROC_PIDTBSDINFO: i32 = 3;
|
|
let pid = i32::try_from(pid)
|
|
.map_err(|_| "Agent Runner pid 超出 macOS proc_pidinfo 范围".to_string())?;
|
|
// SAFETY: ProcBsdInfo is plain data filled by proc_pidinfo.
|
|
let mut info = unsafe { std::mem::zeroed::<ProcBsdInfo>() };
|
|
let expected_size = std::mem::size_of::<ProcBsdInfo>();
|
|
let read = unsafe {
|
|
proc_pidinfo(
|
|
pid,
|
|
PROC_PIDTBSDINFO,
|
|
0,
|
|
(&mut info as *mut ProcBsdInfo).cast(),
|
|
expected_size as i32,
|
|
)
|
|
};
|
|
if read != expected_size as i32 {
|
|
return Err(format!(
|
|
"读取 Agent Runner macOS 进程启动身份失败:{}",
|
|
io::Error::last_os_error()
|
|
));
|
|
}
|
|
return Ok(Some(format!(
|
|
"{}:{}",
|
|
info.pbi_start_tvsec, info.pbi_start_tvusec
|
|
)));
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", windows, target_os = "macos")))]
|
|
{
|
|
let _ = pid;
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub(crate) fn validate_windows_regular_file_handle(file: &File, label: &str) -> Result<(), String> {
|
|
use std::ffi::c_void;
|
|
use std::os::windows::io::AsRawHandle;
|
|
|
|
#[repr(C)]
|
|
struct FileTime {
|
|
low_date_time: u32,
|
|
high_date_time: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
struct ByHandleFileInformation {
|
|
file_attributes: u32,
|
|
creation_time: FileTime,
|
|
last_access_time: FileTime,
|
|
last_write_time: FileTime,
|
|
volume_serial_number: u32,
|
|
file_size_high: u32,
|
|
file_size_low: u32,
|
|
number_of_links: u32,
|
|
file_index_high: u32,
|
|
file_index_low: u32,
|
|
}
|
|
|
|
#[link(name = "kernel32")]
|
|
unsafe extern "system" {
|
|
fn GetFileInformationByHandle(
|
|
file: *mut c_void,
|
|
information: *mut ByHandleFileInformation,
|
|
) -> i32;
|
|
}
|
|
|
|
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x0000_0010;
|
|
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
|
|
// SAFETY: the structure is plain data initialized by GetFileInformationByHandle.
|
|
let mut information = unsafe { std::mem::zeroed::<ByHandleFileInformation>() };
|
|
// SAFETY: file owns a live kernel handle and information is a valid output pointer.
|
|
if unsafe { GetFileInformationByHandle(file.as_raw_handle().cast(), &mut information) } == 0 {
|
|
return Err(format!(
|
|
"读取 {label} Windows 文件句柄信息失败:{}",
|
|
io::Error::last_os_error()
|
|
));
|
|
}
|
|
if information.file_attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) != 0
|
|
|| information.number_of_links != 1
|
|
{
|
|
return Err(format!(
|
|
"{label} 必须是无硬链接普通文件且不能是 Windows reparse point"
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|