Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/process_identity.rs
T
kdletters 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 在 dev feature 组合下编不过
- process_identity:补 use std::io;,dev 构建(no-default-features + editor-execute)下 io::Error 解析不到,挡住本地开发栈启动
2026-10-02 18:51:11 +08:00

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(())
}