use {
agave_io_uring::{Completion, Ring, RingAccess as _, RingOp},
io_uring::{IoUring, opcode, squeue, types},
slab::Slab,
std::{
collections::VecDeque,
ffi::{CString, OsString},
fs, io,
os::{
fd::{AsRawFd, IntoRawFd, OwnedFd, RawFd},
unix::ffi::OsStringExt as _,
},
path::PathBuf,
},
};
pub struct RingDirRemover {
ring: Ring<State, Op>,
}
impl RingDirRemover {
pub fn new() -> io::Result<RingDirRemover> {
let ring = IoUring::builder().setup_sqpoll(1000).build(1024)?;
ring.submitter().register_iowq_max_workers(&mut [12, 0])?;
Ok(Self::with_ring(ring))
}
fn with_ring(ring: IoUring) -> RingDirRemover {
RingDirRemover {
ring: Ring::new(ring, State { dirs: Slab::new() }),
}
}
pub fn remove_dir_all(&mut self, path: impl Into<PathBuf>) -> io::Result<()> {
self.remove(path, true)
}
pub fn remove_dir_contents(&mut self, path: impl Into<PathBuf>) -> io::Result<()> {
self.remove(path, false)
}
#[allow(clippy::arithmetic_side_effects)]
fn remove(&mut self, path: impl Into<PathBuf>, remove_root: bool) -> io::Result<()> {
let path = path.into();
let root_path = path.clone();
let mut stack = VecDeque::new();
stack.push_back(path);
while let Some(dir_path) = stack.pop_front() {
let file = std::fs::File::open(&dir_path)?;
let fd = OwnedFd::from(file);
let dir_fd = fd.as_raw_fd();
let dir_key = self.ring.context_mut().dirs.insert(Directory::new(fd));
for entry in fs::read_dir(&dir_path)? {
let entry = entry?;
let dir = &mut self.ring.context_mut().dirs[dir_key];
let is_dir = entry.file_type()?.is_dir();
if is_dir {
stack.push_back(entry.path());
} else {
dir.unlink_started += 1;
let op = UnlinkOp::new(dir_key, dir_fd, entry.file_name());
self.ring.push(Op::Unlink(op))?;
}
}
let dir = &mut self.ring.context_mut().dirs[dir_key];
dir.set_finished_scanning();
if dir.scanned_and_unlinked() {
let fd = dir.fd.take();
if let Some(fd) = fd {
self.ring
.push(Op::Close(CloseOp::new(dir_key, fd.into_raw_fd())))?;
}
}
}
self.ring.drain()?;
if remove_root {
fs::remove_dir_all(root_path)?;
} else {
for dir in fs::read_dir(root_path)? {
let dir = dir?;
if dir.file_type()?.is_dir() {
fs::remove_dir_all(dir.path())?;
}
}
}
Ok(())
}
}
pub struct State {
dirs: Slab<Directory>,
}
pub struct Directory {
fd: Option<OwnedFd>,
unlink_started: usize,
unlink_completed: usize,
finished_scanning: bool,
}
impl Directory {
pub fn new(fd: OwnedFd) -> Directory {
Directory {
fd: Some(fd),
unlink_started: 0,
unlink_completed: 0,
finished_scanning: false,
}
}
fn set_finished_scanning(&mut self) {
self.finished_scanning = true;
}
fn scanned_and_unlinked(&self) -> bool {
self.finished_scanning && self.unlink_started == self.unlink_completed
}
}
struct UnlinkOp {
dir_key: usize,
dir_fd: RawFd,
path: CString,
}
impl std::fmt::Debug for UnlinkOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UnlinkOp")
.field("dir_key", &self.dir_key)
.field("dir_fd", &self.dir_fd)
.field("path", &self.path.as_c_str())
.finish()
}
}
impl UnlinkOp {
fn new(dir_key: usize, dir_fd: RawFd, file_name: OsString) -> UnlinkOp {
UnlinkOp {
dir_key,
dir_fd,
path: CString::new(file_name.into_vec()).unwrap(),
}
}
fn entry(&mut self) -> squeue::Entry {
opcode::UnlinkAt::new(types::Fd(self.dir_fd), self.path.as_ptr() as _).build()
}
#[allow(clippy::arithmetic_side_effects)]
fn complete(
&mut self,
comp: &mut Completion<State, Op>,
res: io::Result<i32>,
) -> io::Result<()> {
let _ = res?;
let dir = &mut comp.context_mut().dirs[self.dir_key];
dir.unlink_completed += 1;
if dir.scanned_and_unlinked() {
if let Some(fd) = dir.fd.take() {
comp.push(Op::Close(CloseOp::new(self.dir_key, fd.into_raw_fd())))?;
}
}
Ok(())
}
}
#[derive(Debug)]
struct CloseOp {
dir_key: usize,
fd: RawFd,
}
impl CloseOp {
fn new(dir_key: usize, fd: RawFd) -> Self {
Self { dir_key, fd }
}
fn entry(&mut self) -> squeue::Entry {
opcode::Close::new(types::Fd(self.fd)).build()
}
fn complete(
&mut self,
comp: &mut Completion<State, Op>,
res: io::Result<i32>,
) -> io::Result<()> {
let _ = res?;
let _ = comp.context_mut().dirs.remove(self.dir_key);
Ok(())
}
}
#[derive(Debug)]
enum Op {
Unlink(UnlinkOp),
Close(CloseOp),
}
impl RingOp<State> for Op {
fn entry(&mut self) -> squeue::Entry {
match self {
Op::Unlink(op) => op.entry(),
Op::Close(op) => op.entry(),
}
}
fn complete(
&mut self,
comp: &mut Completion<State, Self>,
res: io::Result<i32>,
) -> io::Result<()> {
match self {
Op::Unlink(op) => op.complete(comp, res),
Op::Close(op) => op.complete(comp, res),
}
}
}