1use crate::state::View;
20use crate::sys::{Result, errno};
21use std::collections::{HashMap, VecDeque};
22use std::sync::atomic::{AtomicUsize, Ordering};
23use std::sync::{Arc, Mutex, RwLock};
24use std::time::{Duration, Instant};
25
26const TID_TTL: Duration = Duration::from_secs(1);
28const ORPHAN_TTL: Duration = Duration::from_secs(10);
30const MAX_ANCESTRY: usize = 64;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
39pub struct Ids {
40 pub ruid: u32,
41 pub euid: u32,
42 pub suid: u32,
43 pub fsuid: u32,
44 pub rgid: u32,
45 pub egid: u32,
46 pub sgid: u32,
47 pub fsgid: u32,
48}
49
50#[derive(Clone, Debug, Default)]
52pub struct TraceeInfo {
53 pub ids: Option<Ids>,
55 pub groups: Option<Vec<u32>>,
57 pub exe_guest: Option<Vec<u8>>,
59 pub exe_host: Option<Vec<u8>>,
61 pub cwd: Option<Vec<u8>>,
64 pub vfork_parent: Option<i32>,
67 pub view: Option<Arc<View>>,
69 pub undumpable: bool,
73 pub cmdline_prefix: Option<Vec<Vec<u8>>>,
78 pub pending: Option<PendingExec>,
82}
83
84#[derive(Clone, Debug)]
86pub struct PendingExec {
87 pub guest: Vec<u8>,
89 pub host: Vec<u8>,
91 pub restore: Option<(i32, u64, Vec<u8>, Vec<u8>)>,
94 pub enter: Option<(Arc<View>, Vec<u8>)>,
97 pub cmdline_prefix: Option<Vec<Vec<u8>>>,
99}
100
101#[derive(Debug)]
103struct Reaper {
104 epoll: i32,
105 pidfds: HashMap<i32, i32>,
106}
107
108#[derive(Debug)]
110pub struct ProcTable {
111 pub lazy: bool,
113 procs: RwLock<HashMap<i32, TraceeInfo>>,
114 tids: RwLock<HashMap<i32, (i32, Instant)>>,
115 defaults: RwLock<TraceeInfo>,
117 forks: Mutex<HashMap<i32, VecDeque<TraceeInfo>>>,
119 orphans: Mutex<VecDeque<(Instant, TraceeInfo)>>,
121 shim: AtomicUsize,
124 reaper: Mutex<Option<Reaper>>,
125}
126
127impl ProcTable {
128 pub fn new(lazy: bool) -> ProcTable {
133 ProcTable {
134 lazy,
135 procs: RwLock::new(HashMap::new()),
136 tids: RwLock::new(HashMap::new()),
137 defaults: RwLock::new(TraceeInfo::default()),
138 forks: Mutex::new(HashMap::new()),
139 orphans: Mutex::new(VecDeque::new()),
140 shim: AtomicUsize::new(0),
141 reaper: Mutex::new(None),
142 }
143 }
144
145 pub fn register_root(&self, root: i32, info: TraceeInfo) {
152 *self.defaults.write().unwrap() = info.clone();
153 self.procs.write().unwrap().insert(root, info);
154 self.watch(root);
155 }
156
157 pub fn tgid(&self, tid: i32) -> i32 {
164 if let Some((tgid, at)) = self.tids.read().unwrap().get(&tid)
165 && at.elapsed() < TID_TTL
166 {
167 return *tgid;
168 }
169 let tgid = read_status(tid).map(|(t, _)| t).unwrap_or(tid);
170 self.tids.write().unwrap().insert(tid, (tgid, Instant::now()));
171 tgid
172 }
173
174 pub fn info(&self, tid: i32) -> TraceeInfo {
179 let tgid = self.tgid(tid);
180 self.ensure(tgid);
181 self.procs.read().unwrap().get(&tgid).cloned().unwrap_or_default()
182 }
183
184 pub fn update(&self, tid: i32, f: impl FnOnce(&mut TraceeInfo)) {
193 let tgid = self.tgid(tid);
194 self.ensure(tgid);
195 if self.lazy {
196 self.adopt_children(tgid);
197 }
198 let mut p = self.procs.write().unwrap();
199 f(p.entry(tgid).or_default());
200 }
201
202 pub fn fork(&self, parent: i32, child: i32) {
208 let tgid = self.tgid(parent);
209 let mut p = self.procs.write().unwrap();
210 let info = p.get(&tgid).cloned().unwrap_or_default();
211 if info.pending.is_some() {
212 self.shim.fetch_add(1, Ordering::Relaxed);
213 }
214 p.insert(child, info);
215 }
216
217 pub fn view_of(&self, tid: i32) -> Option<Arc<View>> {
222 let tgid = self.tgid(tid);
223 self.ensure(tgid);
224 self.procs.read().unwrap().get(&tgid).and_then(|t| t.view.clone())
225 }
226
227 pub fn forked(&self, tid: i32, vfork: bool) {
233 let tgid = self.tgid(tid);
234 let mut info = self.info(tgid);
235 info.vfork_parent = if vfork { Some(tgid) } else { None };
236 self.forks
237 .lock()
238 .unwrap()
239 .entry(tgid)
240 .or_default()
241 .push_back(info);
242 }
243
244 pub fn forget(&self, pid: i32) {
251 self.tids
252 .write()
253 .unwrap()
254 .retain(|t, (g, _)| *t != pid && *g != pid);
255 let Some(gone) = self.procs.write().unwrap().remove(&pid) else {
256 return;
257 };
258 if gone.pending.is_some() {
259 self.shim.fetch_sub(1, Ordering::Relaxed);
260 }
261 if let Some(q) = self.forks.lock().unwrap().remove(&pid) {
262 let now = Instant::now();
263 let mut orphans = self.orphans.lock().unwrap();
264 orphans.extend(q.into_iter().map(|s| (now, s)));
265 }
266 let mut r = self.reaper.lock().unwrap();
267 if let Some(r) = r.as_mut()
268 && let Some(fd) = r.pidfds.remove(&pid)
269 {
270 unsafe { libc::close(fd) };
271 }
272 }
273
274 pub fn set_exe(&self, tid: i32, guest: Vec<u8>, host: Vec<u8>) {
276 self.update(tid, |t| {
277 t.exe_guest = Some(guest);
278 t.exe_host = Some(host);
279 t.undumpable = false;
280 });
281 }
282
283 pub fn dumpable(&self, tid: i32) -> bool {
285 !self.info(tid).undumpable
286 }
287
288 pub fn set_dumpable(&self, tid: i32, dumpable: bool) {
290 self.update(tid, |t| t.undumpable = !dumpable);
291 }
292
293 pub fn exe_of(&self, tid: i32) -> Option<Vec<u8>> {
295 self.info(tid).exe_guest
296 }
297
298 pub fn cwd_of(&self, tid: i32) -> Option<Vec<u8>> {
300 self.info(tid).cwd
301 }
302
303 pub fn set_cwd(&self, tid: i32, cwd: Vec<u8>) {
305 self.update(tid, |t| t.cwd = Some(cwd));
306 }
307
308 pub fn ids(&self, tid: i32) -> Ids {
310 self.info(tid).ids.unwrap_or_default()
311 }
312
313 pub fn set_ids(&self, tid: i32, ids: Ids) {
315 self.update(tid, |t| t.ids = Some(ids));
316 }
317
318 pub fn groups(&self, tid: i32) -> Option<Vec<u32>> {
320 self.info(tid).groups
321 }
322
323 pub fn set_groups(&self, tid: i32, groups: Vec<u32>) {
325 self.update(tid, |t| t.groups = Some(groups));
326 }
327
328 pub fn shim_enter(&self, tgid: i32, pending: PendingExec) {
330 self.shim.fetch_add(1, Ordering::Relaxed);
331 self.update(tgid, |t| {
332 t.pending = Some(pending);
333 });
334 }
335
336 pub fn shim_pending(&self, tgid: i32) -> Option<PendingExec> {
338 if self.shim.load(Ordering::Relaxed) == 0 {
339 return None;
340 }
341 self.ensure(tgid);
342 self.procs
343 .read()
344 .unwrap()
345 .get(&tgid)
346 .and_then(|t| t.pending.clone())
347 }
348
349 pub fn shim_leave(&self, tgid: i32) {
351 let mut p = self.procs.write().unwrap();
352 if let Some(t) = p.get_mut(&tgid)
353 && t.pending.take().is_some()
354 {
355 self.shim.fetch_sub(1, Ordering::Relaxed);
356 }
357 }
358
359 fn ensure(&self, tgid: i32) {
361 if !self.lazy || self.procs.read().unwrap().contains_key(&tgid) {
362 return;
363 }
364 self.discover(tgid, 0);
365 }
366
367 fn discover(&self, tgid: i32, depth: usize) {
374 let me = std::process::id() as i32;
375 let ppid = read_status(tgid).map(|(_, p)| p).unwrap_or(0);
376 let state = if ppid > 1 && ppid != me && depth < MAX_ANCESTRY {
377 if !self.procs.read().unwrap().contains_key(&ppid) {
378 self.discover(ppid, depth + 1);
379 }
380 let snapshot = self
381 .forks
382 .lock()
383 .unwrap()
384 .get_mut(&ppid)
385 .and_then(|q| q.pop_front());
386 match snapshot {
387 Some(s) => s,
388 None => {
389 let mut s = self.procs.read().unwrap().get(&ppid).cloned().unwrap_or_default();
390 s.vfork_parent = None;
391 s
392 }
393 }
394 } else {
395 let mut orphans = self.orphans.lock().unwrap();
396 while let Some((at, _)) = orphans.front()
397 && at.elapsed() > ORPHAN_TTL
398 {
399 orphans.pop_front();
400 }
401 match orphans.pop_front() {
402 Some((_, s)) => s,
403 None => self.defaults.read().unwrap().clone(),
404 }
405 };
406 let pending = state.pending.is_some();
407 let inserted = self.procs.write().unwrap().try_insert_entry(tgid, state);
408 if inserted {
409 if pending {
410 self.shim.fetch_add(1, Ordering::Relaxed);
411 }
412 self.watch(tgid);
413 }
414 }
415
416 fn adopt_children(&self, tgid: i32) {
421 if self.forks.lock().unwrap().get(&tgid).is_none_or(|q| q.is_empty()) {
422 return;
423 }
424 let Ok(dir) = std::fs::read_dir("/proc") else {
425 return;
426 };
427 for entry in dir.flatten() {
428 let Some(pid) = entry.file_name().to_str().and_then(|n| n.parse::<i32>().ok()) else {
429 continue;
430 };
431 if self.procs.read().unwrap().contains_key(&pid) {
432 continue;
433 }
434 if read_status(pid).map(|(_, p)| p) != Some(tgid) {
435 continue;
436 }
437 let snapshot = self
438 .forks
439 .lock()
440 .unwrap()
441 .get_mut(&tgid)
442 .and_then(|q| q.pop_front());
443 let Some(state) = snapshot else { break };
444 let pending = state.pending.is_some();
445 if self.procs.write().unwrap().try_insert_entry(pid, state) {
446 if pending {
447 self.shim.fetch_add(1, Ordering::Relaxed);
448 }
449 self.watch(pid);
450 }
451 }
452 }
453
454 fn watch(&self, tgid: i32) {
456 let mut r = self.reaper.lock().unwrap();
457 let Some(r) = r.as_mut() else { return };
458 if r.pidfds.contains_key(&tgid) {
459 return;
460 }
461 let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, tgid, 0) } as i32;
462 if pidfd < 0 {
463 return;
464 }
465 let mut ev = libc::epoll_event {
466 events: libc::EPOLLIN as u32,
467 u64: tgid as u32 as u64,
468 };
469 if unsafe { libc::epoll_ctl(r.epoll, libc::EPOLL_CTL_ADD, pidfd, &mut ev) } < 0 {
470 unsafe { libc::close(pidfd) };
471 return;
472 }
473 r.pidfds.insert(tgid, pidfd);
474 }
475
476 pub fn start_reaper(table: &Arc<ProcTable>) -> Result<()> {
486 let epoll = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
487 if epoll < 0 {
488 return Err(errno());
489 }
490 *table.reaper.lock().unwrap() = Some(Reaper {
491 epoll,
492 pidfds: HashMap::new(),
493 });
494 let known: Vec<i32> = table.procs.read().unwrap().keys().copied().collect();
495 for pid in known {
496 table.watch(pid);
497 }
498 let t = table.clone();
499 std::thread::Builder::new()
500 .name("reaper".into())
501 .spawn(move || {
502 let mut events = [libc::epoll_event { events: 0, u64: 0 }; 64];
503 loop {
504 let n = unsafe { libc::epoll_wait(epoll, events.as_mut_ptr(), 64, -1) };
505 if n < 0 {
506 if errno() == libc::EINTR {
507 continue;
508 }
509 return;
510 }
511 for ev in &events[..n as usize] {
512 t.forget(ev.u64 as u32 as i32);
513 }
514 }
515 })
516 .map_err(|_| libc::EAGAIN)?;
517 Ok(())
518 }
519}
520
521trait TryInsert {
523 fn try_insert_entry(&mut self, key: i32, value: TraceeInfo) -> bool;
524}
525
526impl TryInsert for HashMap<i32, TraceeInfo> {
527 fn try_insert_entry(&mut self, key: i32, value: TraceeInfo) -> bool {
528 if self.contains_key(&key) {
529 return false;
530 }
531 self.insert(key, value);
532 true
533 }
534}
535
536pub fn read_status(tid: i32) -> Option<(i32, i32)> {
541 let s = std::fs::read_to_string(format!("/proc/{tid}/status")).ok()?;
542 let (mut tgid, mut ppid) = (None, None);
543 for line in s.lines() {
544 if let Some(v) = line.strip_prefix("Tgid:") {
545 tgid = v.trim().parse().ok();
546 } else if let Some(v) = line.strip_prefix("PPid:") {
547 ppid = v.trim().parse().ok();
548 }
549 if tgid.is_some() && ppid.is_some() {
550 break;
551 }
552 }
553 Some((tgid?, ppid?))
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn eager_table_copies_on_fork_and_forgets() {
562 let me = std::process::id() as i32;
563 let t = ProcTable::new(false);
564 t.set_exe(me, b"/bin/x".to_vec(), b"/host/x".to_vec());
565 t.fork(me, 4_000_001);
566 assert_eq!(t.exe_of(4_000_001), Some(b"/bin/x".to_vec()));
567 t.forget(4_000_001);
568 assert_eq!(t.exe_of(4_000_001), None);
569 assert_eq!(t.tgid(me), me);
570 }
571
572 #[test]
573 fn lazy_table_inherits_from_the_parent_and_takes_snapshots() {
574 let me = std::process::id() as i32;
575 let (_, parent) = read_status(me).unwrap();
576 let t = ProcTable::new(true);
577 t.register_root(
578 parent,
579 TraceeInfo {
580 cwd: Some(b"/start".to_vec()),
581 ..Default::default()
582 },
583 );
584 assert_eq!(t.cwd_of(me), Some(b"/start".to_vec()));
585 t.forked(me, false);
586 t.set_cwd(me, b"/later".to_vec());
587 let snapshot = t.forks.lock().unwrap().get(&me).and_then(|q| q.front().cloned());
588 assert_eq!(snapshot.and_then(|s| s.cwd), Some(b"/start".to_vec()));
589 assert_eq!(t.cwd_of(me), Some(b"/later".to_vec()));
590 t.forget(me);
591 let orphan = t.orphans.lock().unwrap().front().cloned();
592 assert_eq!(orphan.and_then(|(_, s)| s.cwd), Some(b"/start".to_vec()));
593 }
594
595 #[test]
596 fn shim_marks_are_per_process() {
597 let t = ProcTable::new(false);
598 assert!(t.shim_pending(7).is_none());
599 t.shim_enter(
600 7,
601 PendingExec {
602 guest: b"/bin/true".to_vec(),
603 host: b"/r/bin/true".to_vec(),
604 restore: None,
605 enter: None,
606 cmdline_prefix: None,
607 },
608 );
609 assert_eq!(t.shim_pending(7).map(|p| p.guest), Some(b"/bin/true".to_vec()));
610 t.fork(7, 8);
611 assert_eq!(t.shim_pending(8).map(|p| p.host), Some(b"/r/bin/true".to_vec()));
612 t.shim_leave(7);
613 assert!(t.shim_pending(7).is_none());
614 assert!(t.shim_pending(8).is_some());
615 t.forget(8);
616 assert_eq!(t.shim.load(Ordering::Relaxed), 0);
617 }
618}