1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Unix final-component symlink rejection followed by descriptor-based reading.
use ;
use ;
/// Read a bounded regular file without following a final-component symlink.
///
/// Uses Unix `NOFOLLOW`, `NONBLOCK` and `CLOEXEC` flags, then validates descriptor
/// metadata with [`read_opened_file`]. A FIFO without a writer can be opened
/// without waiting and is rejected before reading. Directories and other opened
/// special files are also rejected. No source bytes are written or removed.
///
/// This is a final-component boundary, not root confinement: ancestor symlinks
/// are followed. Consumers must supply trusted ancestors and retain their root,
/// permission, ownership and missing-file policy. Filesystem syscalls and regular
/// reads can still block; deadlines remain caller-owned. Concurrent writers can
/// change contents even after this descriptor is selected.
/// Existing [`super::read_file`] and [`super::hash_file`] continue to follow links.
///
/// # Errors
/// Returns [`ArtifactError::Io`] for open failures (including final symlinks),
/// or typed non-regular-file, metadata, size, read and allocation failures.
/// Hash a regular file without following a final-component symlink.
///
/// Uses the same opening flags and caller-owned confinement constraints as
/// [`read_file_no_follow`], then validates the opened descriptor's type and size.
/// Hashes from its initial position with constant working memory through
/// [`hash_reader`]. Metadata is an early rejection only; the stream is bounded
/// independently to detect growth beyond `max_bytes`. Empty files accept zero.
///
/// Ancestor symlinks are followed. An open descriptor selects an inode, not
/// immutable contents; concurrent-writer custody and any exact expected-length
/// check remain caller-owned. No source bytes are written or removed.
///
/// # Errors
/// Returns [`ArtifactError::Io`] with the original open/metadata/read cause,
/// including final-symlink rejection, or typed non-regular-file/byte-limit errors.
/// Hash a final-component no-follow file and retain its descriptor metadata.
///
/// Shares [`hash_file_no_follow`]'s opening, regular-file and byte admission.
/// Metadata is observed before the bounded hash, without another pathname lookup.
/// Ancestors are followed; callers retain namespace/writer exclusion, permission
/// and digest policy. This establishes neither immutable bytes nor atomic execution.
///
/// # Errors
/// Returns the same open, metadata, type, byte-limit and read failures as [`hash_file_no_follow`].
/// Read an optional bounded regular file, rejecting final-component symlinks.
///
/// `None` means the initial pathname observation was missing. A dangling symlink
/// is rejected as [`ArtifactError::NotRegularFile`], not treated as absent.
/// Subsequent open/read failures remain errors, including concurrent removal.
/// Metadata and the actual stream are bounded independently. Allocation failures
/// retain their artifact error; no unbounded read or diagnostic conversion occurs.
///
/// Ancestor symlinks, concurrent writers and filesystem latency have the same
/// caller-owned constraints as [`read_file_no_follow`].
///
/// # Errors
/// Returns typed admission, open, metadata, size, read and allocation failures.
// The consuming reader must admit the selected descriptor before reading.
pub