# PROGRESS — proc-connector crate 开发计划
> **动机**:fsmon 项目中 `proc_cache.rs` 包含 10 处无法消除的 `unsafe`,均来自 Linux Netlink Proc Connector 的 FFI 调用。市面上缺少一个完整、现代化、覆盖所有 proc 事件类型的 safe Rust 封装。本 crate 填补这个空白,对标 `fanotify-fid` 的设计哲学。
## 目标
- 完整覆盖 Proc Connector **所有 10+ 种事件类型**(而非仅限 `EXEC`)
- 完全 safe 的 API(内部 unsafe 集中在协议解析层,向调用者呈现纯 safe 接口)
- 零开销抽象(解析仅在成功 recv 后执行)
- 可组合(暴露 `as_raw_fd()` 支持 tokio/mio 等 async runtime)
- 不越界(不做缓存、查 /proc、线程管理、业务策略)
## 与 `fanotify-fid` 对标的哲学
| 系统调用封装 | `fanotify_init/mark` safe 函数 | socket/bind/send(订阅)/recv safe 函数 |
| 类型安全常量 | `FAN_CREATE` 等 `u64` 常量 | `CN_IDX_PROC`、`PROC_EVENT_EXEC` 等 |
| 事件枚举 | `FidEvent { mask, pid, path, ... }` | `ProcEvent::Exec { pid, tgid, ... }` |
| 全协议覆盖 | 2 种报告模式 + FID/DFID_NAME 路径解析 | 全 10+ 种处理器事件 |
| 不做什么 | 不管理线程、不缓存路径 | 不查 /proc、不缓存、不管理线程 |
| async 支持 | `AsRawFd` 暴露 raw fd | 同样暴露 `as_raw_fd()` |
| 编程风格 | 现代化常量命名、文档完整 | 对标 |
## 模块设计
```
proc-connector
├── Cargo.toml
└── src/
├── lib.rs // re-export, prelude
├── socket.rs // ProcConnector 核心类型 + 系统调用封装
├── event.rs // ProcEvent 枚举 + 协议解析器
├── consts.rs // 内核常量(全覆盖)
└── error.rs // 错误类型
```
## API 设计
### `socket.rs` — 核心类型
```rust
pub struct ProcConnector {
fd: OwnedFd, // safe: RAII close on drop
}
impl ProcConnector {
/// 创建 netlink socket + bind + 订阅,一步到位
pub fn new() -> Result<Self, Error>
/// 重新订阅(断开后重连时使用)
pub fn subscribe(&mut self) -> Result<(), Error>
/// 退订
pub fn unsubscribe(&mut self) -> Result<(), Error>
/// 接收并解析一个 proc 事件。
/// buf 由调用者提供以控制分配策略。
pub fn recv(&self, buf: &mut [u8]) -> Result<ProcEvent, Error>
/// 带超时的接收
pub fn recv_timeout(&self, buf: &mut [u8], timeout: Duration)
-> Result<Option<ProcEvent>, Error>
/// 暴露 raw fd 供 tokio::AsyncFd / mio 集成
pub fn as_raw_fd(&self) -> RawFd
}
```
### `event.rs` — 事件枚举(全 10+ 种覆盖)
```rust
#[derive(Debug, Clone)]
pub enum ProcEvent {
/// 进程执行 (exec)
Exec { pid: u32, tgid: u32 },
/// 进程派生 (fork)
Fork {
child_pid: u32, child_tgid: u32,
parent_pid: u32, parent_tgid: u32,
},
/// 进程退出 (exit)
Exit { pid: u32, tgid: u32, exit_code: u32, exit_signal: u32 },
/// UID 变化
UidChange { pid: u32, tgid: u32, ruid: u32, euid: u32 },
/// GID 变化
GidChange { pid: u32, tgid: u32, rgid: u32, egid: u32 },
/// Session ID 变化
SidChange { pid: u32, tgid: u32 },
/// ptrace 附加/分离
Ptrace { pid: u32, tgid: u32, tracer_pid: u32, tracer_tgid: u32 },
/// 进程名变更
CommChange { pid: u32, tgid: u32, comm: [u8; 16] },
/// 核心转储
Coredump { pid: u32, tgid: u32 },
/// 内核新增但本 crate 未显式覆盖的事件(未来兼容)
Unknown { what: u32, raw_data: Vec<u8> },
}
```
### `error.rs` — 错误类型
```rust
#[derive(Debug)]
pub enum Error {
/// 系统调用失败(socket/bind/send/recv)
Os(std::io::Error),
/// 报文长度短于最小协议头
Truncated,
/// 缓冲区太小,需要至少 N 字节
BufferTooSmall { needed: usize },
/// recv 被信号中断,应重试
Interrupted,
/// socket 关闭(recv 返回 0)
ConnectionClosed,
}
```
## 不合规的 msg_type 处理
netlink 报文可能包含 `NLMSG_DONE`、`NLMSG_ERROR`、`NLMSG_NOOP` 等控制消息,或者 `NLMSG_OVERRUN`(当订阅者消费不够快时)。处理策略:
| `NLMSG_DONE` | 忽略,继续 recv |
| `NLMSG_ERROR` | 返回 `Err(Error::Os(...))` |
| `NLMSG_NOOP` | 忽略,继续 recv |
| `NLMSG_OVERRUN` | 返回 `Err(Error::Overrun)` — 告知调用者可能丢事件 |
| `NLMSG_DATA` + proc_event | 正常解析 |
## 实施步骤
### Step 1 — 项目骨架 ✅
- [x] `cargo init --lib` + 配置 `Cargo.toml`(依赖:`libc`)
- [x] 错误类型 `error.rs`
- [x] 内核常量 `consts.rs`(全事件类型掩码 + cn_msg 偏移量常量)
### Step 2 — 核心 socket 操作 ✅
- [x] `socket.rs`:`ProcConnector::new()`(socket + bind)
- [x] subscribe / unsubscribe
- [x] recv_raw / recv_raw_timeout
### Step 3 — 协议解析器 ✅
- [x] `event.rs`:解析 `nlmsghdr → cn_msg → proc_event` 三层嵌套
- [x] 所有事件变体的解析(Exec, Fork, Exit, Uid, Gid, Sid, Ptrace, Comm, Coredump)
- [x] 带 `Unknown` 兜底的未来兼容
- [x] 22 个单元测试全部通过
### Step 4 — 进阶功能 ✅
- [x] `recv_timeout`(`recv_raw_timeout` + `recv_timeout`)
- [x] 集成测试(`tests/integration_test.rs`,标记 `#[ignore]`,需 root 运行)
- [x] 文档和示例(`examples/proc_watch.rs` + `examples/async_integration.rs`)
### Step 5 — 替换 fsmon 中的 proc_cache.rs
- [ ] 在 fsmon 中引入 `proc-connector` 依赖
- [ ] 替换 `proc_cache.rs` 中的全部 unsafe 为 safe 调用
- [ ] 确认所有测试通过
## 边界规则(绝不放进 crate)
```
❌ 查 /proc/{pid}/comm → 调用者的事
❌ 查 /proc/{pid}/status 拿 UID → 调用者的事
❌ UID 转用户名 → 调用者的事
❌ PID → cmd/user 的 DashMap 缓存 → 调用者的事
❌ 线程管理 (std::thread::spawn) → 调用者的事
❌ tokio::spawn / async 封装 → 调用者通过 as_raw_fd() 自己集成
❌ AtomicBool 就绪信号量 → 调用者的事
```