shadow_rs/utility/
shm_cleanup.rs
1use std::collections::HashSet;
2use std::fs;
3use std::path::Path;
4use std::path::PathBuf;
5use std::str::FromStr;
6
7use anyhow::Context;
8
9pub const SHM_DIR_PATH: &str = "/dev/shm/";
10const PROC_DIR_PATH: &str = "/proc/";
11const SHADOW_SHM_FILE_PREFIX: &str = "shadow_shmemfile";
12
13fn get_dir_contents(dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
15 fs::read_dir(dir)
16 .context(format!("Reading all directory entries from {:?}", dir))?
17 .map(|entry| {
18 Ok(entry
19 .context(format!("Reading a directory entry from {:?}", dir))?
20 .path())
21 })
22 .collect()
23}
24
25fn get_shadow_shm_file_paths(dir_path: &Path) -> anyhow::Result<Vec<PathBuf>> {
27 let vec = get_dir_contents(dir_path)?
28 .into_iter()
29 .filter_map(|path| match path.file_name() {
30 Some(name) => name
31 .to_string_lossy()
32 .starts_with(SHADOW_SHM_FILE_PREFIX)
33 .then_some(Some(path)),
34 None => None, })
36 .flatten()
37 .collect();
38 Ok(vec)
39}
40
41fn get_running_pid_set(dir_path: &Path) -> anyhow::Result<HashSet<i32>> {
43 let set: HashSet<i32> = get_dir_contents(dir_path)?
44 .into_iter()
45 .filter_map(|path| match path.file_name() {
46 Some(name) => i32::from_str(&name.to_string_lossy()).ok(),
48 None => None, })
50 .collect();
51 Ok(set)
52}
53
54fn pid_from_shadow_shm_file_name(file_name: &str) -> anyhow::Result<i32> {
58 let pid_str = file_name.split('-').next_back().context(format!(
59 "Parsing PID separator '-' from shm file name {:?}",
60 file_name
61 ))?;
62 let pid = i32::from_str(pid_str).context(format!(
63 "Parsing PID '{}' from shm file name {:?}",
64 pid_str, file_name
65 ))?;
66 Ok(pid)
67}
68
69pub fn shm_cleanup(shm_dir: impl AsRef<Path>) -> anyhow::Result<u32> {
73 let shm_paths = get_shadow_shm_file_paths(shm_dir.as_ref())?;
75 log::debug!(
76 "Found {} shadow shared memory files in {}",
77 shm_paths.len(),
78 shm_dir.as_ref().display()
79 );
80
81 let running_pids = get_running_pid_set(Path::new(PROC_DIR_PATH))?;
82 log::debug!(
83 "Found {} running PIDs in {}",
84 running_pids.len(),
85 PROC_DIR_PATH
86 );
87
88 let mut num_removed = 0;
90
91 for path in shm_paths {
93 if let Some(file_name) = path.file_name() {
95 let creator_pid = match pid_from_shadow_shm_file_name(&file_name.to_string_lossy()) {
96 Ok(pid) => pid,
97 Err(e) => {
98 log::warn!(
99 "Unable to parse PID from shared memory file {:?}: {:?}",
100 path,
101 e
102 );
103 continue;
105 }
106 };
107
108 if !running_pids.contains(&creator_pid) {
110 log::trace!("Removing orphaned shared memory file {:?}", path);
111 if fs::remove_file(path).is_ok() {
112 num_removed += 1;
113 }
114 }
115 }
116 }
117
118 log::debug!("Removed {} total shared memory files.", num_removed);
119 Ok(num_removed)
120}
121
122#[cfg(test)]
123mod tests {
124 use std::fs::OpenOptions;
125 use std::io;
126 use std::process;
127
128 use super::*;
129
130 fn touch(path: impl AsRef<Path>) -> io::Result<()> {
131 OpenOptions::new()
132 .create(true)
133 .truncate(false)
134 .write(true)
135 .open(path.as_ref())?;
136 Ok(())
137 }
138
139 #[test]
140 fn test_expired_shm_file_is_removed() {
141 let dir = tempfile::tempdir().unwrap();
142 let s = "shadow_shmemfile_6379761.950298775-999999999";
143 let expired: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
144
145 touch(&expired).unwrap();
146 assert_eq!(shm_cleanup(&dir).unwrap(), 1);
147 assert!(!expired.exists(), "Exists: {}", expired.display());
148 }
149
150 #[test]
151 fn test_valid_shm_file_is_not_removed() {
152 let my_pid = process::id();
153 let dir = tempfile::tempdir().unwrap();
154 let s = format!("shadow_shmemfile_6379761.950298775-{my_pid}");
155 let valid: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
156
157 touch(&valid).unwrap();
158 assert_eq!(shm_cleanup(&dir).unwrap(), 0);
159 assert!(valid.exists(), "Doesn't exist: {}", valid.display());
160 }
161
162 #[test]
163 fn test_nonshadow_shm_file_is_not_removed() {
164 let dir = tempfile::tempdir().unwrap();
165 let s = "shadow_unimportant_test_file";
166 let nonshadow: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
167
168 touch(&nonshadow).unwrap();
169 assert_eq!(shm_cleanup(&dir).unwrap(), 0);
170 assert!(nonshadow.exists(), "Doesn't exist: {}", nonshadow.display());
171 }
172}