use std::fs::File;
use std::ffi::CString;
use std::path::Path;
use std::sync::Arc;
use tracing::debug;
#[cfg(target_os = "linux")]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "linux")]
use std::os::fd::{RawFd, FromRawFd, IntoRawFd, OwnedFd, AsRawFd};
#[cfg(not(target_os = "linux"))]
struct RawFd {}
#[cfg(not(target_os = "linux"))]
struct OwnedFd {}
#[derive(Clone)]
pub struct DupFd {
fd: RawFd,
}
impl DupFd {
pub fn new(file: File) -> Self {
Self {
fd: file.into_raw_fd()
}
}
pub fn open(&self) -> Option<File> {
unsafe {
let cloned_fd = libc::dup(self.fd);
if cloned_fd != -1 {
return Some(File::from_raw_fd(cloned_fd));
}
None
}
}
}
#[derive(Clone)]
pub struct OpenAt {
fd: Arc<OwnedFd>,
}
impl OpenAt {
pub fn new(dir: File) -> Self {
Self {
fd: Arc::new(OwnedFd::from(dir))
}
}
pub fn open_file(
&self,
path: &Path) -> anyhow::Result<File> {
let path_cstring = CString::new(path.as_os_str().as_bytes())?;
let mut path_bytes = path_cstring.as_bytes_with_nul();
if path_bytes[0] == b'/' {
path_bytes = &path_bytes[1..]
}
unsafe {
let fd = libc::openat(
self.fd.as_raw_fd(),
path_bytes.as_ptr() as *const libc::c_char,
libc::O_RDONLY | libc::O_CLOEXEC);
if fd == -1 {
debug!("Failed to open file with openat: path={:?}", path);
return Err(std::io::Error::last_os_error().into());
}
debug!("File opened via openat: path={:?}, fd={}", path, fd);
Ok(File::from_raw_fd(fd))
}
}
pub fn remove(
&self,
path: &Path) -> anyhow::Result<()> {
let path = CString::new(path.as_os_str().as_bytes())?;
let mut path = path.as_bytes_with_nul();
if path[0] == b'/' {
path = &path[1..]
}
unsafe {
let result = libc::unlinkat(
self.fd.as_raw_fd(),
path.as_ptr() as *const libc::c_char,
0);
if result != 0 && result != libc::ENOENT {
return Err(std::io::Error::last_os_error().into());
}
Ok(())
}
}
pub fn find(
&self,
path: &Path,
prefix: &str) -> Option<Vec<String>> {
let file = self.open_file(path);
if file.is_err() {
debug!("Failed to open directory for find operation: path={:?}, prefix={}", path, prefix);
return None;
}
let file = file.unwrap();
let fd = file.into_raw_fd();
let mut paths = Vec::new();
unsafe {
let dir = libc::fdopendir(fd);
if dir.is_null() {
debug!("Failed to open directory stream: path={:?}", path);
return None;
}
loop {
let entry = libc::readdir(dir);
if entry.is_null() {
break;
}
#[cfg(target_arch = "x86_64")]
let name = &(*entry).d_name as *const i8;
#[cfg(target_arch = "aarch64")]
let name = &(*entry).d_name as *const u8;
let len = libc::strlen(name);
let name = std::str::from_utf8_unchecked(
std::slice::from_raw_parts(
name as *const u8,
len));
if name.starts_with(prefix) {
paths.push(name.to_string());
}
}
libc::closedir(dir);
}
if paths.is_empty() {
debug!("No matching paths found: path={:?}, prefix={}", path, prefix);
return None;
}
debug!("Found matching paths: path={:?}, count={}, prefix={}", path, paths.len(), prefix);
Some(paths)
}
}