Skip to main content

powerio_core/
source.rs

1use std::collections::BTreeMap;
2use std::fmt;
3use std::io::Read;
4use std::path::{Component, Path, PathBuf};
5use std::sync::{Arc, Mutex, MutexGuard};
6
7use crate::validation::{MAX_FORMAT_ID_BYTES, valid_nonempty_text};
8use crate::{Error, SourceId};
9
10/// Referenced files one source may acquire. Matches the OpenDSS include
11/// budget the distribution reader has enforced since 0.7.
12const MAX_REFERENCED_FILES: usize = 4_096;
13
14/// Total bytes of referenced files one source may acquire.
15const MAX_REFERENCED_BYTES: u64 = 64 << 20;
16
17/// Deepest resolved or walked path beneath one acquisition root, in segments.
18const MAX_REFERENCED_DEPTH: usize = 64;
19
20const UTF8_BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
21
22/// Open stable identifier used to select a parser or writer.
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct FormatId(Box<str>);
25
26impl FormatId {
27    /// Validate the shared C, Python, Julia, JSON, MCP, and Rust spelling.
28    pub fn new(id: impl Into<String>) -> Result<Self, Error> {
29        let id = id.into();
30        if !valid_format_id(&id) {
31            return Err(Error::new(
32                &crate::codes::REQUEST_FORMAT_INVALID_ID,
33                "a format ID must be bounded lower case ASCII segments separated by single hyphens",
34            ));
35        }
36        Ok(Self(id.into_boxed_str()))
37    }
38
39    #[must_use]
40    pub fn as_str(&self) -> &str {
41        &self.0
42    }
43}
44
45impl fmt::Display for FormatId {
46    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47        formatter.write_str(&self.0)
48    }
49}
50
51fn valid_format_id(id: &str) -> bool {
52    if id.is_empty() || id.len() > MAX_FORMAT_ID_BYTES {
53        return false;
54    }
55    let bytes = id.as_bytes();
56    if !bytes[0].is_ascii_lowercase() || bytes.last() == Some(&b'-') {
57        return false;
58    }
59    let mut previous_hyphen = false;
60    for byte in bytes {
61        if *byte == b'-' {
62            if previous_hyphen {
63                return false;
64            }
65            previous_hyphen = true;
66        } else if byte.is_ascii_lowercase() || byte.is_ascii_digit() {
67            previous_hyphen = false;
68        } else {
69            return false;
70        }
71    }
72    true
73}
74
75#[derive(Debug)]
76struct SourceBufferData {
77    id: SourceId,
78    name: Box<str>,
79    bytes: Arc<[u8]>,
80    /// Directory of this buffer relative to the acquisition root, as the
81    /// segments a referenced name resolves against. Empty for memory buffers
82    /// and for files sitting directly in the root.
83    directory: Box<[Box<str>]>,
84}
85
86/// Immutable named bytes retained by a [`Source`].
87#[derive(Clone, Debug)]
88pub struct SourceBuffer(Arc<SourceBufferData>);
89
90impl SourceBuffer {
91    fn new(
92        id: SourceId,
93        name: impl Into<String>,
94        bytes: Arc<[u8]>,
95        directory: Vec<String>,
96    ) -> Self {
97        Self(Arc::new(SourceBufferData {
98            id,
99            name: name.into().into_boxed_str(),
100            bytes,
101            directory: directory.into_iter().map(String::into_boxed_str).collect(),
102        }))
103    }
104
105    #[must_use]
106    pub fn id(&self) -> &SourceId {
107        &self.0.id
108    }
109
110    #[must_use]
111    pub fn name(&self) -> &str {
112        &self.0.name
113    }
114
115    /// The exact retained bytes, including a UTF-8 byte order mark when the
116    /// input carried one. Same format writing echoes these bytes.
117    #[must_use]
118    pub fn bytes(&self) -> &[u8] {
119        &self.0.bytes
120    }
121
122    #[must_use]
123    pub fn shared_bytes(&self) -> Arc<[u8]> {
124        Arc::clone(&self.0.bytes)
125    }
126
127    /// True when the retained bytes begin with a UTF-8 byte order mark.
128    #[must_use]
129    pub fn has_utf8_bom(&self) -> bool {
130        self.0.bytes.starts_with(&UTF8_BOM)
131    }
132
133    /// The bytes a parser decodes: the retained bytes with a leading UTF-8
134    /// byte order mark skipped. This is a subslice of the one retained buffer,
135    /// never a second decoded copy.
136    #[must_use]
137    pub fn content_bytes(&self) -> &[u8] {
138        let bytes: &[u8] = &self.0.bytes;
139        if bytes.starts_with(&UTF8_BOM) {
140            &bytes[UTF8_BOM.len()..]
141        } else {
142            bytes
143        }
144    }
145
146    /// Directory of this buffer relative to the acquisition root, as path
147    /// segments. Empty for in-memory buffers and for files sitting directly
148    /// in the root. A parser that resolves referenced names itself joins
149    /// these onto the root to seed its resolution base.
150    pub fn directory_segments(&self) -> impl Iterator<Item = &str> {
151        self.0.directory.iter().map(AsRef::as_ref)
152    }
153}
154
155/// Files acquired beneath one pinned root directory.
156///
157/// The root is pinned as an open directory handle where the platform supports
158/// it, opened once on the first acquisition and reused for every later one, so
159/// the number of descriptors a process holds does not grow with the number of
160/// live sources that never acquire a referenced file. Referenced names are
161/// resolved lexically first and then walked one component at a time relative
162/// to that handle with symbolic links refused, so a path component replaced
163/// during acquisition cannot redirect the read outside the root. Acquisition
164/// is serialized by the one lock, which also makes the cache and the budget
165/// exact: a file is read and retained once, and concurrent requests for one
166/// name share the same buffer.
167#[derive(Debug)]
168struct FileAcquisition {
169    root_display: PathBuf,
170    /// True when the root was selected with [`Source::with_acquisition_root`]
171    /// rather than defaulted to the containing directory.
172    selected: bool,
173    state: Mutex<AcquisitionState>,
174}
175
176#[derive(Debug, Default)]
177struct AcquisitionState {
178    root: Option<platform::RootHandle>,
179    cache: BTreeMap<String, SourceBuffer>,
180    /// The one directory listing, cached on the first successful walk. Every
181    /// caller then reads the same immutable view, and a directory handle
182    /// whose stream position a platform shares across duplicated descriptors
183    /// cannot silently return an empty second listing.
184    listed: Option<Vec<crate::ArtifactPath>>,
185    files: usize,
186    bytes: u64,
187}
188
189impl AcquisitionState {
190    /// The pinned root handle, opened on first use with symbolic links at the
191    /// root refused and the directory confirmed on the opened descriptor.
192    fn pinned_root(&mut self, root_display: &Path) -> Result<&platform::RootHandle, Error> {
193        if self.root.is_none() {
194            let root = platform::open_root(root_display)
195                .map_err(|cause| open_error(&crate::codes::READ_IO_OPEN, root_display, cause))?;
196            self.root = Some(root);
197        }
198        Ok(self.root.as_ref().expect("pinned above"))
199    }
200}
201
202#[derive(Debug)]
203enum SourceProvider {
204    Memory {
205        primary: SourceBuffer,
206        named: BTreeMap<String, SourceBuffer>,
207    },
208    File {
209        primary: SourceBuffer,
210        acquisition: FileAcquisition,
211    },
212    Directory {
213        acquisition: FileAcquisition,
214    },
215}
216
217/// Opaque owner or provider of named immutable input buffers.
218///
219/// File acquisition policy belongs here rather than to parser entry points.
220/// The primary buffer's reserved identity. The leading slash is a spelling
221/// [`resolve_segments`] can never produce (a referenced name must be
222/// relative), so an acquired or named buffer's identity is disjoint from the
223/// primary's by construction.
224pub const PRIMARY_SOURCE_ID: &str = "/input";
225
226/// [`Source::open`] on a file retains the primary bytes and permits
227/// constrained acquisition of referenced files beneath the file's canonical
228/// containing directory; [`Source::with_acquisition_root`] widens that root at
229/// construction, and never from a parser. [`Source::from_bytes`] grants no
230/// filesystem access; referenced content reaches an in-memory source only
231/// through [`Source::with_named_buffer`].
232#[derive(Clone)]
233pub struct Source {
234    name: Arc<str>,
235    provider: Arc<SourceProvider>,
236    declared_format: Option<FormatId>,
237}
238
239impl Source {
240    /// Acquire a file eagerly or a directory lazily.
241    pub fn open(path: impl Into<PathBuf>) -> Result<Self, Error> {
242        let path = path.into();
243        if path.as_os_str().is_empty() {
244            return Err(Error::new(
245                &crate::codes::REQUEST_SOURCE_INVALID_PATH,
246                "source path cannot be empty",
247            ));
248        }
249        let name: Arc<str> = path.to_string_lossy().into_owned().into();
250
251        // The open itself refuses a symbolic link at the named path, so there
252        // is no metadata inspection that a concurrent replacement could
253        // invalidate before the read; the file or directory decision reads
254        // the already opened handle.
255        let file = match platform::open_no_follow(&path) {
256            Ok(file) => file,
257            Err(error) if platform::is_symlink_refusal(&error) => {
258                return Err(Error::new(
259                    &crate::codes::REQUEST_SOURCE_SYMLINK_REFUSED,
260                    format!("source `{}` is a symbolic link", path.display()),
261                ));
262            }
263            Err(error) if platform::is_directory_open_failure(&error, &path) => {
264                return Self::open_directory(name, &path);
265            }
266            Err(cause) => return Err(open_error(&crate::codes::READ_IO_OPEN, &path, cause)),
267        };
268        let metadata = file
269            .metadata()
270            .map_err(|cause| open_error(&crate::codes::READ_IO_METADATA, &path, cause))?;
271        if metadata.is_dir() {
272            drop(file);
273            return Self::open_directory(name, &path);
274        }
275        let bytes = read_open_file(file, &name, u64::MAX)?;
276        let root_display = canonical_parent(&path)?;
277        let primary = SourceBuffer::new(
278            SourceId::new(PRIMARY_SOURCE_ID)?,
279            name.to_string(),
280            bytes,
281            Vec::new(),
282        );
283        Ok(Self {
284            name,
285            provider: Arc::new(SourceProvider::File {
286                primary,
287                acquisition: FileAcquisition {
288                    root_display,
289                    selected: false,
290                    state: Mutex::new(AcquisitionState::default()),
291                },
292            }),
293            declared_format: None,
294        })
295    }
296
297    fn open_directory(name: Arc<str>, path: &Path) -> Result<Self, Error> {
298        let root_display = std::fs::canonicalize(path)
299            .map_err(|cause| open_error(&crate::codes::READ_IO_METADATA, path, cause))?;
300        Ok(Self {
301            name,
302            provider: Arc::new(SourceProvider::Directory {
303                acquisition: FileAcquisition {
304                    root_display,
305                    selected: false,
306                    state: Mutex::new(AcquisitionState::default()),
307                },
308            }),
309            declared_format: None,
310        })
311    }
312
313    /// Retain a caller-owned binary or text buffer. An `Arc<[u8]>` argument
314    /// is retained without copying; a `Vec<u8>` is copied once into the
315    /// shared buffer, since `Arc<[u8]>` needs its own allocation.
316    pub fn from_bytes(name: impl Into<String>, bytes: impl Into<Arc<[u8]>>) -> Result<Self, Error> {
317        let name = name.into();
318        if !valid_nonempty_text(&name) {
319            return Err(Error::new(
320                &crate::codes::REQUEST_SOURCE_INVALID_NAME,
321                "an in-memory source requires a nonempty bounded name",
322            ));
323        }
324        let primary = SourceBuffer::new(
325            SourceId::new(PRIMARY_SOURCE_ID)?,
326            name.clone(),
327            bytes.into(),
328            Vec::new(),
329        );
330        Ok(Self {
331            name: name.into(),
332            provider: Arc::new(SourceProvider::Memory {
333                primary,
334                named: BTreeMap::new(),
335            }),
336            declared_format: None,
337        })
338    }
339
340    /// Supply one referenced buffer to an in-memory source under the relative
341    /// name a format uses to refer to it. This is the only way referenced
342    /// content reaches a source built by [`Source::from_bytes`]; such a source
343    /// never touches the filesystem.
344    pub fn with_named_buffer(
345        self,
346        name: impl Into<String>,
347        bytes: impl Into<Arc<[u8]>>,
348    ) -> Result<Self, Error> {
349        let name = name.into();
350        let segments = resolve_segments(&[], &name)?;
351        let key = segments.join("/");
352        let mut provider = Arc::try_unwrap(self.provider).map_err(|_| {
353            Error::new(
354                &crate::codes::REQUEST_SOURCE_INVALID_NAME,
355                "named buffers are supplied while constructing a source, before it is shared",
356            )
357        })?;
358        let SourceProvider::Memory { named, .. } = &mut provider else {
359            return Err(Error::new(
360                &crate::codes::REQUEST_SOURCE_INVALID_NAME,
361                "named buffers belong to in-memory sources; a file source acquires referenced files beneath its root",
362            ));
363        };
364        let directory = segments[..segments.len() - 1].to_vec();
365        let buffer = SourceBuffer::new(SourceId::new(&key)?, key.clone(), bytes.into(), directory);
366        named.insert(key, buffer);
367        Ok(Self {
368            name: self.name,
369            provider: Arc::new(provider),
370            declared_format: self.declared_format,
371        })
372    }
373
374    /// Widen the acquisition root of a file source to a directory that
375    /// contains the file, selected while constructing the source. A parser
376    /// can never widen the root.
377    pub fn with_acquisition_root(self, root: impl Into<PathBuf>) -> Result<Self, Error> {
378        let requested = root.into();
379        let SourceProvider::File {
380            primary,
381            acquisition,
382        } = &*self.provider
383        else {
384            return Err(Error::new(
385                &crate::codes::REQUEST_SOURCE_INVALID_PATH,
386                "an acquisition root applies to a file source",
387            ));
388        };
389        let canonical = std::fs::canonicalize(&requested)
390            .map_err(|cause| open_error(&crate::codes::READ_IO_METADATA, &requested, cause))?;
391        let Ok(remainder) = acquisition.root_display.strip_prefix(&canonical) else {
392            return Err(Error::new(
393                &crate::codes::REQUEST_SOURCE_INVALID_PATH,
394                format!(
395                    "the case file directory {} is outside the requested acquisition root {}",
396                    acquisition.root_display.display(),
397                    canonical.display()
398                ),
399            ));
400        };
401        let mut directory = Vec::new();
402        for component in remainder.components() {
403            let Component::Normal(segment) = component else {
404                return Err(Error::new(
405                    &crate::codes::REQUEST_SOURCE_INVALID_PATH,
406                    "the acquisition root does not resolve to a plain prefix of the case directory",
407                ));
408            };
409            let Some(segment) = segment.to_str().filter(|text| plain_segment(text)) else {
410                return Err(Error::new(
411                    &crate::codes::REQUEST_SOURCE_INVALID_PATH,
412                    "the acquisition root does not resolve to a plain prefix of the case directory",
413                ));
414            };
415            directory.push(segment.to_owned());
416        }
417        let primary = SourceBuffer::new(
418            primary.id().clone(),
419            primary.name().to_owned(),
420            primary.shared_bytes(),
421            directory,
422        );
423        Ok(Self {
424            name: self.name,
425            provider: Arc::new(SourceProvider::File {
426                primary,
427                acquisition: FileAcquisition {
428                    root_display: canonical,
429                    selected: true,
430                    state: Mutex::new(AcquisitionState::default()),
431                },
432            }),
433            declared_format: self.declared_format,
434        })
435    }
436
437    /// Select one parser explicitly while retaining the same source owner.
438    #[must_use]
439    pub fn with_format(mut self, format: FormatId) -> Self {
440        self.declared_format = Some(format);
441        self
442    }
443
444    #[must_use]
445    pub fn name(&self) -> &str {
446        &self.name
447    }
448
449    #[must_use]
450    pub const fn format(&self) -> Option<&FormatId> {
451        self.declared_format.as_ref()
452    }
453
454    #[must_use]
455    pub fn is_directory(&self) -> bool {
456        matches!(&*self.provider, SourceProvider::Directory { .. })
457    }
458
459    /// Borrow the sole primary buffer of a file or memory source.
460    pub fn primary_buffer(&self) -> Result<SourceBuffer, Error> {
461        match &*self.provider {
462            SourceProvider::Memory { primary, .. } | SourceProvider::File { primary, .. } => {
463                Ok(primary.clone())
464            }
465            SourceProvider::Directory { .. } => Err(Error::new(
466                &crate::codes::REQUEST_SOURCE_DIRECTORY_REQUIRED,
467                "a directory source has no implicit primary buffer",
468            )
469            .with_source(self.clone())),
470        }
471    }
472
473    /// Acquire and retain one file of a directory source by its root relative
474    /// name.
475    pub fn buffer(&self, name: &crate::ArtifactPath) -> Result<SourceBuffer, Error> {
476        let SourceProvider::Directory { acquisition } = &*self.provider else {
477            return Err(Error::new(
478                &crate::codes::REQUEST_SOURCE_DIRECTORY_REQUIRED,
479                "named child buffers require a directory source",
480            )
481            .with_source(self.clone()));
482        };
483        let segments = resolve_segments(&[], name.as_str())
484            .map_err(|error| error.with_source(self.clone()))?;
485        acquisition
486            .acquire(&segments)
487            .map_err(|error| error.with_source(self.clone()))
488    }
489
490    /// Acquire and retain one file beneath the acquisition root by its root
491    /// relative name, for a parser that resolves referenced names itself and
492    /// hands over the resolved result. An in-memory source consults the
493    /// buffers the caller supplied.
494    pub fn root_buffer(&self, name: &str) -> Result<SourceBuffer, Error> {
495        match &*self.provider {
496            SourceProvider::Memory { named, .. } => {
497                let segments = resolve_segments(&[], name)?;
498                let key = segments.join("/");
499                named.get(&key).cloned().ok_or_else(|| {
500                    Error::new(
501                        &crate::codes::REQUEST_SOURCE_UNKNOWN_BUFFER,
502                        format!(
503                            "referenced buffer `{key}` was not supplied to this in-memory source"
504                        ),
505                    )
506                    .with_source(self.clone())
507                })
508            }
509            SourceProvider::File { acquisition, .. }
510            | SourceProvider::Directory { acquisition } => {
511                let segments = resolve_segments(&[], name)?;
512                acquisition
513                    .acquire(&segments)
514                    .map_err(|error| error.with_source(self.clone()))
515            }
516        }
517    }
518
519    /// The canonical root selected with [`Source::with_acquisition_root`],
520    /// `None` when the root defaulted to the containing directory or the
521    /// source is not file backed. Read-only context for a parser's own
522    /// resolution and refusal wording; acquisition itself always goes
523    /// through this source.
524    #[must_use]
525    pub fn selected_acquisition_root(&self) -> Option<&Path> {
526        match &*self.provider {
527            SourceProvider::Memory { .. } | SourceProvider::Directory { .. } => None,
528            SourceProvider::File { acquisition, .. } => acquisition
529                .selected
530                .then_some(acquisition.root_display.as_path()),
531        }
532    }
533
534    /// Acquire and retain one file referenced by `referrer`, resolved against
535    /// the referring file's directory and confined beneath the acquisition
536    /// root. An in-memory source resolves the same name against the buffers
537    /// the caller supplied and never touches the filesystem.
538    pub fn referenced_buffer(
539        &self,
540        referrer: &SourceBuffer,
541        name: &str,
542    ) -> Result<SourceBuffer, Error> {
543        let referrer_directory: Vec<&str> = referrer.directory_segments().collect();
544        match &*self.provider {
545            SourceProvider::Memory { named, .. } => {
546                let segments = resolve_segments(&referrer_directory, name)?;
547                let key = segments.join("/");
548                named.get(&key).cloned().ok_or_else(|| {
549                    Error::new(
550                        &crate::codes::REQUEST_SOURCE_UNKNOWN_BUFFER,
551                        format!(
552                            "referenced buffer `{key}` was not supplied to this in-memory source"
553                        ),
554                    )
555                    .with_source(self.clone())
556                })
557            }
558            SourceProvider::File { acquisition, .. }
559            | SourceProvider::Directory { acquisition } => {
560                let segments = match absolute_to_root_relative(&acquisition.root_display, name) {
561                    Some(root_relative) => root_relative?,
562                    None => resolve_segments(&referrer_directory, name)?,
563                };
564                acquisition
565                    .acquire(&segments)
566                    .map_err(|error| error.with_source(self.clone()))
567            }
568        }
569    }
570
571    /// The root relative file names of a directory source, in sorted order,
572    /// so a directory format can report files outside its profile without
573    /// touching the filesystem itself. Symbolic links are listed by name and
574    /// refused if acquired. The listing is bounded by the referenced file
575    /// budget; a directory holding more entries is refused.
576    #[allow(clippy::too_many_lines)] // one bounded walk, framed and budgeted in place
577    pub fn entry_names(&self) -> Result<Vec<crate::ArtifactPath>, Error> {
578        match &*self.provider {
579            SourceProvider::Memory { named, .. } => named
580                .keys()
581                .map(|name| crate::ArtifactPath::new(name.clone()))
582                .collect(),
583            SourceProvider::File { .. } => Err(Error::new(
584                &crate::codes::REQUEST_SOURCE_DIRECTORY_REQUIRED,
585                "entry listing requires a directory source",
586            )
587            .with_source(self.clone())),
588            SourceProvider::Directory { acquisition } => {
589                // The walk runs against the pinned root handle so a directory
590                // component swapped for a symbolic link mid-listing fails the
591                // descriptor walk rather than redirecting the listing outside
592                // the root. The walk is depth first over frames, one open
593                // handle per level: a child is opened from its parent's live
594                // handle, and a frame's handle closes when its subdirectories
595                // are exhausted, so the descriptors held at any moment are
596                // bounded by the depth bound, never by the entry budget or a
597                // directory's fan-out. Every budget bounds the work before it
598                // is incurred: entries stop being read the moment the
599                // remaining allowance is exhausted, and a prefix at the depth
600                // bound is refused before it is walked. The lock is held
601                // across the walk, like acquisition.
602                struct Frame<Handle> {
603                    prefix: Vec<String>,
604                    directory: Handle,
605                    /// Subdirectory names discovered and not yet descended.
606                    subdirectories: Vec<String>,
607                }
608
609                let mut state = acquisition.lock();
610                if let Some(listed) = &state.listed {
611                    return Ok(listed.clone());
612                }
613                let root = state.pinned_root(&acquisition.root_display)?;
614                let budget_refusal = || {
615                    Error::new(
616                        &crate::codes::READ_IO_REFERENCE_BUDGET,
617                        format!(
618                            "the source directory holds more than {MAX_REFERENCED_FILES} entries"
619                        ),
620                    )
621                };
622                let root_handle = root.duplicate_handle().map_err(|cause| {
623                    Error::new(
624                        &crate::codes::READ_IO_METADATA,
625                        "cannot list the source directory root",
626                    )
627                    .with_cause(cause)
628                })?;
629                let mut names = Vec::new();
630                // Subdirectory names discovered across every frame and not
631                // yet listed; each still charges the entry budget.
632                let mut undescended = 0usize;
633                let mut frames = Vec::new();
634                let mut arriving = Some((Vec::<String>::new(), root_handle));
635                while let Some((prefix, directory)) = arriving.take() {
636                    let allowance = MAX_REFERENCED_FILES
637                        .checked_sub(names.len() + undescended)
638                        .filter(|allowance| *allowance > 0)
639                        .ok_or_else(budget_refusal)?;
640                    let entries =
641                        platform::list_entries(&directory, allowance).map_err(|cause| {
642                            if platform::is_entry_budget(&cause) {
643                                budget_refusal()
644                            } else {
645                                listing_error(&prefix.join("/"), cause)
646                            }
647                        })?;
648                    let mut subdirectories = Vec::new();
649                    for (name, is_directory) in entries {
650                        if names.len() + undescended + subdirectories.len() >= MAX_REFERENCED_FILES
651                        {
652                            return Err(budget_refusal());
653                        }
654                        if is_directory {
655                            if prefix.len() + 1 >= MAX_REFERENCED_DEPTH {
656                                return Err(Error::new(
657                                    &crate::codes::READ_IO_REFERENCE_BUDGET,
658                                    format!(
659                                        "the source directory nests more than {MAX_REFERENCED_DEPTH} levels deep"
660                                    ),
661                                ));
662                            }
663                            subdirectories.push(name);
664                        } else {
665                            let mut child = prefix.clone();
666                            child.push(name);
667                            names.push(crate::ArtifactPath::new(child.join("/"))?);
668                        }
669                    }
670                    undescended += subdirectories.len();
671                    frames.push(Frame {
672                        prefix,
673                        directory,
674                        subdirectories,
675                    });
676
677                    // Descend into the deepest frame's next subdirectory;
678                    // pop and drop a frame — closing its handle — as soon as
679                    // its subdirectories are exhausted, before any sibling
680                    // opens.
681                    while let Some(frame) = frames.last_mut() {
682                        let Some(name) = frame.subdirectories.pop() else {
683                            frames.pop();
684                            continue;
685                        };
686                        undescended -= 1;
687                        let mut child = frame.prefix.clone();
688                        let handle = platform::open_child_directory(&frame.directory, &name)
689                            .map_err(|cause| {
690                                child.push(name.clone());
691                                listing_error(&child.join("/"), cause)
692                            })?;
693                        child.push(name);
694                        arriving = Some((child, handle));
695                        break;
696                    }
697                }
698                names.sort();
699                state.listed = Some(names.clone());
700                Ok(names)
701            }
702        }
703    }
704
705    /// Buffers already retained by this source, in deterministic name order.
706    #[must_use]
707    pub fn acquired_buffers(&self) -> Vec<SourceBuffer> {
708        match &*self.provider {
709            SourceProvider::Memory { primary, named } => {
710                let mut buffers = vec![primary.clone()];
711                buffers.extend(named.values().cloned());
712                buffers
713            }
714            SourceProvider::File {
715                primary,
716                acquisition,
717            } => {
718                let mut buffers = vec![primary.clone()];
719                buffers.extend(acquisition.lock().cache.values().cloned());
720                buffers
721            }
722            SourceProvider::Directory { acquisition } => {
723                acquisition.lock().cache.values().cloned().collect()
724            }
725        }
726    }
727}
728
729impl fmt::Debug for Source {
730    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
731        formatter
732            .debug_struct("Source")
733            .field("name", &self.name)
734            .field("is_directory", &self.is_directory())
735            .field("declared_format", &self.declared_format)
736            .field("acquired_buffer_count", &self.acquired_buffers().len())
737            .finish_non_exhaustive()
738    }
739}
740
741impl FileAcquisition {
742    fn lock(&self) -> MutexGuard<'_, AcquisitionState> {
743        self.state
744            .lock()
745            .unwrap_or_else(std::sync::PoisonError::into_inner)
746    }
747
748    /// Read and retain one file beneath the root. The lock is held across the
749    /// read so a file is read once, retained once, and charged once even under
750    /// concurrent requests. The remaining byte budget bounds the allocation
751    /// itself: it is computed before the read and handed to the reader, so a
752    /// file that would overrun the budget is refused before its bytes are
753    /// reserved.
754    fn acquire(&self, segments: &[String]) -> Result<SourceBuffer, Error> {
755        let key = segments.join("/");
756        let mut state = self.lock();
757        if let Some(buffer) = state.cache.get(&key) {
758            return Ok(buffer.clone());
759        }
760        if state.files >= MAX_REFERENCED_FILES {
761            return Err(Error::new(
762                &crate::codes::READ_IO_REFERENCE_BUDGET,
763                format!("this source already acquired {MAX_REFERENCED_FILES} referenced files"),
764            ));
765        }
766        let remaining = MAX_REFERENCED_BYTES.saturating_sub(state.bytes);
767        let root = state.pinned_root(&self.root_display)?;
768        let file = root.open_beneath(segments).map_err(|error| {
769            if platform::is_symlink_refusal(&error) {
770                Error::new(
771                    &crate::codes::REQUEST_SOURCE_SYMLINK_REFUSED,
772                    format!("referenced file `{key}` crosses a symbolic link"),
773                )
774            } else {
775                Error::new(
776                    &crate::codes::READ_IO_OPEN,
777                    format!("cannot open referenced file `{key}`"),
778                )
779                .with_cause(error)
780            }
781        })?;
782        let bytes = read_open_file(file, &key, remaining)?;
783        let directory = segments[..segments.len() - 1].to_vec();
784        let buffer = SourceBuffer::new(SourceId::new(&key)?, key.clone(), bytes, directory);
785        state.files += 1;
786        state.bytes += buffer.bytes().len() as u64;
787        state.cache.insert(key, buffer.clone());
788        Ok(buffer)
789    }
790}
791
792/// True when `segment` is a single plain file name on every supported
793/// platform: it reduces to exactly one normal path component equal to itself,
794/// so it carries no separator of any platform, no drive or root prefix, no
795/// NUL, and is neither `.` nor `..` nor empty. Joining such segments one at a
796/// time onto a directory cannot reach outside that directory.
797fn plain_segment(segment: &str) -> bool {
798    if segment.is_empty()
799        || segment == "."
800        || segment == ".."
801        || segment.contains(['/', '\\', '\0', ':'])
802    {
803        return false;
804    }
805    let mut components = Path::new(segment).components();
806    matches!(components.next(), Some(Component::Normal(text)) if text == std::ffi::OsStr::new(segment))
807        && components.next().is_none()
808}
809
810/// Resolve a referenced name against the referring directory, lexically and
811/// before any filesystem access. The name is split on both separators, `.` is
812/// dropped, `..` pops within the root and is refused past it, a leading
813/// separator or drive spelling is refused, and every remaining segment must be
814/// a single plain file name on the platform under test. The result is the
815/// exact component list the platform walk opens one at a time.
816fn resolve_segments(referrer_directory: &[&str], name: &str) -> Result<Vec<String>, Error> {
817    if name.is_empty()
818        || name.len() > crate::validation::MAX_ARTIFACT_PATH_BYTES
819        || name.contains('\0')
820        || name.starts_with(['/', '\\'])
821    {
822        return Err(Error::new(
823            &crate::codes::REQUEST_SOURCE_INVALID_PATH,
824            "a referenced name must be a nonempty bounded relative path",
825        ));
826    }
827    let mut segments: Vec<String> = referrer_directory
828        .iter()
829        .map(|segment| (*segment).to_owned())
830        .collect();
831    for raw in name.split(['/', '\\']) {
832        match raw {
833            "" | "." => {}
834            ".." => {
835                if segments.pop().is_none() {
836                    return Err(Error::new(
837                        &crate::codes::REQUEST_SOURCE_ESCAPES_ROOT,
838                        format!("referenced name `{name}` resolves outside the acquisition root"),
839                    ));
840                }
841            }
842            segment => {
843                if segment.len() > crate::validation::MAX_ARTIFACT_PATH_BYTES
844                    || !plain_segment(segment)
845                {
846                    return Err(Error::new(
847                        &crate::codes::REQUEST_SOURCE_INVALID_PATH,
848                        format!("referenced name `{name}` is not a portable relative path"),
849                    ));
850                }
851                segments.push(segment.to_owned());
852            }
853        }
854    }
855    if segments.is_empty() {
856        return Err(Error::new(
857            &crate::codes::REQUEST_SOURCE_INVALID_PATH,
858            format!("referenced name `{name}` does not name a file"),
859        ));
860    }
861    if segments.len() > MAX_REFERENCED_DEPTH {
862        return Err(Error::new(
863            &crate::codes::READ_IO_REFERENCE_BUDGET,
864            format!("referenced name `{name}` nests more than {MAX_REFERENCED_DEPTH} levels deep"),
865        ));
866    }
867    Ok(segments)
868}
869
870/// An absolute referenced name is accepted only when it sits lexically beneath
871/// the canonical root; the walk then reopens it component by component from
872/// the pinned root handle. Returns `None` for a relative name. The segment
873/// list is built from the platform's own path components, one segment per
874/// directory component, each required to be a plain file name.
875fn absolute_to_root_relative(root: &Path, name: &str) -> Option<Result<Vec<String>, Error>> {
876    let path = Path::new(name);
877    if !path.is_absolute() {
878        return None;
879    }
880    let Ok(remainder) = path.strip_prefix(root) else {
881        return Some(Err(Error::new(
882            &crate::codes::REQUEST_SOURCE_ESCAPES_ROOT,
883            format!("referenced name `{name}` resolves outside the acquisition root"),
884        )));
885    };
886    let mut segments = Vec::new();
887    for component in remainder.components() {
888        let text = match component {
889            Component::Normal(text) => text.to_str(),
890            _ => None,
891        };
892        let Some(text) = text.filter(|text| plain_segment(text)) else {
893            return Some(Err(Error::new(
894                &crate::codes::REQUEST_SOURCE_INVALID_PATH,
895                format!("referenced name `{name}` is not a portable path beneath the root"),
896            )));
897        };
898        segments.push(text.to_owned());
899    }
900    if segments.is_empty() {
901        return Some(Err(Error::new(
902            &crate::codes::REQUEST_SOURCE_INVALID_PATH,
903            format!("referenced name `{name}` does not name a file"),
904        )));
905    }
906    if segments.len() > MAX_REFERENCED_DEPTH {
907        return Some(Err(Error::new(
908            &crate::codes::READ_IO_REFERENCE_BUDGET,
909            format!("referenced name `{name}` nests more than {MAX_REFERENCED_DEPTH} levels deep"),
910        )));
911    }
912    Some(Ok(segments))
913}
914
915fn canonical_parent(path: &Path) -> Result<PathBuf, Error> {
916    let parent = match path.parent() {
917        Some(parent) if !parent.as_os_str().is_empty() => parent,
918        _ => Path::new("."),
919    };
920    std::fs::canonicalize(parent)
921        .map_err(|cause| open_error(&crate::codes::READ_IO_METADATA, parent, cause))
922}
923
924fn open_error(info: &'static crate::DiagnosticInfo, path: &Path, cause: std::io::Error) -> Error {
925    Error::new(info, format!("cannot open source `{}`", path.display())).with_cause(cause)
926}
927
928/// A listing walk failure at one root relative directory: a symbolic link is
929/// the acquisition refusal; anything else is an I/O failure.
930fn listing_error(display: &str, cause: std::io::Error) -> Error {
931    if platform::is_symlink_refusal(&cause) {
932        Error::new(
933            &crate::codes::REQUEST_SOURCE_SYMLINK_REFUSED,
934            format!("source directory `{display}` crosses a symbolic link"),
935        )
936    } else {
937        Error::new(
938            &crate::codes::READ_IO_METADATA,
939            format!("cannot list source directory `{display}`"),
940        )
941        .with_cause(cause)
942    }
943}
944
945/// Read an already opened regular file completely. The handle was opened with
946/// symbolic links refused, and the regular file check runs on the open
947/// descriptor, so no path is consulted twice. `max_bytes` bounds the
948/// allocation itself: a file whose declared length exceeds it is refused
949/// before any bytes are reserved, and the reader is capped so a file that
950/// grows past the bound during the read is refused rather than read.
951fn read_open_file(file: std::fs::File, name: &str, max_bytes: u64) -> Result<Arc<[u8]>, Error> {
952    let metadata = file.metadata().map_err(|cause| {
953        Error::new(
954            &crate::codes::READ_IO_METADATA,
955            format!("cannot inspect source buffer `{name}`"),
956        )
957        .with_cause(cause)
958    })?;
959    if !metadata.is_file() {
960        return Err(Error::new(
961            &crate::codes::REQUEST_SOURCE_NOT_A_FILE,
962            format!("source buffer `{name}` is not a regular file"),
963        ));
964    }
965    let declared_length = metadata.len();
966    if declared_length > max_bytes {
967        return Err(Error::new(
968            &crate::codes::READ_IO_REFERENCE_BUDGET,
969            format!(
970                "referenced file `{name}` would take this source past its {MAX_REFERENCED_BYTES} byte acquisition budget"
971            ),
972        ));
973    }
974    let capacity = usize::try_from(declared_length).map_err(|cause| {
975        Error::new(
976            &crate::codes::READ_IO_ALLOCATION_REFUSED,
977            format!("source buffer `{name}` is too large for this platform"),
978        )
979        .with_cause(cause)
980    })?;
981    let mut bytes = Vec::new();
982    bytes.try_reserve_exact(capacity).map_err(|cause| {
983        Error::new(
984            &crate::codes::READ_IO_ALLOCATION_REFUSED,
985            format!("cannot reserve {declared_length} bytes for source buffer `{name}`"),
986        )
987        .with_cause(cause)
988    })?;
989    let read_limit = declared_length
990        .checked_add(1)
991        .ok_or_else(|| {
992            Error::new(
993                &crate::codes::READ_IO_ALLOCATION_REFUSED,
994                format!("source buffer `{name}` is too large to read safely"),
995            )
996        })?
997        .min(max_bytes.saturating_add(1));
998    let mut file = file;
999    file.by_ref()
1000        .take(read_limit)
1001        .read_to_end(&mut bytes)
1002        .map_err(|cause| {
1003            Error::new(
1004                &crate::codes::READ_IO_READ,
1005                format!("cannot read source buffer `{name}`"),
1006            )
1007            .with_cause(cause)
1008        })?;
1009    if bytes.len() != capacity {
1010        return Err(Error::new(
1011            &crate::codes::READ_IO_SOURCE_CHANGED,
1012            format!("source buffer `{name}` changed length while it was read"),
1013        ));
1014    }
1015    Ok(bytes.into())
1016}
1017
1018#[cfg(unix)]
1019mod platform {
1020    //! Descriptor-relative acquisition: the root directory is pinned by an
1021    //! open descriptor, and every referenced component is opened relative to
1022    //! it with `O_NOFOLLOW`, so replacing a component with a symbolic link
1023    //! during acquisition fails instead of redirecting the read outside the
1024    //! root. Every open also carries `O_NONBLOCK`, so the open call itself
1025    //! never waits on another process (a FIFO with no writer opens
1026    //! immediately and is then refused by the regular file check on the
1027    //! descriptor); the flag is cleared before any read.
1028
1029    use std::ffi::CString;
1030    use std::fs::File;
1031    use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
1032    use std::os::unix::ffi::OsStrExt;
1033    use std::path::Path;
1034
1035    #[derive(Debug)]
1036    pub(super) struct RootHandle(OwnedFd);
1037
1038    /// Open the named path with symbolic links at the final component refused
1039    /// by the kernel and without the open itself blocking on another process.
1040    pub(super) fn open_no_follow(path: &Path) -> std::io::Result<File> {
1041        let path = c_string(path.as_os_str().as_bytes())?;
1042        // SAFETY: the pointer references a NUL-terminated buffer owned by
1043        // `path`, which outlives the call; the returned descriptor is owned
1044        // exclusively by the `File` constructed below.
1045        let fd = unsafe {
1046            libc::open(
1047                path.as_ptr(),
1048                libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK,
1049            )
1050        };
1051        if fd < 0 {
1052            return Err(std::io::Error::last_os_error());
1053        }
1054        // SAFETY: `fd` is a freshly opened descriptor this function owns.
1055        let file = unsafe { File::from_raw_fd(fd) };
1056        clear_nonblock(&file)?;
1057        Ok(file)
1058    }
1059
1060    /// Open a root directory with a symbolic link at the final component
1061    /// refused, and the directory confirmed on the opened descriptor. Not
1062    /// `O_DIRECTORY | O_NOFOLLOW`: Darwin reports that combination on a
1063    /// symbolic link as `ENOTDIR`, hiding the refusal reason.
1064    pub(super) fn open_root(path: &Path) -> std::io::Result<RootHandle> {
1065        let path = c_string(path.as_os_str().as_bytes())?;
1066        // SAFETY: as in `open_no_follow`.
1067        let fd = unsafe {
1068            libc::open(
1069                path.as_ptr(),
1070                libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK,
1071            )
1072        };
1073        if fd < 0 {
1074            return Err(std::io::Error::last_os_error());
1075        }
1076        // SAFETY: `fd` is a freshly opened descriptor this function owns.
1077        let file = unsafe { File::from_raw_fd(fd) };
1078        if !file.metadata()?.is_dir() {
1079            return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory));
1080        }
1081        clear_nonblock(&file)?;
1082        Ok(RootHandle(file.into()))
1083    }
1084
1085    impl RootHandle {
1086        pub(super) fn open_beneath(&self, segments: &[String]) -> std::io::Result<File> {
1087            let mut directory: Option<OwnedFd> = None;
1088            let (file_segment, directories) =
1089                segments.split_last().expect("resolution yields a file");
1090            for segment in directories {
1091                // `O_NOFOLLOW` alone, then a directory check on the opened
1092                // descriptor: Darwin reports `O_NOFOLLOW | O_DIRECTORY` on a
1093                // symbolic link as `ENOTDIR`, which would hide the refusal
1094                // reason, and checking the descriptor cannot race.
1095                let next = self.open_at(
1096                    directory.as_ref(),
1097                    segment,
1098                    libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK,
1099                )?;
1100                let next = File::from(next);
1101                if !next.metadata()?.is_dir() {
1102                    return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory));
1103                }
1104                directory = Some(next.into());
1105            }
1106            let fd = self.open_at(
1107                directory.as_ref(),
1108                file_segment,
1109                libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK,
1110            )?;
1111            let file = File::from(fd);
1112            clear_nonblock(&file)?;
1113            Ok(file)
1114        }
1115
1116        /// A fresh open file description of the root, for a listing walk
1117        /// that opens each child directory relative to its parent. `dup`
1118        /// would share the directory stream position with the pinned root
1119        /// (and with every other walk), so a walk that stops early would
1120        /// leave the next one a shorter listing; `openat(fd, ".")` yields an
1121        /// independent description of the same directory with no path in
1122        /// between.
1123        pub(super) fn duplicate_handle(&self) -> std::io::Result<DirectoryHandle> {
1124            let segment = c_string(b".")?;
1125            // SAFETY: the root descriptor is live for the call and the
1126            // pointer references a NUL-terminated buffer owned by `segment`.
1127            let fd = unsafe {
1128                libc::openat(
1129                    self.0.as_raw_fd(),
1130                    segment.as_ptr(),
1131                    libc::O_RDONLY | libc::O_CLOEXEC | libc::O_DIRECTORY,
1132                )
1133            };
1134            if fd < 0 {
1135                return Err(std::io::Error::last_os_error());
1136            }
1137            // SAFETY: `fd` is a freshly opened descriptor this function owns.
1138            Ok(unsafe { OwnedFd::from_raw_fd(fd) })
1139        }
1140
1141        fn open_at(
1142            &self,
1143            directory: Option<&OwnedFd>,
1144            segment: &str,
1145            flags: libc::c_int,
1146        ) -> std::io::Result<OwnedFd> {
1147            if !super::plain_segment(segment) {
1148                return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
1149            }
1150            let at = directory.map_or_else(|| self.0.as_raw_fd(), AsRawFd::as_raw_fd);
1151            let segment = c_string(segment.as_bytes())?;
1152            // SAFETY: `at` is a live descriptor owned by `self` or by the
1153            // caller's `directory` for the duration of the call, and the
1154            // pointer references a NUL-terminated buffer owned by `segment`.
1155            let fd = unsafe { libc::openat(at, segment.as_ptr(), flags) };
1156            if fd < 0 {
1157                return Err(std::io::Error::last_os_error());
1158            }
1159            // SAFETY: `fd` is a freshly opened descriptor this function owns.
1160            Ok(unsafe { OwnedFd::from_raw_fd(fd) })
1161        }
1162    }
1163
1164    /// An open directory the listing walk can read entries from and open
1165    /// children relative to.
1166    pub(super) type DirectoryHandle = OwnedFd;
1167
1168    /// Open one child directory of an already opened directory, with a
1169    /// symbolic link at the child refused and the directory confirmed on the
1170    /// opened descriptor.
1171    pub(super) fn open_child_directory(
1172        parent: &DirectoryHandle,
1173        name: &str,
1174    ) -> std::io::Result<DirectoryHandle> {
1175        if !super::plain_segment(name) {
1176            return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
1177        }
1178        let segment = c_string(name.as_bytes())?;
1179        // SAFETY: `parent` is a live descriptor for the duration of the call,
1180        // and the pointer references the NUL-terminated buffer owned by
1181        // `segment`.
1182        let fd = unsafe {
1183            libc::openat(
1184                parent.as_raw_fd(),
1185                segment.as_ptr(),
1186                libc::O_RDONLY | libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK,
1187            )
1188        };
1189        if fd < 0 {
1190            return Err(std::io::Error::last_os_error());
1191        }
1192        // SAFETY: `fd` is a freshly opened descriptor this function owns.
1193        let file = unsafe { File::from_raw_fd(fd) };
1194        if !file.metadata()?.is_dir() {
1195            return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory));
1196        }
1197        Ok(file.into())
1198    }
1199
1200    const ENTRY_BUDGET_MARKER: &str = "directory entry allowance exhausted";
1201
1202    fn entry_budget_error() -> std::io::Error {
1203        std::io::Error::other(ENTRY_BUDGET_MARKER)
1204    }
1205
1206    /// True when a listing failed because it reached the caller's entry
1207    /// allowance rather than a real I/O failure.
1208    pub(super) fn is_entry_budget(error: &std::io::Error) -> bool {
1209        error.kind() == std::io::ErrorKind::Other && error.to_string().contains(ENTRY_BUDGET_MARKER)
1210    }
1211
1212    /// Read the entries of an open directory, at most `max` of them: the
1213    /// bound is enforced inside the read loop, before the entry that would
1214    /// cross it is accepted, so the work and the memory of a listing are
1215    /// bounded by the allowance rather than by the directory's true entry
1216    /// count. Returns each UTF-8 entry name with whether it is a directory;
1217    /// symbolic links are listed by name and refused when acquired.
1218    pub(super) fn list_entries(
1219        directory: &DirectoryHandle,
1220        max: usize,
1221    ) -> std::io::Result<Vec<(String, bool)>> {
1222        use std::os::fd::IntoRawFd;
1223
1224        // The stream takes ownership of a duplicate, so the caller's handle
1225        // stays usable for opening children.
1226        let raw = directory.try_clone()?.into_raw_fd();
1227        // SAFETY: `raw` is a live directory descriptor whose ownership
1228        // transfers to the returned stream; on failure it is closed here.
1229        let stream = unsafe { libc::fdopendir(raw) };
1230        if stream.is_null() {
1231            let error = std::io::Error::last_os_error();
1232            // SAFETY: `raw` is still owned by this function when `fdopendir`
1233            // fails.
1234            unsafe { libc::close(raw) };
1235            return Err(error);
1236        }
1237        let mut entries = Vec::new();
1238        loop {
1239            // SAFETY: `stream` is the live directory stream opened above.
1240            errno_clear();
1241            let entry = unsafe { libc::readdir(stream) };
1242            if entry.is_null() {
1243                let error = std::io::Error::last_os_error();
1244                // SAFETY: `stream` is the live directory stream opened above;
1245                // it is closed exactly once.
1246                unsafe { libc::closedir(stream) };
1247                if error.raw_os_error().is_some_and(|code| code != 0) {
1248                    return Err(error);
1249                }
1250                break;
1251            }
1252            // SAFETY: `entry` is valid until the next `readdir` on this
1253            // stream, and only the NUL-terminated name within the entry is
1254            // read: the raw pointer to the array's first element is followed
1255            // to its terminator, never the whole declared array.
1256            let name_bytes = unsafe {
1257                std::ffi::CStr::from_ptr((&raw const (*entry).d_name).cast::<libc::c_char>())
1258            };
1259            let Ok(name) = name_bytes.to_str() else {
1260                continue;
1261            };
1262            if name == "." || name == ".." {
1263                continue;
1264            }
1265            if entries.len() == max {
1266                // SAFETY: as above; the stream is closed exactly once.
1267                unsafe { libc::closedir(stream) };
1268                return Err(entry_budget_error());
1269            }
1270            // SAFETY: as above; `d_type` is a plain byte field read by copy.
1271            let kind = unsafe { (*entry).d_type };
1272            let is_directory = match kind {
1273                libc::DT_DIR => true,
1274                libc::DT_UNKNOWN => {
1275                    // A filesystem without `d_type` support: ask the
1276                    // descriptor, without following a symbolic link.
1277                    let mut stat: libc::stat = unsafe { std::mem::zeroed() };
1278                    let segment =
1279                        c_string(name.as_bytes()).expect("directory entry names carry no NUL");
1280                    // SAFETY: `stream` is live, so `dirfd` is a live
1281                    // descriptor; the name pointer references the
1282                    // NUL-terminated buffer owned by `segment`.
1283                    let status = unsafe {
1284                        libc::fstatat(
1285                            libc::dirfd(stream),
1286                            segment.as_ptr(),
1287                            &raw mut stat,
1288                            libc::AT_SYMLINK_NOFOLLOW,
1289                        )
1290                    };
1291                    status == 0 && stat.st_mode & libc::S_IFMT == libc::S_IFDIR
1292                }
1293                _ => false,
1294            };
1295            entries.push((name.to_owned(), is_directory));
1296        }
1297        Ok(entries)
1298    }
1299
1300    fn errno_clear() {
1301        // SAFETY: writing 0 to the calling thread's errno location.
1302        unsafe {
1303            *errno_location() = 0;
1304        }
1305    }
1306
1307    #[cfg(target_os = "macos")]
1308    fn errno_location() -> *mut libc::c_int {
1309        // SAFETY: `__error` returns the calling thread's errno location.
1310        unsafe { libc::__error() }
1311    }
1312
1313    #[cfg(not(target_os = "macos"))]
1314    fn errno_location() -> *mut libc::c_int {
1315        // SAFETY: `__errno_location` returns the calling thread's errno
1316        // location.
1317        unsafe { libc::__errno_location() }
1318    }
1319
1320    /// Clear `O_NONBLOCK` on an opened descriptor before it is read.
1321    fn clear_nonblock(file: &File) -> std::io::Result<()> {
1322        let fd = file.as_raw_fd();
1323        // SAFETY: `fd` is a live descriptor owned by `file` for the duration
1324        // of both calls.
1325        let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
1326        if flags < 0 {
1327            return Err(std::io::Error::last_os_error());
1328        }
1329        // SAFETY: as above.
1330        let status = unsafe { libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK) };
1331        if status < 0 {
1332            return Err(std::io::Error::last_os_error());
1333        }
1334        Ok(())
1335    }
1336
1337    pub(super) fn is_symlink_refusal(error: &std::io::Error) -> bool {
1338        matches!(error.raw_os_error(), Some(libc::ELOOP | libc::EMLINK))
1339    }
1340
1341    pub(super) fn is_directory_open_failure(error: &std::io::Error, path: &Path) -> bool {
1342        // Opening a directory read-only succeeds on Linux and macOS, so the
1343        // usual route is the metadata check on the opened handle; this covers
1344        // a platform whose open refuses directories outright.
1345        let _ = path;
1346        error.raw_os_error() == Some(libc::EISDIR)
1347    }
1348
1349    fn c_string(bytes: &[u8]) -> std::io::Result<CString> {
1350        CString::new(bytes).map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidInput))
1351    }
1352
1353    #[cfg(test)]
1354    mod tests {
1355        use super::*;
1356
1357        #[test]
1358        fn open_root_refuses_a_symbolic_link_to_a_directory() {
1359            let base = std::env::temp_dir().join(format!(
1360                "powerio-core-open-root-{}-{}",
1361                std::process::id(),
1362                std::time::SystemTime::now()
1363                    .duration_since(std::time::UNIX_EPOCH)
1364                    .unwrap()
1365                    .as_nanos()
1366            ));
1367            std::fs::create_dir_all(&base).unwrap();
1368            assert!(open_root(&base).is_ok());
1369            let link = base.join("link");
1370            std::os::unix::fs::symlink(&base, &link).unwrap();
1371            let error = open_root(&link).unwrap_err();
1372            assert!(
1373                is_symlink_refusal(&error) || error.kind() == std::io::ErrorKind::NotADirectory,
1374                "{error:?}"
1375            );
1376            std::fs::remove_dir_all(&base).unwrap();
1377        }
1378    }
1379}
1380
1381#[cfg(not(unix))]
1382mod platform {
1383    //! Windows and other platforms have no `openat`. The walk opens every
1384    //! intermediate directory into a handle held for the remainder of the
1385    //! walk, with reparse points refused on the opened handle and the share
1386    //! mode excluding delete, so a held component can be neither replaced by
1387    //! a link nor renamed away while a child is opened beneath it — the same
1388    //! invariant the Unix descriptor walk enforces.
1389
1390    use std::fs::File;
1391    use std::path::{Path, PathBuf};
1392
1393    #[derive(Debug)]
1394    pub(super) struct RootHandle {
1395        root: PathBuf,
1396    }
1397
1398    pub(super) fn open_no_follow(path: &Path) -> std::io::Result<File> {
1399        let file = open_reparse_refused(path)?;
1400        Ok(file)
1401    }
1402
1403    pub(super) fn open_root(path: &Path) -> std::io::Result<RootHandle> {
1404        // Verified at open; each walk re-pins the root for its own duration,
1405        // so the source's lifetime holds no lock that would block legitimate
1406        // tree changes between walks.
1407        drop(open_directory_pinned(path)?);
1408        Ok(RootHandle {
1409            root: path.to_path_buf(),
1410        })
1411    }
1412
1413    impl RootHandle {
1414        pub(super) fn open_beneath(&self, segments: &[String]) -> std::io::Result<File> {
1415            let mut path = self.root.clone();
1416            let (file_segment, directories) =
1417                segments.split_last().expect("resolution yields a file");
1418            // Every handle from the root down stays alive until the final
1419            // open: the next component is opened only while every ancestor
1420            // is still held, and all release when the walk returns.
1421            let mut held = Vec::with_capacity(directories.len() + 1);
1422            held.push(open_directory_pinned(&self.root)?);
1423            for segment in directories {
1424                push_plain_segment(&mut path, segment)?;
1425                held.push(open_directory_pinned(&path)?);
1426            }
1427            push_plain_segment(&mut path, file_segment)?;
1428            let file = open_reparse_refused(&path)?;
1429            drop(held);
1430            Ok(file)
1431        }
1432
1433        /// The root as a listing handle: the pinned handle plus the verified
1434        /// path each child extends.
1435        pub(super) fn duplicate_handle(&self) -> std::io::Result<DirectoryHandle> {
1436            let handle = open_directory_pinned(&self.root)?;
1437            Ok(DirectoryHandle {
1438                path: self.root.clone(),
1439                _handle: handle,
1440            })
1441        }
1442    }
1443
1444    /// A verified directory the listing walk extends one plain component at
1445    /// a time, holding its own pinned handle; the walk keeps a frame per
1446    /// level, so every ancestor of an open frame stays held.
1447    #[derive(Debug)]
1448    pub(super) struct DirectoryHandle {
1449        path: PathBuf,
1450        _handle: File,
1451    }
1452
1453    /// Extend the walk by one child directory, opened while the parent's
1454    /// handle is held, with a reparse point at the child refused on the
1455    /// opened handle.
1456    pub(super) fn open_child_directory(
1457        parent: &DirectoryHandle,
1458        name: &str,
1459    ) -> std::io::Result<DirectoryHandle> {
1460        let mut path = parent.path.clone();
1461        push_plain_segment(&mut path, name)?;
1462        let handle = open_directory_pinned(&path)?;
1463        Ok(DirectoryHandle {
1464            path,
1465            _handle: handle,
1466        })
1467    }
1468
1469    const ENTRY_BUDGET_MARKER: &str = "directory entry allowance exhausted";
1470
1471    fn entry_budget_error() -> std::io::Error {
1472        std::io::Error::other(ENTRY_BUDGET_MARKER)
1473    }
1474
1475    /// True when a listing failed because it reached the caller's entry
1476    /// allowance rather than a real I/O failure.
1477    pub(super) fn is_entry_budget(error: &std::io::Error) -> bool {
1478        error.kind() == std::io::ErrorKind::Other && error.to_string().contains(ENTRY_BUDGET_MARKER)
1479    }
1480
1481    /// Read the entries of one held directory, at most `max` of them: the
1482    /// bound is enforced inside the read loop, before the entry that would
1483    /// cross it is accepted. The held handle keeps the listed path the
1484    /// verified directory for the duration.
1485    pub(super) fn list_entries(
1486        directory: &DirectoryHandle,
1487        max: usize,
1488    ) -> std::io::Result<Vec<(String, bool)>> {
1489        let mut entries = Vec::new();
1490        for entry in std::fs::read_dir(&directory.path)? {
1491            let entry = entry?;
1492            let Ok(name) = entry.file_name().into_string() else {
1493                continue;
1494            };
1495            if entries.len() == max {
1496                return Err(entry_budget_error());
1497            }
1498            let is_directory = entry.file_type()?.is_dir();
1499            entries.push((name, is_directory));
1500        }
1501        Ok(entries)
1502    }
1503
1504    /// Refuse a segment that is not a single plain file name before it is
1505    /// joined onto the walked path.
1506    fn push_plain_segment(path: &mut PathBuf, segment: &str) -> std::io::Result<()> {
1507        if !super::plain_segment(segment) {
1508            return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
1509        }
1510        path.push(segment);
1511        Ok(())
1512    }
1513
1514    /// Open one directory into a held handle with the reparse point refused
1515    /// on the handle itself. On Windows the open uses backup semantics (a
1516    /// directory needs it), keeps `FILE_SHARE_DELETE` out of the share mode
1517    /// so the held component cannot be renamed or deleted, and refuses a
1518    /// handle whose attributes carry the reparse flag.
1519    #[cfg(windows)]
1520    fn open_directory_pinned(path: &Path) -> std::io::Result<File> {
1521        use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
1522
1523        const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
1524        const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
1525        const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
1526        const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x0000_0010;
1527        const FILE_SHARE_READ: u32 = 0x0000_0001;
1528        const FILE_SHARE_WRITE: u32 = 0x0000_0002;
1529
1530        let file = std::fs::OpenOptions::new()
1531            .read(true)
1532            .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
1533            .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
1534            .open(path)?;
1535        let attributes = file.metadata()?.file_attributes();
1536        if attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1537            return Err(symlink_error());
1538        }
1539        if attributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
1540            return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory));
1541        }
1542        Ok(file)
1543    }
1544
1545    #[cfg(not(windows))]
1546    fn open_directory_pinned(path: &Path) -> std::io::Result<File> {
1547        let metadata = std::fs::symlink_metadata(path)?;
1548        if metadata.file_type().is_symlink() {
1549            return Err(symlink_error());
1550        }
1551        if !metadata.is_dir() {
1552            return Err(std::io::Error::from(std::io::ErrorKind::NotADirectory));
1553        }
1554        File::open(path)
1555    }
1556
1557    #[cfg(windows)]
1558    fn open_reparse_refused(path: &Path) -> std::io::Result<File> {
1559        use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
1560
1561        const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
1562        const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
1563
1564        let file = std::fs::OpenOptions::new()
1565            .read(true)
1566            .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
1567            .open(path)?;
1568        let attributes = file.metadata()?.file_attributes();
1569        if attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1570            return Err(symlink_error());
1571        }
1572        Ok(file)
1573    }
1574
1575    #[cfg(not(windows))]
1576    fn open_reparse_refused(path: &Path) -> std::io::Result<File> {
1577        let metadata = std::fs::symlink_metadata(path)?;
1578        if metadata.file_type().is_symlink() {
1579            return Err(symlink_error());
1580        }
1581        File::open(path)
1582    }
1583
1584    fn symlink_error() -> std::io::Error {
1585        std::io::Error::new(std::io::ErrorKind::InvalidData, "symbolic link refused")
1586    }
1587
1588    pub(super) fn is_symlink_refusal(error: &std::io::Error) -> bool {
1589        error.kind() == std::io::ErrorKind::InvalidData
1590            && error.to_string().contains("symbolic link refused")
1591    }
1592
1593    pub(super) fn is_directory_open_failure(error: &std::io::Error, path: &Path) -> bool {
1594        let _ = error;
1595        std::fs::symlink_metadata(path).is_ok_and(|metadata| metadata.is_dir())
1596    }
1597}
1598
1599#[cfg(test)]
1600mod tests {
1601    use std::time::{SystemTime, UNIX_EPOCH};
1602
1603    use super::*;
1604    use crate::ArtifactPath;
1605
1606    /// Serializes the tests that measure process wide resources (open
1607    /// descriptor counts, descriptor limits), so a measurement compares
1608    /// against a baseline taken under the same guard rather than a free
1609    /// running count other tests move.
1610    static PROCESS_RESOURCE_TESTS: Mutex<()> = Mutex::new(());
1611
1612    fn process_resource_guard() -> MutexGuard<'static, ()> {
1613        PROCESS_RESOURCE_TESTS
1614            .lock()
1615            .unwrap_or_else(std::sync::PoisonError::into_inner)
1616    }
1617
1618    /// Byte counting global allocator, scoped to the one measuring thread so
1619    /// parallel tests never pollute a measurement.
1620    struct CountingAllocator;
1621
1622    static ALLOCATED_BYTES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
1623
1624    thread_local! {
1625        static MEASURING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1626    }
1627
1628    fn measured_bytes<T>(work: impl FnOnce() -> T) -> (T, usize) {
1629        let _ = MEASURING.try_with(|flag| flag.set(true));
1630        let before = ALLOCATED_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1631        let value = work();
1632        let after = ALLOCATED_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1633        let _ = MEASURING.try_with(|flag| flag.set(false));
1634        (value, after.saturating_sub(before))
1635    }
1636
1637    // SAFETY: delegates every operation to the system allocator; the counter
1638    // is a side effect on the measuring thread only.
1639    unsafe impl std::alloc::GlobalAlloc for CountingAllocator {
1640        unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 {
1641            if MEASURING.try_with(std::cell::Cell::get).unwrap_or(false) {
1642                ALLOCATED_BYTES.fetch_add(layout.size(), std::sync::atomic::Ordering::Relaxed);
1643            }
1644            unsafe { std::alloc::System.alloc(layout) }
1645        }
1646
1647        unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) {
1648            unsafe { std::alloc::System.dealloc(ptr, layout) }
1649        }
1650
1651        unsafe fn realloc(
1652            &self,
1653            ptr: *mut u8,
1654            layout: std::alloc::Layout,
1655            new_size: usize,
1656        ) -> *mut u8 {
1657            if MEASURING.try_with(std::cell::Cell::get).unwrap_or(false) {
1658                ALLOCATED_BYTES.fetch_add(new_size, std::sync::atomic::Ordering::Relaxed);
1659            }
1660            unsafe { std::alloc::System.realloc(ptr, layout, new_size) }
1661        }
1662    }
1663
1664    #[global_allocator]
1665    static COUNTING: CountingAllocator = CountingAllocator;
1666
1667    fn test_root(name: &str) -> PathBuf {
1668        let nonce = SystemTime::now()
1669            .duration_since(UNIX_EPOCH)
1670            .unwrap()
1671            .as_nanos();
1672        std::env::temp_dir().join(format!(
1673            "powerio-core-{name}-{}-{nonce}",
1674            std::process::id()
1675        ))
1676    }
1677
1678    #[test]
1679    fn format_ids_use_the_exact_open_grammar() {
1680        for id in ["matpower", "psse-raw", "doe-go-3", "x1"] {
1681            assert_eq!(FormatId::new(id).unwrap().as_str(), id);
1682        }
1683        for id in ["", "1matpower", "MATPOWER", "psse--raw", "psse-", "p_sse"] {
1684            assert!(FormatId::new(id).is_err(), "{id}");
1685        }
1686        assert!(FormatId::new("a".repeat(MAX_FORMAT_ID_BYTES + 1)).is_err());
1687    }
1688
1689    #[test]
1690    fn memory_sources_retain_arbitrary_binary_bytes_without_copying() {
1691        let bytes: Arc<[u8]> = vec![0, 255, 0, 128].into();
1692        let pointer = bytes.as_ptr();
1693        let source = Source::from_bytes("input.bin", Arc::clone(&bytes))
1694            .unwrap()
1695            .with_format(FormatId::new("pwb").unwrap());
1696        let buffer = source.primary_buffer().unwrap();
1697        assert_eq!(buffer.bytes(), [0, 255, 0, 128]);
1698        assert_eq!(buffer.bytes().as_ptr(), pointer);
1699        assert_eq!(source.format().unwrap().as_str(), "pwb");
1700        assert!(Source::from_bytes("", Vec::new()).is_err());
1701        assert!(Source::from_bytes("x\0y", Vec::new()).is_err());
1702    }
1703
1704    #[test]
1705    fn a_bom_is_retained_and_skipped_for_the_parser_without_a_second_buffer() {
1706        let bytes: Vec<u8> = [0xEF, 0xBB, 0xBF, b'm', b'p', b'c'].to_vec();
1707        let source = Source::from_bytes("case.m", bytes).unwrap();
1708        let buffer = source.primary_buffer().unwrap();
1709        assert!(buffer.has_utf8_bom());
1710        assert_eq!(buffer.bytes().len(), 6);
1711        assert_eq!(buffer.content_bytes(), b"mpc");
1712        // The parser slice points into the one retained buffer.
1713        assert_eq!(
1714            buffer.content_bytes().as_ptr(),
1715            buffer.bytes()[3..].as_ptr()
1716        );
1717
1718        let plain = Source::from_bytes("case.m", b"mpc".to_vec()).unwrap();
1719        let plain = plain.primary_buffer().unwrap();
1720        assert!(!plain.has_utf8_bom());
1721        assert_eq!(plain.content_bytes(), plain.bytes());
1722    }
1723
1724    #[test]
1725    fn a_memory_source_resolves_named_buffers_and_never_the_filesystem() {
1726        let source = Source::from_bytes("master.dss", b"redirect sub/feeder.dss".to_vec())
1727            .unwrap()
1728            .with_named_buffer("sub/feeder.dss", b"feeder".to_vec())
1729            .unwrap();
1730        let primary = source.primary_buffer().unwrap();
1731        let feeder = source
1732            .referenced_buffer(&primary, "sub/feeder.dss")
1733            .unwrap();
1734        assert_eq!(feeder.bytes(), b"feeder");
1735
1736        // Referrer-relative resolution: from inside `sub/`, a sibling name
1737        // resolves beneath `sub/` and `..` climbs within the supplied names.
1738        let sibling = source
1739            .referenced_buffer(&feeder, "../sub/feeder.dss")
1740            .unwrap();
1741        assert_eq!(sibling.bytes(), b"feeder");
1742
1743        assert!(source.referenced_buffer(&primary, "missing.dss").is_err());
1744        let escape = source.referenced_buffer(&primary, "../outside.dss");
1745        assert_eq!(
1746            escape.unwrap_err().category(),
1747            crate::ErrorCategory::Request
1748        );
1749    }
1750
1751    #[test]
1752    fn a_file_source_acquires_referenced_files_beneath_its_containing_directory() {
1753        let root = test_root("file-refs");
1754        std::fs::create_dir_all(root.join("sub")).unwrap();
1755        std::fs::write(root.join("master.dss"), b"master").unwrap();
1756        std::fs::write(root.join("sub/feeder.dss"), b"feeder").unwrap();
1757        std::fs::write(root.join("sub/coords.csv"), b"coords").unwrap();
1758
1759        let source = Source::open(root.join("master.dss")).unwrap();
1760        let primary = source.primary_buffer().unwrap();
1761        assert_eq!(primary.bytes(), b"master");
1762
1763        let feeder = source
1764            .referenced_buffer(&primary, "sub/feeder.dss")
1765            .unwrap();
1766        assert_eq!(feeder.bytes(), b"feeder");
1767        // Names resolve against the referring file: from the feeder, a bare
1768        // sibling name lands in `sub/`.
1769        let coords = source.referenced_buffer(&feeder, "coords.csv").unwrap();
1770        assert_eq!(coords.bytes(), b"coords");
1771        // And `..` climbs back toward the root but never past it.
1772        let master_again = source.referenced_buffer(&feeder, "../master.dss").unwrap();
1773        assert_eq!(master_again.bytes(), b"master");
1774        let escape = source.referenced_buffer(&primary, "../escape.dss");
1775        assert_eq!(
1776            escape.unwrap_err().category(),
1777            crate::ErrorCategory::Request
1778        );
1779
1780        // The same file is retained once.
1781        let again = source
1782            .referenced_buffer(&primary, "sub/feeder.dss")
1783            .unwrap();
1784        assert_eq!(again.bytes().as_ptr(), feeder.bytes().as_ptr());
1785        assert_eq!(source.acquired_buffers().len(), 4);
1786        std::fs::remove_dir_all(root).unwrap();
1787    }
1788
1789    #[test]
1790    fn an_explicitly_wider_root_admits_shared_files_and_still_confines() {
1791        let root = test_root("wider-root");
1792        std::fs::create_dir_all(root.join("cases")).unwrap();
1793        std::fs::create_dir_all(root.join("shared")).unwrap();
1794        std::fs::write(root.join("cases/master.dss"), b"master").unwrap();
1795        std::fs::write(root.join("shared/wires.dss"), b"wires").unwrap();
1796
1797        // Confined by default: the sibling directory is outside.
1798        let narrow = Source::open(root.join("cases/master.dss")).unwrap();
1799        let primary = narrow.primary_buffer().unwrap();
1800        assert!(
1801            narrow
1802                .referenced_buffer(&primary, "../shared/wires.dss")
1803                .is_err()
1804        );
1805
1806        // The wider root is selected while constructing the source.
1807        let wide = Source::open(root.join("cases/master.dss"))
1808            .unwrap()
1809            .with_acquisition_root(&root)
1810            .unwrap();
1811        let primary = wide.primary_buffer().unwrap();
1812        let wires = wide
1813            .referenced_buffer(&primary, "../shared/wires.dss")
1814            .unwrap();
1815        assert_eq!(wires.bytes(), b"wires");
1816        assert!(
1817            wide.referenced_buffer(&primary, "../../etc/passwd")
1818                .is_err()
1819        );
1820
1821        // A root that does not contain the case file is refused.
1822        let outside = test_root("wider-root-outside");
1823        std::fs::create_dir_all(&outside).unwrap();
1824        assert!(
1825            Source::open(root.join("cases/master.dss"))
1826                .unwrap()
1827                .with_acquisition_root(&outside)
1828                .is_err()
1829        );
1830        std::fs::remove_dir_all(outside).ok();
1831        std::fs::remove_dir_all(root).unwrap();
1832    }
1833
1834    #[test]
1835    fn directory_buffers_are_lazy_cached_and_binary_safe() {
1836        let root = test_root("directory");
1837        std::fs::create_dir_all(root.join("nested")).unwrap();
1838        std::fs::write(root.join("nested/data.bin"), [0, 255, 7]).unwrap();
1839        let source = Source::open(&root).unwrap();
1840        assert!(source.is_directory());
1841        assert!(source.acquired_buffers().is_empty());
1842        let name = ArtifactPath::new("nested/data.bin").unwrap();
1843        let first = source.buffer(&name).unwrap();
1844        let second = source.buffer(&name).unwrap();
1845        assert_eq!(first.bytes(), [0, 255, 7]);
1846        assert_eq!(first.bytes().as_ptr(), second.bytes().as_ptr());
1847        assert_eq!(source.acquired_buffers().len(), 1);
1848        std::fs::remove_dir_all(root).unwrap();
1849    }
1850
1851    #[cfg(unix)]
1852    #[test]
1853    fn source_acquisition_refuses_root_and_child_symlinks() {
1854        use std::os::unix::fs::symlink;
1855
1856        let root = test_root("symlink");
1857        std::fs::create_dir_all(&root).unwrap();
1858        std::fs::write(root.join("real.bin"), b"real").unwrap();
1859        symlink(root.join("real.bin"), root.join("link.bin")).unwrap();
1860        let source = Source::open(&root).unwrap();
1861        let error = source
1862            .buffer(&ArtifactPath::new("link.bin").unwrap())
1863            .unwrap_err();
1864        assert_eq!(error.category(), crate::ErrorCategory::Request);
1865
1866        // A symlinked intermediate directory is refused by the component walk.
1867        std::fs::create_dir_all(root.join("real-dir")).unwrap();
1868        std::fs::write(root.join("real-dir/inner.bin"), b"inner").unwrap();
1869        symlink(root.join("real-dir"), root.join("link-dir")).unwrap();
1870        let error = source
1871            .buffer(&ArtifactPath::new("link-dir/inner.bin").unwrap())
1872            .unwrap_err();
1873        assert_eq!(error.category(), crate::ErrorCategory::Request);
1874
1875        let root_link = root.with_extension("link");
1876        symlink(&root, &root_link).unwrap();
1877        assert!(Source::open(&root_link).is_err());
1878        std::fs::remove_file(root_link).unwrap();
1879        std::fs::remove_dir_all(root).unwrap();
1880    }
1881
1882    #[cfg(unix)]
1883    #[test]
1884    fn a_named_pipe_is_refused_promptly_and_siblings_still_acquire() {
1885        use std::os::unix::ffi::OsStrExt;
1886
1887        let root = test_root("fifo");
1888        std::fs::create_dir_all(&root).unwrap();
1889        std::fs::write(root.join("real.csv"), b"real").unwrap();
1890        let fifo = root.join("pipe.dat");
1891        let c_path = std::ffi::CString::new(fifo.as_os_str().as_bytes()).unwrap();
1892        // SAFETY: the pointer references the NUL-terminated buffer owned by
1893        // `c_path`, which outlives the call.
1894        assert_eq!(unsafe { libc::mkfifo(c_path.as_ptr(), 0o644) }, 0);
1895
1896        // No process is attached to the pipe, so a blocking open would hang;
1897        // each operation is driven from a worker with a bounded wait so a
1898        // regression fails the test rather than hanging it.
1899        let (sender, receiver) = std::sync::mpsc::channel();
1900        let opened_root = root.clone();
1901        let worker = std::thread::spawn(move || {
1902            let open_error = Source::open(opened_root.join("pipe.dat")).map(|_| ());
1903            let directory = Source::open(&opened_root).unwrap();
1904            let buffer_error = directory
1905                .buffer(&ArtifactPath::new("pipe.dat").unwrap())
1906                .map(|_| ());
1907            let sibling = directory
1908                .buffer(&ArtifactPath::new("real.csv").unwrap())
1909                .map(|buffer| buffer.bytes().to_vec());
1910            sender.send((open_error, buffer_error, sibling)).unwrap();
1911        });
1912        let (open_error, buffer_error, sibling) = receiver
1913            .recv_timeout(std::time::Duration::from_secs(10))
1914            .expect("acquisition on a writerless pipe completes promptly");
1915        worker.join().unwrap();
1916        assert_eq!(
1917            open_error.unwrap_err().category(),
1918            crate::ErrorCategory::Request
1919        );
1920        assert_eq!(
1921            buffer_error.unwrap_err().category(),
1922            crate::ErrorCategory::Request
1923        );
1924        assert_eq!(sibling.unwrap(), b"real");
1925        std::fs::remove_dir_all(root).unwrap();
1926    }
1927
1928    #[test]
1929    fn an_over_budget_referenced_file_is_refused_before_allocation() {
1930        let root = test_root("budget");
1931        std::fs::create_dir_all(&root).unwrap();
1932        std::fs::write(root.join("master.dss"), b"master").unwrap();
1933        // A sparse file whose declared length far exceeds the acquisition
1934        // budget; no bytes are written, so the refusal must come from the
1935        // declared length, before any reservation.
1936        let big = std::fs::File::create(root.join("big.dat")).unwrap();
1937        big.set_len(MAX_REFERENCED_BYTES * 4).unwrap();
1938        drop(big);
1939
1940        let source = Source::open(root.join("master.dss")).unwrap();
1941        let primary = source.primary_buffer().unwrap();
1942        // The refusal happens before the reserve: the bytes allocated on this
1943        // thread during the refused acquisition are a tiny fraction of the
1944        // declared length. The measurement is thread scoped, so it fails when
1945        // the pre-reserve refusal is removed and never passes by accident.
1946        let (error, allocated) =
1947            measured_bytes(|| source.referenced_buffer(&primary, "big.dat").unwrap_err());
1948        assert!(error.to_string().contains("acquisition budget"), "{error}");
1949        assert!(
1950            (allocated as u64) < MAX_REFERENCED_BYTES / 16,
1951            "the refused acquisition allocated {allocated} bytes"
1952        );
1953        std::fs::remove_dir_all(root).unwrap();
1954    }
1955
1956    #[cfg(unix)]
1957    #[test]
1958    fn racing_entry_listing_never_names_files_outside_the_root() {
1959        use std::os::unix::fs::symlink;
1960
1961        let root = test_root("race-list");
1962        std::fs::create_dir_all(root.join("sub")).unwrap();
1963        std::fs::write(root.join("sub/inside.txt"), b"inside").unwrap();
1964        let outside = test_root("race-list-outside");
1965        std::fs::create_dir_all(&outside).unwrap();
1966        std::fs::write(outside.join("outside-only.txt"), b"outside").unwrap();
1967
1968        let source = Source::open(&root).unwrap();
1969        let stop = std::sync::atomic::AtomicBool::new(false);
1970        std::thread::scope(|scope| {
1971            let flipper = scope.spawn(|| {
1972                while !stop.load(std::sync::atomic::Ordering::Relaxed) {
1973                    let _ = std::fs::remove_dir_all(root.join("sub"));
1974                    let _ = symlink(&outside, root.join("sub"));
1975                    let _ = std::fs::remove_file(root.join("sub"));
1976                    let _ = std::fs::create_dir(root.join("sub"));
1977                    let _ = std::fs::write(root.join("sub/inside.txt"), b"inside");
1978                }
1979            });
1980            for _ in 0..50 {
1981                // Either the real subtree lists, or the walk fails; a name
1982                // that exists only outside the root never appears.
1983                if let Ok(names) = source.entry_names() {
1984                    assert!(
1985                        names
1986                            .iter()
1987                            .all(|name| !name.as_str().contains("outside-only")),
1988                        "{names:?}"
1989                    );
1990                }
1991            }
1992            stop.store(true, std::sync::atomic::Ordering::Relaxed);
1993            flipper.join().unwrap();
1994        });
1995        std::fs::remove_dir_all(&root).ok();
1996        std::fs::remove_dir_all(&outside).ok();
1997    }
1998
1999    #[test]
2000    fn referenced_names_must_be_portable_relative_paths() {
2001        let root = test_root("portable-names");
2002        std::fs::create_dir_all(root.join("sub")).unwrap();
2003        std::fs::write(root.join("master.dss"), b"master").unwrap();
2004        std::fs::write(root.join("sub/feeder.dss"), b"feeder").unwrap();
2005        let source = Source::open(root.join("master.dss")).unwrap();
2006        let primary = source.primary_buffer().unwrap();
2007
2008        // A climb spelled with the platform's alternate separator is the same
2009        // climb; a leading separator and a drive spelling are refused.
2010        for name in ["..\\escape.dss", "\\escape.dss", "C:\\escape.dss", "C:x"] {
2011            let error = source.referenced_buffer(&primary, name).unwrap_err();
2012            assert_eq!(
2013                error.category(),
2014                crate::ErrorCategory::Request,
2015                "{name}: {error}"
2016            );
2017        }
2018        assert!(source.root_buffer("..\\master.dss").is_err());
2019        assert!(source.root_buffer("\\master.dss").is_err());
2020
2021        // Ordinary relative names keep resolving.
2022        let feeder = source
2023            .referenced_buffer(&primary, "sub/feeder.dss")
2024            .unwrap();
2025        assert_eq!(feeder.bytes(), b"feeder");
2026
2027        // An absolute in-root name resolves to one segment per directory
2028        // component: it lands on the same cached buffer the relative name
2029        // produced, proving the per component walk ran on the same key. The
2030        // acquisition root is the canonical containing directory, so the
2031        // absolute spelling is canonical too.
2032        let absolute = root.canonicalize().unwrap().join("sub").join("feeder.dss");
2033        let again = source
2034            .referenced_buffer(&primary, absolute.to_str().unwrap())
2035            .unwrap();
2036        assert_eq!(again.bytes().as_ptr(), feeder.bytes().as_ptr());
2037        // No acquisition returned bytes from outside the root.
2038        assert!(
2039            source
2040                .acquired_buffers()
2041                .iter()
2042                .all(|buffer| !buffer.name().contains("escape"))
2043        );
2044        std::fs::remove_dir_all(root).unwrap();
2045    }
2046
2047    #[cfg(unix)]
2048    #[test]
2049    fn live_sources_hold_no_directory_descriptors_before_acquisition() {
2050        fn open_descriptor_count() -> usize {
2051            let table = if cfg!(target_os = "macos") {
2052                "/dev/fd"
2053            } else {
2054                "/proc/self/fd"
2055            };
2056            std::fs::read_dir(table).unwrap().count()
2057        }
2058
2059        let _guard = process_resource_guard();
2060        let root = test_root("fd-count");
2061        std::fs::create_dir_all(&root).unwrap();
2062        std::fs::write(root.join("case.m"), b"case").unwrap();
2063        std::fs::write(root.join("ref.csv"), b"ref").unwrap();
2064
2065        let before = open_descriptor_count();
2066        let sources: Vec<Source> = (0..300)
2067            .map(|_| Source::open(root.join("case.m")).unwrap())
2068            .collect();
2069        let held = open_descriptor_count();
2070        assert!(
2071            held <= before + 4,
2072            "{} sources hold {} descriptors over the baseline {}",
2073            sources.len(),
2074            held - before,
2075            before
2076        );
2077
2078        // Sources still acquire afterwards, and one source's two acquisitions
2079        // resolve through the same pinned root to one buffer. Acquisition
2080        // pins one descriptor per source, so this runs on a subset that stays
2081        // under the default descriptor limit.
2082        for source in sources.iter().take(32) {
2083            let primary = source.primary_buffer().unwrap();
2084            let first = source.referenced_buffer(&primary, "ref.csv").unwrap();
2085            let second = source.referenced_buffer(&primary, "ref.csv").unwrap();
2086            assert_eq!(first.bytes().as_ptr(), second.bytes().as_ptr());
2087        }
2088        drop(sources);
2089        std::fs::remove_dir_all(root).unwrap();
2090    }
2091
2092    #[test]
2093    fn entry_listing_returns_names_of_every_length_exactly() {
2094        // Guard on the entry-name read: names are read to their terminator,
2095        // whatever length the entry actually occupies.
2096        let root = test_root("name-lengths");
2097        std::fs::create_dir_all(&root).unwrap();
2098        let long = "n".repeat(200);
2099        for name in ["a", "medium-name.csv", long.as_str()] {
2100            std::fs::write(root.join(name), b"x").unwrap();
2101        }
2102        let source = Source::open(&root).unwrap();
2103        let mut names: Vec<String> = source
2104            .entry_names()
2105            .unwrap()
2106            .iter()
2107            .map(|name| name.as_str().to_owned())
2108            .collect();
2109        names.sort();
2110        let mut expected = vec!["a".to_owned(), "medium-name.csv".to_owned(), long];
2111        expected.sort();
2112        assert_eq!(names, expected);
2113        std::fs::remove_dir_all(root).unwrap();
2114    }
2115
2116    #[cfg(unix)]
2117    #[test]
2118    fn a_directory_nested_past_the_depth_bound_is_refused_promptly() {
2119        use std::os::fd::{AsRawFd, FromRawFd};
2120
2121        // The unwind below holds one descriptor per level; serialize with the
2122        // other descriptor sensitive tests so their measurements stay exact.
2123        let _guard = process_resource_guard();
2124
2125        let root = test_root("deep-chain");
2126        std::fs::create_dir_all(&root).unwrap();
2127        // Build the chain by relative creation from inside each level, so the
2128        // tree reaches past any absolute path length limit.
2129        let name = std::ffi::CString::new("d").unwrap();
2130        let mut level = std::fs::File::open(&root).unwrap();
2131        for _ in 0..(MAX_REFERENCED_DEPTH + 40) {
2132            // SAFETY: `level` owns a live directory descriptor for both
2133            // calls, and the pointer references the NUL-terminated buffer
2134            // owned by `name`.
2135            unsafe {
2136                assert_eq!(libc::mkdirat(level.as_raw_fd(), name.as_ptr(), 0o755), 0);
2137                let fd = libc::openat(
2138                    level.as_raw_fd(),
2139                    name.as_ptr(),
2140                    libc::O_RDONLY | libc::O_CLOEXEC,
2141                );
2142                assert!(fd >= 0);
2143                level = std::fs::File::from_raw_fd(fd);
2144            }
2145        }
2146        drop(level);
2147
2148        let (sender, receiver) = std::sync::mpsc::channel();
2149        let listed_root = root.clone();
2150        let worker = std::thread::spawn(move || {
2151            let source = Source::open(&listed_root).unwrap();
2152            sender.send(source.entry_names().map(|_| ())).unwrap();
2153        });
2154        let outcome = receiver
2155            .recv_timeout(std::time::Duration::from_secs(10))
2156            .expect("the depth refusal returns promptly");
2157        worker.join().unwrap();
2158        let error = outcome.expect_err("a chain past the depth bound refuses");
2159        assert!(error.to_string().contains("levels deep"), "{error}");
2160
2161        // The chain is deeper than remove_dir_all's own recursion budget on
2162        // some platforms; unwind it level by level with the same descriptors.
2163        let mut fds = vec![std::fs::File::open(&root).unwrap()];
2164        loop {
2165            let last = fds.last().unwrap();
2166            // SAFETY: as above.
2167            let fd = unsafe {
2168                libc::openat(
2169                    last.as_raw_fd(),
2170                    name.as_ptr(),
2171                    libc::O_RDONLY | libc::O_CLOEXEC,
2172                )
2173            };
2174            if fd < 0 {
2175                break;
2176            }
2177            // SAFETY: `fd` is a freshly opened descriptor.
2178            fds.push(unsafe { std::fs::File::from_raw_fd(fd) });
2179        }
2180        while fds.len() > 1 {
2181            let parent = &fds[fds.len() - 2];
2182            // SAFETY: as above; AT_REMOVEDIR removes the empty directory.
2183            unsafe {
2184                libc::unlinkat(parent.as_raw_fd(), name.as_ptr(), libc::AT_REMOVEDIR);
2185            }
2186            fds.pop();
2187        }
2188        drop(fds);
2189        std::fs::remove_dir_all(&root).unwrap();
2190    }
2191
2192    #[test]
2193    fn a_directory_past_the_entry_budget_is_refused_with_bounded_memory() {
2194        let root = test_root("entry-budget");
2195        std::fs::create_dir_all(&root).unwrap();
2196        // Four times the budget: an unbounded read would materialize four
2197        // times the names the allowance admits, which the thread scoped
2198        // allocation measurement separates decisively from the bounded read.
2199        let excess = MAX_REFERENCED_FILES * 4;
2200        for index in 0..excess {
2201            std::fs::write(root.join(format!("f{index:05}.csv")), b"").unwrap();
2202        }
2203        let source = Source::open(&root).unwrap();
2204        let (error, allocated) = measured_bytes(|| source.entry_names().unwrap_err());
2205        assert!(error.to_string().contains("entries"), "{error}");
2206        // The listing stopped reading at the allowance: the bytes allocated
2207        // on this thread are bounded by the entry budget, never by the
2208        // directory's true entry count. Removing the in-loop bound reads all
2209        // `excess` names and fails this assertion.
2210        assert!(
2211            allocated < MAX_REFERENCED_FILES * 192,
2212            "the refused listing allocated {allocated} bytes"
2213        );
2214        std::fs::remove_dir_all(root).unwrap();
2215    }
2216
2217    #[cfg(unix)]
2218    #[test]
2219    fn listing_breadth_never_scales_open_descriptors() {
2220        fn open_descriptor_count() -> usize {
2221            let table = if cfg!(target_os = "macos") {
2222                "/dev/fd"
2223            } else {
2224                "/proc/self/fd"
2225            };
2226            std::fs::read_dir(table).unwrap().count()
2227        }
2228
2229        let _guard = process_resource_guard();
2230        let root = test_root("breadth");
2231        // Far more immediate subdirectories than the lowered descriptor
2232        // limit, each holding one file, plus one nested chain, so the walk
2233        // proves its held descriptors follow depth rather than breadth.
2234        let breadth = 400usize;
2235        for index in 0..breadth {
2236            let sub = root.join(format!("s{index:03}"));
2237            std::fs::create_dir_all(&sub).unwrap();
2238            std::fs::write(sub.join("data.csv"), b"x").unwrap();
2239        }
2240        std::fs::create_dir_all(root.join("nested/a/b/c")).unwrap();
2241        std::fs::write(root.join("nested/a/b/c/deep.csv"), b"x").unwrap();
2242
2243        // Lower the descriptor soft limit for the duration, so a walk whose
2244        // descriptor use scales with breadth fails here rather than passing
2245        // on a machine with a raised limit.
2246        // SAFETY: `getrlimit` fills the zeroed out-parameter; the lowered
2247        // limit is restored below.
2248        let mut original: libc::rlimit = unsafe { std::mem::zeroed() };
2249        assert_eq!(
2250            // SAFETY: as above.
2251            unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &raw mut original) },
2252            0
2253        );
2254        let lowered = libc::rlimit {
2255            rlim_cur: 256,
2256            rlim_max: original.rlim_max,
2257        };
2258        // SAFETY: lowering the soft limit for this process; restored below.
2259        assert_eq!(
2260            unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &raw const lowered) },
2261            0
2262        );
2263
2264        let baseline = open_descriptor_count();
2265        let peak = std::sync::atomic::AtomicUsize::new(0);
2266        let stop = std::sync::atomic::AtomicBool::new(false);
2267        let names = std::thread::scope(|scope| {
2268            let sampler = scope.spawn(|| {
2269                while !stop.load(std::sync::atomic::Ordering::Relaxed) {
2270                    let count = open_descriptor_count();
2271                    peak.fetch_max(count, std::sync::atomic::Ordering::Relaxed);
2272                }
2273            });
2274            let source = Source::open(&root).unwrap();
2275            let names = source.entry_names().unwrap();
2276            stop.store(true, std::sync::atomic::Ordering::Relaxed);
2277            sampler.join().unwrap();
2278            drop(source);
2279            names
2280        });
2281        // SAFETY: restoring the limit read above.
2282        assert_eq!(
2283            unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &raw const original) },
2284            0
2285        );
2286
2287        assert_eq!(names.len(), breadth + 1, "every file listed");
2288        let sampled_peak = peak.load(std::sync::atomic::Ordering::Relaxed);
2289        assert!(
2290            sampled_peak <= baseline + MAX_REFERENCED_DEPTH + 16,
2291            "the walk held {sampled_peak} descriptors over a baseline of {baseline}"
2292        );
2293        std::fs::remove_dir_all(root).unwrap();
2294    }
2295
2296    #[test]
2297    fn a_directory_listing_still_names_windows_reserved_spellings() {
2298        // The output predicate refusing reserved device stems must not narrow
2299        // what a source directory can list.
2300        let root = test_root("reserved-listing");
2301        std::fs::create_dir_all(&root).unwrap();
2302        std::fs::write(root.join("aux.dss"), b"content").unwrap();
2303        let source = Source::open(&root).unwrap();
2304        let names = source.entry_names().unwrap();
2305        assert_eq!(names.len(), 1);
2306        assert_eq!(names[0].as_str(), "aux.dss");
2307        std::fs::remove_dir_all(root).unwrap();
2308    }
2309
2310    #[test]
2311    fn a_directory_listing_repeats_and_survives_acquisition() {
2312        // The walk runs once and its result is the source's one immutable
2313        // listing: a second call between or after buffer acquisitions
2314        // returns the same names rather than an empty second walk from a
2315        // directory stream a platform shares across duplicated descriptors.
2316        let root = test_root("repeat-listing");
2317        std::fs::create_dir_all(&root).unwrap();
2318        std::fs::write(root.join("network.csv"), b"name\nseq\n").unwrap();
2319        std::fs::write(root.join("buses.csv"), b"name\nB1\n").unwrap();
2320        let source = Source::open(&root).unwrap();
2321        let first = source.entry_names().unwrap();
2322        assert_eq!(first.len(), 2);
2323        let name = ArtifactPath::new("network.csv").unwrap();
2324        source.buffer(&name).unwrap();
2325        let second = source.entry_names().unwrap();
2326        assert_eq!(first, second);
2327        std::fs::remove_dir_all(root).unwrap();
2328    }
2329
2330    #[test]
2331    fn concurrent_acquisition_retains_one_buffer_for_one_name() {
2332        let root = test_root("concurrent");
2333        std::fs::create_dir_all(&root).unwrap();
2334        std::fs::write(root.join("shared.csv"), b"shared").unwrap();
2335        let source = Source::open(&root).unwrap();
2336        let name = ArtifactPath::new("shared.csv").unwrap();
2337        let buffers: Vec<_> = std::thread::scope(|scope| {
2338            (0..8)
2339                .map(|_| {
2340                    let source = source.clone();
2341                    let name = name.clone();
2342                    scope.spawn(move || source.buffer(&name).unwrap())
2343                })
2344                .collect::<Vec<_>>()
2345                .into_iter()
2346                .map(|handle| handle.join().unwrap())
2347                .collect()
2348        });
2349        let pointer = buffers[0].bytes().as_ptr();
2350        assert!(
2351            buffers
2352                .iter()
2353                .all(|buffer| buffer.bytes().as_ptr() == pointer)
2354        );
2355        assert_eq!(source.acquired_buffers().len(), 1);
2356        std::fs::remove_dir_all(root).unwrap();
2357    }
2358    #[cfg(unix)]
2359    #[test]
2360    fn a_refused_listing_never_shortens_the_next_one() {
2361        // A walk that stops early (the entry budget) must not leave the next
2362        // walk a partially consumed directory stream: every retry reads the
2363        // whole directory again and refuses the same way.
2364        let root = test_root("refused-listing");
2365        std::fs::create_dir_all(&root).unwrap();
2366        for index in 0..(MAX_REFERENCED_FILES + 5) {
2367            std::fs::write(root.join(format!("f{index}.txt")), b"x").unwrap();
2368        }
2369        let source = Source::open(&root).unwrap();
2370        let first = source.entry_names().unwrap_err();
2371        let second = source.entry_names().unwrap_err();
2372        assert_eq!(
2373            first.diagnostics().first().map(|d| d.code().to_owned()),
2374            second.diagnostics().first().map(|d| d.code().to_owned()),
2375            "the refusal repeats rather than shrinking into a partial listing"
2376        );
2377        drop(source);
2378        let _ = std::fs::remove_dir_all(&root);
2379    }
2380}