Skip to main content

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/// ```text
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 test_that::prelude::*;
113
114  use super::*;
115
116  #[test]
117  fn test_parent_event_map() {
118    let r#become: ParentEvent<u32> = ParentEvent::Become(10);
119    let spawn: ParentEvent<u32> = ParentEvent::Spawn(20);
120
121    let mapped_become = r#become.map(|x| x * 2);
122    let mapped_spawn = spawn.map(|x| x + 5);
123
124    match mapped_become {
125      ParentEvent::Become(v) => assert_eq!(v, 20),
126      _ => panic!("Expected Become variant"),
127    }
128    match mapped_spawn {
129      ParentEvent::Spawn(v) => assert_eq!(v, 25),
130      _ => panic!("Expected Spawn variant"),
131    }
132  }
133
134  #[test]
135  fn test_parent_event_transpose() {
136    let become_some: ParentEvent<Option<u32>> = ParentEvent::Become(Some(10));
137    let become_none: ParentEvent<Option<u32>> = ParentEvent::Become(None);
138    let spawn_some: ParentEvent<Option<u32>> = ParentEvent::Spawn(Some(5));
139    let spawn_none: ParentEvent<Option<u32>> = ParentEvent::Spawn(None);
140
141    let transposed_become_some = become_some.transpose();
142    let transposed_become_none = become_none.transpose();
143    let transposed_spawn_some = spawn_some.transpose();
144    let transposed_spawn_none = spawn_none.transpose();
145
146    assert_eq!(transposed_become_some, Some(ParentEvent::Become(10)));
147    assert_eq!(transposed_become_none, None);
148    assert_eq!(transposed_spawn_some, Some(ParentEvent::Spawn(5)));
149    assert_eq!(transposed_spawn_none, None);
150  }
151
152  #[test]
153  fn test_parent_tracker_save_and_update() {
154    let mut parent = ParentTracker::new();
155    let mut child = ParentTracker::new();
156
157    let parent_exec1 = EventId::new(1);
158    let parent_exec2 = EventId::new(2);
159
160    // First exec for parent
161    assert_eq!(parent.update_last_exec(parent_exec1, true), None);
162    // Second exec for parent
163    let parent_become = parent.update_last_exec(parent_exec2, true);
164    assert_eq!(parent_become.unwrap(), ParentEvent::Become(EventId::new(1)));
165
166    // Save parent's last successful exec to child
167    child.save_parent_last_exec(&parent);
168
169    let child_exec = EventId::new(10);
170    let parent_event = child.update_last_exec(child_exec, true);
171    // First exec in child should reference parent's last exec as Spawn
172    assert!(matches!(parent_event, Some(ParentEvent::Spawn(_))));
173  }
174
175  #[test]
176  fn test_parent_tracker_update_unsuccessful_exec() {
177    let mut tracker = ParentTracker::new();
178    let parent_id = EventId::new(5);
179    tracker.parent_last_exec = Some(parent_id);
180
181    let exec_id = EventId::new(10);
182    // unsuccessful exec does not update last_successful_exec
183    let parent_event = tracker.update_last_exec(exec_id, false);
184    assert!(matches!(parent_event, Some(ParentEvent::Spawn(_))));
185    assert_eq!(tracker.last_successful_exec, None);
186  }
187
188  #[test]
189  fn test_parent_tracker_multiple_execs() {
190    let mut tracker = ParentTracker::new();
191    let first_exec = EventId::new(1);
192    let second_exec = EventId::new(2);
193
194    // First successful exec
195    let parent_event1 = tracker.update_last_exec(first_exec, true);
196    assert_that!(parent_event1, none());
197    assert_eq!(tracker.last_successful_exec.unwrap().into_inner(), 1);
198
199    // Second successful exec
200    let parent_event2 = tracker.update_last_exec(second_exec, true);
201    // Should return Become of previous exec
202    assert_eq!(parent_event2.unwrap(), ParentEvent::Become(EventId::new(1)));
203    assert_eq!(tracker.last_successful_exec.unwrap().into_inner(), 2);
204  }
205}