1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #![deny(missing_docs)]
6
7 use std::fs::read_to_string;
8 use std::num::ParseIntError;
9 use std::str::FromStr;
10 use std::thread::sleep;
11 use std::time::Duration;
12
13 use anyhow::anyhow;
14 use anyhow::Context;
15 use anyhow::Result;
16 use base::unix::getpid;
17 use base::unix::kill;
18 use base::unix::Pid;
19 use base::unix::Signal;
20
21 /// Stops all the crosvm device processes during moving the guest memory to the staging memory.
22 ///
23 /// While moving, we must guarantee that no one changes the guest memory contents. This supports
24 /// devices in sandbox mode only.
25 ///
26 /// We stop all the crosvm processes instead of the alternatives.
27 ///
28 /// * Just stop vCPUs
29 /// * devices still may works in the child process and write something to the guest memory.
30 /// * Use write protection of userfaultfd
31 /// * UFFDIO_REGISTER_MODE_WP for shmem is WIP and not supported yet.
32 /// * `devices::Suspendable::sleep()`
33 /// * `Suspendable` is not supported by all devices yet.
34 pub struct ProcessesGuard {
35 pids: Vec<Pid>,
36 }
37
38 /// Stops all crosvm child processes except this monitor process using signals.
39 ///
40 /// The stopped processes are resumed when the freezer object is freed.
41 ///
42 /// This must be called from the main process.
freeze_child_processes(monitor_pid: Pid) -> Result<ProcessesGuard>43 pub fn freeze_child_processes(monitor_pid: Pid) -> Result<ProcessesGuard> {
44 let guard = ProcessesGuard {
45 pids: load_children(monitor_pid)?,
46 };
47
48 guard.stop_the_world().context("stop the world")?;
49
50 Ok(guard)
51 }
52
53 impl ProcessesGuard {
54 /// Stops all the crosvm processes by sending SIGSTOP signal.
stop_the_world(&self) -> Result<()>55 fn stop_the_world(&self) -> Result<()> {
56 for pid in &self.pids {
57 // safe because pid in pids are crosvm processes except this monitor process.
58 unsafe { kill(*pid, Signal::Stop as i32) }.context("failed to stop process")?;
59 }
60 for pid in &self.pids {
61 wait_process_stopped(*pid).context("wait process stopped")?;
62 }
63 Ok(())
64 }
65
66 /// Resumes all the crosvm processes by sending SIGCONT signal.
continue_the_world(&self)67 fn continue_the_world(&self) {
68 for pid in &self.pids {
69 // safe because pid in pids are crosvm processes except this monitor process and
70 // continue signal does not have side effects.
71 // ignore the result because we don't care whether it succeeds.
72 let _ = unsafe { kill(*pid, Signal::Continue as i32) };
73 }
74 }
75 }
76
77 impl Drop for ProcessesGuard {
drop(&mut self)78 fn drop(&mut self) {
79 self.continue_the_world();
80 }
81 }
82
83 /// Loads Pids of crosvm child processes except the monitor procesess.
load_children(monitor_pid: Pid) -> Result<Vec<Pid>>84 fn load_children(monitor_pid: Pid) -> Result<Vec<Pid>> {
85 // children of the main process.
86 let children = read_to_string(format!("/proc/self/task/{}/children", getpid()))
87 .context("read children")?;
88 let pids: std::result::Result<Vec<i32>, ParseIntError> = children
89 .trim()
90 .split(" ")
91 .map(i32::from_str)
92 // except this monitor process
93 .filter(|pid| match pid {
94 Ok(pid) => *pid != monitor_pid,
95 _ => true,
96 })
97 .collect();
98 pids.context("parse pids")
99 }
100
101 /// Extract process state from /proc/pid/stat.
102 ///
103 /// `/proc/<pid>/stat` file contains metadata for the process including the process state.
104 ///
105 /// See [proc(5)](https://man7.org/linux/man-pages/man5/proc.5.html) for the format.
parse_process_state(text: &str) -> Option<char>106 fn parse_process_state(text: &str) -> Option<char> {
107 let chars = text.chars();
108 let mut chars = chars.peekable();
109 // skip to the end of "comm"
110 while match chars.next() {
111 Some(c) => c != ')',
112 None => false,
113 } {}
114 // skip the whitespace between "comm" and "state"
115 while match chars.peek() {
116 Some(c) => {
117 let is_whitespace = *c == ' ';
118 if is_whitespace {
119 chars.next();
120 }
121 is_whitespace
122 }
123 None => false,
124 } {}
125 // the state
126 chars.next()
127 }
128
wait_process_stopped(pid: Pid) -> Result<()>129 fn wait_process_stopped(pid: Pid) -> Result<()> {
130 let process_stat_path = format!("/proc/{}/stat", pid);
131 for _ in 0..10 {
132 let stat = read_to_string(&process_stat_path).context("read process status")?;
133 if let Some(state) = parse_process_state(&stat) {
134 if state == 'T' {
135 return Ok(());
136 }
137 }
138 sleep(Duration::from_millis(50));
139 }
140 Err(anyhow!("time out"))
141 }
142
143 #[cfg(test)]
144 mod tests {
145 use super::*;
146
147 #[test]
parse_process_state_tests()148 fn parse_process_state_tests() {
149 assert_eq!(parse_process_state("1234 (crosvm) T 0 0 0").unwrap(), 'T');
150 assert_eq!(parse_process_state("1234 (crosvm) R 0 0 0").unwrap(), 'R');
151 // more than 1 white space
152 assert_eq!(parse_process_state("1234 (crosvm) T 0 0 0").unwrap(), 'T');
153 // no white space between comm and state
154 assert_eq!(parse_process_state("1234 (crosvm)T 0 0 0").unwrap(), 'T');
155 // white space in the comm
156 assert_eq!(
157 parse_process_state("1234 (crosvm --test) T 0 0 0").unwrap(),
158 'T'
159 );
160 // no status
161 assert_eq!(parse_process_state("1234 (crosvm)").is_none(), true);
162 }
163 }
164