Skip to main content

acyclic_fs/
demand.rs

1//! Bounded, versioned access to unresolved filesystem sources.
2//!
3//! A source reference identifies a provider capability. It never contains a
4//! host path or provider credential. Resolving one request does not publish a
5//! workspace generation or make the result part of recorded history.
6
7use crate::cancellation::CancellationToken;
8use crate::kernel::{NamespacePath, NamespacePathError};
9use crate::performance::{OperationFailure, OperationReceipt, WorkCounters};
10use bytes::Bytes;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14/// Opaque identity and invalidation epoch of an attached source.
15#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
16pub struct SourceReference {
17    /// Provider-scoped opaque identity; no local path is encoded here.
18    pub identity: [u8; 16],
19    /// Incremented when source knowledge is invalidated.
20    pub epoch: u64,
21}
22
23/// Evidence for the exact node observed by a source request.
24#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
25pub struct SourceVersion(pub [u8; 32]);
26
27/// The kind of an observed source node.
28#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub enum SourceNodeKind {
30    /// Directory, whose children remain unresolved until requested.
31    Directory,
32    /// Regular file, whose content ranges remain unresolved until requested.
33    RegularFile,
34    /// Symbolic link, never followed by the source provider.
35    SymbolicLink,
36    /// POSIX named pipe.
37    Fifo,
38    /// POSIX local socket node.
39    Socket,
40    /// Native character device.
41    CharacterDevice,
42    /// Native block device.
43    BlockDevice,
44    /// Another native node kind which cannot be projected losslessly.
45    Unsupported,
46}
47
48/// Scalar host metadata. `None` means the provider did not make that fact
49/// available; it does not mean a numeric zero.
50#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
51pub struct SourceMetadata {
52    /// POSIX permission and kind bits, when available.
53    pub posix_mode: Option<u32>,
54    /// POSIX numeric owner, when available.
55    pub posix_uid: Option<u32>,
56    /// POSIX numeric group, when available.
57    pub posix_gid: Option<u32>,
58    /// POSIX inode flags, when available.
59    pub posix_flags: Option<u64>,
60    /// Windows file attributes, when available.
61    pub windows_attributes: Option<u32>,
62    /// Creation/birth time in signed Unix nanoseconds.
63    pub created_ns: Option<i128>,
64    /// Last content modification time in signed Unix nanoseconds.
65    pub modified_ns: Option<i128>,
66    /// Last access time in signed Unix nanoseconds.
67    pub accessed_ns: Option<i128>,
68    /// Last metadata-change time in signed Unix nanoseconds.
69    pub changed_ns: Option<i128>,
70}
71
72/// Metadata returned by an exact path lookup.
73#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct SourceNode {
75    /// Source node kind.
76    pub kind: SourceNodeKind,
77    /// Stable path-independent source identity. Hard-link aliases share it.
78    pub file_identity: [u8; 32],
79    /// Exact number of source namespace bindings when the provider exposes it.
80    pub link_count: Option<u64>,
81    /// Native device identity for character and block devices.
82    pub device: Option<(u32, u32)>,
83    /// Logical length for a regular file; absent for other kinds.
84    pub logical_bytes: Option<u64>,
85    /// Version that must match a later read of this node.
86    pub version: SourceVersion,
87    /// Explicitly available scalar metadata.
88    pub metadata: SourceMetadata,
89}
90
91/// One directory entry in native enumeration order.
92#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct SourceDirectoryEntry {
94    /// Lossless host component encoding.
95    pub name: crate::kernel::LogicalName,
96    /// Entry kind, observed without reading its body.
97    pub kind: SourceNodeKind,
98}
99
100/// Cursor bound to one directory and one source epoch.
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct SourceCursor {
103    source: SourceReference,
104    directory: NamespacePath,
105    directory_version: SourceVersion,
106    token: u64,
107}
108
109/// One bounded directory page. Native pages retain native enumeration order.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct SourceDirectoryPage {
112    /// At most the requested number of entries.
113    pub entries: Vec<SourceDirectoryEntry>,
114    /// Opaque continuation bound to this source and directory version.
115    pub next: Option<SourceCursor>,
116    /// Directory metadata evidence for this page.
117    pub version: SourceVersion,
118}
119
120/// Typed failures from a source dependency.
121#[derive(Debug, Error)]
122pub enum DemandError {
123    /// The reference is for another source or an invalidated epoch.
124    #[error("source reference is stale")]
125    StaleSource,
126    /// The node changed while a request was being answered.
127    #[error("source node changed during the request")]
128    StaleVersion,
129    /// A directory cursor is expired or belongs to another query.
130    #[error("source directory cursor is expired or mismatched")]
131    StaleCursor,
132    /// Another request is currently advancing this cursor.
133    #[error("source directory cursor is in use")]
134    CursorBusy,
135    /// Source root disappeared or was replaced.
136    #[error("source root is unavailable")]
137    SourceUnavailable,
138    /// Exact requested path is absent.
139    #[error("source path is absent")]
140    Absent,
141    /// Request requires a regular file.
142    #[error("source node is not a regular file")]
143    NotRegularFile,
144    /// Request requires a directory.
145    #[error("source node is not a directory")]
146    NotDirectory,
147    /// Bounds or path are invalid.
148    #[error("source request is invalid")]
149    InvalidRequest,
150    /// Request was cancelled.
151    #[error("source request was cancelled")]
152    Cancelled,
153    /// The bounded native worker could not complete the request.
154    #[error("native source worker failed")]
155    WorkerUnavailable,
156    /// Native or remote source failed.
157    #[error("source failed: {0}")]
158    Io(#[from] std::io::Error),
159    /// Demand-driven native observation could not be admitted exactly.
160    #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
161    #[error(transparent)]
162    Observation(#[from] crate::watch::NativeWatchError),
163}
164
165impl From<NamespacePathError> for DemandError {
166    fn from(_: NamespacePathError) -> Self {
167        Self::InvalidRequest
168    }
169}
170
171/// Demand result with source-work counters on both success and failure.
172pub type DemandResult<T> = Result<OperationReceipt<T>, OperationFailure<DemandError>>;
173
174#[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
175fn measured<T>(
176    operation: impl FnOnce(&mut WorkCounters) -> Result<T, DemandError>,
177) -> DemandResult<T> {
178    let mut work = WorkCounters::default();
179    operation(&mut work)
180        .map(|value| OperationReceipt { value, work })
181        .map_err(|error| OperationFailure::new(error, work))
182}
183
184/// Provider contract for exact lookup, bounded pages, ranges, and metadata.
185/// Implementations must validate the reference and version for each request.
186/// Async methods do not by themselves guarantee kernel completion I/O; each
187/// provider must document its execution mechanism separately.
188#[async_trait::async_trait]
189pub trait DemandSource: Send + Sync {
190    /// Current opaque source reference.
191    fn reference(&self) -> SourceReference;
192
193    /// Resolve only the requested path. `None` is known absence.
194    async fn lookup(
195        &self,
196        source: SourceReference,
197        path: &NamespacePath,
198        cancellation: &CancellationToken,
199    ) -> DemandResult<Option<SourceNode>>;
200
201    /// Continue one directory enumeration by at most `maximum_entries`.
202    async fn list_page(
203        &self,
204        source: SourceReference,
205        directory: &NamespacePath,
206        cursor: Option<SourceCursor>,
207        maximum_entries: u32,
208        cancellation: &CancellationToken,
209    ) -> DemandResult<SourceDirectoryPage>;
210
211    /// Read at most `length` bytes at `offset`, using exact version evidence.
212    async fn read_range(
213        &self,
214        source: SourceReference,
215        path: &NamespacePath,
216        expected: SourceVersion,
217        offset: u64,
218        length: u64,
219        cancellation: &CancellationToken,
220    ) -> DemandResult<Bytes>;
221
222    /// Reads one symbolic link's exact opaque target without following it.
223    async fn read_link(
224        &self,
225        source: SourceReference,
226        path: &NamespacePath,
227        expected: SourceVersion,
228        cancellation: &CancellationToken,
229    ) -> DemandResult<Bytes>;
230
231    /// Read metadata without reading a file body.
232    async fn read_metadata(
233        &self,
234        source: SourceReference,
235        path: &NamespacePath,
236        expected: Option<SourceVersion>,
237        cancellation: &CancellationToken,
238    ) -> DemandResult<Option<SourceNode>> {
239        let answer = self.lookup(source, path, cancellation).await?;
240        if let Some(expected) = expected {
241            match answer.value {
242                Some(node) if node.version == expected => {}
243                _ => {
244                    return Err(OperationFailure::new(
245                        DemandError::StaleVersion,
246                        answer.work,
247                    ));
248                }
249            }
250        }
251        Ok(answer)
252    }
253}
254
255/// Synchronous admission hook for directories reached by a native lazy source.
256///
257/// Implementations normally install one non-recursive native notification
258/// subscription. The hook runs before the corresponding filesystem read, so a
259/// change cannot race between first observation and watcher admission. It must
260/// inspect only the named directory and its ancestors; recursive enumeration
261/// would violate the lazy-source contract.
262#[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
263pub trait DemandDirectoryObserver: Send + Sync {
264    /// Admits one exact directory for observation.
265    ///
266    /// # Errors
267    ///
268    /// Fails closed when the directory cannot be observed exactly.
269    fn observe_directory(&self, directory: &NamespacePath) -> Result<(), DemandError>;
270}
271
272/// A composable source view that hides exact subtrees before they enter lazy
273/// observation state. This is the SDK boundary used for host-private entries
274/// such as a physical checkout's `.git` directory.
275#[derive(Clone)]
276pub struct FilteredDemandSource<D> {
277    inner: D,
278    excluded: std::sync::Arc<Vec<NamespacePath>>,
279}
280
281impl<D> FilteredDemandSource<D> {
282    /// Wraps a source with canonical excluded subtree roots.
283    #[must_use]
284    pub fn new(inner: D, excluded: Vec<NamespacePath>) -> Self {
285        Self {
286            inner,
287            excluded: std::sync::Arc::new(excluded),
288        }
289    }
290
291    /// Returns the wrapped source capability.
292    #[must_use]
293    pub const fn inner(&self) -> &D {
294        &self.inner
295    }
296
297    fn excludes(&self, path: &NamespacePath) -> bool {
298        self.excluded.iter().any(|root| path.is_within(root))
299    }
300
301    fn excludes_child(&self, directory: &NamespacePath, name: &crate::kernel::LogicalName) -> bool {
302        self.excluded.iter().any(|root| {
303            root.components().len() == directory.components().len().saturating_add(1)
304                && root.components().starts_with(directory.components())
305                && root.components().last() == Some(name)
306        })
307    }
308}
309
310#[async_trait::async_trait]
311impl<D: DemandSource> DemandSource for FilteredDemandSource<D> {
312    fn reference(&self) -> SourceReference {
313        self.inner.reference()
314    }
315
316    async fn lookup(
317        &self,
318        source: SourceReference,
319        path: &NamespacePath,
320        cancellation: &CancellationToken,
321    ) -> DemandResult<Option<SourceNode>> {
322        if self.excludes(path) {
323            return Ok(OperationReceipt {
324                value: None,
325                work: WorkCounters::default(),
326            });
327        }
328        self.inner.lookup(source, path, cancellation).await
329    }
330
331    async fn list_page(
332        &self,
333        source: SourceReference,
334        directory: &NamespacePath,
335        cursor: Option<SourceCursor>,
336        maximum_entries: u32,
337        cancellation: &CancellationToken,
338    ) -> DemandResult<SourceDirectoryPage> {
339        if self.excludes(directory) {
340            return Err(OperationFailure::new(
341                DemandError::Absent,
342                WorkCounters::default(),
343            ));
344        }
345        let mut page = self
346            .inner
347            .list_page(source, directory, cursor, maximum_entries, cancellation)
348            .await?;
349        page.value
350            .entries
351            .retain(|entry| !self.excludes_child(directory, &entry.name));
352        Ok(page)
353    }
354
355    async fn read_range(
356        &self,
357        source: SourceReference,
358        path: &NamespacePath,
359        expected: SourceVersion,
360        offset: u64,
361        length: u64,
362        cancellation: &CancellationToken,
363    ) -> DemandResult<Bytes> {
364        if self.excludes(path) {
365            return Err(OperationFailure::new(
366                DemandError::Absent,
367                WorkCounters::default(),
368            ));
369        }
370        self.inner
371            .read_range(source, path, expected, offset, length, cancellation)
372            .await
373    }
374
375    async fn read_link(
376        &self,
377        source: SourceReference,
378        path: &NamespacePath,
379        expected: SourceVersion,
380        cancellation: &CancellationToken,
381    ) -> DemandResult<Bytes> {
382        if self.excludes(path) {
383            return Err(OperationFailure::new(
384                DemandError::Absent,
385                WorkCounters::default(),
386            ));
387        }
388        self.inner
389            .read_link(source, path, expected, cancellation)
390            .await
391    }
392}
393
394/// Native provider backed by a held, no-follow directory capability.
395#[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
396pub mod native {
397    use super::*;
398    use crate::model::{FilesystemProfile, VolumeLimits};
399    use crate::native_host::HostRoot;
400    #[cfg(unix)]
401    use cap_std::fs::FileTypeExt as _;
402    use cap_std::fs::{Metadata, MetadataExt, ReadDir};
403    use std::collections::{BTreeMap, VecDeque};
404    use std::path::{Path, PathBuf};
405    use std::sync::atomic::{AtomicU64, Ordering};
406    use std::sync::{Arc, Mutex};
407    use tokio::sync::Semaphore;
408
409    const MAXIMUM_NATIVE_REQUESTS: usize = 64;
410
411    struct DirectoryState {
412        directory: NamespacePath,
413        version: SourceVersion,
414        entries: ReadDir,
415        pending: Option<cap_std::fs::DirEntry>,
416        replay: VecDeque<SourceDirectoryEntry>,
417    }
418
419    struct CursorState {
420        directory: Option<DirectoryState>,
421    }
422
423    pub(super) struct CursorLease<'a> {
424        source: &'a NativeDemandSource,
425        reference: SourceReference,
426        token: u64,
427        state: Option<DirectoryState>,
428        restore: bool,
429    }
430
431    impl CursorLease<'_> {
432        fn state(&mut self) -> Result<&mut DirectoryState, DemandError> {
433            self.state.as_mut().ok_or(DemandError::StaleCursor)
434        }
435
436        fn take(&mut self) -> Result<DirectoryState, DemandError> {
437            self.state.take().ok_or(DemandError::StaleCursor)
438        }
439
440        fn discard(&mut self) {
441            self.state.take();
442            if self.restore {
443                if let Ok(mut active) = self.source.inner.cursors.lock() {
444                    active.remove(&self.token);
445                }
446                self.restore = false;
447            }
448        }
449
450        fn abandon(&mut self) {
451            self.discard();
452        }
453
454        fn commit(&mut self) -> Result<(), DemandError> {
455            let mut active = self
456                .source
457                .inner
458                .cursors
459                .lock()
460                .map_err(|_| DemandError::StaleCursor)?;
461            if self.source.reference() != self.reference {
462                return Err(DemandError::StaleSource);
463            }
464            if self.restore {
465                let Some(slot) = active.get_mut(&self.token) else {
466                    return Err(DemandError::StaleCursor);
467                };
468                slot.directory = Some(self.take()?);
469                self.restore = false;
470            } else {
471                if active.len() >= 1024
472                    && let Some(oldest) = active.keys().next().copied()
473                {
474                    active.remove(&oldest);
475                }
476                active.insert(
477                    self.token,
478                    CursorState {
479                        directory: Some(self.take()?),
480                    },
481                );
482            }
483            Ok(())
484        }
485    }
486
487    impl Drop for CursorLease<'_> {
488        fn drop(&mut self) {
489            if !self.restore {
490                return;
491            }
492            if let Ok(mut active) = self.source.inner.cursors.lock()
493                && self.source.reference() == self.reference
494                && let Some(slot) = active.get_mut(&self.token)
495            {
496                if let Some(state) = self.state.take() {
497                    slot.directory = Some(state);
498                } else {
499                    active.remove(&self.token);
500                }
501            }
502        }
503    }
504
505    /// One native root. Opening it does not enumerate any descendants.
506    /// Filesystem calls currently run on Tokio blocking workers on every host.
507    #[derive(Clone)]
508    pub struct NativeDemandSource {
509        inner: Arc<NativeDemandInner>,
510    }
511
512    struct NativeDemandInner {
513        path: PathBuf,
514        root: HostRoot,
515        identity: [u8; 16],
516        epoch: AtomicU64,
517        profile: FilesystemProfile,
518        limits: VolumeLimits,
519        cursors: Mutex<BTreeMap<u64, CursorState>>,
520        next_cursor: AtomicU64,
521        requests: Arc<Semaphore>,
522        observer: Option<Arc<dyn DemandDirectoryObserver>>,
523    }
524
525    struct CancelWorkerOnDrop(CancellationToken);
526
527    impl Drop for CancelWorkerOnDrop {
528        fn drop(&mut self) {
529            self.0.cancel();
530        }
531    }
532
533    impl NativeDemandSource {
534        /// Returns the stable identity of the already-open native root.
535        #[must_use]
536        pub fn root_identity(&self) -> crate::NativeRootIdentity {
537            self.inner.root.identity()
538        }
539
540        #[cfg(test)]
541        pub(super) async fn occupy_all_requests(
542            &self,
543        ) -> Result<tokio::sync::OwnedSemaphorePermit, tokio::sync::AcquireError> {
544            self.inner.requests.clone().acquire_many_owned(64).await
545        }
546
547        #[cfg(test)]
548        pub(super) async fn wait_for_cancelled_worker(
549            &self,
550            started: tokio::sync::oneshot::Sender<()>,
551            stopped: tokio::sync::oneshot::Sender<()>,
552        ) {
553            let cancellation = CancellationToken::new();
554            let _ = self
555                .run_blocking(&cancellation, move |_, worker_cancellation| {
556                    let _ = started.send(());
557                    while !worker_cancellation.is_cancelled() {
558                        std::thread::yield_now();
559                    }
560                    let _ = stopped.send(());
561                    Ok(OperationReceipt {
562                        value: (),
563                        work: WorkCounters::default(),
564                    })
565                })
566                .await;
567        }
568
569        #[cfg(test)]
570        pub(super) async fn validate_after_work_for_test(
571            &self,
572            work: WorkCounters,
573            cancellation: &CancellationToken,
574        ) -> DemandResult<()> {
575            self.run_blocking_after_work(work, cancellation, |_, _| {
576                unreachable!("validation must not start")
577            })
578            .await
579        }
580
581        #[cfg(test)]
582        pub(super) fn cancel_during_page(
583            &self,
584            source: SourceReference,
585            directory: &NamespacePath,
586            cursor: SourceCursor,
587            cancellation: &CancellationToken,
588        ) -> DemandResult<()> {
589            measured(|work| {
590                let metadata = self.metadata(directory)?.ok_or(DemandError::Absent)?;
591                let mut lease =
592                    self.cursor_lease(source, directory, version(&metadata), Some(cursor))?;
593                let mut visited = 0;
594                let result = self.page_entries(lease.state()?, 4096, cancellation, work, || {
595                    visited += 1;
596                    if visited == 2 {
597                        cancellation.cancel();
598                    }
599                });
600                match result {
601                    Err(DemandError::Cancelled) => {
602                        drop(lease);
603                        Ok(())
604                    }
605                    Err(error) => Err(error),
606                    Ok(_) => Err(DemandError::InvalidRequest),
607                }
608            })
609        }
610
611        /// Opens and identifies only the root directory off the async executor.
612        pub async fn open(
613            path: impl AsRef<Path>,
614            profile: FilesystemProfile,
615            limits: VolumeLimits,
616        ) -> Result<Self, DemandError> {
617            Self::open_configured(
618                path,
619                profile,
620                limits,
621                SourceReference {
622                    identity: *uuid::Uuid::new_v4().as_bytes(),
623                    epoch: 0,
624                },
625                None,
626            )
627            .await
628        }
629
630        /// Opens a lazy native source whose demanded directories are admitted
631        /// to an external observer before any corresponding filesystem read.
632        ///
633        /// # Errors
634        ///
635        /// Returns the same bounded native admission failures as [`Self::open`].
636        pub async fn open_with_observer(
637            path: impl AsRef<Path>,
638            profile: FilesystemProfile,
639            limits: VolumeLimits,
640            observer: Arc<dyn DemandDirectoryObserver>,
641        ) -> Result<Self, DemandError> {
642            Self::open_with_reference_and_observer(
643                path,
644                profile,
645                limits,
646                SourceReference {
647                    identity: *uuid::Uuid::new_v4().as_bytes(),
648                    epoch: 0,
649                },
650                observer,
651            )
652            .await
653        }
654
655        /// Reopens only the root directory using a durable provider identity.
656        ///
657        /// Adapters persist this opaque reference beside the lazy workspace;
658        /// reopening never enumerates descendants or reads file contents.
659        pub async fn open_with_reference(
660            path: impl AsRef<Path>,
661            profile: FilesystemProfile,
662            limits: VolumeLimits,
663            reference: SourceReference,
664        ) -> Result<Self, DemandError> {
665            Self::open_configured(path, profile, limits, reference, None).await
666        }
667
668        /// Reopens a durably identified lazy source with demand-driven
669        /// directory observation.
670        ///
671        /// # Errors
672        ///
673        /// Returns the same bounded native admission failures as
674        /// [`Self::open_with_reference`].
675        pub async fn open_with_reference_and_observer(
676            path: impl AsRef<Path>,
677            profile: FilesystemProfile,
678            limits: VolumeLimits,
679            reference: SourceReference,
680            observer: Arc<dyn DemandDirectoryObserver>,
681        ) -> Result<Self, DemandError> {
682            Self::open_configured(path, profile, limits, reference, Some(observer)).await
683        }
684
685        async fn open_configured(
686            path: impl AsRef<Path>,
687            profile: FilesystemProfile,
688            limits: VolumeLimits,
689            reference: SourceReference,
690            observer: Option<Arc<dyn DemandDirectoryObserver>>,
691        ) -> Result<Self, DemandError> {
692            let path = path.as_ref().to_path_buf();
693            tokio::task::spawn_blocking(move || {
694                Self::open_blocking(path, profile, limits, reference, observer)
695            })
696            .await
697            .map_err(|_| DemandError::WorkerUnavailable)?
698        }
699
700        fn open_blocking(
701            path: PathBuf,
702            profile: FilesystemProfile,
703            limits: VolumeLimits,
704            reference: SourceReference,
705            observer: Option<Arc<dyn DemandDirectoryObserver>>,
706        ) -> Result<Self, DemandError> {
707            let path = if path.is_absolute() {
708                path
709            } else {
710                std::env::current_dir()?.join(path)
711            };
712            let root = HostRoot::open(&path)?;
713            Ok(Self {
714                inner: Arc::new(NativeDemandInner {
715                    path,
716                    root,
717                    identity: reference.identity,
718                    epoch: AtomicU64::new(reference.epoch),
719                    profile,
720                    limits,
721                    cursors: Mutex::new(BTreeMap::new()),
722                    next_cursor: AtomicU64::new(0),
723                    requests: Arc::new(Semaphore::new(MAXIMUM_NATIVE_REQUESTS)),
724                    observer,
725                }),
726            })
727        }
728
729        fn observe_directory(&self, directory: &NamespacePath) -> Result<(), DemandError> {
730            self.inner
731                .observer
732                .as_ref()
733                .map_or(Ok(()), |observer| observer.observe_directory(directory))
734        }
735
736        fn observe_parent(&self, path: &NamespacePath) -> Result<(), DemandError> {
737            let parent = path.parent().unwrap_or_else(|| path.clone());
738            self.observe_directory(&parent)
739        }
740
741        async fn run_blocking<T: Send + 'static>(
742            &self,
743            cancellation: &CancellationToken,
744            job: impl FnOnce(Self, CancellationToken) -> DemandResult<T> + Send + 'static,
745        ) -> DemandResult<T> {
746            if cancellation.is_cancelled() {
747                return Err(OperationFailure::before_work(DemandError::Cancelled));
748            }
749            let permit = tokio::select! {
750                acquired = self.inner.requests.clone().acquire_owned() =>
751                    acquired.map_err(|_| OperationFailure::before_work(DemandError::WorkerUnavailable))?,
752                () = cancellation.cancelled() =>
753                    return Err(OperationFailure::before_work(DemandError::Cancelled)),
754            };
755            if cancellation.is_cancelled() {
756                return Err(OperationFailure::before_work(DemandError::Cancelled));
757            }
758            let source = self.clone();
759            let cancel_on_drop = CancelWorkerOnDrop(CancellationToken::new());
760            let worker_cancellation = cancel_on_drop.0.clone();
761            let mut worker = tokio::task::spawn_blocking(move || {
762                let _permit = permit;
763                job(source, worker_cancellation)
764            });
765            let result = tokio::select! {
766                result = &mut worker => result,
767                () = cancellation.cancelled() => {
768                    cancel_on_drop.0.cancel();
769                    worker.await
770                },
771            };
772            result.map_err(|_| OperationFailure::before_work(DemandError::WorkerUnavailable))?
773        }
774
775        async fn run_blocking_after_work<T: Send + 'static>(
776            &self,
777            work: WorkCounters,
778            cancellation: &CancellationToken,
779            job: impl FnOnce(Self, CancellationToken) -> DemandResult<T> + Send + 'static,
780        ) -> DemandResult<T> {
781            if cancellation.is_cancelled() {
782                return Err(OperationFailure::new(DemandError::Cancelled, work));
783            }
784            let permit = tokio::select! {
785                acquired = self.inner.requests.clone().acquire_owned() =>
786                    acquired.map_err(|_| OperationFailure::new(DemandError::WorkerUnavailable, work))?,
787                () = cancellation.cancelled() =>
788                    return Err(OperationFailure::new(DemandError::Cancelled, work)),
789            };
790            let source = self.clone();
791            let cancel_on_drop = CancelWorkerOnDrop(CancellationToken::new());
792            let worker_cancellation = cancel_on_drop.0.clone();
793            let mut worker = tokio::task::spawn_blocking(move || {
794                let _permit = permit;
795                job(source, worker_cancellation)
796            });
797            let result = tokio::select! {
798                result = &mut worker => result,
799                () = cancellation.cancelled() => {
800                    cancel_on_drop.0.cancel();
801                    worker.await
802                },
803            };
804            result.map_err(|_| OperationFailure::new(DemandError::WorkerUnavailable, work))?
805        }
806
807        async fn validate_read_after_work(
808            &self,
809            relative: PathBuf,
810            validation_file: std::fs::File,
811            source: SourceReference,
812            expected: SourceVersion,
813            work: WorkCounters,
814            cancellation: &CancellationToken,
815        ) -> DemandResult<()> {
816            self.run_blocking_after_work(
817                work,
818                cancellation,
819                move |provider, worker_cancellation| {
820                    let after = cap_std::fs::File::from_std(validation_file)
821                        .metadata()
822                        .map_err(|error| OperationFailure::new(error.into(), work))?;
823                    if version(&after) != expected
824                        || provider
825                            .inner
826                            .root
827                            .symlink_metadata(&relative)
828                            .as_ref()
829                            .map(version)
830                            .ok()
831                            != Some(expected)
832                    {
833                        return Err(OperationFailure::new(DemandError::StaleVersion, work));
834                    }
835                    provider
836                        .check(source, &worker_cancellation)
837                        .map_err(|error| OperationFailure::new(error, work))?;
838                    Ok(OperationReceipt { value: (), work })
839                },
840            )
841            .await
842        }
843
844        /// Invalidates prior source references and directory cursors without
845        /// reading any descendant. Watch overflow and broad hints use this.
846        pub fn invalidate(&self) -> SourceReference {
847            let epoch = self.inner.epoch.fetch_add(1, Ordering::AcqRel) + 1;
848            if let Ok(mut cursors) = self.inner.cursors.lock() {
849                cursors.clear();
850            }
851            SourceReference {
852                identity: self.inner.identity,
853                epoch,
854            }
855        }
856
857        fn check(
858            &self,
859            source: SourceReference,
860            cancellation: &CancellationToken,
861        ) -> Result<(), DemandError> {
862            if source != self.reference() {
863                return Err(DemandError::StaleSource);
864            }
865            if cancellation.is_cancelled() {
866                return Err(DemandError::Cancelled);
867            }
868            let current =
869                HostRoot::open(&self.inner.path).map_err(|_| DemandError::SourceUnavailable)?;
870            if current.identity() != self.inner.root.identity() {
871                return Err(DemandError::SourceUnavailable);
872            }
873            Ok(())
874        }
875
876        fn relative(&self, path: &NamespacePath) -> Result<PathBuf, DemandError> {
877            let limits = self.inner.limits;
878            if path.depth() > usize::from(limits.maximum_path_depth)
879                || path.encoded_bytes() > limits.maximum_path_bytes
880                || path.components().iter().any(|component| {
881                    component.as_bytes().len()
882                        > usize::try_from(limits.maximum_component_bytes).unwrap_or(usize::MAX)
883                })
884            {
885                return Err(DemandError::InvalidRequest);
886            }
887            crate::native_capture::namespace_to_host_path(path)
888                .map_err(|_| DemandError::InvalidRequest)
889        }
890
891        fn metadata(&self, path: &NamespacePath) -> Result<Option<Metadata>, DemandError> {
892            match self.inner.root.symlink_metadata(&self.relative(path)?) {
893                Ok(metadata) => Ok(Some(metadata)),
894                Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
895                Err(error) => Err(error.into()),
896            }
897        }
898
899        fn node(metadata: &Metadata) -> SourceNode {
900            let kind = node_kind(metadata.file_type());
901            SourceNode {
902                kind,
903                file_identity: file_identity(metadata),
904                link_count: link_count(metadata),
905                device: device_identity(metadata, kind),
906                logical_bytes: (kind == SourceNodeKind::RegularFile
907                    || cfg!(unix) && kind == SourceNodeKind::SymbolicLink)
908                    .then_some(metadata.len()),
909                version: version(metadata),
910                metadata: source_metadata(metadata),
911            }
912        }
913
914        pub(super) fn cursor_lease(
915            &self,
916            source: SourceReference,
917            directory: &NamespacePath,
918            observed: SourceVersion,
919            cursor: Option<SourceCursor>,
920        ) -> Result<CursorLease<'_>, DemandError> {
921            let restore = cursor.is_some();
922            let (token, state) = if let Some(cursor) = cursor {
923                if cursor.source != source
924                    || cursor.directory != *directory
925                    || cursor.directory_version != observed
926                {
927                    return Err(DemandError::StaleCursor);
928                }
929                let mut active = self
930                    .inner
931                    .cursors
932                    .lock()
933                    .map_err(|_| DemandError::StaleCursor)?;
934                let slot = active
935                    .get_mut(&cursor.token)
936                    .ok_or(DemandError::StaleCursor)?;
937                let state = slot.directory.take().ok_or(DemandError::CursorBusy)?;
938                (cursor.token, state)
939            } else {
940                let token = self
941                    .inner
942                    .next_cursor
943                    .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
944                        current.checked_add(1)
945                    })
946                    .map_err(|_| DemandError::StaleCursor)?
947                    + 1;
948                (token, self.open_directory_state(directory, observed)?)
949            };
950            Ok(CursorLease {
951                source: self,
952                reference: source,
953                token,
954                state: Some(state),
955                restore,
956            })
957        }
958
959        fn open_directory_state(
960            &self,
961            directory: &NamespacePath,
962            observed: SourceVersion,
963        ) -> Result<DirectoryState, DemandError> {
964            Ok(DirectoryState {
965                directory: directory.clone(),
966                version: observed,
967                entries: self.inner.root.read_dir(&self.relative(directory)?)?,
968                pending: None,
969                replay: VecDeque::new(),
970            })
971        }
972
973        fn next_entry(
974            &self,
975            entry: &cap_std::fs::DirEntry,
976        ) -> Result<SourceDirectoryEntry, DemandError> {
977            let (encoding, bytes) = crate::native_name::host_name_bytes(
978                &entry.file_name(),
979                self.inner.profile,
980                self.inner.limits.maximum_component_bytes,
981            )
982            .map_err(|_| DemandError::InvalidRequest)?;
983            let name = crate::kernel::LogicalName::new(
984                encoding,
985                bytes,
986                self.inner.limits.maximum_component_bytes,
987            )
988            .map_err(|_| DemandError::InvalidRequest)?;
989            let kind = node_kind(entry.file_type()?);
990            Ok(SourceDirectoryEntry { name, kind })
991        }
992
993        fn page_entries(
994            &self,
995            state: &mut DirectoryState,
996            maximum_entries: u32,
997            cancellation: &CancellationToken,
998            work: &mut WorkCounters,
999            mut on_entry: impl FnMut(),
1000        ) -> Result<Vec<SourceDirectoryEntry>, DemandError> {
1001            let mut entries = Vec::with_capacity(maximum_entries as usize);
1002            let result = (|| {
1003                while entries.len() < maximum_entries as usize {
1004                    if cancellation.is_cancelled() {
1005                        return Err(DemandError::Cancelled);
1006                    }
1007                    if let Some(entry) = state.replay.pop_front() {
1008                        entries.push(entry);
1009                        continue;
1010                    }
1011                    let next = if let Some(pending) = state.pending.take() {
1012                        Some(Ok(pending))
1013                    } else {
1014                        let next = state.entries.next();
1015                        if next.is_some() {
1016                            work.source_entries_visited += 1;
1017                        }
1018                        next
1019                    };
1020                    let Some(entry) = next else { break };
1021                    let entry = entry?;
1022                    match self.next_entry(&entry) {
1023                        Ok(converted) => entries.push(converted),
1024                        Err(error) => {
1025                            state.pending = Some(entry);
1026                            return Err(error);
1027                        }
1028                    }
1029                    on_entry();
1030                }
1031                if state.pending.is_none()
1032                    && let Some(next) = state.entries.next()
1033                {
1034                    state.pending = Some(next?);
1035                    work.source_entries_visited += 1;
1036                }
1037                Ok(())
1038            })();
1039            if result.is_err() {
1040                entries.extend(state.replay.drain(..));
1041                state.replay = std::mem::take(&mut entries).into();
1042            }
1043            result.map(|()| entries)
1044        }
1045    }
1046
1047    fn node_kind(file_type: cap_std::fs::FileType) -> SourceNodeKind {
1048        if file_type.is_dir() {
1049            SourceNodeKind::Directory
1050        } else if file_type.is_file() {
1051            SourceNodeKind::RegularFile
1052        } else if file_type.is_symlink() {
1053            SourceNodeKind::SymbolicLink
1054        } else {
1055            special_node_kind(&file_type)
1056        }
1057    }
1058
1059    #[cfg(unix)]
1060    fn special_node_kind(file_type: &cap_std::fs::FileType) -> SourceNodeKind {
1061        if file_type.is_fifo() {
1062            SourceNodeKind::Fifo
1063        } else if file_type.is_socket() {
1064            SourceNodeKind::Socket
1065        } else if file_type.is_char_device() {
1066            SourceNodeKind::CharacterDevice
1067        } else if file_type.is_block_device() {
1068            SourceNodeKind::BlockDevice
1069        } else {
1070            SourceNodeKind::Unsupported
1071        }
1072    }
1073
1074    #[cfg(not(unix))]
1075    fn special_node_kind(_file_type: &cap_std::fs::FileType) -> SourceNodeKind {
1076        SourceNodeKind::Unsupported
1077    }
1078
1079    #[async_trait::async_trait]
1080    impl DemandSource for NativeDemandSource {
1081        fn reference(&self) -> SourceReference {
1082            SourceReference {
1083                identity: self.inner.identity,
1084                epoch: self.inner.epoch.load(Ordering::Acquire),
1085            }
1086        }
1087
1088        async fn lookup(
1089            &self,
1090            source: SourceReference,
1091            path: &NamespacePath,
1092            cancellation: &CancellationToken,
1093        ) -> DemandResult<Option<SourceNode>> {
1094            let path = path.clone();
1095            self.run_blocking(cancellation, move |provider, request_cancellation| {
1096                measured(|work| {
1097                    provider.check(source, &request_cancellation)?;
1098                    provider.observe_parent(&path)?;
1099                    work.source_path_components = path.depth() as u64;
1100                    let node = provider.metadata(&path)?.as_ref().map(Self::node);
1101                    provider.check(source, &request_cancellation)?;
1102                    Ok(node)
1103                })
1104            })
1105            .await
1106        }
1107
1108        async fn list_page(
1109            &self,
1110            source: SourceReference,
1111            directory: &NamespacePath,
1112            cursor: Option<SourceCursor>,
1113            maximum_entries: u32,
1114            cancellation: &CancellationToken,
1115        ) -> DemandResult<SourceDirectoryPage> {
1116            let directory = directory.clone();
1117            self.run_blocking(cancellation, move |provider, request_cancellation| {
1118                measured(|work| {
1119                    provider.check(source, &request_cancellation)?;
1120                    provider.observe_directory(&directory)?;
1121                    work.source_path_components = directory.depth() as u64;
1122                    if maximum_entries == 0 || maximum_entries > 4096 {
1123                        return Err(DemandError::InvalidRequest);
1124                    }
1125                    let metadata = provider.metadata(&directory)?.ok_or(DemandError::Absent)?;
1126                    if !metadata.is_dir() {
1127                        return Err(DemandError::NotDirectory);
1128                    }
1129                    let observed = version(&metadata);
1130                    let mut lease = provider.cursor_lease(source, &directory, observed, cursor)?;
1131                    if lease.state()?.directory != directory || lease.state()?.version != observed {
1132                        lease.discard();
1133                        return Err(DemandError::StaleCursor);
1134                    }
1135                    let entries = provider.page_entries(
1136                        lease.state()?,
1137                        maximum_entries,
1138                        &request_cancellation,
1139                        work,
1140                        || {},
1141                    );
1142                    if entries.is_err() && !matches!(entries, Err(DemandError::Cancelled)) {
1143                        lease.abandon();
1144                    }
1145                    let entries = entries?;
1146                    if let Err(error) = provider.check(source, &request_cancellation) {
1147                        if matches!(error, DemandError::Cancelled) {
1148                            lease.state()?.replay.extend(entries);
1149                        } else {
1150                            lease.abandon();
1151                        }
1152                        return Err(error);
1153                    }
1154                    let current = provider.metadata(&directory);
1155                    if current.is_err() {
1156                        lease.abandon();
1157                    }
1158                    if current?.as_ref().map(version) != Some(observed) {
1159                        lease.discard();
1160                        return Err(DemandError::StaleVersion);
1161                    }
1162                    let next =
1163                        if lease.state()?.pending.is_some() || !lease.state()?.replay.is_empty() {
1164                            let token = lease.token;
1165                            lease.commit()?;
1166                            Some(SourceCursor {
1167                                source,
1168                                directory: directory.clone(),
1169                                directory_version: observed,
1170                                token,
1171                            })
1172                        } else {
1173                            lease.discard();
1174                            None
1175                        };
1176                    work.items_returned = entries.len() as u64;
1177                    Ok(SourceDirectoryPage {
1178                        entries,
1179                        next,
1180                        version: observed,
1181                    })
1182                })
1183            })
1184            .await
1185        }
1186
1187        async fn read_range(
1188            &self,
1189            source: SourceReference,
1190            path: &NamespacePath,
1191            expected: SourceVersion,
1192            offset: u64,
1193            length: u64,
1194            cancellation: &CancellationToken,
1195        ) -> DemandResult<Bytes> {
1196            if cancellation.is_cancelled() {
1197                return Err(OperationFailure::before_work(DemandError::Cancelled));
1198            }
1199            let length = usize::try_from(length)
1200                .map_err(|_| OperationFailure::before_work(DemandError::InvalidRequest))?;
1201            if length > 1024 * 1024 || offset.checked_add(length as u64).is_none() {
1202                return Err(OperationFailure::before_work(DemandError::InvalidRequest));
1203            }
1204            let mut work = WorkCounters {
1205                source_path_components: path.depth() as u64,
1206                ..WorkCounters::default()
1207            };
1208            let path = path.clone();
1209            let prepared = self
1210                .run_blocking(cancellation, move |provider, worker_cancellation| {
1211                    provider
1212                        .check(source, &worker_cancellation)
1213                        .map_err(|error| OperationFailure::new(error, work))?;
1214                    provider
1215                        .observe_parent(&path)
1216                        .map_err(|error| OperationFailure::new(error, work))?;
1217                    let relative = provider
1218                        .relative(&path)
1219                        .map_err(|error| OperationFailure::new(error, work))?;
1220                    let file = match provider.inner.root.open_file(&relative) {
1221                        Ok(file) => file,
1222                        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
1223                            return Err(OperationFailure::new(DemandError::Absent, work));
1224                        }
1225                        Err(error) => {
1226                            return Err(OperationFailure::new(error.into(), work));
1227                        }
1228                    };
1229                    let before = file
1230                        .metadata()
1231                        .map_err(|error| OperationFailure::new(error.into(), work))?;
1232                    if !before.is_file() {
1233                        return Err(OperationFailure::new(DemandError::NotRegularFile, work));
1234                    }
1235                    if version(&before) != expected {
1236                        return Err(OperationFailure::new(DemandError::StaleVersion, work));
1237                    }
1238                    let available = before.len().saturating_sub(offset);
1239                    let length = length.min(usize::try_from(available).unwrap_or(usize::MAX));
1240                    Ok(OperationReceipt {
1241                        value: (relative, file.into_std(), length),
1242                        work,
1243                    })
1244                })
1245                .await?
1246                .value;
1247            let (relative, file, length) = prepared;
1248            let validation_file = file
1249                .try_clone()
1250                .map_err(|error| OperationFailure::new(error.into(), work))?;
1251            let mut read = acyclic_native_runtime::read_batch_async(
1252                file,
1253                vec![acyclic_native_runtime::OwnedRead { offset, length }],
1254            );
1255            let mut cancelled = false;
1256            let result = tokio::select! {
1257                result = &mut read => result,
1258                () = cancellation.cancelled() => {
1259                    cancelled = true;
1260                    read.await
1261                }
1262            }
1263            .map_err(|error| OperationFailure::new(error.into(), work))?;
1264            let bytes = result
1265                .into_iter()
1266                .next()
1267                .ok_or_else(|| OperationFailure::new(DemandError::SourceUnavailable, work))?;
1268            work.source_bytes_read = bytes.len() as u64;
1269            if cancelled {
1270                return Err(OperationFailure::new(DemandError::Cancelled, work));
1271            }
1272            self.validate_read_after_work(
1273                relative,
1274                validation_file,
1275                source,
1276                expected,
1277                work,
1278                cancellation,
1279            )
1280            .await?;
1281            work.output_bytes = bytes.len() as u64;
1282            Ok(OperationReceipt { value: bytes, work })
1283        }
1284
1285        async fn read_link(
1286            &self,
1287            source: SourceReference,
1288            path: &NamespacePath,
1289            expected: SourceVersion,
1290            cancellation: &CancellationToken,
1291        ) -> DemandResult<Bytes> {
1292            let path = path.clone();
1293            self.run_blocking(cancellation, move |provider, request_cancellation| {
1294                measured(|work| {
1295                    provider.check(source, &request_cancellation)?;
1296                    provider.observe_parent(&path)?;
1297                    work.source_path_components = path.depth() as u64;
1298                    let before = provider.metadata(&path)?.ok_or(DemandError::Absent)?;
1299                    if !before.file_type().is_symlink() {
1300                        return Err(DemandError::InvalidRequest);
1301                    }
1302                    if version(&before) != expected {
1303                        return Err(DemandError::StaleVersion);
1304                    }
1305                    let target = provider.inner.root.read_link(&provider.relative(&path)?)?;
1306                    let bytes = os_string_bytes(target.as_os_str());
1307                    work.source_bytes_read = bytes.len() as u64;
1308                    work.output_bytes = bytes.len() as u64;
1309                    let after = provider.metadata(&path)?.ok_or(DemandError::Absent)?;
1310                    if version(&after) != expected {
1311                        return Err(DemandError::StaleVersion);
1312                    }
1313                    provider.check(source, &request_cancellation)?;
1314                    Ok(Bytes::from(bytes))
1315                })
1316            })
1317            .await
1318        }
1319    }
1320
1321    #[cfg(unix)]
1322    fn os_string_bytes(value: &std::ffi::OsStr) -> Vec<u8> {
1323        use std::os::unix::ffi::OsStrExt as _;
1324        value.as_bytes().to_vec()
1325    }
1326
1327    #[cfg(windows)]
1328    fn os_string_bytes(value: &std::ffi::OsStr) -> Vec<u8> {
1329        use std::os::windows::ffi::OsStrExt as _;
1330        value.encode_wide().flat_map(u16::to_le_bytes).collect()
1331    }
1332
1333    fn version(metadata: &Metadata) -> SourceVersion {
1334        let mut hash = blake3::Hasher::new();
1335        hash.update(b"acyclic-fs-native-source-version-v1\0");
1336        hash.update(&metadata.len().to_le_bytes());
1337        #[cfg(unix)]
1338        {
1339            hash.update(&metadata.dev().to_le_bytes());
1340            hash.update(&metadata.ino().to_le_bytes());
1341            hash.update(&metadata.mtime().to_le_bytes());
1342            hash.update(&metadata.mtime_nsec().to_le_bytes());
1343            hash.update(&metadata.ctime().to_le_bytes());
1344            hash.update(&metadata.ctime_nsec().to_le_bytes());
1345        }
1346        #[cfg(windows)]
1347        {
1348            hash.update(&metadata.file_attributes().to_le_bytes());
1349            hash.update(&metadata.last_write_time().to_le_bytes());
1350            hash.update(&metadata.creation_time().to_le_bytes());
1351            hash.update(
1352                &cap_primitives::fs::_WindowsByHandle::volume_serial_number(metadata)
1353                    .unwrap_or_default()
1354                    .to_le_bytes(),
1355            );
1356            hash.update(
1357                &cap_primitives::fs::_WindowsByHandle::file_index(metadata)
1358                    .unwrap_or_default()
1359                    .to_le_bytes(),
1360            );
1361        }
1362        SourceVersion(*hash.finalize().as_bytes())
1363    }
1364
1365    fn file_identity(metadata: &Metadata) -> [u8; 32] {
1366        let mut hash = blake3::Hasher::new();
1367        hash.update(b"acyclic-fs-native-source-file-v1\0");
1368        #[cfg(unix)]
1369        {
1370            hash.update(&metadata.dev().to_le_bytes());
1371            hash.update(&metadata.ino().to_le_bytes());
1372        }
1373        #[cfg(windows)]
1374        {
1375            hash.update(
1376                &cap_primitives::fs::_WindowsByHandle::volume_serial_number(metadata)
1377                    .unwrap_or_default()
1378                    .to_le_bytes(),
1379            );
1380            hash.update(
1381                &cap_primitives::fs::_WindowsByHandle::file_index(metadata)
1382                    .unwrap_or_default()
1383                    .to_le_bytes(),
1384            );
1385        }
1386        *hash.finalize().as_bytes()
1387    }
1388
1389    fn link_count(metadata: &Metadata) -> Option<u64> {
1390        #[cfg(unix)]
1391        {
1392            Some(metadata.nlink())
1393        }
1394        #[cfg(windows)]
1395        {
1396            cap_primitives::fs::_WindowsByHandle::number_of_links(metadata).map(u64::from)
1397        }
1398        #[cfg(not(any(unix, windows)))]
1399        {
1400            let _ = metadata;
1401            None
1402        }
1403    }
1404
1405    #[allow(
1406        clippy::useless_conversion,
1407        reason = "dev_t and major/minor result widths differ across Unix targets"
1408    )]
1409    fn device_identity(metadata: &Metadata, kind: SourceNodeKind) -> Option<(u32, u32)> {
1410        #[cfg(unix)]
1411        {
1412            if !matches!(
1413                kind,
1414                SourceNodeKind::CharacterDevice | SourceNodeKind::BlockDevice
1415            ) {
1416                return None;
1417            }
1418            let raw: libc::dev_t = metadata.rdev().try_into().ok()?;
1419            Some((
1420                libc::major(raw).try_into().ok()?,
1421                libc::minor(raw).try_into().ok()?,
1422            ))
1423        }
1424        #[cfg(not(unix))]
1425        {
1426            let _ = (metadata, kind);
1427            None
1428        }
1429    }
1430
1431    fn source_metadata(metadata: &Metadata) -> SourceMetadata {
1432        let mut result = SourceMetadata {
1433            created_ns: metadata.created().ok().and_then(system_time_nanos),
1434            modified_ns: metadata.modified().ok().and_then(system_time_nanos),
1435            accessed_ns: metadata.accessed().ok().and_then(system_time_nanos),
1436            ..SourceMetadata::default()
1437        };
1438        #[cfg(unix)]
1439        {
1440            result.posix_mode = Some(metadata.mode());
1441            result.posix_uid = Some(metadata.uid());
1442            result.posix_gid = Some(metadata.gid());
1443            result.changed_ns = Some(
1444                i128::from(metadata.ctime()) * 1_000_000_000 + i128::from(metadata.ctime_nsec()),
1445            );
1446        }
1447        #[cfg(windows)]
1448        {
1449            result.windows_attributes = Some(metadata.file_attributes());
1450        }
1451        result
1452    }
1453
1454    fn system_time_nanos(time: cap_std::time::SystemTime) -> Option<i128> {
1455        match time.into_std().duration_since(std::time::UNIX_EPOCH) {
1456            Ok(duration) => Some(
1457                i128::from(duration.as_secs()) * 1_000_000_000
1458                    + i128::from(duration.subsec_nanos()),
1459            ),
1460            Err(error) => Some(
1461                -(i128::from(error.duration().as_secs()) * 1_000_000_000
1462                    + i128::from(error.duration().subsec_nanos())),
1463            ),
1464        }
1465    }
1466}
1467
1468#[cfg(all(test, feature = "native-watch", not(target_arch = "wasm32")))]
1469mod tests {
1470    use super::native::NativeDemandSource;
1471    use super::*;
1472    use crate::model::{FilesystemProfile, VolumeLimits};
1473    use std::error::Error;
1474    use std::sync::{Arc, Mutex};
1475
1476    #[derive(Default)]
1477    struct RecordingObserver {
1478        directories: Mutex<Vec<NamespacePath>>,
1479    }
1480
1481    impl DemandDirectoryObserver for RecordingObserver {
1482        fn observe_directory(&self, directory: &NamespacePath) -> Result<(), DemandError> {
1483            self.directories
1484                .lock()
1485                .map_err(|_| DemandError::SourceUnavailable)?
1486                .push(directory.clone());
1487            Ok(())
1488        }
1489    }
1490
1491    fn path(value: &str) -> Result<NamespacePath, Box<dyn Error>> {
1492        let portable = crate::path::PortablePath::parse(value, VolumeLimits::default())?;
1493        Ok(NamespacePath::from_portable(
1494            &portable,
1495            VolumeLimits::default(),
1496        )?)
1497    }
1498
1499    #[cfg(unix)]
1500    #[tokio::test]
1501    async fn native_source_reports_symbolic_link_target_bytes() -> Result<(), Box<dyn Error>> {
1502        let root = tempfile::tempdir()?;
1503        std::os::unix::fs::symlink("target", root.path().join("link"))?;
1504        let source = NativeDemandSource::open(
1505            root.path(),
1506            FilesystemProfile::Posix,
1507            VolumeLimits::default(),
1508        )
1509        .await?;
1510        let reference = source.reference();
1511        let cancellation = CancellationToken::new();
1512        let node = source
1513            .lookup(reference, &path("/link")?, &cancellation)
1514            .await?
1515            .value
1516            .ok_or("symbolic link was absent")?;
1517
1518        assert_eq!(node.kind, SourceNodeKind::SymbolicLink);
1519        assert_eq!(node.logical_bytes, Some(6));
1520        assert_eq!(
1521            source
1522                .read_link(reference, &path("/link")?, node.version, &cancellation)
1523                .await?
1524                .value
1525                .as_ref(),
1526            b"target"
1527        );
1528        Ok(())
1529    }
1530
1531    #[tokio::test]
1532    async fn native_source_observes_only_the_demanded_directory_before_reading()
1533    -> Result<(), Box<dyn Error>> {
1534        let root = tempfile::tempdir()?;
1535        std::fs::create_dir(root.path().join("nested"))?;
1536        std::fs::write(root.path().join("nested/file"), b"content")?;
1537        let observer = Arc::new(RecordingObserver::default());
1538        let source = NativeDemandSource::open_with_observer(
1539            root.path(),
1540            FilesystemProfile::Portable,
1541            VolumeLimits::default(),
1542            observer.clone(),
1543        )
1544        .await?;
1545        let reference = source.reference();
1546        source
1547            .lookup(reference, &path("/nested/file")?, &CancellationToken::new())
1548            .await?;
1549        assert_eq!(
1550            observer
1551                .directories
1552                .lock()
1553                .map_err(|_| "observer state poisoned")?
1554                .as_slice(),
1555            &[path("/nested")?]
1556        );
1557        Ok(())
1558    }
1559
1560    #[tokio::test]
1561    async fn native_provider_reads_do_not_visit_unrelated_content() -> Result<(), Box<dyn Error>> {
1562        let root = tempfile::tempdir()?;
1563        std::fs::create_dir(root.path().join("forbidden"))?;
1564        std::fs::write(
1565            root.path().join("forbidden").join("unrelated"),
1566            vec![7; 8 * 1024 * 1024],
1567        )?;
1568        let mut body = vec![b'0'; 1024 * 1024];
1569        body[3..7].copy_from_slice(b"3456");
1570        std::fs::write(root.path().join("seen"), body)?;
1571
1572        let source = NativeDemandSource::open(
1573            root.path(),
1574            FilesystemProfile::Portable,
1575            VolumeLimits::default(),
1576        )
1577        .await?;
1578        let provider: &dyn DemandSource = &source;
1579        let reference = provider.reference();
1580        let cancellation = CancellationToken::new();
1581        let node = provider
1582            .lookup(reference, &path("/seen")?, &cancellation)
1583            .await?;
1584        assert_eq!(node.work.source_path_components, 1);
1585        assert_eq!(node.work.source_entries_visited, 0);
1586        assert_eq!(node.work.source_bytes_read, 0);
1587        let version = node.value.ok_or("seen path was absent")?.version;
1588
1589        let bytes = provider
1590            .read_range(reference, &path("/seen")?, version, 3, 4, &cancellation)
1591            .await?;
1592        assert_eq!(bytes.value.as_ref(), b"3456");
1593        assert_eq!(bytes.work.source_bytes_read, 4);
1594        assert_eq!(bytes.work.source_entries_visited, 0);
1595
1596        let first = provider
1597            .list_page(reference, &path("/")?, None, 1, &cancellation)
1598            .await?;
1599        assert_eq!(first.value.entries.len(), 1);
1600        assert!(first.work.source_entries_visited <= 2);
1601        let continuation = first.value.next.ok_or("second root entry missing")?;
1602        let second = provider
1603            .list_page(reference, &path("/")?, Some(continuation), 1, &cancellation)
1604            .await?;
1605        assert_eq!(second.value.entries.len(), 1);
1606        assert!(second.work.source_entries_visited <= 1);
1607        Ok(())
1608    }
1609
1610    #[tokio::test]
1611    async fn native_requests_wait_for_capacity_and_cancel_before_work() -> Result<(), Box<dyn Error>>
1612    {
1613        let root = tempfile::tempdir()?;
1614        let source = NativeDemandSource::open(
1615            root.path(),
1616            FilesystemProfile::Portable,
1617            VolumeLimits::default(),
1618        )
1619        .await?;
1620        let permits = source.occupy_all_requests().await?;
1621        let cancellation = CancellationToken::new();
1622        let root_path = path("/")?;
1623        let query = source.lookup(source.reference(), &root_path, &cancellation);
1624        tokio::pin!(query);
1625        assert!(
1626            tokio::time::timeout(std::time::Duration::from_millis(50), &mut query)
1627                .await
1628                .is_err()
1629        );
1630        cancellation.cancel();
1631        let Err(failure) =
1632            tokio::time::timeout(std::time::Duration::from_secs(1), &mut query).await?
1633        else {
1634            return Err("cancelled request entered the worker".into());
1635        };
1636        assert!(matches!(failure.error, DemandError::Cancelled));
1637        assert_eq!(*failure.work, WorkCounters::default());
1638        drop(permits);
1639        Ok(())
1640    }
1641
1642    #[tokio::test]
1643    async fn post_read_validation_cancels_with_exact_prior_work() -> Result<(), Box<dyn Error>> {
1644        let root = tempfile::tempdir()?;
1645        let source = NativeDemandSource::open(
1646            root.path(),
1647            FilesystemProfile::Portable,
1648            VolumeLimits::default(),
1649        )
1650        .await?;
1651        let permits = source.occupy_all_requests().await?;
1652        let cancellation = CancellationToken::new();
1653        let work = WorkCounters {
1654            source_path_components: 2,
1655            source_bytes_read: 17,
1656            ..WorkCounters::default()
1657        };
1658        let validation = source.validate_after_work_for_test(work, &cancellation);
1659        tokio::pin!(validation);
1660        assert!(
1661            tokio::time::timeout(std::time::Duration::from_millis(50), &mut validation)
1662                .await
1663                .is_err()
1664        );
1665        cancellation.cancel();
1666        let failure = tokio::time::timeout(std::time::Duration::from_secs(1), &mut validation)
1667            .await?
1668            .err()
1669            .ok_or("cancelled validation unexpectedly succeeded")?;
1670        assert!(matches!(failure.error, DemandError::Cancelled));
1671        assert_eq!(*failure.work, work);
1672        drop(permits);
1673        Ok(())
1674    }
1675
1676    #[tokio::test]
1677    async fn dropping_request_cancels_admitted_worker() -> Result<(), Box<dyn Error>> {
1678        let root = tempfile::tempdir()?;
1679        let source = NativeDemandSource::open(
1680            root.path(),
1681            FilesystemProfile::Portable,
1682            VolumeLimits::default(),
1683        )
1684        .await?;
1685        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1686        let (stopped_tx, stopped_rx) = tokio::sync::oneshot::channel();
1687        let request = tokio::spawn(async move {
1688            source
1689                .wait_for_cancelled_worker(started_tx, stopped_tx)
1690                .await;
1691        });
1692        tokio::time::timeout(std::time::Duration::from_secs(1), started_rx).await??;
1693        request.abort();
1694        let _ = request.await;
1695        tokio::time::timeout(std::time::Duration::from_secs(1), stopped_rx).await??;
1696        Ok(())
1697    }
1698
1699    #[tokio::test]
1700    async fn cancelled_directory_page_retries_without_skipping_entries()
1701    -> Result<(), Box<dyn Error>> {
1702        let root = tempfile::tempdir()?;
1703        for name in ["a", "b", "c", "d", "e", "f"] {
1704            std::fs::write(root.path().join(name), b"x")?;
1705        }
1706        let source = NativeDemandSource::open(
1707            root.path(),
1708            FilesystemProfile::Portable,
1709            VolumeLimits::default(),
1710        )
1711        .await?;
1712        let reference = source.reference();
1713        let directory = path("/")?;
1714        let live = CancellationToken::new();
1715        let first = source
1716            .list_page(reference, &directory, None, 1, &live)
1717            .await?;
1718        let cursor = first.value.next.ok_or("expected continuation")?;
1719        let interrupted = CancellationToken::new();
1720        source.cancel_during_page(reference, &directory, cursor.clone(), &interrupted)?;
1721        let mut found = first.value.entries;
1722        let mut next = Some(cursor);
1723        while let Some(cursor) = next {
1724            let page = source
1725                .list_page(reference, &directory, Some(cursor), 2, &live)
1726                .await?;
1727            found.extend(page.value.entries);
1728            next = page.value.next;
1729        }
1730        let mut names: Vec<_> = found
1731            .into_iter()
1732            .map(|entry| entry.name.as_bytes().to_vec())
1733            .collect();
1734        names.sort();
1735        assert_eq!(names.len(), 6);
1736        assert_eq!(names.first().map(Vec::as_slice), Some(&b"a"[..]));
1737        assert_eq!(names.last().map(Vec::as_slice), Some(&b"f"[..]));
1738        Ok(())
1739    }
1740
1741    #[tokio::test]
1742    async fn concurrent_use_reports_busy_without_losing_cursor() -> Result<(), Box<dyn Error>> {
1743        let root = tempfile::tempdir()?;
1744        for name in ["a", "b", "c"] {
1745            std::fs::write(root.path().join(name), b"x")?;
1746        }
1747        let source = NativeDemandSource::open(
1748            root.path(),
1749            FilesystemProfile::Portable,
1750            VolumeLimits::default(),
1751        )
1752        .await?;
1753        let reference = source.reference();
1754        let directory = path("/")?;
1755        let cancellation = CancellationToken::new();
1756        let first = source
1757            .list_page(reference, &directory, None, 1, &cancellation)
1758            .await?;
1759        let cursor = first.value.next.ok_or("expected continuation")?;
1760        let lease = source.cursor_lease(
1761            reference,
1762            &directory,
1763            first.value.version,
1764            Some(cursor.clone()),
1765        )?;
1766        assert!(matches!(
1767            source.cursor_lease(
1768                reference,
1769                &directory,
1770                first.value.version,
1771                Some(cursor.clone())
1772            ),
1773            Err(DemandError::CursorBusy)
1774        ));
1775        drop(lease);
1776        let second = source
1777            .list_page(reference, &directory, Some(cursor), 1, &cancellation)
1778            .await?;
1779        assert_eq!(second.value.entries.len(), 1);
1780        Ok(())
1781    }
1782
1783    #[tokio::test]
1784    async fn version_and_cursor_evidence_fail_closed() -> Result<(), Box<dyn Error>> {
1785        let root = tempfile::tempdir()?;
1786        std::fs::create_dir(root.path().join("directory"))?;
1787        std::fs::write(root.path().join("file"), b"old")?;
1788        let source = NativeDemandSource::open(
1789            root.path(),
1790            FilesystemProfile::Portable,
1791            VolumeLimits::default(),
1792        )
1793        .await?;
1794        let reference = source.reference();
1795        let cancellation = CancellationToken::new();
1796        let node = source
1797            .lookup(reference, &path("/file")?, &cancellation)
1798            .await?
1799            .value
1800            .ok_or("file absent")?;
1801        std::fs::write(root.path().join("file"), b"newer contents")?;
1802        let Err(stale) = source
1803            .read_range(
1804                reference,
1805                &path("/file")?,
1806                node.version,
1807                0,
1808                3,
1809                &cancellation,
1810            )
1811            .await
1812        else {
1813            return Err("stale source content was accepted".into());
1814        };
1815        assert!(matches!(stale.error, DemandError::StaleVersion));
1816        assert_eq!(stale.work.source_path_components, 1);
1817        assert_eq!(stale.work.source_bytes_read, 0);
1818        assert!(matches!(
1819            source
1820                .read_metadata(reference, &path("/file")?, Some(node.version), &cancellation)
1821                .await,
1822            Err(failure) if matches!(failure.error, DemandError::StaleVersion)
1823        ));
1824
1825        let first = source
1826            .list_page(reference, &path("/")?, None, 1, &cancellation)
1827            .await?;
1828        let cursor = first.value.next.ok_or("second root entry missing")?;
1829        assert!(matches!(
1830            source
1831                .list_page(
1832                    reference,
1833                    &path("/directory")?,
1834                    Some(cursor),
1835                    1,
1836                    &cancellation
1837                )
1838                .await,
1839            Err(failure) if matches!(failure.error, DemandError::StaleCursor)
1840        ));
1841        source.invalidate();
1842        assert!(matches!(
1843            source
1844                .lookup(reference, &path("/file")?, &cancellation)
1845                .await,
1846            Err(failure) if matches!(failure.error, DemandError::StaleSource)
1847        ));
1848        Ok(())
1849    }
1850
1851    #[cfg(target_os = "linux")]
1852    #[tokio::test]
1853    async fn native_lookup_preserves_non_utf8_posix_names() -> Result<(), Box<dyn Error>> {
1854        use crate::kernel::{LogicalName, NameEncoding};
1855        use std::os::unix::ffi::OsStringExt;
1856
1857        let raw = vec![b'n', 0xff];
1858        let name = LogicalName::new(
1859            NameEncoding::PosixBytes,
1860            raw.clone(),
1861            VolumeLimits::default().maximum_component_bytes,
1862        )?;
1863        assert_lossless_name(
1864            FilesystemProfile::Posix,
1865            std::ffi::OsString::from_vec(raw),
1866            name,
1867        )
1868        .await
1869    }
1870
1871    #[cfg(windows)]
1872    #[tokio::test]
1873    async fn native_lookup_preserves_utf16_windows_names() -> Result<(), Box<dyn Error>> {
1874        use crate::kernel::{LogicalName, NameEncoding};
1875
1876        let raw = "snowman-☃"
1877            .encode_utf16()
1878            .flat_map(u16::to_le_bytes)
1879            .collect();
1880        let name = LogicalName::new(
1881            NameEncoding::WindowsUtf16Le,
1882            raw,
1883            VolumeLimits::default().maximum_component_bytes,
1884        )?;
1885        assert_lossless_name(
1886            FilesystemProfile::Windows,
1887            std::ffi::OsString::from("snowman-☃"),
1888            name,
1889        )
1890        .await
1891    }
1892
1893    #[cfg(windows)]
1894    #[tokio::test]
1895    async fn native_lookup_accepts_windows_names_longer_than_127_code_units()
1896    -> Result<(), Box<dyn Error>> {
1897        use crate::kernel::{LogicalName, NameEncoding};
1898
1899        let host_name = "x".repeat(200);
1900        let limits = crate::model::VolumeConfig::native(crate::model::Lifecycle::Durable).limits;
1901        let raw = host_name
1902            .encode_utf16()
1903            .flat_map(u16::to_le_bytes)
1904            .collect();
1905        let name = LogicalName::new(
1906            NameEncoding::WindowsUtf16Le,
1907            raw,
1908            limits.maximum_component_bytes,
1909        )?;
1910        let root = tempfile::tempdir()?;
1911        std::fs::write(root.path().join(&host_name), b"data")?;
1912        let source =
1913            NativeDemandSource::open(root.path(), FilesystemProfile::Windows, limits).await?;
1914        let exact = NamespacePath::new(vec![name], limits)?;
1915        assert!(
1916            source
1917                .lookup(source.reference(), &exact, &CancellationToken::new())
1918                .await?
1919                .value
1920                .is_some()
1921        );
1922        Ok(())
1923    }
1924
1925    #[cfg(any(target_os = "linux", windows))]
1926    async fn assert_lossless_name(
1927        profile: FilesystemProfile,
1928        host_name: std::ffi::OsString,
1929        name: crate::kernel::LogicalName,
1930    ) -> Result<(), Box<dyn Error>> {
1931        let root = tempfile::tempdir()?;
1932        std::fs::write(root.path().join(host_name), b"data")?;
1933        let source =
1934            NativeDemandSource::open(root.path(), profile, VolumeLimits::default()).await?;
1935        let exact = NamespacePath::new(vec![name], VolumeLimits::default())?;
1936        let node = source
1937            .lookup(source.reference(), &exact, &CancellationToken::new())
1938            .await?
1939            .value
1940            .ok_or("lossless file absent")?;
1941        let answer = source
1942            .read_range(
1943                source.reference(),
1944                &exact,
1945                node.version,
1946                0,
1947                4,
1948                &CancellationToken::new(),
1949            )
1950            .await?;
1951        assert_eq!(answer.value.as_ref(), b"data");
1952        Ok(())
1953    }
1954}