Files
crank/crates/crank-artifacts/src/housekeeping.rs
T
bsodfather c3188637b3
CI / Rust Checks (push) Failing after 9m29s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped
refactor(artifacts): split oversized modules
2026-08-30 00:15:07 +03:00

857 lines
30 KiB
Rust

use std::{
fmt,
os::fd::{AsRawFd, FromRawFd, OwnedFd},
};
use crate::temp_scan::valid_temp_name;
use crate::{
ArtifactError, ArtifactStore, ReconciliationMutation, ReconciliationNamespace,
ReconciliationScan, ReconciliationScanStop,
recovery::{FINAL_NAMESPACE, namespace_name, namespace_number},
store::{
QUARANTINE_DIR, RootLock, check_file, checkpointed, ensure_root_unchanged, fsync_fd,
open_existing_dir, open_or_create_dir, rename_no_replace_at, stat_fd, unlinkat,
},
};
mod stale_temp;
pub use stale_temp::{StaleTemp, TempScan, TempScanCursor};
/// Opaque bounded-scan continuation, valid only for an unchanged namespace.
/// Discard it after any put, quarantine, delete, or external mutation.
pub struct ReconciliationCursor {
root_dev: u64,
root_ino: u64,
namespace_start: u8,
namespace: u8,
namespace_end: u8,
shard: u8,
state: ReconciliationCursorState,
}
enum ReconciliationCursorState {
OpenNamespace,
ValidateNamespace(OwnedFd),
OpenShard(OwnedFd),
ValidateShard {
namespace: OwnedFd,
shard: OwnedFd,
},
ReadShard {
namespace: OwnedFd,
shard: OwnedFd,
shard_dev: u64,
shard_ino: u64,
buffer: Vec<u8>,
offset: usize,
filled: usize,
cookie: i64,
},
SeekShard {
namespace: OwnedFd,
shard: OwnedFd,
shard_dev: u64,
shard_ino: u64,
buffer: Vec<u8>,
cookie: i64,
},
Transition,
}
struct ShardIdentity(u64, u64);
impl fmt::Debug for ReconciliationCursor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("ReconciliationCursor(..)")
}
}
/// Opaque inode-bound evidence that exposes neither digest nor filesystem path.
#[derive(Clone)]
pub struct ReconciliationCandidate {
pub(crate) root_dev: u64,
pub(crate) root_ino: u64,
pub(crate) namespace: ReconciliationNamespace,
pub(crate) shard: String,
pub(crate) shard_dev: u64,
pub(crate) shard_ino: u64,
pub(crate) name: String,
pub(crate) dev: u64,
pub(crate) ino: u64,
pub(crate) modified_seconds: i64,
pub(crate) modified_nanoseconds: i64,
}
impl ArtifactStore {
pub(crate) fn scan_reconciliation_bounded(
&self,
start_namespace: ReconciliationNamespace,
end_namespace: ReconciliationNamespace,
continuation: Option<ReconciliationCursor>,
scan_budget: usize,
result_limit: usize,
) -> Result<(ReconciliationScan, Vec<ReconciliationCandidate>), ArtifactError> {
let root = self.root()?;
let namespace = namespace_number(start_namespace);
let namespace_end = namespace_number(end_namespace);
let mut cursor = match continuation {
Some(cursor) => {
if cursor.root_dev != root.dev
|| cursor.root_ino != root.ino
|| cursor.namespace_start != namespace
|| cursor.namespace < namespace
|| cursor.namespace > cursor.namespace_end
|| cursor.namespace_end != namespace_end
{
return Err(ArtifactError::UnsafeRoot);
}
cursor
}
None => reconciliation_cursor(root.dev, root.ino, namespace, namespace_end),
};
let report = ReconciliationScan::empty(None);
let _lock = match RootLock::shared(root) {
Ok(lock) => lock,
Err(ArtifactError::Storage) => {
return Ok(reconciliation_page(
report,
Vec::new(),
cursor,
ReconciliationScanStop::Retryable,
));
}
Err(error) => return Err(error),
};
match ensure_root_unchanged(root) {
Ok(()) => {}
Err(ArtifactError::Storage) => {
return Ok(reconciliation_page(
report,
Vec::new(),
cursor,
ReconciliationScanStop::Retryable,
));
}
Err(error) => return Err(error),
}
let mut report = report;
if scan_budget == 0 {
return Ok(reconciliation_page(
report,
Vec::new(),
cursor,
ReconciliationScanStop::ScanBudget,
));
}
if result_limit == 0 {
return Ok(reconciliation_page(
report,
Vec::new(),
cursor,
ReconciliationScanStop::ResultLimit,
));
}
let mut entries = Vec::new();
while cursor.namespace <= cursor.namespace_end {
if entries.len() == result_limit {
return Ok(reconciliation_page(
report,
entries,
cursor,
ReconciliationScanStop::ResultLimit,
));
}
if report.traversal_syscalls == scan_budget {
return Ok(reconciliation_page(
report,
entries,
cursor,
ReconciliationScanStop::ScanBudget,
));
}
let state = std::mem::replace(&mut cursor.state, ReconciliationCursorState::Transition);
match reconciliation_step(root, &mut cursor, state, &mut report, &mut entries) {
Ok(()) => {}
Err((state, ArtifactError::Storage)) => {
cursor.state = state;
return Ok(reconciliation_page(
report,
entries,
cursor,
ReconciliationScanStop::Retryable,
));
}
Err((_, error)) => return Err(error),
}
}
report.stop = ReconciliationScanStop::Complete;
Ok((report, entries))
}
/// Moves a revalidated final inode to quarantine without replacing an inode.
pub fn quarantine_reconciliation(
&self,
candidate: ReconciliationCandidate,
) -> Result<ReconciliationMutation, ArtifactError> {
if candidate.namespace != ReconciliationNamespace::Final {
return Err(ArtifactError::UnsafeRoot);
}
let root = self.root()?;
let _lock = RootLock::exclusive(root)?;
ensure_reconciliation_root(root, &candidate)?;
let final_root = open_existing_dir(root.fd.as_raw_fd(), namespace_name(FINAL_NAMESPACE))?;
let final_shard = open_existing_dir(final_root.as_raw_fd(), candidate.shard.as_bytes())?;
match revalidate_candidate(&candidate, final_shard.as_raw_fd()) {
Ok(()) => {}
Err(ArtifactError::NotFound) => {
return self.recover_quarantine(root, &candidate);
}
Err(ArtifactError::UnsafeRoot) => {
return match self.recover_quarantine(root, &candidate) {
Ok(outcome) => Ok(outcome),
Err(ArtifactError::NotFound) => Err(ArtifactError::UnsafeRoot),
Err(error) => Err(error),
};
}
Err(error) => return Err(error),
}
let quarantine_root = open_or_create_dir(root.fd.as_raw_fd(), QUARANTINE_DIR)?;
let quarantine_shard =
open_or_create_dir(quarantine_root.as_raw_fd(), candidate.shard.as_bytes())?;
match checkpointed("reconciliation_quarantine_rename", || {
rename_no_replace_at(
final_shard.as_raw_fd(),
candidate.name.as_bytes(),
quarantine_shard.as_raw_fd(),
candidate.name.as_bytes(),
)
})? {
true => match fsync_quarantine_dirs(&final_shard, &quarantine_shard) {
Ok(()) => Ok(ReconciliationMutation::Quarantined),
Err(_) => Ok(ReconciliationMutation::Retryable),
},
false => self.recover_quarantine(root, &candidate),
}
}
/// Idempotently removes a revalidated quarantined inode and fsyncs its directory.
pub fn delete_quarantined_reconciliation(
&self,
candidate: ReconciliationCandidate,
) -> Result<ReconciliationMutation, ArtifactError> {
if candidate.namespace != ReconciliationNamespace::Quarantine {
return Err(ArtifactError::UnsafeRoot);
}
let root = self.root()?;
let _lock = RootLock::exclusive(root)?;
ensure_reconciliation_root(root, &candidate)?;
let quarantine_root = match open_existing_dir(root.fd.as_raw_fd(), QUARANTINE_DIR) {
Ok(fd) => fd,
Err(ArtifactError::NotFound) => return Ok(ReconciliationMutation::AlreadyAbsent),
Err(error) => return Err(error),
};
let shard = match open_existing_dir(quarantine_root.as_raw_fd(), candidate.shard.as_bytes())
{
Ok(fd) => fd,
Err(ArtifactError::NotFound) => return Ok(ReconciliationMutation::AlreadyAbsent),
Err(error) => return Err(error),
};
match revalidate_candidate(&candidate, shard.as_raw_fd()) {
Ok(()) => {}
Err(ArtifactError::NotFound) => {
return fsync_mutation(&shard, ReconciliationMutation::AlreadyAbsent);
}
Err(error) => return Err(error),
}
match checkpointed("reconciliation_delete_unlink", || {
unlinkat(shard.as_raw_fd(), candidate.name.as_bytes())
}) {
Ok(()) => fsync_mutation(&shard, ReconciliationMutation::Deleted),
Err(ArtifactError::NotFound) => {
fsync_mutation(&shard, ReconciliationMutation::AlreadyAbsent)
}
Err(error) => Err(error),
}
}
fn recover_quarantine(
&self,
root: &crate::store::Root,
candidate: &ReconciliationCandidate,
) -> Result<ReconciliationMutation, ArtifactError> {
let final_root = open_existing_dir(root.fd.as_raw_fd(), namespace_name(FINAL_NAMESPACE))?;
let final_shard = open_existing_dir(final_root.as_raw_fd(), candidate.shard.as_bytes())?;
let quarantine_root = match open_existing_dir(root.fd.as_raw_fd(), QUARANTINE_DIR) {
Ok(fd) => fd,
Err(ArtifactError::NotFound) => return Err(ArtifactError::NotFound),
Err(error) => return Err(error),
};
let shard = match open_existing_dir(quarantine_root.as_raw_fd(), candidate.shard.as_bytes())
{
Ok(fd) => fd,
Err(ArtifactError::NotFound) => return Err(ArtifactError::NotFound),
Err(error) => return Err(error),
};
revalidate_moved_candidate(candidate, shard.as_raw_fd())?;
match fsync_quarantine_dirs(&final_shard, &shard) {
Ok(()) => Ok(ReconciliationMutation::AlreadyQuarantined),
Err(_) => Ok(ReconciliationMutation::Retryable),
}
}
}
fn reconciliation_cursor(
root_dev: u64,
root_ino: u64,
namespace: u8,
namespace_end: u8,
) -> ReconciliationCursor {
ReconciliationCursor {
root_dev,
root_ino,
namespace_start: namespace,
namespace,
namespace_end,
shard: 0,
state: ReconciliationCursorState::OpenNamespace,
}
}
fn reconciliation_page(
mut report: ReconciliationScan,
entries: Vec<ReconciliationCandidate>,
cursor: ReconciliationCursor,
stop: ReconciliationScanStop,
) -> (ReconciliationScan, Vec<ReconciliationCandidate>) {
report.stop = stop;
report.continuation = Some(cursor);
(report, entries)
}
fn reconciliation_step(
root: &crate::store::Root,
cursor: &mut ReconciliationCursor,
state: ReconciliationCursorState,
report: &mut ReconciliationScan,
entries: &mut Vec<ReconciliationCandidate>,
) -> Result<(), (ReconciliationCursorState, ArtifactError)> {
match state {
ReconciliationCursorState::OpenNamespace => {
count_traversal_syscall(report);
match open_traversal_dir(root.fd.as_raw_fd(), namespace_name(cursor.namespace)) {
Ok(namespace) => {
cursor.state = ReconciliationCursorState::ValidateNamespace(namespace);
}
Err(ArtifactError::NotFound) => advance_namespace(cursor),
Err(ArtifactError::UnsafeRoot) => {
report.unsafe_entries += 1;
advance_namespace(cursor);
}
Err(error) => {
return Err((ReconciliationCursorState::OpenNamespace, error));
}
}
}
ReconciliationCursorState::ValidateNamespace(namespace) => {
count_traversal_syscall(report);
match validate_traversal_dir(&namespace) {
Ok(_) => {
cursor.state = ReconciliationCursorState::OpenShard(namespace);
}
Err(ArtifactError::UnsafeRoot) => {
report.unsafe_entries += 1;
advance_namespace(cursor);
}
Err(error) => {
return Err((
ReconciliationCursorState::ValidateNamespace(namespace),
error,
));
}
}
}
ReconciliationCursorState::OpenShard(namespace) => {
let shard_name = format!("{:02x}", cursor.shard);
count_traversal_syscall(report);
match open_traversal_dir(namespace.as_raw_fd(), shard_name.as_bytes()) {
Ok(shard) => {
cursor.state = ReconciliationCursorState::ValidateShard { namespace, shard };
}
Err(ArtifactError::NotFound) => advance_reconciliation_shard(cursor, namespace),
Err(ArtifactError::UnsafeRoot) => {
report.unsafe_entries += 1;
advance_reconciliation_shard(cursor, namespace);
}
Err(error) => {
return Err((ReconciliationCursorState::OpenShard(namespace), error));
}
}
}
ReconciliationCursorState::ValidateShard { namespace, shard } => {
count_traversal_syscall(report);
match validate_traversal_dir(&shard) {
Ok(stat) => {
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(stat.st_dev, stat.st_ino),
vec![0; 8192],
0,
0,
0,
);
}
Err(ArtifactError::UnsafeRoot) => {
report.unsafe_entries += 1;
advance_reconciliation_shard(cursor, namespace);
}
Err(error) => {
return Err((
ReconciliationCursorState::ValidateShard { namespace, shard },
error,
));
}
}
}
ReconciliationCursorState::ReadShard {
namespace,
shard,
shard_dev,
shard_ino,
mut buffer,
mut offset,
mut filled,
mut cookie,
} => {
if offset == filled {
count_traversal_syscall(report);
match checkpointed("reconciliation_getdents", || {
getdents64(shard.as_raw_fd(), &mut buffer)
}) {
Ok(0) => advance_reconciliation_shard(cursor, namespace),
Ok(count) => {
offset = 0;
filled = count;
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
);
}
Err(error) => {
return Err((
ReconciliationCursorState::SeekShard {
namespace,
shard,
shard_dev,
shard_ino,
buffer,
cookie,
},
error,
));
}
}
return Ok(());
}
let (name, after, after_cookie) = match parse_dirent(&buffer[..filled], offset) {
Ok(entry) => entry,
Err(error) => {
return Err((
read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
),
error,
));
}
};
if name == b"." || name == b".." {
offset = after;
cookie = after_cookie;
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
);
return Ok(());
}
let shard_name = format!("{:02x}", cursor.shard);
let Ok(name) = String::from_utf8(name) else {
report.scanned += 1;
report.malformed += 1;
offset = after;
cookie = after_cookie;
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
);
return Ok(());
};
if !valid_final_name(&name, &shard_name) {
report.scanned += 1;
if cursor.namespace != FINAL_NAMESPACE || !valid_temp_name(&name) {
report.malformed += 1;
}
offset = after;
cookie = after_cookie;
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
);
return Ok(());
}
#[cfg(debug_assertions)]
let _ = crate::test_support::checkpoint("reconciliation_before_stat");
count_traversal_syscall(report);
let stat = match checkpointed("reconciliation_stat", || {
record_traversal_call();
nofollow_stat(shard.as_raw_fd(), &name)
}) {
Ok(stat) => Some(stat),
Err(ArtifactError::NotFound) => None,
Err(ArtifactError::UnsafeRoot) => {
report.unsafe_entries += 1;
None
}
Err(error) => {
return Err((
read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
),
error,
));
}
};
report.scanned += 1;
offset = after;
cookie = after_cookie;
if let Some(stat) = stat {
if check_file(&stat).is_err() {
report.unsafe_entries += 1;
} else {
let candidate_namespace = if cursor.namespace == FINAL_NAMESPACE {
report.final_entries += 1;
ReconciliationNamespace::Final
} else {
report.quarantined_entries += 1;
ReconciliationNamespace::Quarantine
};
entries.push(ReconciliationCandidate {
root_dev: root.dev,
root_ino: root.ino,
namespace: candidate_namespace,
shard: shard_name,
shard_dev,
shard_ino,
name,
dev: stat.st_dev,
ino: stat.st_ino,
modified_seconds: stat.st_mtime,
modified_nanoseconds: stat.st_mtime_nsec,
});
}
}
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
offset,
filled,
cookie,
);
}
ReconciliationCursorState::SeekShard {
namespace,
shard,
shard_dev,
shard_ino,
buffer,
cookie,
} => {
count_traversal_syscall(report);
record_traversal_call();
if unsafe { libc::lseek(shard.as_raw_fd(), cookie, libc::SEEK_SET) } != cookie {
return Err((
ReconciliationCursorState::SeekShard {
namespace,
shard,
shard_dev,
shard_ino,
buffer,
cookie,
},
ArtifactError::Storage,
));
}
cursor.state = read_shard_state(
namespace,
shard,
ShardIdentity(shard_dev, shard_ino),
buffer,
0,
0,
cookie,
);
}
ReconciliationCursorState::Transition => unreachable!("temporary scanner state"),
}
Ok(())
}
fn count_traversal_syscall(report: &mut ReconciliationScan) {
report.traversal_syscalls += 1;
}
fn read_shard_state(
namespace: OwnedFd,
shard: OwnedFd,
shard_identity: ShardIdentity,
buffer: Vec<u8>,
offset: usize,
filled: usize,
cookie: i64,
) -> ReconciliationCursorState {
let ShardIdentity(shard_dev, shard_ino) = shard_identity;
ReconciliationCursorState::ReadShard {
namespace,
shard,
shard_dev,
shard_ino,
buffer,
offset,
filled,
cookie,
}
}
fn open_traversal_dir(parent: i32, name: &[u8]) -> Result<OwnedFd, ArtifactError> {
let name = crate::store::c_name(name)?;
record_traversal_call();
let raw = unsafe {
libc::openat(
parent,
name.as_ptr(),
libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
)
};
if raw < 0 {
match unsafe { *libc::__errno_location() } {
libc::ENOENT => Err(ArtifactError::NotFound),
libc::EACCES | libc::ELOOP | libc::ENOTDIR => Err(ArtifactError::UnsafeRoot),
_ => Err(ArtifactError::Storage),
}
} else {
Ok(unsafe { OwnedFd::from_raw_fd(raw) })
}
}
fn validate_traversal_dir(directory: &OwnedFd) -> Result<libc::stat, ArtifactError> {
record_traversal_call();
let stat = stat_fd(directory.as_raw_fd())?;
if stat.st_uid != unsafe { libc::geteuid() }
|| (stat.st_mode & libc::S_IFMT) != libc::S_IFDIR
|| (stat.st_mode & 0o777) != 0o700
{
Err(ArtifactError::UnsafeRoot)
} else {
Ok(stat)
}
}
fn getdents64(directory: i32, buffer: &mut [u8]) -> Result<usize, ArtifactError> {
record_traversal_call();
let count = unsafe {
libc::syscall(
libc::SYS_getdents64,
directory,
buffer.as_mut_ptr(),
buffer.len(),
)
};
if count < 0 {
Err(ArtifactError::Storage)
} else {
usize::try_from(count).map_err(|_| ArtifactError::Storage)
}
}
fn parse_dirent(buffer: &[u8], offset: usize) -> Result<(Vec<u8>, usize, i64), ArtifactError> {
const HEADER: usize = 19;
if buffer.len().saturating_sub(offset) < HEADER {
return Err(ArtifactError::Storage);
}
let record_length = usize::from(u16::from_ne_bytes([
buffer[offset + 16],
buffer[offset + 17],
]));
let after = offset
.checked_add(record_length)
.filter(|after| record_length >= HEADER && *after <= buffer.len())
.ok_or(ArtifactError::Storage)?;
let raw_name = &buffer[offset + HEADER..after];
let end = raw_name
.iter()
.position(|byte| *byte == 0)
.ok_or(ArtifactError::Storage)?;
let cookie = i64::from_ne_bytes(
buffer[offset + 8..offset + 16]
.try_into()
.map_err(|_| ArtifactError::Storage)?,
);
Ok((raw_name[..end].to_vec(), after, cookie))
}
fn advance_reconciliation_shard(cursor: &mut ReconciliationCursor, namespace: OwnedFd) {
if cursor.shard == u8::MAX {
advance_namespace(cursor);
} else {
cursor.shard += 1;
cursor.state = ReconciliationCursorState::OpenShard(namespace);
}
}
fn advance_namespace(cursor: &mut ReconciliationCursor) {
cursor.namespace = if cursor.namespace >= cursor.namespace_end {
cursor.namespace_end.saturating_add(1)
} else {
cursor.namespace + 1
};
cursor.shard = 0;
cursor.state = ReconciliationCursorState::OpenNamespace;
}
pub(crate) fn ensure_reconciliation_root(
root: &crate::store::Root,
candidate: &ReconciliationCandidate,
) -> Result<(), ArtifactError> {
ensure_root_unchanged(root)?;
if candidate.root_dev != root.dev
|| candidate.root_ino != root.ino
|| candidate.shard.len() != 2
|| !candidate.shard.bytes().all(is_lower_hex)
|| !valid_final_name(&candidate.name, &candidate.shard)
{
return Err(ArtifactError::UnsafeRoot);
}
Ok(())
}
pub(crate) fn revalidate_candidate(
candidate: &ReconciliationCandidate,
shard: i32,
) -> Result<(), ArtifactError> {
let shard_stat = stat_fd(shard)?;
if shard_stat.st_dev != candidate.shard_dev || shard_stat.st_ino != candidate.shard_ino {
return Err(ArtifactError::UnsafeRoot);
}
let stat = nofollow_stat(shard, &candidate.name)?;
if stat.st_dev != candidate.dev
|| stat.st_ino != candidate.ino
|| stat.st_mtime != candidate.modified_seconds
|| stat.st_mtime_nsec != candidate.modified_nanoseconds
{
return Err(ArtifactError::UnsafeRoot);
}
check_file(&stat).map_err(|_| ArtifactError::UnsafeRoot)
}
/// After cross-directory rename, only canonical name and inode remain common;
/// the quarantine shard cannot equal the capability's recorded final shard.
fn revalidate_moved_candidate(
candidate: &ReconciliationCandidate,
shard: i32,
) -> Result<(), ArtifactError> {
let stat = nofollow_stat(shard, &candidate.name)?;
if stat.st_dev != candidate.dev
|| stat.st_ino != candidate.ino
|| stat.st_mtime != candidate.modified_seconds
|| stat.st_mtime_nsec != candidate.modified_nanoseconds
{
return Err(ArtifactError::UnsafeRoot);
}
check_file(&stat).map_err(|_| ArtifactError::UnsafeRoot)
}
fn fsync_mutation(
shard: &std::os::fd::OwnedFd,
success: ReconciliationMutation,
) -> Result<ReconciliationMutation, ArtifactError> {
match checkpointed("reconciliation_delete_fsync", || {
fsync_fd(shard.as_raw_fd())
}) {
Ok(()) => Ok(success),
Err(_) => Ok(ReconciliationMutation::Retryable),
}
}
/// Attempts both directory fsyncs; either failure leaves a retryable ambiguity.
fn fsync_quarantine_dirs(
final_shard: &std::os::fd::OwnedFd,
quarantine_shard: &std::os::fd::OwnedFd,
) -> Result<(), ArtifactError> {
let source = checkpointed("reconciliation_quarantine_source_fsync", || {
fsync_fd(final_shard.as_raw_fd())
});
let destination = checkpointed("reconciliation_quarantine_destination_fsync", || {
fsync_fd(quarantine_shard.as_raw_fd())
});
source.and(destination)
}
fn valid_final_name(name: &str, shard: &str) -> bool {
name.len() == 64 && name.starts_with(shard) && name.bytes().all(is_lower_hex)
}
fn is_lower_hex(byte: u8) -> bool {
byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)
}
fn nofollow_stat(parent: i32, name: &str) -> Result<libc::stat, ArtifactError> {
let name = crate::store::c_name(name.as_bytes())?;
let mut stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstatat(parent, name.as_ptr(), &mut stat, libc::AT_SYMLINK_NOFOLLOW) } == 0 {
Ok(stat)
} else {
Err(crate::store::classify_errno())
}
}
fn record_traversal_call() {
#[cfg(debug_assertions)]
crate::test_support::record_traversal_call();
}