use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Fresh,
Attempted,
Refused,
Created,
Removed,
}
#[doc(hidden)]
#[derive(Debug)]
pub struct CapturedDirectoryCandidate {
capture_id: u64,
name: [u8; 256],
length: usize,
state: State,
}
impl CapturedDirectoryCandidate {
pub fn name(&self) -> &str {
std::str::from_utf8(&self.name[..self.length]).expect("validated ASCII component")
}
pub fn may_own_entry(&self) -> bool {
matches!(self.state, State::Attempted | State::Created)
}
pub fn creation_completed(&self) -> bool {
self.state == State::Created
}
pub fn creation_unattempted(&self) -> bool {
self.state == State::Fresh
}
pub fn removal_completed(&self) -> bool {
self.state == State::Removed
}
}
impl SourceDirectory {
pub fn prepare_directory_candidate(
&self,
name: &str,
) -> io::Result<CapturedDirectoryCandidate> {
let name = ChildName::new(name)?;
let mut bytes = [0; 256];
bytes[..name.0.len()].copy_from_slice(name.0.as_bytes());
Ok(CapturedDirectoryCandidate {
capture_id: self.capture_id,
name: bytes,
length: name.0.len(),
state: State::Fresh,
})
}
fn require_candidate(&self, candidate: &CapturedDirectoryCandidate) -> io::Result<()> {
if candidate.capture_id != self.capture_id {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"directory candidate belongs to another parent capture",
));
}
Ok(())
}
pub fn create_directory_candidate(
&self,
context: &FileOpenContext,
candidate: &mut CapturedDirectoryCandidate,
) -> io::Result<()> {
self.require_context(context)?;
self.require_candidate(candidate)?;
if candidate.state != State::Fresh {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"directory candidate creation was already attempted",
));
}
let scope = native_scope(context)?;
self.require_scope(&scope)?;
let _permit = context.acquire(DescriptorKind::Transient)?;
let CapturedDirectoryCandidate {
name,
length,
state,
..
} = candidate;
let name = ChildName(std::str::from_utf8(&name[..*length]).expect("validated component"));
let mut native = || {
*state = State::Attempted;
let result = self
.root
.with_native(|root| platform::create_directory(root, name));
match &result {
Ok(()) => *state = State::Created,
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {
*state = State::Refused
}
Err(_) => {}
}
result
};
#[cfg(any(test, feature = "test-support"))]
let result = match self.trace.as_deref() {
Some(trace) => trace.create_directory(Path::new(name.0), native),
None => native(),
};
#[cfg(not(any(test, feature = "test-support")))]
let result = native();
result.map_err(|error| context.map_open_error(error))
}
pub fn open_created_directory(
&self,
context: &FileOpenContext,
candidate: &CapturedDirectoryCandidate,
) -> io::Result<Self> {
self.require_candidate(candidate)?;
if !candidate.creation_completed() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"directory candidate has no completed creation evidence",
));
}
self.open_directory(context, candidate.name())
}
pub fn remove_created_directory(
&self,
context: &FileOpenContext,
candidate: &mut CapturedDirectoryCandidate,
) -> io::Result<()> {
self.require_context(context)?;
self.require_candidate(candidate)?;
if candidate.removal_completed() {
return Ok(());
}
if !candidate.creation_completed() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cannot remove a directory without completed creation evidence",
));
}
let scope = native_scope(context)?;
self.require_scope(&scope)?;
let CapturedDirectoryCandidate {
name,
length,
state,
..
} = candidate;
let name = ChildName(std::str::from_utf8(&name[..*length]).expect("validated component"));
let mut native = || {
self.root
.with_native(|root| platform::remove_directory(root, name, context))?;
*state = State::Removed;
Ok(())
};
#[cfg(any(test, feature = "test-support"))]
let result = match self.trace.as_deref() {
Some(trace) => trace.remove_directory(Path::new(name.0), native),
None => native(),
};
#[cfg(not(any(test, feature = "test-support")))]
let result = native();
result.map_err(|error| context.map_open_error(error))
}
}
#[cfg(test)]
mod tests;