118 lines
3.6 KiB
Rust
118 lines
3.6 KiB
Rust
use crate::{ArtifactError, ArtifactStore, TempScan};
|
|
|
|
pub(crate) struct NamePage {
|
|
pub(crate) names: Vec<NameEntry>,
|
|
/// Opaque `telldir` position immediately after the last consumed entry.
|
|
/// It is meaningful only for the same opened directory inode.
|
|
pub(crate) continuation: Option<i64>,
|
|
}
|
|
|
|
pub(crate) struct NameEntry {
|
|
pub(crate) name: String,
|
|
/// Directory position immediately before this entry. Resuming here
|
|
/// guarantees a candidate omitted by a result limit is reconsidered.
|
|
pub(crate) cookie_before: i64,
|
|
}
|
|
|
|
pub(crate) fn valid_temp_name(name: &str) -> bool {
|
|
let Some(rest) = name.strip_prefix(ArtifactStore::temp_prefix()) else {
|
|
return false;
|
|
};
|
|
let mut fields = rest.split('-');
|
|
let (Some(nonce), Some(pid), Some(sequence), None) =
|
|
(fields.next(), fields.next(), fields.next(), fields.next())
|
|
else {
|
|
return false;
|
|
};
|
|
nonce.len() == 32
|
|
&& nonce
|
|
.bytes()
|
|
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
|
|
&& !pid.is_empty()
|
|
&& pid.bytes().all(|byte| byte.is_ascii_digit())
|
|
&& !sequence.is_empty()
|
|
&& sequence.bytes().all(|byte| byte.is_ascii_digit())
|
|
}
|
|
|
|
pub(crate) fn list_names_after(
|
|
parent: i32,
|
|
remaining: &mut usize,
|
|
report: &mut TempScan,
|
|
continuation: Option<i64>,
|
|
) -> Result<NamePage, ArtifactError> {
|
|
let dot = std::ffi::CString::new(".").map_err(|_| ArtifactError::Storage)?;
|
|
let raw = unsafe {
|
|
libc::openat(
|
|
parent,
|
|
dot.as_ptr(),
|
|
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,
|
|
)
|
|
};
|
|
if raw < 0 {
|
|
return Err(ArtifactError::Storage);
|
|
}
|
|
let directory = unsafe { libc::fdopendir(raw) };
|
|
if directory.is_null() {
|
|
unsafe {
|
|
libc::close(raw);
|
|
}
|
|
return Err(ArtifactError::Storage);
|
|
}
|
|
let mut names = Vec::new();
|
|
if let Some(cookie) = continuation {
|
|
unsafe {
|
|
libc::seekdir(directory, cookie as libc::c_long);
|
|
}
|
|
}
|
|
loop {
|
|
if *remaining == 0 {
|
|
let continuation = unsafe { libc::telldir(directory) };
|
|
unsafe {
|
|
libc::closedir(directory);
|
|
}
|
|
return Ok(NamePage {
|
|
names,
|
|
continuation: (continuation >= 0).then_some(continuation as i64),
|
|
});
|
|
}
|
|
unsafe {
|
|
*libc::__errno_location() = 0;
|
|
}
|
|
let cookie_before = unsafe { libc::telldir(directory) };
|
|
if cookie_before < 0 {
|
|
unsafe {
|
|
libc::closedir(directory);
|
|
}
|
|
return Err(ArtifactError::Storage);
|
|
}
|
|
let entry = unsafe { libc::readdir(directory) };
|
|
if entry.is_null() {
|
|
if unsafe { *libc::__errno_location() } != 0 {
|
|
unsafe {
|
|
libc::closedir(directory);
|
|
}
|
|
return Err(ArtifactError::Storage);
|
|
}
|
|
unsafe {
|
|
libc::closedir(directory);
|
|
}
|
|
return Ok(NamePage {
|
|
names,
|
|
continuation: None,
|
|
});
|
|
}
|
|
let name = unsafe { std::ffi::CStr::from_ptr((*entry).d_name.as_ptr()) }.to_bytes();
|
|
if name == b"." || name == b".." {
|
|
continue;
|
|
}
|
|
report.scanned += 1;
|
|
*remaining -= 1;
|
|
if let Ok(name) = std::str::from_utf8(name) {
|
|
names.push(NameEntry {
|
|
name: name.to_owned(),
|
|
cookie_before: cookie_before as i64,
|
|
});
|
|
}
|
|
}
|
|
}
|