shadow_rs/utility/
shm_cleanup.rs1use 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 {file_name:?}",
60 ))?;
61 let pid = i32::from_str(pid_str).context(format!(
62 "Parsing PID '{pid_str}' from shm file name {file_name:?}",
63 ))?;
64 Ok(pid)
65}
66
67pub fn shm_cleanup(shm_dir: impl AsRef<Path>) -> anyhow::Result<u32> {
71 let shm_paths = get_shadow_shm_file_paths(shm_dir.as_ref())?;
73 log::debug!(
74 "Found {} shadow shared memory files in {}",
75 shm_paths.len(),
76 shm_dir.as_ref().display()
77 );
78
79 let running_pids = get_running_pid_set(Path::new(PROC_DIR_PATH))?;
80 log::debug!(
81 "Found {} running PIDs in {}",
82 running_pids.len(),
83 PROC_DIR_PATH
84 );
85
86 let mut num_removed = 0;
88
89 for path in shm_paths {
91 if let Some(file_name) = path.file_name() {
93 let creator_pid = match pid_from_shadow_shm_file_name(&file_name.to_string_lossy()) {
94 Ok(pid) => pid,
95 Err(e) => {
96 log::warn!("Unable to parse PID from shared memory file {path:?}: {e:?}");
97 continue;
99 }
100 };
101
102 if !running_pids.contains(&creator_pid) {
104 log::trace!("Removing orphaned shared memory file {path:?}");
105 if fs::remove_file(path).is_ok() {
106 num_removed += 1;
107 }
108 }
109 }
110 }
111
112 log::debug!("Removed {num_removed} total shared memory files.");
113 Ok(num_removed)
114}
115
116#[cfg(test)]
117mod tests {
118 use std::fs::OpenOptions;
119 use std::io;
120 use std::process;
121
122 use super::*;
123
124 fn touch(path: impl AsRef<Path>) -> io::Result<()> {
125 OpenOptions::new()
126 .create(true)
127 .truncate(false)
128 .write(true)
129 .open(path.as_ref())?;
130 Ok(())
131 }
132
133 #[test]
134 fn test_expired_shm_file_is_removed() {
135 let dir = tempfile::tempdir().unwrap();
136 let s = "shadow_shmemfile_6379761.950298775-999999999";
137 let expired: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
138
139 touch(&expired).unwrap();
140 assert_eq!(shm_cleanup(&dir).unwrap(), 1);
141 assert!(!expired.exists(), "Exists: {}", expired.display());
142 }
143
144 #[test]
145 fn test_valid_shm_file_is_not_removed() {
146 let my_pid = process::id();
147 let dir = tempfile::tempdir().unwrap();
148 let s = format!("shadow_shmemfile_6379761.950298775-{my_pid}");
149 let valid: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
150
151 touch(&valid).unwrap();
152 assert_eq!(shm_cleanup(&dir).unwrap(), 0);
153 assert!(valid.exists(), "Doesn't exist: {}", valid.display());
154 }
155
156 #[test]
157 fn test_nonshadow_shm_file_is_not_removed() {
158 let dir = tempfile::tempdir().unwrap();
159 let s = "shadow_unimportant_test_file";
160 let nonshadow: PathBuf = [dir.as_ref(), s.as_ref()].iter().collect();
161
162 touch(&nonshadow).unwrap();
163 assert_eq!(shm_cleanup(&dir).unwrap(), 0);
164 assert!(nonshadow.exists(), "Doesn't exist: {}", nonshadow.display());
165 }
166}