use anyhow::Result;
use crate::{is_zisk_sem_file, is_zisk_shmem_file, sem_file_to_posix_name};
fn unlink_shmem(name: &str) -> Result<()> {
let cstr = std::ffi::CString::new(name)?;
unsafe { libc::shm_unlink(cstr.as_ptr()) };
Ok(())
}
fn unlink_sem_file(name: &str) {
if let Some(sem_name) = sem_file_to_posix_name(name) {
if let Ok(cstr) = std::ffi::CString::new(sem_name) {
unsafe { libc::sem_unlink(cstr.as_ptr()) };
}
}
}
pub(super) fn cleanup_stale() {
tracing::info!("Cleaning up stale shared memory and semaphores");
let dev_shm = std::path::Path::new("/dev/shm");
let entries = match std::fs::read_dir(dev_shm) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let name = match entry.file_name().into_string() {
Ok(n) => n,
Err(_) => continue,
};
let is_sem = is_zisk_sem_file(&name);
let is_shm = is_zisk_shmem_file(&name);
if !is_shm && !is_sem {
continue;
}
let parts: Vec<&str> = name.splitn(3, '_').collect();
if parts.len() < 3 {
continue;
}
let Ok(pid) = parts[1].parse::<u32>() else { continue };
let alive = unsafe { libc::kill(pid as i32, 0) };
if alive == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM) {
continue; }
if is_sem {
tracing::debug!("Cleaning up stale semaphore: /dev/shm/{}", name);
unlink_sem_file(&name);
} else {
tracing::debug!("Cleaning up stale shared memory: /dev/shm/{}", name);
let _ = unlink_shmem(&name);
}
}
}
pub(super) fn cleanup_prefix(shm_prefix: &str, sem_prefix: &str) {
let dev_shm = std::path::Path::new("/dev/shm");
let entries = match std::fs::read_dir(dev_shm) {
Ok(e) => e,
Err(e) => {
tracing::warn!("Cannot scan /dev/shm for cleanup: {e}");
return;
}
};
let sem_marker = format!("sem.{}", sem_prefix);
for entry in entries.flatten() {
let Some(name) = entry.file_name().to_str().map(str::to_string) else { continue };
if name.starts_with(shm_prefix) {
let _ = unlink_shmem(&name);
} else if name.starts_with(&sem_marker) {
unlink_sem_file(&name);
}
}
}
#[cfg(all(test, target_os = "linux", target_arch = "x86_64"))]
mod tests {
use super::*;
use std::ffi::CString;
fn shm_create(name: &str) {
let c = CString::new(name).unwrap();
unsafe {
let fd = libc::shm_open(c.as_ptr(), libc::O_CREAT | libc::O_RDWR, 0o600);
assert!(fd >= 0, "shm_open create failed for {name}");
libc::ftruncate(fd, 64);
libc::close(fd);
}
}
fn shm_exists(name: &str) -> bool {
let c = CString::new(name).unwrap();
unsafe {
let fd = libc::shm_open(c.as_ptr(), libc::O_RDONLY, 0);
if fd >= 0 {
libc::close(fd);
true
} else {
false
}
}
}
#[test]
fn cleanup_prefix_unlinks_matching_shmem_and_semaphores() {
let shm_prefix = format!("ZISK_unittest_jan_{}", std::process::id());
let sem_prefix = format!("ZISK_unittest_jansem_{}", std::process::id());
let seg_a = format!("{shm_prefix}_input");
let seg_b = format!("{shm_prefix}_MT_output");
shm_create(&seg_a);
shm_create(&seg_b);
assert!(shm_exists(&seg_a) && shm_exists(&seg_b));
let sem_name = format!("/{sem_prefix}_chunk_done");
let _sem = named_sem::NamedSemaphore::create(&sem_name, 0).unwrap();
let sem_backing = format!("/dev/shm/sem.{sem_prefix}_chunk_done");
assert!(std::path::Path::new(&sem_backing).exists());
cleanup_prefix(&shm_prefix, &sem_prefix);
assert!(!shm_exists(&seg_a), "shmem segment should be unlinked");
assert!(!shm_exists(&seg_b), "shmem segment should be unlinked");
assert!(!std::path::Path::new(&sem_backing).exists(), "semaphore should be unlinked");
}
}