use std::os::windows::io::OwnedHandle;
use std::time::{Duration, Instant};
use windows_impersonation_token_sys::ImpersonationToken;
use crate::buffer::NativeBuffer;
use crate::completion::{Completion, EnumerationId, TerminalOutcome};
use crate::completion_ring::CompletionRing;
use crate::entry::{DirectoryEntry, FileIdentityMode};
use crate::error::EnumerationError;
use crate::native::{self, Refill, RefillOutcome};
use crate::record;
use crate::request::EnumerationRequest;
use crate::session::QuantumOutcome;
const MAX_RECORDS_PER_QUANTUM: u32 = 256;
const MAX_QUANTUM_DURATION: Duration = Duration::from_millis(2);
fn quantum_budget_exhausted(examined: u32, elapsed: Duration) -> bool {
examined > 0 && (examined >= MAX_RECORDS_PER_QUANTUM || elapsed >= MAX_QUANTUM_DURATION)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Phase {
Unopened,
Opened,
Reading,
}
pub(crate) struct EngineState {
request: EnumerationRequest,
token: ImpersonationToken,
buffer: NativeBuffer,
directory: Option<OwnedHandle>,
volume_serial: Option<u64>,
phase: Phase,
cursor: Option<usize>,
awaiting_room: bool,
}
impl EngineState {
pub(crate) fn new(
request: EnumerationRequest,
token: ImpersonationToken,
buffer: NativeBuffer,
) -> Self {
Self {
request,
token,
buffer,
directory: None,
volume_serial: None,
phase: Phase::Unopened,
cursor: None,
awaiting_room: false,
}
}
pub(crate) fn volume_serial(&self) -> Option<u64> {
self.volume_serial
}
pub(crate) fn request(&self) -> &EnumerationRequest {
&self.request
}
pub(crate) fn into_parts(self) -> (EnumerationRequest, ImpersonationToken) {
(self.request, self.token)
}
#[cfg(test)]
pub(crate) fn has_pending_batch(&self) -> bool {
self.cursor.is_some()
}
}
impl std::fmt::Debug for EngineState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EngineState")
.field("phase", &self.phase)
.field("open", &self.directory.is_some())
.field("volume_serial", &self.volume_serial)
.field("cursor", &self.cursor)
.field("awaiting_room", &self.awaiting_room)
.finish_non_exhaustive()
}
}
pub(crate) fn advance(
engine: &mut EngineState,
enumeration: EnumerationId,
completions: &CompletionRing,
) -> QuantumOutcome {
if engine.phase == Phase::Unopened
&& let Some(failure) = start(engine)
{
return QuantumOutcome::Finished(TerminalOutcome::Failed(failure));
}
let Some(directory) = engine.directory.as_ref() else {
return QuantumOutcome::Finished(TerminalOutcome::Failed(
EnumerationError::DirectoryQuery(crate::error::Win32Error::from_code(0)),
));
};
if engine.cursor.is_none() {
let which = match engine.phase {
Phase::Opened => Refill::First,
_ => Refill::Next,
};
match native::refill(directory, &mut engine.buffer, which) {
RefillOutcome::Batch => {
engine.phase = Phase::Reading;
engine.cursor = Some(0);
}
RefillOutcome::Exhausted => {
return QuantumOutcome::Finished(TerminalOutcome::Completed);
}
RefillOutcome::Failed(error) => {
return QuantumOutcome::Finished(TerminalOutcome::Failed(error));
}
}
}
let started = Instant::now();
let mut examined: u32 = 0;
while let Some(offset) = engine.cursor {
if engine.awaiting_room {
if !completions.has_data_room() {
return QuantumOutcome::Parked;
}
engine.awaiting_room = false;
}
if quantum_budget_exhausted(examined, started.elapsed()) {
return QuantumOutcome::Yielded;
}
let (parsed, next) = match record::parse_record(engine.buffer.as_bytes(), offset) {
Ok(parsed) => parsed,
Err(detail) => {
return QuantumOutcome::Finished(TerminalOutcome::Failed(
EnumerationError::MalformedRecord(detail),
));
}
};
examined += 1;
if parsed.is_dot_or_dotdot() {
engine.cursor = next;
continue;
}
let entry = DirectoryEntry::from_fields(parsed.into_fields(engine.volume_serial()));
if !engine.request().predicate().matches(&entry) {
engine.cursor = next;
continue;
}
match completions.try_send_entry(Completion::Entry { enumeration, entry }) {
Ok(()) => engine.cursor = next,
Err(_) => {
engine.awaiting_room = true;
return QuantumOutcome::Parked;
}
}
}
QuantumOutcome::Yielded
}
fn start(engine: &mut EngineState) -> Option<EnumerationError> {
let directory = match native::open_directory(engine.request.path(), &engine.token) {
Ok(directory) => directory,
Err(error) => return Some(error),
};
let mode = engine.request.file_identity_mode();
if mode.queries_volume() {
match native::volume_serial(&directory) {
Ok(serial) => engine.volume_serial = Some(serial),
Err(code) => {
if mode == FileIdentityMode::Required {
return Some(EnumerationError::VolumeIdentity(code));
}
}
}
}
engine.directory = Some(directory);
engine.phase = Phase::Opened;
None
}
#[cfg(test)]
mod tests;