tracexec_core/event/
parent.rs

1//! Code for locating the id of parent event of an event.
2
3use std::fmt::Debug;
4
5use super::EventId;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ParentEvent<T> {
9  /// The parent process destroys itself and become a new process
10  Become(T),
11  /// The parent process spawns a new process.
12  Spawn(T),
13}
14
15impl From<ParentEvent<Self>> for EventId {
16  fn from(value: ParentEvent<Self>) -> Self {
17    match value {
18      ParentEvent::Become(event_id) | ParentEvent::Spawn(event_id) => event_id,
19    }
20  }
21}
22
23impl<T> ParentEvent<T> {
24  pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ParentEvent<U> {
25    match self {
26      Self::Become(v) => ParentEvent::Become(f(v)),
27      Self::Spawn(v) => ParentEvent::Spawn(f(v)),
28    }
29  }
30}
31
32impl<T> ParentEvent<Option<T>> {
33  pub fn transpose(self) -> Option<ParentEvent<T>> {
34    match self {
35      Self::Become(v) => v.map(ParentEvent::Become),
36      Self::Spawn(v) => v.map(ParentEvent::Spawn),
37    }
38  }
39}
40
41pub type ParentEventId = ParentEvent<EventId>;
42
43/// How this works
44///
45/// Consider the following two situations:
46///
47/// ```ignore
48///           pid 2
49///          Proc A
50///            │  fork   pid 3
51///  pid 2     ├────────►Proc A
52/// Proc C exec│           │      pid 3
53///   ┌───◄────┘           │exec Proc B
54///   │        *           └───►────┐
55///   │*********                    │
56///   │ alt exec                    │
57/// C exec Proc D
58///
59/// We will derive the following relations:
60///
61/// Unknown ?> A
62/// |- A spawns B
63/// |- A becomes C
64///    |- C becomes D
65/// ```
66///
67/// To achieve this, we
68/// 1) for `spawns`(A spawns B), record the id of last exec event(Unknown ?> A) of the parent process(A) at fork time.
69/// 2) for `becomes`(C becomes D), record the id of last exec event(A becomes C)
70///
71/// If the process itself have successful execs, then the parent event is `last_successful_exec`
72/// Otherwise, the parent is the corresponding successful exec event of its parent process.
73#[derive(Debug, Clone, Default)]
74pub struct ParentTracker {
75  /// The parent event recorded at fork time,
76  parent_last_exec: Option<EventId>,
77  /// The last exec event of this process
78  last_successful_exec: Option<EventId>,
79}
80
81impl ParentTracker {
82  pub fn new() -> Self {
83    Default::default()
84  }
85
86  pub fn save_parent_last_exec(&mut self, parent: &Self) {
87    self.parent_last_exec = parent.last_successful_exec.or(parent.parent_last_exec);
88  }
89
90  /// Updates parent tracker with an exec event
91  /// and returns the parent event id of this exec event
92  pub fn update_last_exec(&mut self, id: EventId, successful: bool) -> Option<ParentEventId> {
93    let has_successful_exec = self.last_successful_exec.is_some();
94    let old_last_exec = if successful {
95      self.last_successful_exec.replace(id)
96    } else {
97      self.last_successful_exec
98    };
99    // If a process has successful exec events, the parent should be the last successful exec,
100    // other wise it should point to the parent exec event
101    if has_successful_exec {
102      // This is at least the second time of exec for this process
103      old_last_exec.map(ParentEvent::Become)
104    } else {
105      self.parent_last_exec.map(ParentEvent::Spawn)
106    }
107  }
108}
109
110#[cfg(test)]
111mod tests {
112  use super::*;
113
114  #[test]
115  fn test_parent_event_map() {
116    let r#become: ParentEvent<u32> = ParentEvent::Become(10);
117    let spawn: ParentEvent<u32> = ParentEvent::Spawn(20);
118
119    let mapped_become = r#become.map(|x| x * 2);
120    let mapped_spawn = spawn.map(|x| x + 5);
121
122    match mapped_become {
123      ParentEvent::Become(v) => assert_eq!(v, 20),
124      _ => panic!("Expected Become variant"),
125    }
126    match mapped_spawn {
127      ParentEvent::Spawn(v) => assert_eq!(v, 25),
128      _ => panic!("Expected Spawn variant"),
129    }
130  }
131
132  #[test]
133  fn test_parent_event_transpose() {
134    let become_some: ParentEvent<Option<u32>> = ParentEvent::Become(Some(10));
135    let become_none: ParentEvent<Option<u32>> = ParentEvent::Become(None);
136    let spawn_some: ParentEvent<Option<u32>> = ParentEvent::Spawn(Some(5));
137    let spawn_none: ParentEvent<Option<u32>> = ParentEvent::Spawn(None);
138
139    let transposed_become_some = become_some.transpose();
140    let transposed_become_none = become_none.transpose();
141    let transposed_spawn_some = spawn_some.transpose();
142    let transposed_spawn_none = spawn_none.transpose();
143
144    assert_eq!(transposed_become_some, Some(ParentEvent::Become(10)));
145    assert_eq!(transposed_become_none, None);
146    assert_eq!(transposed_spawn_some, Some(ParentEvent::Spawn(5)));
147    assert_eq!(transposed_spawn_none, None);
148  }
149
150  #[test]
151  fn test_parent_tracker_save_and_update() {
152    let mut parent = ParentTracker::new();
153    let mut child = ParentTracker::new();
154
155    let parent_exec1 = EventId::new(1);
156    let parent_exec2 = EventId::new(2);
157
158    // First exec for parent
159    assert_eq!(parent.update_last_exec(parent_exec1, true), None);
160    // Second exec for parent
161    let parent_become = parent.update_last_exec(parent_exec2, true);
162    assert_eq!(parent_become.unwrap(), ParentEvent::Become(EventId::new(1)));
163
164    // Save parent's last successful exec to child
165    child.save_parent_last_exec(&parent);
166
167    let child_exec = EventId::new(10);
168    let parent_event = child.update_last_exec(child_exec, true);
169    // First exec in child should reference parent's last exec as Spawn
170    assert!(matches!(parent_event, Some(ParentEvent::Spawn(_))));
171  }
172
173  #[test]
174  fn test_parent_tracker_update_unsuccessful_exec() {
175    let mut tracker = ParentTracker::new();
176    let parent_id = EventId::new(5);
177    tracker.parent_last_exec = Some(parent_id);
178
179    let exec_id = EventId::new(10);
180    // unsuccessful exec does not update last_successful_exec
181    let parent_event = tracker.update_last_exec(exec_id, false);
182    assert!(matches!(parent_event, Some(ParentEvent::Spawn(_))));
183    assert_eq!(tracker.last_successful_exec, None);
184  }
185
186  #[test]
187  fn test_parent_tracker_multiple_execs() {
188    let mut tracker = ParentTracker::new();
189    let first_exec = EventId::new(1);
190    let second_exec = EventId::new(2);
191
192    // First successful exec
193    let parent_event1 = tracker.update_last_exec(first_exec, true);
194    assert!(parent_event1.is_none());
195    assert_eq!(tracker.last_successful_exec.unwrap().into_inner(), 1);
196
197    // Second successful exec
198    let parent_event2 = tracker.update_last_exec(second_exec, true);
199    // Should return Become of previous exec
200    assert_eq!(parent_event2.unwrap(), ParentEvent::Become(EventId::new(1)));
201    assert_eq!(tracker.last_successful_exec.unwrap().into_inner(), 2);
202  }
203}