1use crate::async_storage::{AsyncAuthorityStore, AsyncObjectStore};
4use crate::cancellation::{CancellationError, CancellationToken};
5use crate::foundation::{
6 Digest, DurableCommit, Epoch, FileId, GenerationId, Head, OperationId, ProposedCommit,
7 Sequence, VolumeId,
8};
9#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
10use crate::kernel::decode_retention_created;
11use crate::kernel::{
12 AsyncBlobSource, AttributeEntry, AttributeListError, AttributeListing, AttributeLookupError,
13 AttributeMutation, AttributeMutationError, AttributeName, AttributePage,
14 AuthenticatedGenerationProbe, AuthenticatedProbeError, BlobBuildError, BlobBuildOptions,
15 BlobReadError, CanonicalDecodeError, CheckoutDependencies, CheckpointError, CheckpointRequest,
16 ClosureError, ClosureLimits, DecodeLimits, Dependency, DependencyError, DependencyRegion,
17 DependencyState, DirectoryPage, DirectoryReadError, ExtentPlan, ExtentRangeRequest,
18 ExtentSeekRequest, ExtentSeekTarget, FileKind, FileMetadata, FileMutation, FilePayload,
19 FileRangeRead, FileRangeReadError, FileRangeRequest, FileRecord, FileRecordReadError,
20 FileTableMutationError, FileTablePage, GenerationExportManifest, GenerationMutationError,
21 GenerationRoot, GenerationTransferBatch, GenerationTransferError, InlineFileData,
22 InlineFileDataError, LivePublicationObservation, LiveRetryAction, LiveRetryError,
23 LiveRetryState, LogicalName, MAXIMUM_GENERATION_ROOT_BYTES, MergeConflictResolution,
24 MergeConflictSide, MergeGenerationError, MergeGenerationOutcome, MergeGenerationRequest,
25 MetadataField, Mutation, NameEncoding, NamespacePath, PathBatchLookup, PathLookup,
26 PathLookupError, PersistentDiffError, ProbeLimits, PublicationError, PublicationIntent,
27 PublicationReceipt, PublishGenerationRequest, RebaseConflict, RebaseDecision, RebaseError,
28 RegularMutation, RegularMutationError, RetentionCreated, RetentionCreatedError, RetentionKind,
29 TransferCursor, TreeMutationError, TreePage, VolumeCreated, VolumeCreatedError,
30 apply_attribute_mutations_async, apply_generation_mutations_retaining_async,
31 apply_regular_mutation_async, authenticate_generation_export_manifest_async, build_blob_async,
32 build_checkpoint_async, build_generation_export_manifest_async, classify_rebase_async,
33 decode_file_metadata, decode_published_generation, decode_volume_created,
34 decode_workspace_deleted, diff_file_records_async, diff_tree_entries_async,
35 encode_attribute_page, encode_file_metadata, encode_file_table_page, encode_generation_root,
36 encode_retention_created, encode_tree_page, encode_volume_created, encode_workspace_deleted,
37 export_generation_batch_async, generation_root_parent_count, import_generation_batch_async,
38 list_attributes_async, list_tree_entries_async, list_tree_entries_at_or_after_async,
39 lookup_attribute_async, lookup_file_record_async, lookup_file_records_async,
40 merge_generation_async, plan_extent_range_async, prove_generation_closure_async,
41 publish_generation_async, publish_generation_async_with_context,
42 publish_generation_async_with_permit, read_blob_range_async, read_file_range_async,
43 retention_authority_id, seek_extent_async, volume_authority_id,
44};
45#[cfg(test)]
46use crate::kernel::{
47 FileTableMutation, TreeMutation, file_table_mutation,
48 merge_directory_record_async as merge_directory_record, merge_file_fields, resolve_three,
49 tree_mutation,
50};
51pub use crate::kernel::{LiveMutationOutcome, MergeConflict};
52#[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
53use crate::kernel::{decode_source_volume, source_authority_id};
54use crate::model::{
55 AccessMode, CaseSensitivity, CheckoutMode, CheckoutModeError, ConcurrencyMode, ConsistencyMode,
56 GenerationSelector, Lifecycle, MutationMode, UnicodePolicy, VolumeConfig, VolumeConfigError,
57 VolumeLimits,
58};
59use crate::performance::{
60 MeasuredResult, OperationFailure, OperationReceipt, WorkBudget, WorkCounters, WorkError,
61};
62use crate::storage::{
63 AppendOutcome, AuthorityStoreError, ByteRange, CreateAuthorityOutcome, FenceOutcome,
64 OBJECT_DIGEST_ENVELOPE_BYTES, ObjectId, ObjectKind, ObjectReadRequest, ObjectReadRetention,
65 ObjectStoreError, PublicationPermit, ReplayLimit, object_digest,
66};
67#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
68use acyclic_native_runtime::OwnershipAnchor;
69#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
70use acyclic_objects::ObjectsProvider as _;
71use bytes::Bytes;
72use futures::{StreamExt as _, stream};
73use std::collections::{BTreeMap, BTreeSet, VecDeque};
74use std::mem::size_of;
75#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
76use std::path::{Path, PathBuf};
77use std::sync::Arc;
78#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
79use std::sync::{OnceLock, Weak};
80use thiserror::Error;
81
82const MAXIMUM_VOLUME_EVENT_BYTES: u64 = 4 * 1024;
83const STAGED_METADATA_CACHE_ENTRIES: usize = 16;
84
85struct AuthoredMetadataCache {
86 entries: [Option<(FileMetadata, ObjectId)>; STAGED_METADATA_CACHE_ENTRIES],
87 next: usize,
88}
89
90impl AuthoredMetadataCache {
91 fn new() -> Self {
92 Self {
93 entries: [None; STAGED_METADATA_CACHE_ENTRIES],
94 next: 0,
95 }
96 }
97
98 fn get(&self, metadata: FileMetadata) -> Option<ObjectId> {
99 self.entries
100 .iter()
101 .flatten()
102 .find_map(|(cached, object)| (*cached == metadata).then_some(*object))
103 }
104
105 fn remember(&mut self, metadata: FileMetadata, object: ObjectId) {
106 if let Some(slot) = self.entries.get_mut(self.next) {
107 *slot = Some((metadata, object));
108 }
109 self.next = (self.next + 1) % STAGED_METADATA_CACHE_ENTRIES;
110 }
111}
112const CASE_FOLD_COLLISION_SCAN_PAGE: u32 = 256;
118const ROOT_FILE_DOMAIN: &[u8] = b"acyclic-fs-root-file-v1\0";
119const CREATION_OPERATION_DOMAIN: &[u8] = b"acyclic-fs-create-operation-v1\0";
120const CREATION_FINGERPRINT_DOMAIN: &[u8] = b"acyclic-fs-create-fingerprint-v1\0";
121const WORKSPACE_JOIN_OPERATION_DOMAIN: &[u8] = b"acyclic-fs-workspace-join-operation-v1\0";
122
123pub(crate) enum WorkspaceJoinOutcome {
124 Applied(GenerationId),
125 AlreadyApplied(GenerationId),
126 NoChanges(GenerationId),
127 Stale(GenerationId),
128 Conflicted(Vec<MergeConflict>, bool),
129 Fenced,
130 IdempotencyConflict,
131}
132
133pub(crate) struct WorkspaceJoinRequest<'a, A, O> {
134 pub target: &'a Volume<A, O>,
135 pub base: WorkspaceCommonAncestor,
136 pub source: &'a crate::Generation<A, O>,
137 pub expected_target: GenerationId,
138 pub expected_head: Head,
139 pub history: crate::workspace::JoinHistory,
140 pub operation_id: OperationId,
141 pub permit: PublicationPermit,
142 pub maximum_generations: u32,
143 pub maximum_changes: u32,
144 pub maximum_conflicts: u32,
145 pub resolutions: BTreeMap<MergeConflict, MergeConflictResolution>,
146}
147
148impl<A, O> WorkspaceJoinRequest<'_, A, O> {
149 fn operation_context(&self) -> Digest {
150 let mut hasher = blake3::Hasher::new();
151 hasher.update(WORKSPACE_JOIN_OPERATION_DOMAIN);
152 hasher.update(&self.source.workspace.volume.id.into_bytes());
153 hasher.update(self.source.id.digest().as_bytes());
154 hasher.update(&self.target.id.into_bytes());
155 hasher.update(self.expected_target.digest().as_bytes());
156 hasher.update(&self.base.volume_id.into_bytes());
157 hasher.update(self.base.id.digest().as_bytes());
158 hasher.update(&[match self.history {
159 crate::workspace::JoinHistory::Merge => 1,
160 crate::workspace::JoinHistory::Rebase => 2,
161 crate::workspace::JoinHistory::Squash => 3,
162 crate::workspace::JoinHistory::CherryPick => 4,
163 }]);
164 hasher.update(&self.maximum_generations.to_le_bytes());
165 hasher.update(&self.maximum_changes.to_le_bytes());
166 hasher.update(&self.maximum_conflicts.to_le_bytes());
167 for (conflict, resolution) in &self.resolutions {
168 match conflict {
169 MergeConflict::File(file_id) => {
170 hasher.update(&[0]);
171 hasher.update(&file_id.into_bytes());
172 }
173 MergeConflict::Binding { directory_id, name } => {
174 hasher.update(&[1]);
175 hasher.update(&directory_id.into_bytes());
176 hasher.update(&[match name.encoding() {
177 NameEncoding::Utf8 => 0,
178 NameEncoding::PosixBytes => 1,
179 NameEncoding::WindowsUtf16Le => 2,
180 }]);
181 hasher.update(&(name.as_bytes().len() as u64).to_le_bytes());
182 hasher.update(name.as_bytes());
183 }
184 }
185 hash_merge_resolution(&mut hasher, resolution);
186 }
187 Digest::from_bytes(*hasher.finalize().as_bytes())
188 }
189}
190
191fn hash_merge_resolution(hasher: &mut blake3::Hasher, resolution: &MergeConflictResolution) {
192 let hash_side = |hasher: &mut blake3::Hasher, side: MergeConflictSide| {
193 hasher.update(&[match side {
194 MergeConflictSide::Base => 0,
195 MergeConflictSide::Ours => 1,
196 MergeConflictSide::Theirs => 2,
197 }]);
198 };
199 match resolution {
200 MergeConflictResolution::Select(side) => {
201 hasher.update(&[0]);
202 hash_side(hasher, *side);
203 }
204 MergeConflictResolution::File(record) => {
205 hasher.update(&[1]);
206 if let Some(record) = record {
207 hasher.update(&[1]);
208 hasher.update(&record.file_id.into_bytes());
209 hasher.update(&[match record.kind {
210 FileKind::Regular => 0,
211 FileKind::Directory => 1,
212 FileKind::SymbolicLink => 2,
213 FileKind::Fifo => 3,
214 FileKind::Socket => 4,
215 FileKind::CharacterDevice => 5,
216 FileKind::BlockDevice => 6,
217 FileKind::ReparsePoint => 7,
218 FileKind::MountBoundary => 8,
219 }]);
220 hasher.update(&record.link_count.to_le_bytes());
221 hash_object_id(hasher, record.metadata);
222 hash_file_payload(hasher, record.payload);
223 } else {
224 hasher.update(&[0]);
225 }
226 }
227 MergeConflictResolution::Binding(file_id) => {
228 hasher.update(&[2]);
229 if let Some(file_id) = file_id {
230 hasher.update(&[1]);
231 hasher.update(&file_id.into_bytes());
232 } else {
233 hasher.update(&[0]);
234 }
235 }
236 }
237}
238
239fn hash_object_id(hasher: &mut blake3::Hasher, object: ObjectId) {
240 hasher.update(&[object.kind.canonical_tag()]);
241 hasher.update(object.digest.as_bytes());
242}
243
244fn hash_file_payload(hasher: &mut blake3::Hasher, payload: FilePayload) {
245 match payload {
246 FilePayload::InlineRegular(data) => {
247 hasher.update(&[0]);
248 hasher.update(&(data.as_bytes().len() as u64).to_le_bytes());
249 hasher.update(data.as_bytes());
250 }
251 FilePayload::Regular {
252 logical_bytes,
253 extents,
254 } => {
255 hasher.update(&[1]);
256 hasher.update(&logical_bytes.to_le_bytes());
257 hash_object_id(hasher, extents);
258 }
259 FilePayload::Directory { entries } => {
260 hasher.update(&[2]);
261 hash_object_id(hasher, entries);
262 }
263 FilePayload::SymbolicLink {
264 target_bytes,
265 target,
266 } => {
267 hasher.update(&[3]);
268 hasher.update(&target_bytes.to_le_bytes());
269 hash_object_id(hasher, target);
270 }
271 FilePayload::Empty => {
272 hasher.update(&[4]);
273 }
274 FilePayload::Device { major, minor } => {
275 hasher.update(&[5]);
276 hasher.update(&major.to_le_bytes());
277 hasher.update(&minor.to_le_bytes());
278 }
279 FilePayload::ReparsePoint {
280 payload_bytes,
281 payload,
282 } => {
283 hasher.update(&[6]);
284 hasher.update(&payload_bytes.to_le_bytes());
285 hash_object_id(hasher, payload);
286 }
287 }
288}
289
290#[derive(Clone, Copy)]
291pub(crate) struct WorkspaceCommonAncestor {
292 pub id: GenerationId,
293 pub volume_id: VolumeId,
294}
295
296pub(crate) struct WorkspaceRebaseRequest<'a, A, O> {
297 pub target: &'a Volume<A, O>,
298 pub operation_id: OperationId,
299 pub maximum_generations: u32,
300 pub maximum_changes: u32,
301 pub maximum_conflicts: u32,
302}
303
304#[derive(Clone, Copy, Debug, Eq, PartialEq)]
306pub struct EmbeddedCapabilities {
307 pub durable: bool,
309}
310
311impl EmbeddedCapabilities {
312 pub const MEMORY: Self = Self { durable: false };
314}
315
316#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
318#[derive(Clone, Debug, Eq, PartialEq)]
319pub struct LocalOptions {
320 pub root: PathBuf,
322 pub stream: acyclic_stream::LocalStreamLimits,
324 pub objects: acyclic_objects::LocalObjectsLimits,
326 pub object_cache: crate::cache::ObjectCacheOptions,
328}
329
330#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
332#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
333pub struct LocalGarbageCollection {
334 pub examined: u64,
336 pub removed: u64,
338 pub segments_removed: u64,
340 pub temporary_files_removed: u64,
342}
343
344#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
345impl LocalOptions {
346 #[must_use]
348 pub fn new(root: impl Into<PathBuf>) -> Self {
349 Self {
350 root: root.into(),
351 stream: acyclic_stream::LocalStreamLimits::default(),
352 objects: acyclic_objects::LocalObjectsLimits::default(),
353 object_cache: crate::cache::ObjectCacheOptions::default(),
354 }
355 }
356}
357
358struct FsInner<A, O> {
359 authority: A,
360 objects: O,
361 capabilities: EmbeddedCapabilities,
362 workspace_namespace: [u8; 16],
363 path_index: Arc<dyn crate::path_index::GenerationPathIndex>,
364 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
367 _local_root_lifecycle: Option<OwnershipAnchor>,
368}
369
370#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
371struct LocalRootRegistration {
372 options: LocalOptions,
373 live: Weak<FsInner<LocalAuthorityBackend, LocalObjectBackend>>,
374 opening: Arc<tokio::sync::Mutex<()>>,
375 lifecycle: Arc<tokio::sync::Mutex<()>>,
376}
377
378#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
379type LocalRootRegistry = std::collections::BTreeMap<PathBuf, LocalRootRegistration>;
380
381#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
382fn prepare_local_root_open(
383 registry: &mut LocalRootRegistry,
384 root: &Path,
385 options: &LocalOptions,
386) -> Arc<tokio::sync::Mutex<()>> {
387 if let Some(registration) = registry.get(root) {
388 return Arc::clone(®istration.opening);
389 }
390 let opening = Arc::new(tokio::sync::Mutex::new(()));
391 let lifecycle = Arc::new(tokio::sync::Mutex::new(()));
392 registry.insert(
393 root.to_path_buf(),
394 LocalRootRegistration {
395 options: options.clone(),
396 live: Weak::new(),
397 opening: Arc::clone(&opening),
398 lifecycle: Arc::clone(&lifecycle),
399 },
400 );
401 opening
402}
403
404#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
405async fn run_local_initialization<T, F, I>(
406 ownership: tokio::sync::OwnedMutexGuard<()>,
407 initialize: I,
408) -> Result<T, FsError>
409where
410 T: Send + 'static,
411 F: std::future::Future<Output = T> + Send + 'static,
412 I: FnOnce(tokio::sync::OwnedMutexGuard<()>) -> F + Send + 'static,
413{
414 tokio::spawn(async move { initialize(ownership).await })
418 .await
419 .map_err(|_| FsError::LocalInitializationWorker)
420}
421
422pub struct Fs<A, O> {
424 inner: Arc<FsInner<A, O>>,
425}
426
427impl<A, O> Clone for Fs<A, O> {
428 fn clone(&self) -> Self {
429 Self {
430 inner: Arc::clone(&self.inner),
431 }
432 }
433}
434
435impl<A, O> Fs<A, O> {
436 #[must_use]
438 pub fn capabilities(&self) -> EmbeddedCapabilities {
439 self.inner.capabilities
440 }
441
442 pub fn workspace_id(
448 &self,
449 name: impl AsRef<str>,
450 ) -> Result<crate::WorkspaceId, crate::WorkspaceNameError> {
451 let name = crate::WorkspaceName::new(name)?;
452 Ok(crate::WorkspaceId::derive(
453 self.inner.workspace_namespace,
454 &name,
455 ))
456 }
457
458 pub(crate) fn same_deployment(&self, other: &Self) -> bool {
459 Arc::ptr_eq(&self.inner, &other.inner)
460 }
461
462 pub(crate) fn path_index(&self) -> &dyn crate::path_index::GenerationPathIndex {
463 &*self.inner.path_index
464 }
465
466 pub(crate) fn authority(&self) -> &A {
467 &self.inner.authority
468 }
469
470 #[cfg(all(test, feature = "s3-http"))]
471 pub(crate) fn objects(&self) -> &O {
472 &self.inner.objects
473 }
474}
475
476impl<P, O> Fs<crate::StreamAuthorityStore<P>, O> {
477 #[must_use]
481 pub fn operation_window_store(&self) -> crate::StreamOperationWindowStore<P> {
482 crate::StreamOperationWindowStore::new(self.inner.authority.provider())
483 }
484}
485
486pub struct Volume<A, O> {
488 pub(crate) fs: Fs<A, O>,
489 pub(crate) id: VolumeId,
490 pub(crate) config: VolumeConfig,
491}
492
493impl<A, O> Clone for Volume<A, O> {
494 fn clone(&self) -> Self {
495 Self {
496 fs: self.fs.clone(),
497 id: self.id,
498 config: self.config,
499 }
500 }
501}
502
503pub struct Checkout<A, O> {
505 volume: Volume<A, O>,
506 base_generation_root: ObjectId,
507 generation_root: ObjectId,
508 base_file_table: ObjectId,
509 base_root: GenerationRoot,
510 root: GenerationRoot,
511 authority_head: Option<Head>,
512 authored_operation_id: Option<OperationId>,
513 live_operation_id: Option<OperationId>,
514 last_commit: Option<LastCommit>,
515 prepared_merge_parent: Option<ObjectId>,
516 dependencies: CheckoutDependencies,
517 mode: CheckoutMode,
518}
519
520pub struct PinnedReader<A, O> {
525 volume: Volume<A, O>,
526 generation_root: ObjectId,
527 root: GenerationRoot,
528}
529
530pub struct ContentStager<A, O> {
535 volume: Volume<A, O>,
536}
537
538impl<A, O> Clone for ContentStager<A, O> {
539 fn clone(&self) -> Self {
540 Self {
541 volume: self.volume.clone(),
542 }
543 }
544}
545
546impl<A, O> Clone for PinnedReader<A, O> {
547 fn clone(&self) -> Self {
548 Self {
549 volume: self.volume.clone(),
550 generation_root: self.generation_root,
551 root: self.root.clone(),
552 }
553 }
554}
555
556#[derive(Clone, Debug, Eq, PartialEq)]
558pub struct FileDescription {
559 pub kind: FileKind,
561 pub logical_bytes: u64,
563 pub metadata: FileMetadata,
565}
566
567pub struct ResolvedFile<A, O> {
571 reader: PinnedReader<A, O>,
572 record: FileRecord,
573 description: FileDescription,
574}
575
576impl<A, O> ResolvedFile<A, O> {
577 #[must_use]
579 pub const fn file_id(&self) -> FileId {
580 self.record.file_id
581 }
582
583 #[must_use]
585 pub const fn description(&self) -> &FileDescription {
586 &self.description
587 }
588}
589
590impl<A: AsyncAuthorityStore, O: AsyncObjectStore> ResolvedFile<A, O> {
591 pub async fn read_range(
593 &self,
594 range: ByteRange,
595 budget: WorkBudget,
596 cancellation: &CancellationToken,
597 ) -> FsResult<FileRangeRead> {
598 self.reader
599 .read_record_range(self.record, range, budget, cancellation)
600 .await
601 }
602
603 pub async fn read_symbolic_link(
605 &self,
606 budget: WorkBudget,
607 cancellation: &CancellationToken,
608 ) -> FsResult<Bytes> {
609 self.reader
610 .read_symbolic_link_record(self.record, budget, cancellation)
611 .await
612 }
613
614 pub async fn plan_extents(
616 &self,
617 range: ByteRange,
618 maximum_spans: u32,
619 budget: WorkBudget,
620 cancellation: &CancellationToken,
621 ) -> FsResult<Option<ExtentPlan>> {
622 self.reader
623 .plan_record_extents(self.record, range, maximum_spans, budget, cancellation)
624 .await
625 }
626}
627
628pub struct ResolvedFileRangeReadRequest<'a, A, O> {
630 pub file: &'a ResolvedFile<A, O>,
632 pub range: ByteRange,
634}
635
636pub struct ResolvedDirectoryEntry<A, O> {
639 pub name: LogicalName,
641 pub file: ResolvedFile<A, O>,
643}
644
645pub struct ResolvedDirectoryPage<A, O> {
647 pub entries: Vec<ResolvedDirectoryEntry<A, O>>,
649 pub has_more: bool,
651}
652
653#[derive(Clone, Copy)]
654struct LastCommit {
655 operation_id: OperationId,
656 generation_id: GenerationId,
657 head: Head,
658}
659
660#[derive(Clone, Copy)]
661struct VolumeCreation {
662 volume_id: VolumeId,
663 config: VolumeConfig,
664 generation_root: ObjectId,
665 operation_id: Option<OperationId>,
666}
667
668#[derive(Clone, Copy)]
669struct VerifiedForkSource {
670 generation_root: ObjectId,
671}
672
673#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
675pub type LocalAuthorityBackend =
676 crate::distributed::StreamAuthorityStore<acyclic_stream::LocalStream>;
677
678#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
680pub type LocalObjectBackend = crate::staged_objects::StagedObjects<
681 crate::cache::CachedObjectStore<
682 crate::distributed::ProviderObjectStore<acyclic_objects::LocalObjects>,
683 >,
684>;
685
686#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
688pub type LocalFs = Fs<LocalAuthorityBackend, LocalObjectBackend>;
689
690#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
693pub type LocalOperationWindowStore = crate::StreamOperationWindowStore<acyclic_stream::LocalStream>;
694
695#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
697pub type LocalVolume = Volume<LocalAuthorityBackend, LocalObjectBackend>;
698
699#[cfg(all(feature = "memory", feature = "distributed"))]
701pub type MemoryFs = Fs<MemoryAuthorityBackend, MemoryObjectBackend>;
702
703#[cfg(all(feature = "memory", feature = "distributed"))]
704pub type MemoryAuthorityBackend =
706 crate::distributed::StreamAuthorityStore<acyclic_stream::MemoryStream>;
707
708#[cfg(all(feature = "memory", feature = "distributed"))]
709pub type MemoryObjectBackend =
711 crate::distributed::ProviderObjectStore<acyclic_objects::MemoryObjects>;
712
713#[cfg(all(test, feature = "memory", feature = "distributed"))]
714pub(crate) type MemoryCheckout = Checkout<MemoryAuthorityBackend, MemoryObjectBackend>;
715
716pub struct DetachedFile<A, O> {
722 volume: Volume<A, O>,
723 record: FileRecord,
724}
725
726pub type FsReceipt<T> = OperationReceipt<T>;
728
729#[derive(Clone, Debug, Eq, PartialEq)]
731pub struct FileCloneRequest {
732 pub source: NamespacePath,
734 pub source_offset: u64,
736 pub destination: NamespacePath,
738 pub destination_offset: u64,
740 pub length: u64,
742}
743
744#[derive(Clone, Debug, Eq, PartialEq)]
747pub enum AuthoredMutation {
748 Reidentify {
750 path: NamespacePath,
752 file_id: FileId,
754 },
755 CreateFile {
757 path: NamespacePath,
759 bytes: Bytes,
761 metadata: FileMetadata,
763 },
764 CreateFileFromContent {
766 path: NamespacePath,
768 content: StagedContent,
770 metadata: FileMetadata,
772 file_id: Option<FileId>,
774 },
775 CreateDirectory {
777 path: NamespacePath,
779 metadata: FileMetadata,
781 },
782 CreateSymbolicLink {
784 path: NamespacePath,
786 target: Bytes,
788 metadata: FileMetadata,
790 },
791 CreateEmptySpecial {
793 path: NamespacePath,
795 kind: FileKind,
797 metadata: FileMetadata,
799 },
800 CreateDevice {
802 path: NamespacePath,
804 kind: FileKind,
806 major: u32,
808 minor: u32,
810 metadata: FileMetadata,
812 },
813 CreateReparsePoint {
815 path: NamespacePath,
817 payload: Bytes,
819 metadata: FileMetadata,
821 },
822 Remove {
824 path: NamespacePath,
826 expected_file_id: Option<FileId>,
828 },
829 Rename {
831 source: NamespacePath,
833 destination: NamespacePath,
835 replace: bool,
837 },
838 HardLink {
840 source: NamespacePath,
842 destination: NamespacePath,
844 },
845 Write {
847 path: NamespacePath,
849 offset: u64,
851 bytes: Bytes,
853 },
854 WriteFromContent {
856 path: NamespacePath,
858 offset: u64,
860 content: StagedContent,
862 },
863 SetMetadata {
865 path: NamespacePath,
867 metadata: FileMetadata,
869 },
870 Resize {
872 path: NamespacePath,
874 logical_bytes: u64,
876 },
877 ZeroRange {
879 path: NamespacePath,
881 range: ByteRange,
883 allocated: bool,
885 extend: bool,
887 },
888 Preallocate {
890 path: NamespacePath,
892 range: ByteRange,
894 keep_size: bool,
896 },
897 CloneRange(FileCloneRequest),
899}
900
901fn authored_mutation_operation_count(mutation: &AuthoredMutation) -> usize {
902 match mutation {
903 AuthoredMutation::CreateFile { bytes, .. } => {
904 usize::from(bytes.len() > crate::kernel::MAXIMUM_INLINE_FILE_BYTES) + 1
905 }
906 AuthoredMutation::CreateFileFromContent { content, .. } => {
907 usize::from(content.logical_bytes != 0) + 1
908 }
909 AuthoredMutation::Reidentify { .. }
910 | AuthoredMutation::Write { .. }
911 | AuthoredMutation::WriteFromContent { .. }
912 | AuthoredMutation::CreateDirectory { .. }
913 | AuthoredMutation::CreateSymbolicLink { .. }
914 | AuthoredMutation::CreateEmptySpecial { .. }
915 | AuthoredMutation::CreateDevice { .. }
916 | AuthoredMutation::CreateReparsePoint { .. }
917 | AuthoredMutation::Remove { .. }
918 | AuthoredMutation::Rename { .. }
919 | AuthoredMutation::HardLink { .. }
920 | AuthoredMutation::SetMetadata { .. }
921 | AuthoredMutation::Resize { .. }
922 | AuthoredMutation::ZeroRange { .. }
923 | AuthoredMutation::Preallocate { .. }
924 | AuthoredMutation::CloneRange(_) => 1,
925 }
926}
927
928fn pending_name_collision(
929 path: &NamespacePath,
930 pending: &[(NamespacePath, FileId)],
931 allowed_file_ids: &[FileId],
932) -> bool {
933 let Some((parent, name)) = path.split_last() else {
934 return false;
935 };
936 let folded = name.case_fold_key();
937 pending.iter().any(|(pending_path, file_id)| {
938 let Some((pending_parent, pending_name)) = pending_path.split_last() else {
939 return false;
940 };
941 pending_parent == parent
942 && pending_name != name
943 && pending_name.case_fold_key() == folded
944 && !allowed_file_ids.contains(file_id)
945 })
946}
947
948fn pending_binding_path(
949 path: &NamespacePath,
950 pending: &[(NamespacePath, FileId)],
951 case_sensitivity: CaseSensitivity,
952) -> Option<(NamespacePath, FileId)> {
953 let (parent, name) = path.split_last()?;
954 pending.iter().rev().find_map(|(pending_path, file_id)| {
955 let (pending_parent, pending_name) = pending_path.split_last()?;
956 let same_name = match case_sensitivity {
957 CaseSensitivity::Sensitive => pending_name == name,
958 CaseSensitivity::ProfileFolded => pending_name.case_fold_key() == name.case_fold_key(),
959 };
960 (pending_parent == parent && same_name).then_some((pending_path.clone(), *file_id))
961 })
962}
963
964fn pending_binding_id(
965 path: &NamespacePath,
966 pending: &[(NamespacePath, FileId)],
967 case_sensitivity: CaseSensitivity,
968) -> Option<FileId> {
969 pending_binding_path(path, pending, case_sensitivity).map(|(_, file_id)| file_id)
970}
971
972fn pending_parent_exists(
973 path: &NamespacePath,
974 pending: &[(NamespacePath, FileId)],
975 case_sensitivity: CaseSensitivity,
976 limits: VolumeLimits,
977) -> bool {
978 let Some((parent, _)) = path.split_last() else {
979 return false;
980 };
981 let parent_path = NamespacePath::new(parent.to_vec(), limits);
982 parent_path
983 .ok()
984 .and_then(|value| pending_binding_id(&value, pending, case_sensitivity))
985 .is_some()
986}
987
988fn removed_binding_id(path: &NamespacePath, removed: &[(NamespacePath, FileId)]) -> Option<FileId> {
989 let (parent, name) = path.split_last()?;
990 let folded = name.case_fold_key();
991 removed.iter().rev().find_map(|(removed_path, file_id)| {
992 let (removed_parent, removed_name) = removed_path.split_last()?;
993 (removed_parent == parent && removed_name.case_fold_key() == folded).then_some(*file_id)
994 })
995}
996
997#[derive(Clone, Debug, Eq, PartialEq)]
999pub struct AuthoredTransactionResult {
1000 pub created_file_ids: Vec<Option<FileId>>,
1003}
1004
1005#[derive(Clone, Debug, Eq, PartialEq)]
1007pub struct AuthoredLiveMutationResult {
1008 pub created_file_ids: Vec<Option<FileId>>,
1011 pub outcome: LiveMutationOutcome,
1013}
1014
1015#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1018pub struct StagedContent {
1019 root: ObjectId,
1021 logical_bytes: u64,
1023}
1024
1025impl StagedContent {
1026 #[cfg(feature = "s3-http")]
1027 pub(crate) const fn from_canonical_parts(root: ObjectId, logical_bytes: u64) -> Self {
1028 Self {
1029 root,
1030 logical_bytes,
1031 }
1032 }
1033
1034 #[must_use]
1036 pub const fn root(&self) -> ObjectId {
1037 self.root
1038 }
1039
1040 #[must_use]
1042 pub const fn logical_bytes(&self) -> u64 {
1043 self.logical_bytes
1044 }
1045}
1046
1047#[derive(Clone, Debug, Eq, PartialEq)]
1049pub struct DirectoryRecordEntry {
1050 pub name: LogicalName,
1052 pub record: FileRecord,
1054 pub metadata: FileMetadata,
1056}
1057
1058#[derive(Clone, Debug, Eq, PartialEq)]
1060pub struct DirectoryRecordPage {
1061 pub entries: Vec<DirectoryRecordEntry>,
1063 pub has_more: bool,
1065}
1066
1067#[derive(Clone, Debug, Eq, PartialEq)]
1069pub struct DirectoryPageRequest {
1070 pub path: NamespacePath,
1072 pub after: Option<LogicalName>,
1074 pub maximum_entries: u32,
1076}
1077
1078#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1081pub struct PathMetadataLookup {
1082 pub record: FileRecord,
1084 pub metadata: FileMetadata,
1086}
1087
1088#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1090pub enum NamedAttributeWriteMode {
1091 Upsert,
1093 Create,
1095 Replace,
1097}
1098
1099#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1101pub enum CheckoutCommitOutcome {
1102 Committed {
1104 generation_id: GenerationId,
1106 head: Head,
1108 },
1109 AlreadyCommitted {
1111 generation_id: GenerationId,
1113 head: Head,
1115 },
1116 Conflict {
1118 actual: Head,
1120 },
1121 Fenced {
1123 actual_epoch: Epoch,
1125 },
1126 IdempotencyConflict {
1128 committed_fingerprint: Digest,
1130 },
1131}
1132
1133pub type FsResult<T> = MeasuredResult<FsReceipt<T>, FsError>;
1135
1136#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1138pub struct FileRecordChange {
1139 pub file_id: FileId,
1141 pub before: Option<FileRecord>,
1143 pub after: Option<FileRecord>,
1145}
1146
1147#[derive(Clone, Debug, Eq, PartialEq)]
1149pub struct DirectoryBindingChange {
1150 pub directory_id: FileId,
1152 pub name: LogicalName,
1154 pub before: Option<crate::kernel::TreeEntry>,
1156 pub after: Option<crate::kernel::TreeEntry>,
1158}
1159
1160#[derive(Clone, Debug, Eq, PartialEq)]
1162pub struct GenerationDiff {
1163 pub files: Vec<FileRecordChange>,
1165 pub bindings: Vec<DirectoryBindingChange>,
1167 pub truncated: bool,
1169}
1170
1171#[derive(Clone, Debug, Eq, PartialEq)]
1173pub enum MergePreparation {
1174 Prepared {
1176 generation_id: GenerationId,
1178 },
1179 Conflicted {
1181 conflicts: Vec<MergeConflict>,
1183 truncated: bool,
1185 },
1186}
1187
1188#[derive(Debug, Error)]
1190pub enum FsError {
1191 #[error(transparent)]
1193 Cancelled(#[from] CancellationError),
1194 #[error(transparent)]
1196 Config(#[from] VolumeConfigError),
1197 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1199 #[error(transparent)]
1200 LocalStream(#[from] acyclic_stream::LocalStreamError),
1201 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1203 #[error(transparent)]
1204 LocalObjects(#[from] acyclic_objects::LocalObjectsError),
1205 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1207 #[error("local filesystem Objects bucket initialization failed: {0}")]
1208 LocalObjectsBucket(acyclic_objects::ObjectsError),
1209 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1211 #[error("local filesystem root setup failed: {0}")]
1212 LocalRoot(std::io::Error),
1213 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1215 #[error("local filesystem root is already open with different options")]
1216 LocalOptionsConflict,
1217 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1219 #[error("local filesystem initialization worker is unavailable")]
1220 LocalInitializationWorker,
1221 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1223 #[error(transparent)]
1224 ObjectCacheConfig(#[from] crate::cache::ObjectCacheConfigError),
1225 #[error(transparent)]
1227 CheckoutMode(#[from] CheckoutModeError),
1228 #[error("durable volume requested from non-durable embedded backends")]
1230 UnsupportedDurability,
1231 #[error("name collides with an existing entry under the volume's case-folding policy")]
1235 NameCollision,
1236 #[error("file identity collides with different authenticated content")]
1238 FileIdentityCollision,
1239 #[error("name is not normalized to NFC as required by the volume's unicode policy")]
1242 NonNormalizedName,
1243 #[error("checkout behavior is not implemented by this facade")]
1245 UnsupportedCheckout,
1246 #[error("direct-live mutation requires serialized-authority concurrency")]
1248 LiveRequiresSerializedAuthority,
1249 #[error("exclusive writer ownership raced with authority head {actual:?}")]
1251 ExclusiveWriterConflict {
1252 actual: Head,
1254 },
1255 #[error("direct-live retry bound must be positive")]
1257 ZeroLiveRetryLimit,
1258 #[error(transparent)]
1260 LiveRetry(#[from] LiveRetryError),
1261 #[error("direct-live checkout already has a pending operation")]
1263 PendingLiveMutation,
1264 #[error("live advancement conflicts with an observed or mutated region")]
1266 LiveConflict {
1267 conflicts: Vec<RebaseConflict>,
1269 truncated: bool,
1271 },
1272 #[error(transparent)]
1274 Authority(#[from] AuthorityStoreError),
1275 #[error(transparent)]
1277 Object(#[from] ObjectStoreError),
1278 #[error(transparent)]
1280 Closure(#[from] ClosureError),
1281 #[error(transparent)]
1283 Decode(#[from] CanonicalDecodeError),
1284 #[error(transparent)]
1286 VolumeCreated(#[from] VolumeCreatedError),
1287 #[error(transparent)]
1289 RetentionCreated(#[from] RetentionCreatedError),
1290 #[error(transparent)]
1292 Path(#[from] PathLookupError),
1293 #[error(transparent)]
1295 Directory(#[from] DirectoryReadError),
1296 #[error(transparent)]
1298 FileRecord(#[from] FileRecordReadError),
1299 #[error(transparent)]
1301 FileRead(#[from] FileRangeReadError),
1302 #[error(transparent)]
1304 BlobBuild(#[from] BlobBuildError),
1305 #[error(transparent)]
1307 BlobRead(#[from] BlobReadError),
1308 #[error(transparent)]
1310 AttributeLookup(#[from] AttributeLookupError),
1311 #[error(transparent)]
1313 AttributeList(#[from] AttributeListError),
1314 #[error(transparent)]
1316 AttributeMutation(#[from] AttributeMutationError),
1317 #[error(transparent)]
1319 InlineFile(#[from] InlineFileDataError),
1320 #[error("filesystem path does not exist")]
1322 NotFound,
1323 #[error("filesystem path is not a directory")]
1325 NotDirectory,
1326 #[error("filesystem path is not a symbolic link")]
1328 NotSymbolicLink,
1329 #[error("filesystem path is not a Windows reparse point")]
1331 NotReparsePoint,
1332 #[error("filesystem special-file kind is incompatible with the requested payload")]
1334 InvalidSpecialKind,
1335 #[error("filesystem kind is unsupported by the configured volume profile")]
1337 UnsupportedFileKind,
1338 #[error("directory entry does not have a matching authenticated file record")]
1340 InvalidDirectoryRecord,
1341 #[error("generation export manifest does not match the imported closure")]
1343 InvalidExportManifest,
1344 #[error(transparent)]
1346 Transfer(#[from] GenerationTransferError),
1347 #[error(transparent)]
1349 Dependency(#[from] DependencyError),
1350 #[error(transparent)]
1352 Probe(#[from] AuthenticatedProbeError),
1353 #[error(transparent)]
1355 Rebase(#[from] RebaseError<AuthenticatedProbeError>),
1356 #[error(transparent)]
1358 Mutation(#[from] GenerationMutationError),
1359 #[error(transparent)]
1361 DetachedMutation(#[from] RegularMutationError),
1362 #[error(transparent)]
1364 Checkpoint(#[from] CheckpointError),
1365 #[error(transparent)]
1367 Publication(#[from] PublicationError),
1368 #[error("volume authority has no creation fact")]
1370 EmptyAuthority,
1371 #[error("volume authority history has an invalid event shape")]
1373 InvalidAuthorityHistory,
1374 #[error("authenticated data belongs to another volume")]
1376 VolumeMismatch,
1377 #[error("volume creation was rejected by authority state")]
1379 CreationRejected,
1380 #[error("workspace is deleted")]
1382 WorkspaceDeleted,
1383 #[error("writable private checkout requires a head selector")]
1385 WritableCheckoutRequiresHead,
1386 #[error("checkout does not admit private overlay mutation")]
1388 MutationNotAllowed,
1389 #[error("checkout has no pending mutation to commit")]
1391 NoPendingMutations,
1392 #[error("checkout does not admit explicit head refresh")]
1394 RefreshNotAllowed,
1395 #[error("pending overlay mutations require safe rebase before refresh")]
1397 PendingMutationsRequireRebase,
1398 #[error("checkout pending mutation count exceeds the volume limit")]
1400 TooManyPendingMutations,
1401 #[error("mutation input capacity exceeds the volume limit")]
1403 ExcessiveMutationCapacity,
1404 #[error("pending mutation replay log allocation failed")]
1406 PendingMutationAllocationFailed,
1407 #[error("prepared merge must be committed or discarded before further mutation")]
1409 PreparedMergePending,
1410 #[error("generation diff structure is invalid")]
1412 InvalidDiff,
1413 #[error("generation diff allocation failed")]
1415 DiffAllocationFailed,
1416 #[error("local garbage-collection live-set allocation failed")]
1418 GarbageCollectionAllocationFailed,
1419 #[error("three-way merge exceeds its change bound")]
1421 MergeChangeLimit,
1422 #[error("merge second parent must be the current authority head")]
1424 MergeParentNotHead,
1425 #[error(transparent)]
1427 MergeFileTable(#[from] FileTableMutationError),
1428 #[error(transparent)]
1430 MergeTree(#[from] TreeMutationError),
1431 #[error(transparent)]
1433 Work(#[from] WorkError),
1434}
1435
1436impl<A, O> Fs<A, O> {
1437 #[must_use]
1439 pub fn new(authority: A, objects: O, capabilities: EmbeddedCapabilities) -> Self {
1440 Self::new_in_namespace(authority, objects, capabilities, [0; 16])
1441 }
1442
1443 #[must_use]
1448 pub fn new_in_namespace(
1449 authority: A,
1450 objects: O,
1451 capabilities: EmbeddedCapabilities,
1452 workspace_namespace: [u8; 16],
1453 ) -> Self {
1454 Self::new_with_path_index(
1455 authority,
1456 objects,
1457 capabilities,
1458 workspace_namespace,
1459 Arc::new(crate::path_index::MemoryGenerationPathIndex::default()),
1460 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1461 None,
1462 )
1463 }
1464
1465 fn new_with_path_index(
1466 authority: A,
1467 objects: O,
1468 capabilities: EmbeddedCapabilities,
1469 workspace_namespace: [u8; 16],
1470 path_index: Arc<dyn crate::path_index::GenerationPathIndex>,
1471 #[cfg(all(feature = "local", not(target_arch = "wasm32")))] local_root_lifecycle: Option<
1472 OwnershipAnchor,
1473 >,
1474 ) -> Self {
1475 Self {
1476 inner: Arc::new(FsInner {
1477 authority,
1478 objects,
1479 capabilities,
1480 workspace_namespace,
1481 path_index,
1482 #[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1483 _local_root_lifecycle: local_root_lifecycle,
1484 }),
1485 }
1486 }
1487}
1488
1489impl<A, S> Fs<A, crate::cache::CachedObjectStore<S>> {
1490 pub fn object_cache_stats(&self) -> Result<crate::cache::ObjectCacheStats, ObjectStoreError> {
1497 self.inner.objects.stats()
1498 }
1499
1500 pub fn clear_object_cache(&self) -> Result<(), ObjectStoreError> {
1508 self.inner.objects.clear()
1509 }
1510}
1511
1512#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1513impl Fs<LocalAuthorityBackend, LocalObjectBackend> {
1514 pub fn object_cache_stats(&self) -> Result<crate::cache::ObjectCacheStats, ObjectStoreError> {
1516 self.inner.objects.inner().stats()
1517 }
1518
1519 pub fn clear_object_cache(&self) -> Result<(), ObjectStoreError> {
1521 self.inner.objects.inner().clear()
1522 }
1523}
1524
1525#[cfg(all(feature = "memory", feature = "distributed"))]
1526impl
1527 Fs<
1528 crate::distributed::StreamAuthorityStore<acyclic_stream::MemoryStream>,
1529 crate::distributed::ProviderObjectStore<acyclic_objects::MemoryObjects>,
1530 >
1531{
1532 #[must_use]
1535 pub fn memory() -> Self {
1536 let stream = std::sync::Arc::new(acyclic_stream::MemoryStream::default());
1537 let (objects, bucket) = acyclic_objects::MemoryObjects::with_default_bucket();
1538 Self::from_memory_providers(stream, std::sync::Arc::new(objects), bucket)
1539 }
1540
1541 #[must_use]
1546 pub fn from_memory_providers(
1547 stream: std::sync::Arc<acyclic_stream::MemoryStream>,
1548 objects: std::sync::Arc<acyclic_objects::MemoryObjects>,
1549 bucket: acyclic_objects::wire::BucketRef,
1550 ) -> Self {
1551 Self::new(
1552 crate::distributed::StreamAuthorityStore::new(stream),
1553 crate::distributed::ProviderObjectStore::new(objects, bucket),
1554 EmbeddedCapabilities::MEMORY,
1555 )
1556 }
1557}
1558
1559#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
1560impl Fs<LocalAuthorityBackend, LocalObjectBackend> {
1561 #[doc(hidden)]
1566 #[must_use]
1567 pub fn local_root_release_barrier(
1568 &self,
1569 ) -> Option<acyclic_native_runtime::OwnershipReleaseBarrier> {
1570 self.inner
1571 ._local_root_lifecycle
1572 .as_ref()
1573 .map(OwnershipAnchor::release_barrier)
1574 }
1575
1576 pub async fn local(options: LocalOptions) -> Result<Self, FsError> {
1582 static REGISTRY: OnceLock<tokio::sync::Mutex<LocalRootRegistry>> = OnceLock::new();
1583 let requested_root = options.root.clone();
1584 let root = tokio::task::spawn_blocking(move || {
1585 std::fs::create_dir_all(&requested_root)?;
1586 std::fs::canonicalize(requested_root)
1587 })
1588 .await
1589 .map_err(|_| FsError::LocalInitializationWorker)?
1590 .map_err(FsError::LocalRoot)?;
1591 let mut options = options;
1592 options.root = root.clone();
1593 let registry = REGISTRY.get_or_init(|| tokio::sync::Mutex::new(LocalRootRegistry::new()));
1594 let opening = {
1595 let mut registrations = registry.lock().await;
1596 if let Some(existing) = registrations.get(&root)
1597 && let Some(inner) = existing.live.upgrade()
1598 {
1599 if existing.options != options {
1600 return Err(FsError::LocalOptionsConflict);
1601 }
1602 return Ok(Self { inner });
1603 }
1604 prepare_local_root_open(&mut registrations, &root, &options)
1605 };
1606 let _opening = opening.lock().await;
1609 let lifecycle = {
1610 let registrations = registry.lock().await;
1611 let existing = registrations
1612 .get(&root)
1613 .ok_or(FsError::LocalInitializationWorker)?;
1614 if let Some(inner) = existing.live.upgrade() {
1615 if existing.options != options {
1616 return Err(FsError::LocalOptionsConflict);
1617 }
1618 return Ok(Self { inner });
1619 }
1620 Arc::clone(&existing.lifecycle)
1621 };
1622 let ownership = lifecycle.lock_owned().await;
1623 let open_options = options.clone();
1624 let fs = run_local_initialization(ownership, move |ownership| async move {
1625 let ownership = OwnershipAnchor::new(ownership);
1626 Self::open_local_unshared(open_options, Some(ownership)).await
1627 })
1628 .await??;
1629 let mut registrations = registry.lock().await;
1630 let existing = registrations
1631 .get_mut(&root)
1632 .ok_or(FsError::LocalInitializationWorker)?;
1633 existing.options = options;
1634 existing.live = Arc::downgrade(&fs.inner);
1635 Ok(fs)
1636 }
1637
1638 async fn open_local_unshared(
1639 options: LocalOptions,
1640 lifecycle: Option<OwnershipAnchor>,
1641 ) -> Result<Self, FsError> {
1642 let LocalOptions {
1643 root,
1644 stream,
1645 objects,
1646 object_cache,
1647 } = options;
1648 let stream = std::sync::Arc::new(match &lifecycle {
1649 Some(ownership) => {
1650 acyclic_stream::LocalStream::open_with_ownership_anchor(
1651 root.join("stream"),
1652 stream,
1653 ownership.clone(),
1654 )
1655 .await?
1656 }
1657 None => acyclic_stream::LocalStream::open(root.join("stream"), stream).await?,
1658 });
1659 let objects = std::sync::Arc::new(match &lifecycle {
1660 Some(ownership) => {
1661 acyclic_objects::LocalObjects::open_with_ownership_anchor(
1662 root.join("objects"),
1663 objects,
1664 ownership.clone(),
1665 )
1666 .await?
1667 }
1668 None => acyclic_objects::LocalObjects::open(root.join("objects"), objects).await?,
1669 });
1670 let bucket = match objects
1671 .bucket_named("filesystem-objects")
1672 .await
1673 .map_err(FsError::LocalObjectsBucket)?
1674 {
1675 Some(bucket) => bucket,
1676 None => objects
1677 .create_bucket(
1678 "filesystem-objects".to_owned(),
1679 Some("filesystem-objects-v1".to_owned()),
1680 )
1681 .await
1682 .map_err(FsError::LocalObjectsBucket)?
1683 .bucket
1684 .ok_or(FsError::LocalObjectsBucket(
1685 acyclic_objects::ObjectsError::Unavailable,
1686 ))?,
1687 };
1688 let objects = crate::cache::CachedObjectStore::new(
1689 crate::distributed::ProviderObjectStore::new(objects, bucket),
1690 object_cache,
1691 )?;
1692 let objects = crate::staged_objects::StagedObjects::new(objects);
1693 Ok(Self::new_with_path_index(
1694 crate::distributed::StreamAuthorityStore::new(stream),
1695 objects,
1696 EmbeddedCapabilities {
1697 durable: cfg!(any(unix, windows)),
1698 },
1699 [0; 16],
1700 Arc::new(crate::path_index::NativeGenerationPathIndex::new(
1701 root.join("generation-path-index-v1"),
1702 )),
1703 lifecycle,
1704 ))
1705 }
1706
1707 pub async fn collect_local_garbage(
1721 options: LocalOptions,
1722 maximum_authorities: u32,
1723 maximum_candidates: u64,
1724 budget: WorkBudget,
1725 cancellation: &CancellationToken,
1726 ) -> FsResult<LocalGarbageCollection> {
1727 cancellation
1728 .check()
1729 .map_err(|error| OperationFailure::before_work(error.into()))?;
1730 let fs = Self::open_local_unshared(options, None)
1731 .await
1732 .map_err(OperationFailure::before_work)?;
1733 fs.collect_local_garbage_exclusive(
1734 maximum_authorities,
1735 maximum_candidates,
1736 budget,
1737 cancellation,
1738 )
1739 .await
1740 }
1741
1742 async fn collect_local_garbage_exclusive(
1743 &self,
1744 maximum_authorities: u32,
1745 maximum_candidates: u64,
1746 budget: WorkBudget,
1747 cancellation: &CancellationToken,
1748 ) -> FsResult<LocalGarbageCollection> {
1749 let authorities = self
1750 .inner
1751 .authority
1752 .authorities(maximum_authorities)
1753 .await
1754 .map_err(|error| OperationFailure::before_work(error.into()))?;
1755 let authority_live_bytes = u64::try_from(authorities.capacity())
1756 .unwrap_or(u64::MAX)
1757 .saturating_mul(
1758 u64::try_from(size_of::<crate::foundation::AuthorityId>()).unwrap_or(u64::MAX),
1759 );
1760 let mut work = WorkCounters {
1761 backend_read_operations: 1,
1762 items_examined: u64::try_from(authorities.len()).unwrap_or(u64::MAX),
1763 items_returned: u64::try_from(authorities.len()).unwrap_or(u64::MAX),
1764 allocation_operations: u64::from(!authorities.is_empty()),
1765 peak_allocation_bytes: authority_live_bytes,
1766 ..WorkCounters::default()
1767 };
1768 work.verify(budget)
1769 .map_err(|error| OperationFailure::new(error.into(), work))?;
1770 let mut reachable = Vec::<ObjectId>::new();
1771 for authority_id in authorities {
1772 let Some((generation_root, config, next_work)) = self
1773 .local_authority_generation(
1774 authority_id,
1775 authority_live_bytes,
1776 work,
1777 budget,
1778 cancellation,
1779 )
1780 .await?
1781 else {
1782 continue;
1783 };
1784 work = next_work;
1785 let live_bytes = authority_live_bytes.saturating_add(object_vec_bytes(&reachable));
1786 let proof = prove_generation_closure_async(
1787 &self.inner.objects,
1788 generation_root,
1789 closure_limits(config),
1790 remaining(work, budget)?,
1791 cancellation,
1792 )
1793 .await
1794 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
1795 work = merge_simultaneous_work(work, proof.work, live_bytes, budget)?;
1796 let incoming_bytes = object_vec_bytes(&proof.objects);
1797 merge_sorted_object_ids(
1798 &mut reachable,
1799 &proof.objects,
1800 incoming_bytes,
1801 authority_live_bytes,
1802 &mut work,
1803 budget,
1804 )?;
1805 }
1806 #[cfg(feature = "s3-http")]
1807 {
1808 work = self
1809 .merge_active_s3_multipart_reachable_objects(
1810 maximum_candidates,
1811 authority_live_bytes,
1812 &mut reachable,
1813 work,
1814 budget,
1815 cancellation,
1816 )
1817 .await?;
1818 }
1819 let collected = self
1820 .collect_unreachable_local_objects(
1821 &reachable,
1822 maximum_candidates,
1823 authority_live_bytes,
1824 &mut work,
1825 budget,
1826 cancellation,
1827 )
1828 .await?;
1829 Ok(FsReceipt {
1830 value: collected,
1831 work,
1832 })
1833 }
1834
1835 #[cfg(feature = "s3-http")]
1838 async fn merge_active_s3_multipart_reachable_objects(
1839 &self,
1840 maximum_candidates: u64,
1841 authority_live_bytes: u64,
1842 reachable: &mut Vec<ObjectId>,
1843 work: WorkCounters,
1844 budget: WorkBudget,
1845 cancellation: &CancellationToken,
1846 ) -> Result<WorkCounters, OperationFailure<FsError>> {
1847 let multipart = crate::active_s3_multipart_objects(
1848 self.inner.authority.provider().as_ref(),
1849 &self.inner.objects,
1850 crate::FilesystemS3Limits::default(),
1851 crate::S3MultipartRetentionLimits {
1852 maximum_uploads: maximum_candidates.max(1),
1853 ..crate::S3MultipartRetentionLimits::default()
1854 },
1855 remaining(work, budget)?,
1856 cancellation,
1857 )
1858 .await
1859 .map_err(|_| {
1860 OperationFailure::new(
1861 FsError::Object(ObjectStoreError::Rejected(
1862 "active S3 multipart retention scan failed".to_owned(),
1863 )),
1864 work,
1865 )
1866 })?;
1867 let mut work = merge_simultaneous_work(
1868 work,
1869 multipart.work,
1870 authority_live_bytes.saturating_add(object_vec_bytes(reachable)),
1871 budget,
1872 )?;
1873 let incoming_bytes = object_vec_bytes(&multipart.value);
1874 merge_sorted_object_ids(
1875 reachable,
1876 &multipart.value,
1877 incoming_bytes,
1878 authority_live_bytes,
1879 &mut work,
1880 budget,
1881 )?;
1882 Ok(work)
1883 }
1884
1885 async fn collect_unreachable_local_objects(
1886 &self,
1887 reachable: &[ObjectId],
1888 maximum_candidates: u64,
1889 retained_bytes: u64,
1890 work: &mut WorkCounters,
1891 budget: WorkBudget,
1892 cancellation: &CancellationToken,
1893 ) -> Result<LocalGarbageCollection, OperationFailure<FsError>> {
1894 if maximum_candidates == 0 {
1895 return Err(OperationFailure::new(
1896 FsError::Object(ObjectStoreError::Rejected(
1897 "garbage-collection candidate bound must be positive".to_owned(),
1898 )),
1899 *work,
1900 ));
1901 }
1902 let (candidates, examined) = self
1903 .list_unreachable_object_candidates(
1904 reachable,
1905 maximum_candidates,
1906 retained_bytes,
1907 work,
1908 budget,
1909 cancellation,
1910 )
1911 .await?;
1912 let provider = self.inner.objects.inner().inner().provider();
1913 let bucket = self.inner.objects.inner().inner().bucket().clone();
1914 let mut removed = 0_u64;
1915 for (object_key, version_id) in candidates {
1916 cancellation
1917 .check()
1918 .map_err(|error| OperationFailure::new(error.into(), *work))?;
1919 let identity = blake3::hash(format!("{object_key}\0{version_id}").as_bytes());
1920 provider
1921 .delete(
1922 bucket.clone(),
1923 object_key,
1924 Some(version_id),
1925 None,
1926 Some(format!("fs-gc-{}", identity.to_hex())),
1927 )
1928 .await
1929 .map_err(|error| {
1930 OperationFailure::new(FsError::LocalObjectsBucket(error), *work)
1931 })?;
1932 removed = removed.saturating_add(1);
1933 *work = work
1934 .checked_add(WorkCounters {
1935 backend_write_operations: 1,
1936 items_examined: 1,
1937 ..WorkCounters::default()
1938 })
1939 .map_err(|error| OperationFailure::new(error.into(), *work))?;
1940 work.verify(budget)
1941 .map_err(|error| OperationFailure::new(error.into(), *work))?;
1942 }
1943 let physical = provider
1944 .collect_garbage(maximum_candidates)
1945 .await
1946 .map_err(|error| OperationFailure::new(FsError::LocalObjects(error), *work))?;
1947 Ok(LocalGarbageCollection {
1948 examined,
1949 removed,
1950 segments_removed: physical.segments_removed,
1951 temporary_files_removed: physical.temporary_files_removed,
1952 })
1953 }
1954
1955 async fn list_unreachable_object_candidates(
1961 &self,
1962 reachable: &[ObjectId],
1963 maximum_candidates: u64,
1964 retained_bytes: u64,
1965 work: &mut WorkCounters,
1966 budget: WorkBudget,
1967 cancellation: &CancellationToken,
1968 ) -> Result<(Vec<(String, String)>, u64), OperationFailure<FsError>> {
1969 let reachable_keys = reachable
1970 .iter()
1971 .copied()
1972 .map(crate::distributed::object_key)
1973 .collect::<BTreeSet<_>>();
1974 let provider = self.inner.objects.inner().inner().provider();
1975 let bucket = self.inner.objects.inner().inner().bucket().clone();
1976 let mut continuation = None;
1977 let mut candidates = Vec::new();
1978 let mut examined = 0_u64;
1979 loop {
1980 cancellation
1981 .check()
1982 .map_err(|error| OperationFailure::new(error.into(), *work))?;
1983 let remaining_candidates = maximum_candidates.saturating_sub(examined);
1984 if remaining_candidates == 0 && continuation.is_some() {
1985 return Err(OperationFailure::new(
1986 FsError::Object(ObjectStoreError::Rejected(
1987 "garbage-collection candidate bound exceeded".to_owned(),
1988 )),
1989 *work,
1990 ));
1991 }
1992 let page_size = u32::try_from(remaining_candidates.min(256)).unwrap_or(256);
1993 let page = provider
1994 .list(
1995 acyclic_objects::ReadTarget::Bucket(bucket.clone()),
1996 "fs/v1/".to_owned(),
1997 None,
1998 true,
1999 page_size,
2000 continuation,
2001 )
2002 .await
2003 .map_err(|error| {
2004 OperationFailure::new(FsError::LocalObjectsBucket(error), *work)
2005 })?;
2006 let page_items = u64::try_from(page.entries.len()).unwrap_or(u64::MAX);
2007 examined = examined
2008 .checked_add(page_items)
2009 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), *work))?;
2010 if examined > maximum_candidates {
2011 return Err(OperationFailure::new(
2012 FsError::Object(ObjectStoreError::Rejected(
2013 "garbage-collection candidate bound exceeded".to_owned(),
2014 )),
2015 *work,
2016 ));
2017 }
2018 for entry in page.entries {
2019 let version = entry.version.ok_or_else(|| {
2020 OperationFailure::new(FsError::Object(ObjectStoreError::Corrupt), *work)
2021 })?;
2022 if !reachable_keys.contains(&entry.object_key) {
2023 candidates.push((entry.object_key, version.version_id));
2024 }
2025 }
2026 *work = work
2027 .checked_add(WorkCounters {
2028 backend_read_operations: 1,
2029 items_examined: page_items,
2030 allocation_operations: u64::from(!candidates.is_empty()),
2031 peak_allocation_bytes: retained_bytes
2032 .saturating_add(object_slice_bytes(reachable)),
2033 ..WorkCounters::default()
2034 })
2035 .map_err(|error| OperationFailure::new(error.into(), *work))?;
2036 work.verify(budget)
2037 .map_err(|error| OperationFailure::new(error.into(), *work))?;
2038 continuation = page.continuation;
2039 if continuation.is_none() {
2040 break;
2041 }
2042 }
2043 Ok((candidates, examined))
2044 }
2045
2046 async fn local_authority_generation(
2047 &self,
2048 authority_id: crate::foundation::AuthorityId,
2049 retained_bytes: u64,
2050 mut work: WorkCounters,
2051 budget: WorkBudget,
2052 cancellation: &CancellationToken,
2053 ) -> Result<Option<(ObjectId, VolumeConfig, WorkCounters)>, OperationFailure<FsError>> {
2054 cancellation
2055 .check()
2056 .map_err(|error| OperationFailure::new(error.into(), work))?;
2057 let head = self
2058 .inner
2059 .authority
2060 .head(authority_id, remaining(work, budget)?, cancellation)
2061 .await
2062 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
2063 work = merge_simultaneous_work(work, head.work, retained_bytes, budget)?;
2064 if head.value.sequence == Sequence::GENESIS {
2065 return Ok(None);
2066 }
2067 let creation = self
2068 .inner
2069 .authority
2070 .replay(
2071 authority_id,
2072 Sequence::GENESIS,
2073 ReplayLimit {
2074 records: 1,
2075 payload_bytes: MAXIMUM_VOLUME_EVENT_BYTES,
2076 },
2077 remaining(work, budget)?,
2078 cancellation,
2079 )
2080 .await
2081 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
2082 work = merge_simultaneous_work(work, creation.work, retained_bytes, budget)?;
2083 let first = creation
2084 .value
2085 .first()
2086 .ok_or_else(|| OperationFailure::new(FsError::InvalidAuthorityHistory, work))?;
2087 let Ok(created) = decode_volume_created(&first.payload, MAXIMUM_VOLUME_EVENT_BYTES) else {
2088 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2089 if let Ok(source_volume) =
2090 decode_source_volume(&first.payload, MAXIMUM_VOLUME_EVENT_BYTES)
2091 {
2092 if source_authority_id(source_volume) != authority_id {
2093 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
2094 }
2095 return Ok(None);
2096 }
2097 let retained = decode_retention_created(&first.payload, MAXIMUM_VOLUME_EVENT_BYTES)
2098 .map_err(|error| OperationFailure::new(error.into(), work))?;
2099 return Ok(Some((retained.generation_root, retained.config, work)));
2100 };
2101 if volume_authority_id(created.volume_id) != authority_id {
2102 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
2103 }
2104 let generation_root = if head.value.sequence == Sequence::new(1) {
2105 created.initial_generation_root
2106 } else {
2107 let latest = self
2108 .inner
2109 .authority
2110 .replay(
2111 authority_id,
2112 Sequence::new(head.value.sequence.get().saturating_sub(1)),
2113 ReplayLimit {
2114 records: 1,
2115 payload_bytes: MAXIMUM_VOLUME_EVENT_BYTES,
2116 },
2117 remaining(work, budget)?,
2118 cancellation,
2119 )
2120 .await
2121 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
2122 work = merge_simultaneous_work(work, latest.work, retained_bytes, budget)?;
2123 let record = latest
2124 .value
2125 .first()
2126 .ok_or_else(|| OperationFailure::new(FsError::InvalidAuthorityHistory, work))?;
2127 if let Ok(deleted) =
2128 decode_workspace_deleted(&record.payload, MAXIMUM_VOLUME_EVENT_BYTES)
2129 {
2130 if deleted != created.volume_id {
2131 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
2132 }
2133 return Ok(None);
2134 }
2135 generation_from_record(record, created.volume_id, work)?
2136 };
2137 Ok(Some((generation_root, created.config, work)))
2138 }
2139}
2140
2141impl<A: AsyncAuthorityStore, O: AsyncObjectStore> Fs<A, O> {
2142 pub(crate) async fn observe_volume_operation(
2143 &self,
2144 volume_id: VolumeId,
2145 operation_id: OperationId,
2146 budget: WorkBudget,
2147 cancellation: &CancellationToken,
2148 ) -> FsResult<Option<DurableCommit>> {
2149 let receipt = self
2150 .inner
2151 .authority
2152 .find_operation(
2153 volume_authority_id(volume_id),
2154 operation_id,
2155 budget,
2156 cancellation,
2157 )
2158 .await
2159 .map_err(|failure| {
2160 OperationFailure::new(FsError::Authority(failure.error), *failure.work)
2161 })?;
2162 Ok(FsReceipt {
2163 value: receipt.value,
2164 work: receipt.work,
2165 })
2166 }
2167
2168 pub(crate) async fn generation_parents(
2169 &self,
2170 volume: &Volume<A, O>,
2171 generation: GenerationId,
2172 ) -> Result<Vec<GenerationId>, crate::workspace::WorkspaceError> {
2173 let object = ObjectId {
2174 kind: ObjectKind::GenerationRoot,
2175 digest: generation.digest(),
2176 };
2177 let (root, _) = read_generation_root(
2178 &self.inner.objects,
2179 object,
2180 volume.config,
2181 WorkBudget::UNBOUNDED,
2182 &CancellationToken::new(),
2183 )
2184 .await
2185 .map_err(crate::workspace::WorkspaceError::engine)?;
2186 if root.volume_id != volume.id {
2187 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2188 }
2189 Ok(root.parents)
2190 }
2191
2192 pub async fn create_workspace(
2200 &self,
2201 name: impl AsRef<str>,
2202 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2203 let lifecycle = if self.inner.capabilities.durable {
2204 Lifecycle::Durable
2205 } else {
2206 Lifecycle::Ephemeral
2207 };
2208 self.create_workspace_with_config(name, VolumeConfig::portable(lifecycle))
2209 .await
2210 }
2211
2212 pub async fn create_workspace_with_config(
2218 &self,
2219 name: impl AsRef<str>,
2220 config: VolumeConfig,
2221 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2222 self.create_workspace_with_config_operation(name, config, None)
2223 .await
2224 }
2225
2226 pub(crate) async fn create_workspace_with_config_operation(
2227 &self,
2228 name: impl AsRef<str>,
2229 config: VolumeConfig,
2230 operation_id: Option<OperationId>,
2231 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2232 let name = crate::WorkspaceName::new(name)?;
2233 let id = crate::WorkspaceId::derive(self.inner.workspace_namespace, &name);
2234 let volume = self
2235 .create_volume_with_id_operation(
2236 id.volume_id(),
2237 config,
2238 operation_id,
2239 WorkBudget::UNBOUNDED,
2240 &CancellationToken::new(),
2241 )
2242 .await
2243 .map_err(crate::workspace::WorkspaceError::engine)?
2244 .value;
2245 volume
2246 .resolve_head_generation(WorkBudget::UNBOUNDED, &CancellationToken::new())
2247 .await
2248 .map_err(crate::workspace::WorkspaceError::engine)?;
2249 Ok(crate::Workspace {
2250 name,
2251 id,
2252 volume,
2253 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2254 source: None,
2255 })
2256 }
2257
2258 pub async fn open_workspace(
2265 &self,
2266 name: impl AsRef<str>,
2267 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2268 let name = crate::WorkspaceName::new(name)?;
2269 let id = crate::WorkspaceId::derive(self.inner.workspace_namespace, &name);
2270 let volume = self
2271 .open_volume(
2272 id.volume_id(),
2273 WorkBudget::UNBOUNDED,
2274 &CancellationToken::new(),
2275 )
2276 .await
2277 .map_err(crate::workspace::WorkspaceError::engine)?
2278 .value;
2279 volume
2280 .resolve_head_generation(WorkBudget::UNBOUNDED, &CancellationToken::new())
2281 .await
2282 .map_err(crate::workspace::WorkspaceError::engine)?;
2283 Ok(crate::Workspace {
2284 name,
2285 id,
2286 volume,
2287 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2288 source: None,
2289 })
2290 }
2291
2292 pub async fn open_volume_workspace(
2303 &self,
2304 name: impl AsRef<str>,
2305 volume_id: VolumeId,
2306 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2307 let name = crate::WorkspaceName::new(name)?;
2308 let volume = self
2309 .open_volume(volume_id, WorkBudget::UNBOUNDED, &CancellationToken::new())
2310 .await
2311 .map_err(crate::workspace::WorkspaceError::engine)?
2312 .value;
2313 volume
2314 .resolve_head_generation(WorkBudget::UNBOUNDED, &CancellationToken::new())
2315 .await
2316 .map_err(crate::workspace::WorkspaceError::engine)?;
2317 Ok(crate::Workspace {
2318 name,
2319 id: crate::WorkspaceId::from_volume_id(volume_id),
2320 volume,
2321 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2322 source: None,
2323 })
2324 }
2325
2326 pub fn adopt_volume_workspace(
2335 &self,
2336 name: impl AsRef<str>,
2337 volume: Volume<A, O>,
2338 ) -> Result<crate::Workspace<A, O>, crate::workspace::WorkspaceError> {
2339 let name = crate::WorkspaceName::new(name)?;
2340 let id = crate::WorkspaceId::from_volume_id(volume.id);
2341 Ok(crate::Workspace {
2342 name,
2343 id,
2344 volume,
2345 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2346 source: None,
2347 })
2348 }
2349
2350 pub async fn delete_workspace(
2358 &self,
2359 name: impl AsRef<str>,
2360 idempotency_key: crate::IdempotencyKey,
2361 ) -> Result<crate::workspace::WorkspaceDelete, crate::workspace::WorkspaceError> {
2362 let name = crate::WorkspaceName::new(name)?;
2363 let id = crate::WorkspaceId::derive(self.inner.workspace_namespace, &name);
2364 let volume = self
2365 .open_volume(
2366 id.volume_id(),
2367 WorkBudget::UNBOUNDED,
2368 &CancellationToken::new(),
2369 )
2370 .await
2371 .map_err(crate::workspace::WorkspaceError::engine)?
2372 .value;
2373 self.delete_workspace_volume(&volume, idempotency_key.operation_id())
2374 .await
2375 }
2376
2377 async fn materialize_forked_generation_root(
2380 &self,
2381 source: &crate::Generation<A, O>,
2382 destination_id: crate::WorkspaceId,
2383 config: VolumeConfig,
2384 budget: WorkBudget,
2385 cancellation: &CancellationToken,
2386 ) -> Result<(ObjectId, VerifiedForkSource, WorkCounters), crate::workspace::WorkspaceError>
2387 {
2388 let source_object = ObjectId {
2389 kind: ObjectKind::GenerationRoot,
2390 digest: source.id.digest(),
2391 };
2392 let (source_root, work) = read_generation_root(
2393 &self.inner.objects,
2394 source_object,
2395 config,
2396 budget,
2397 cancellation,
2398 )
2399 .await
2400 .map_err(crate::workspace::WorkspaceError::engine)?;
2401 if source_root.volume_id != source.workspace.volume.id {
2402 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2403 }
2404 let fork_root = GenerationRoot {
2405 volume_id: destination_id.volume_id(),
2406 root_file_id: source_root.root_file_id,
2407 file_table: source_root.file_table,
2408 parents: vec![source.id],
2409 required_features: source_root.required_features,
2410 };
2411 let encoded =
2412 encode_generation_root(&fork_root).map_err(crate::workspace::WorkspaceError::engine)?;
2413 let (generation_root, work) = self
2414 .put_encoded(
2415 ObjectKind::GenerationRoot,
2416 encoded,
2417 work,
2418 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2419 cancellation,
2420 )
2421 .await
2422 .map_err(crate::workspace::WorkspaceError::engine)?;
2423 let proof = prove_generation_closure_async(
2424 &self.inner.objects,
2425 generation_root,
2426 closure_limits(config),
2427 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2428 cancellation,
2429 )
2430 .await
2431 .map_err(crate::workspace::WorkspaceError::engine)?;
2432 let work = add(work, proof.work).map_err(crate::workspace::WorkspaceError::engine)?;
2433 Ok((
2434 generation_root,
2435 VerifiedForkSource {
2436 generation_root: source_object,
2437 },
2438 work,
2439 ))
2440 }
2441
2442 pub(crate) async fn fork_workspace_measured(
2448 &self,
2449 destination: crate::WorkspaceName,
2450 source: &crate::Generation<A, O>,
2451 idempotency_key: crate::IdempotencyKey,
2452 budget: WorkBudget,
2453 cancellation: &CancellationToken,
2454 ) -> Result<OperationReceipt<crate::Workspace<A, O>>, crate::workspace::WorkspaceError> {
2455 if !Arc::ptr_eq(&self.inner, &source.workspace.volume.fs.inner) {
2456 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2457 }
2458 cancellation
2459 .check()
2460 .map_err(crate::workspace::WorkspaceError::from)?;
2461 let destination_id =
2462 crate::WorkspaceId::derive(self.inner.workspace_namespace, &destination);
2463 let config = source.workspace.volume.config;
2464 let (generation_root, verified_source, mut work) =
2465 Box::pin(self.materialize_forked_generation_root(
2466 source,
2467 destination_id,
2468 config,
2469 budget,
2470 cancellation,
2471 ))
2472 .await?;
2473 let retained = Box::pin(self.retain_verified_workspace_generation_measured(
2474 &source.workspace.volume,
2475 verified_source,
2476 RetentionKind::ForkBase,
2477 hex::encode(destination_id.into_bytes()),
2478 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2479 cancellation,
2480 ))
2481 .await?;
2482 work = add(work, retained).map_err(crate::workspace::WorkspaceError::engine)?;
2483 if self.inner.authority.supports_generation_lineage_prefix() {
2484 let head = Box::pin(source.workspace.head_measured(
2485 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2486 cancellation,
2487 ))
2488 .await?;
2489 work = add(work, head.work).map_err(crate::workspace::WorkspaceError::engine)?;
2490 let lineage = if head.value.id == source.id {
2491 crate::GenerationFork::PublishedPrefix
2492 } else {
2493 crate::GenerationFork::Independent
2494 };
2495 let forked = Box::pin(self.inner.authority.fork_generation_authority(
2496 crate::GenerationForkSource {
2497 authority: volume_authority_id(source.workspace.volume.id),
2498 generation: source.id,
2499 lineage,
2500 },
2501 volume_authority_id(destination_id.volume_id()),
2502 idempotency_key.operation_id(),
2503 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2504 cancellation,
2505 ))
2506 .await
2507 .map_err(crate::workspace::WorkspaceError::engine)?;
2508 work = add(work, forked.work).map_err(crate::workspace::WorkspaceError::engine)?;
2509 }
2510 let volume = Box::pin(self.publish_volume_creation(
2511 VolumeCreation {
2512 volume_id: destination_id.volume_id(),
2513 config,
2514 generation_root,
2515 operation_id: Some(idempotency_key.operation_id()),
2516 },
2517 work,
2518 budget,
2519 cancellation,
2520 ))
2521 .await
2522 .map_err(crate::workspace::WorkspaceError::engine)?;
2523 work = volume.work;
2524 let volume = volume.value;
2525 let resolved = Box::pin(volume.resolve_head_generation(
2526 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2527 cancellation,
2528 ))
2529 .await
2530 .map_err(crate::workspace::WorkspaceError::engine)?;
2531 work = add(work, resolved.2).map_err(crate::workspace::WorkspaceError::engine)?;
2532 Ok(OperationReceipt {
2533 value: crate::Workspace {
2534 name: destination,
2535 id: destination_id,
2536 volume,
2537 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
2538 source: None,
2539 },
2540 work,
2541 })
2542 }
2543
2544 pub(crate) async fn retain_workspace_generation(
2545 &self,
2546 volume: &Volume<A, O>,
2547 generation_id: GenerationId,
2548 kind: RetentionKind,
2549 label: String,
2550 ) -> Result<(), crate::workspace::WorkspaceError> {
2551 let cancellation = CancellationToken::new();
2552 let generation_root = ObjectId {
2553 kind: ObjectKind::GenerationRoot,
2554 digest: generation_id.digest(),
2555 };
2556 let proof = prove_generation_closure_async(
2557 &self.inner.objects,
2558 generation_root,
2559 closure_limits(volume.config),
2560 WorkBudget::UNBOUNDED,
2561 &cancellation,
2562 )
2563 .await
2564 .map_err(crate::workspace::WorkspaceError::engine)?;
2565 if proof.root.volume_id != volume.id {
2566 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2567 }
2568 self.retain_verified_workspace_generation(
2569 volume,
2570 VerifiedForkSource { generation_root },
2571 kind,
2572 label,
2573 &cancellation,
2574 )
2575 .await
2576 }
2577
2578 async fn retain_verified_workspace_generation(
2579 &self,
2580 volume: &Volume<A, O>,
2581 source: VerifiedForkSource,
2582 kind: RetentionKind,
2583 label: String,
2584 cancellation: &CancellationToken,
2585 ) -> Result<(), crate::workspace::WorkspaceError> {
2586 self.retain_verified_workspace_generation_measured(
2587 volume,
2588 source,
2589 kind,
2590 label,
2591 WorkBudget::UNBOUNDED,
2592 cancellation,
2593 )
2594 .await
2595 .map(|_| ())
2596 }
2597
2598 async fn retain_verified_workspace_generation_measured(
2599 &self,
2600 volume: &Volume<A, O>,
2601 source: VerifiedForkSource,
2602 kind: RetentionKind,
2603 label: String,
2604 budget: WorkBudget,
2605 cancellation: &CancellationToken,
2606 ) -> Result<WorkCounters, crate::workspace::WorkspaceError> {
2607 let generation_root = source.generation_root;
2608 let authority_id = retention_authority_id(volume.id, kind, &label);
2609 let created = self
2610 .inner
2611 .authority
2612 .create_authority(authority_id, Epoch::GENESIS, budget, cancellation)
2613 .await
2614 .map_err(crate::workspace::WorkspaceError::engine)?;
2615 let mut work = created.work;
2616 let active_head = match created.value {
2617 CreateAuthorityOutcome::Created(head) | CreateAuthorityOutcome::Existing(head) => head,
2618 };
2619 let payload = encode_retention_created(&RetentionCreated {
2620 volume_id: volume.id,
2621 kind,
2622 label,
2623 generation_root,
2624 config: volume.config,
2625 })
2626 .map_err(crate::workspace::WorkspaceError::engine)?;
2627 let operation_id = OperationId::from_bytes(authority_id.into_bytes());
2628 let (commit, _) = creation_commit(operation_id, payload);
2629 let drained = self
2630 .inner
2631 .objects
2632 .flush_before_publish(
2633 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2634 cancellation,
2635 )
2636 .await
2637 .map_err(crate::workspace::WorkspaceError::engine)?;
2638 work = add(work, drained.work).map_err(crate::workspace::WorkspaceError::engine)?;
2639 let appended = self
2640 .inner
2641 .authority
2642 .compare_and_append(
2643 authority_id,
2644 active_head.epoch,
2645 Head::genesis(active_head.epoch),
2646 commit,
2647 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2648 cancellation,
2649 )
2650 .await
2651 .map_err(crate::workspace::WorkspaceError::engine)?;
2652 work = add(work, appended.work).map_err(crate::workspace::WorkspaceError::engine)?;
2653 match appended.value {
2654 AppendOutcome::Committed(_) | AppendOutcome::AlreadyCommitted(_) => Ok(work),
2655 AppendOutcome::Conflict { .. }
2656 | AppendOutcome::Fenced { .. }
2657 | AppendOutcome::IdempotencyConflict { .. } => {
2658 Err(crate::workspace::WorkspaceError::RetentionConflict)
2659 }
2660 }
2661 }
2662
2663 pub(crate) async fn delete_workspace_volume(
2664 &self,
2665 volume: &Volume<A, O>,
2666 operation_id: OperationId,
2667 ) -> Result<crate::workspace::WorkspaceDelete, crate::workspace::WorkspaceError> {
2668 let cancellation = CancellationToken::new();
2669 let authority_id = volume_authority_id(volume.id);
2670 let head = self
2671 .inner
2672 .authority
2673 .head(authority_id, WorkBudget::UNBOUNDED, &cancellation)
2674 .await
2675 .map_err(crate::workspace::WorkspaceError::engine)?
2676 .value;
2677 if head.sequence == Sequence::GENESIS {
2678 return Err(crate::workspace::WorkspaceError::engine(
2679 FsError::EmptyAuthority,
2680 ));
2681 }
2682 let latest = self
2683 .inner
2684 .authority
2685 .replay(
2686 authority_id,
2687 Sequence::new(head.sequence.get().saturating_sub(1)),
2688 ReplayLimit {
2689 records: 1,
2690 payload_bytes: MAXIMUM_VOLUME_EVENT_BYTES,
2691 },
2692 WorkBudget::UNBOUNDED,
2693 &cancellation,
2694 )
2695 .await
2696 .map_err(crate::workspace::WorkspaceError::engine)?;
2697 let latest = latest.value.first().ok_or_else(|| {
2698 crate::workspace::WorkspaceError::engine(FsError::InvalidAuthorityHistory)
2699 })?;
2700 if let Ok(deleted) = decode_workspace_deleted(&latest.payload, MAXIMUM_VOLUME_EVENT_BYTES) {
2701 if deleted != volume.id {
2702 return Err(crate::workspace::WorkspaceError::engine(
2703 FsError::VolumeMismatch,
2704 ));
2705 }
2706 return Ok(crate::workspace::WorkspaceDelete::AlreadyDeleted);
2707 }
2708 generation_from_record(latest, volume.id, WorkCounters::default())
2709 .map_err(crate::workspace::WorkspaceError::engine)?;
2710 let (commit, _) = creation_commit(operation_id, encode_workspace_deleted(volume.id));
2711 let appended = self
2712 .inner
2713 .authority
2714 .compare_and_append(
2715 authority_id,
2716 head.epoch,
2717 head,
2718 commit,
2719 WorkBudget::UNBOUNDED,
2720 &cancellation,
2721 )
2722 .await
2723 .map_err(crate::workspace::WorkspaceError::engine)?;
2724 Ok(match appended.value {
2725 AppendOutcome::Committed(_) | AppendOutcome::AlreadyCommitted(_) => {
2726 crate::workspace::WorkspaceDelete::Deleted
2727 }
2728 AppendOutcome::Conflict { .. } | AppendOutcome::Fenced { .. } => {
2729 crate::workspace::WorkspaceDelete::Conflict
2730 }
2731 AppendOutcome::IdempotencyConflict { .. } => {
2732 crate::workspace::WorkspaceDelete::IdempotencyConflict
2733 }
2734 })
2735 }
2736
2737 pub(crate) async fn workspace_common_ancestor(
2738 &self,
2739 source: &crate::Generation<A, O>,
2740 target: &crate::Generation<A, O>,
2741 maximum_generations: u32,
2742 ) -> Result<WorkspaceCommonAncestor, crate::workspace::WorkspaceError> {
2743 if maximum_generations == 0
2744 || !Arc::ptr_eq(&self.inner, &source.workspace.volume.fs.inner)
2745 || !Arc::ptr_eq(&self.inner, &target.workspace.volume.fs.inner)
2746 {
2747 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2748 }
2749 if source.workspace.volume.config != target.workspace.volume.config {
2750 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
2751 }
2752 let cancellation = CancellationToken::new();
2753 let (target_ancestors, target_truncated) = collect_generation_ancestors(
2754 &self.inner.objects,
2755 target.id,
2756 target.workspace.volume.config,
2757 maximum_generations,
2758 &cancellation,
2759 )
2760 .await?;
2761 find_first_generation_ancestor(
2762 &self.inner.objects,
2763 source.id,
2764 source.workspace.volume.config,
2765 maximum_generations,
2766 &target_ancestors,
2767 target_truncated,
2768 &cancellation,
2769 )
2770 .await
2771 }
2772
2773 pub(crate) async fn workspace_join_changes(
2774 &self,
2775 base: GenerationId,
2776 source: &crate::Generation<A, O>,
2777 maximum_changes: u32,
2778 ) -> Result<FsReceipt<GenerationDiff>, crate::workspace::WorkspaceError> {
2779 self.workspace_join_changes_bounded(
2780 base,
2781 source,
2782 maximum_changes,
2783 WorkBudget::UNBOUNDED,
2784 &CancellationToken::new(),
2785 )
2786 .await
2787 }
2788
2789 pub(crate) async fn workspace_join_changes_bounded(
2790 &self,
2791 base: GenerationId,
2792 source: &crate::Generation<A, O>,
2793 maximum_changes: u32,
2794 budget: WorkBudget,
2795 cancellation: &CancellationToken,
2796 ) -> Result<FsReceipt<GenerationDiff>, crate::workspace::WorkspaceError> {
2797 if maximum_changes == 0 || !Arc::ptr_eq(&self.inner, &source.workspace.volume.fs.inner) {
2798 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
2799 }
2800 let config = source.workspace.volume.config;
2801 let mut work = WorkCounters::default();
2802 let (base_root, base_work) = read_generation_root(
2803 &self.inner.objects,
2804 ObjectId {
2805 kind: ObjectKind::GenerationRoot,
2806 digest: base.digest(),
2807 },
2808 config,
2809 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2810 cancellation,
2811 )
2812 .await
2813 .map_err(crate::workspace::WorkspaceError::engine)?;
2814 work = add(work, base_work).map_err(crate::workspace::WorkspaceError::engine)?;
2815 let (source_root, source_work) = read_generation_root(
2816 &self.inner.objects,
2817 ObjectId {
2818 kind: ObjectKind::GenerationRoot,
2819 digest: source.id.digest(),
2820 },
2821 config,
2822 remaining(work, budget).map_err(crate::workspace::WorkspaceError::engine)?,
2823 cancellation,
2824 )
2825 .await
2826 .map_err(crate::workspace::WorkspaceError::engine)?;
2827 work = add(work, source_work).map_err(crate::workspace::WorkspaceError::engine)?;
2828 if source_root.volume_id != source.workspace.volume.id
2829 || base_root.root_file_id != source_root.root_file_id
2830 {
2831 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
2832 }
2833 let receipt = diff_generation_file_tables(
2834 &self.inner.objects,
2835 base_root.file_table,
2836 source_root.file_table,
2837 maximum_changes,
2838 source.workspace.volume.config,
2839 work,
2840 budget,
2841 cancellation,
2842 )
2843 .await
2844 .map_err(crate::workspace::WorkspaceError::engine)?;
2845 if receipt.value.truncated {
2846 return Err(crate::workspace::WorkspaceError::JoinLimit);
2847 }
2848 Ok(receipt)
2849 }
2850
2851 pub(crate) async fn workspace_head_state(
2852 &self,
2853 workspace: &Volume<A, O>,
2854 ) -> Result<(GenerationId, Head), crate::workspace::WorkspaceError> {
2855 let (root, head, _) = workspace
2856 .resolve_head_generation(WorkBudget::UNBOUNDED, &CancellationToken::new())
2857 .await
2858 .map_err(crate::workspace::WorkspaceError::engine)?;
2859 Ok((GenerationId::new(root.digest), head))
2860 }
2861
2862 pub(crate) async fn workspace_operation_generation(
2863 &self,
2864 workspace: &Volume<A, O>,
2865 operation_id: OperationId,
2866 ) -> Result<Option<GenerationId>, crate::workspace::WorkspaceError> {
2867 let cancellation = CancellationToken::new();
2868 let observed = self
2869 .observe_volume_operation(
2870 workspace.id,
2871 operation_id,
2872 WorkBudget::UNBOUNDED,
2873 &cancellation,
2874 )
2875 .await
2876 .map_err(crate::workspace::WorkspaceError::engine)?;
2877 observed
2878 .value
2879 .as_ref()
2880 .map(|commit| {
2881 generation_from_record(commit, workspace.id, observed.work)
2882 .map(|object| GenerationId::new(object.digest))
2883 .map_err(crate::workspace::WorkspaceError::engine)
2884 })
2885 .transpose()
2886 }
2887
2888 pub(crate) async fn workspace_conflict_record(
2889 &self,
2890 workspace: &Volume<A, O>,
2891 generation: GenerationId,
2892 file_id: FileId,
2893 ) -> Result<Option<FileRecord>, crate::workspace::WorkspaceError> {
2894 let cancellation = CancellationToken::new();
2895 let generation_root = ObjectId {
2896 kind: ObjectKind::GenerationRoot,
2897 digest: generation.digest(),
2898 };
2899 let (root, work) = read_generation_root(
2900 &self.inner.objects,
2901 generation_root,
2902 workspace.config,
2903 WorkBudget::UNBOUNDED,
2904 &cancellation,
2905 )
2906 .await
2907 .map_err(crate::workspace::WorkspaceError::engine)?;
2908 let lookup = lookup_file_record_async(
2909 &self.inner.objects,
2910 root.file_table,
2911 file_id,
2912 decode_limits(workspace.config),
2913 WorkBudget::UNBOUNDED,
2914 &cancellation,
2915 )
2916 .await
2917 .map_err(crate::workspace::WorkspaceError::engine)?;
2918 let _ = work;
2919 Ok(lookup.record)
2920 }
2921
2922 pub(crate) async fn workspace_conflict_bytes(
2923 &self,
2924 workspace: &Volume<A, O>,
2925 record: FileRecord,
2926 ) -> Result<Option<Bytes>, crate::workspace::WorkspaceError> {
2927 let logical_bytes = match record.payload {
2928 FilePayload::InlineRegular(data) => {
2929 return Ok(Some(Bytes::copy_from_slice(data.as_bytes())));
2930 }
2931 FilePayload::Regular { logical_bytes, .. } => logical_bytes,
2932 _ => return Ok(None),
2933 };
2934 if logical_bytes > workspace.config.limits.maximum_read_bytes {
2935 return Ok(None);
2936 }
2937 let read = read_file_range_async(
2938 &self.inner.objects,
2939 FileRangeRequest {
2940 record,
2941 range: ByteRange {
2942 offset: 0,
2943 length: logical_bytes,
2944 },
2945 maximum_spans: workspace.config.limits.maximum_directory_page_entries,
2946 limits: decode_limits(workspace.config),
2947 budget: WorkBudget::UNBOUNDED,
2948 },
2949 &CancellationToken::new(),
2950 )
2951 .await
2952 .map_err(crate::workspace::WorkspaceError::engine)?;
2953 Ok(Some(read.bytes))
2954 }
2955
2956 pub(crate) async fn workspace_conflict_metadata(
2957 &self,
2958 workspace: &Volume<A, O>,
2959 record: FileRecord,
2960 ) -> Result<Vec<u8>, crate::workspace::WorkspaceError> {
2961 let read = self
2962 .inner
2963 .objects
2964 .read(
2965 record.metadata,
2966 workspace.config.limits.maximum_object_bytes,
2967 WorkBudget::UNBOUNDED,
2968 &CancellationToken::new(),
2969 )
2970 .await
2971 .map_err(crate::workspace::WorkspaceError::engine)?;
2972 decode_file_metadata(&read.value.bytes, decode_limits(workspace.config))
2973 .map_err(crate::workspace::WorkspaceError::engine)?;
2974 Ok(read.value.bytes.to_vec())
2975 }
2976
2977 pub(crate) async fn stage_merge_regular_record(
2978 &self,
2979 workspace: &Volume<A, O>,
2980 mut record: FileRecord,
2981 bytes: Bytes,
2982 ) -> Result<FileRecord, crate::workspace::WorkspaceError> {
2983 if record.kind != FileKind::Regular
2984 || u64::try_from(bytes.len()).unwrap_or(u64::MAX)
2985 > workspace.config.limits.maximum_read_bytes
2986 {
2987 return Err(crate::workspace::WorkspaceError::InvalidMergeResolution);
2988 }
2989 if bytes.len() <= crate::kernel::MAXIMUM_INLINE_FILE_BYTES {
2990 record.payload = FilePayload::InlineRegular(
2991 InlineFileData::new(&bytes).map_err(crate::workspace::WorkspaceError::engine)?,
2992 );
2993 return Ok(record);
2994 }
2995 let maximum_blob_bytes = u64::try_from(bytes.len()).unwrap_or(u64::MAX).max(1);
2996 let mut source = std::io::Cursor::new(bytes);
2997 let blob = build_blob_async(
2998 &self.inner.objects,
2999 &mut source,
3000 BlobBuildOptions {
3001 chunk_bytes: u32::try_from(
3002 workspace
3003 .config
3004 .limits
3005 .maximum_object_bytes
3006 .min(1024 * 1024),
3007 )
3008 .unwrap_or(u32::MAX)
3009 .max(1),
3010 page_items: workspace.config.limits.maximum_directory_page_entries,
3011 page_bytes: u32::try_from(workspace.config.limits.maximum_object_bytes)
3012 .unwrap_or(u32::MAX)
3013 .max(1),
3014 maximum_blob_bytes,
3015 },
3016 WorkBudget::UNBOUNDED,
3017 &CancellationToken::new(),
3018 )
3019 .await
3020 .map_err(crate::workspace::WorkspaceError::engine)?;
3021 let payload = apply_regular_mutation_async(
3022 &self.inner.objects,
3023 FilePayload::InlineRegular(
3024 InlineFileData::new(&[]).map_err(crate::workspace::WorkspaceError::engine)?,
3025 ),
3026 RegularMutation::Write {
3027 offset: 0,
3028 length: blob.logical_bytes,
3029 content: blob.root,
3030 content_offset: 0,
3031 },
3032 workspace.config,
3033 WorkBudget::UNBOUNDED,
3034 &CancellationToken::new(),
3035 )
3036 .await
3037 .map_err(crate::workspace::WorkspaceError::engine)?;
3038 record.payload = payload.payload;
3039 Ok(record)
3040 }
3041
3042 pub(crate) async fn stage_merge_metadata(
3043 &self,
3044 workspace: &Volume<A, O>,
3045 bytes: &[u8],
3046 ) -> Result<ObjectId, crate::workspace::WorkspaceError> {
3047 decode_file_metadata(bytes, decode_limits(workspace.config))
3048 .map_err(crate::workspace::WorkspaceError::engine)?;
3049 let (object, _) = self
3050 .put_encoded(
3051 ObjectKind::Metadata,
3052 bytes.to_vec(),
3053 WorkCounters::default(),
3054 WorkBudget::UNBOUNDED,
3055 &CancellationToken::new(),
3056 )
3057 .await
3058 .map_err(crate::workspace::WorkspaceError::engine)?;
3059 Ok(object)
3060 }
3061
3062 pub(crate) async fn restore_workspace_generation(
3063 &self,
3064 workspace: &Volume<A, O>,
3065 generation: GenerationId,
3066 expected: GenerationId,
3067 operation_id: OperationId,
3068 permit: PublicationPermit,
3069 ) -> Result<crate::workspace::WorkspaceRestoreOutcome, crate::workspace::WorkspaceError> {
3070 let cancellation = CancellationToken::new();
3071 if let Some(recorded) = self
3072 .workspace_operation_generation(workspace, operation_id)
3073 .await?
3074 {
3075 return Ok(if recorded == generation {
3076 crate::workspace::WorkspaceRestoreOutcome::AlreadyRestored(recorded)
3077 } else {
3078 crate::workspace::WorkspaceRestoreOutcome::IdempotencyConflict
3079 });
3080 }
3081 let (current, authority_head, _) = workspace
3082 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3083 .await
3084 .map_err(crate::workspace::WorkspaceError::engine)?;
3085 let current = GenerationId::new(current.digest);
3086 if current != expected {
3087 return Ok(crate::workspace::WorkspaceRestoreOutcome::Stale(current));
3088 }
3089 if current == generation {
3090 return Ok(crate::workspace::WorkspaceRestoreOutcome::Current(current));
3091 }
3092 let generation_root = ObjectId {
3093 kind: ObjectKind::GenerationRoot,
3094 digest: generation.digest(),
3095 };
3096 let (root, _) = read_generation_root(
3097 &self.inner.objects,
3098 generation_root,
3099 workspace.config,
3100 WorkBudget::UNBOUNDED,
3101 &cancellation,
3102 )
3103 .await
3104 .map_err(crate::workspace::WorkspaceError::engine)?;
3105 if root.volume_id != workspace.id {
3106 return Err(crate::workspace::WorkspaceError::ForeignGeneration);
3107 }
3108 let publication = publish_generation_async_with_permit(
3109 &self.inner.objects,
3110 &self.inner.authority,
3111 PublishGenerationRequest {
3112 authority_id: volume_authority_id(workspace.id),
3113 volume_id: workspace.id,
3114 epoch: authority_head.epoch,
3115 expected: authority_head,
3116 operation_id,
3117 generation_root,
3118 },
3119 PublicationIntent::guarded(None, permit),
3120 closure_limits(workspace.config),
3121 WorkBudget::UNBOUNDED,
3122 &cancellation,
3123 )
3124 .await
3125 .map_err(crate::workspace::WorkspaceError::engine)?;
3126 Ok(match publication.outcome {
3127 AppendOutcome::Committed(_) => {
3128 crate::workspace::WorkspaceRestoreOutcome::Restored(publication.proof.generation_id)
3129 }
3130 AppendOutcome::AlreadyCommitted(_) => {
3131 crate::workspace::WorkspaceRestoreOutcome::AlreadyRestored(
3132 publication.proof.generation_id,
3133 )
3134 }
3135 AppendOutcome::Conflict { .. } => {
3136 let (actual, _, _) = workspace
3137 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3138 .await
3139 .map_err(crate::workspace::WorkspaceError::engine)?;
3140 crate::workspace::WorkspaceRestoreOutcome::Stale(GenerationId::new(actual.digest))
3141 }
3142 AppendOutcome::Fenced { .. } => crate::workspace::WorkspaceRestoreOutcome::Fenced,
3143 AppendOutcome::IdempotencyConflict { .. } => {
3144 crate::workspace::WorkspaceRestoreOutcome::IdempotencyConflict
3145 }
3146 })
3147 }
3148
3149 #[allow(clippy::too_many_lines)]
3150 pub(crate) async fn live_rebase_workspace(
3151 &self,
3152 request: WorkspaceRebaseRequest<'_, A, O>,
3153 ) -> Result<WorkspaceJoinOutcome, crate::workspace::WorkspaceError> {
3154 let WorkspaceRebaseRequest {
3155 target,
3156 operation_id,
3157 maximum_generations,
3158 maximum_changes,
3159 maximum_conflicts,
3160 } = request;
3161 if maximum_generations == 0 || maximum_changes == 0 || maximum_conflicts == 0 {
3162 return Err(crate::workspace::WorkspaceError::JoinLimit);
3163 }
3164 let cancellation = CancellationToken::new();
3165 let (target_object, expected_head, _) = target
3166 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3167 .await
3168 .map_err(crate::workspace::WorkspaceError::engine)?;
3169 let (target_root, _) = read_generation_root(
3170 &self.inner.objects,
3171 target_object,
3172 target.config,
3173 WorkBudget::UNBOUNDED,
3174 &cancellation,
3175 )
3176 .await
3177 .map_err(crate::workspace::WorkspaceError::engine)?;
3178
3179 let mut cursor = target_object;
3180 let mut adopted = None;
3181 for _ in 0..maximum_generations {
3182 let (root, _) = read_generation_root(
3183 &self.inner.objects,
3184 cursor,
3185 target.config,
3186 WorkBudget::UNBOUNDED,
3187 &cancellation,
3188 )
3189 .await
3190 .map_err(crate::workspace::WorkspaceError::engine)?;
3191 if root.volume_id != target.id {
3192 adopted = Some((cursor, root));
3193 break;
3194 }
3195 let Some(parent) = root.parents.first() else {
3196 return Err(crate::workspace::WorkspaceError::NotFork);
3197 };
3198 cursor = ObjectId {
3199 kind: ObjectKind::GenerationRoot,
3200 digest: parent.digest(),
3201 };
3202 }
3203 let Some((base_object, base_root)) = adopted else {
3204 return Err(crate::workspace::WorkspaceError::LineageLimit);
3205 };
3206 let source = self
3207 .open_volume(base_root.volume_id, WorkBudget::UNBOUNDED, &cancellation)
3208 .await
3209 .map_err(crate::workspace::WorkspaceError::engine)?
3210 .value;
3211 if source.config != target.config || base_root.root_file_id != target_root.root_file_id {
3212 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
3213 }
3214 let (source_object, _, _) = source
3215 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3216 .await
3217 .map_err(crate::workspace::WorkspaceError::engine)?;
3218 if source_object == base_object {
3219 return Ok(WorkspaceJoinOutcome::NoChanges(GenerationId::new(
3220 target_object.digest,
3221 )));
3222 }
3223 let (source_root, _) = read_generation_root(
3224 &self.inner.objects,
3225 source_object,
3226 target.config,
3227 WorkBudget::UNBOUNDED,
3228 &cancellation,
3229 )
3230 .await
3231 .map_err(crate::workspace::WorkspaceError::engine)?;
3232 if source_root.volume_id != source.id
3233 || source_root.root_file_id != target_root.root_file_id
3234 {
3235 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
3236 }
3237
3238 let normalize = |root: &GenerationRoot| GenerationRoot {
3239 volume_id: target.id,
3240 root_file_id: target_root.root_file_id,
3241 file_table: root.file_table,
3242 parents: Vec::new(),
3243 required_features: root.required_features,
3244 };
3245 let normalized_base = normalize(&base_root);
3246 let normalized_source = normalize(&source_root);
3247 let (normalized_base_object, _) = self
3248 .put_encoded(
3249 ObjectKind::GenerationRoot,
3250 encode_generation_root(&normalized_base)
3251 .map_err(crate::workspace::WorkspaceError::engine)?,
3252 WorkCounters::default(),
3253 WorkBudget::UNBOUNDED,
3254 &cancellation,
3255 )
3256 .await
3257 .map_err(crate::workspace::WorkspaceError::engine)?;
3258 let (normalized_source_object, _) = self
3259 .put_encoded(
3260 ObjectKind::GenerationRoot,
3261 encode_generation_root(&normalized_source)
3262 .map_err(crate::workspace::WorkspaceError::engine)?,
3263 WorkCounters::default(),
3264 WorkBudget::UNBOUNDED,
3265 &cancellation,
3266 )
3267 .await
3268 .map_err(crate::workspace::WorkspaceError::engine)?;
3269 let merged = merge_generation_async(
3270 &self.inner.objects,
3271 MergeGenerationRequest {
3272 base_generation: normalized_base_object,
3273 base: normalized_base,
3274 ours_generation: Some(normalized_source_object),
3275 ours: normalized_source,
3276 theirs_generation: target_object,
3277 theirs: target_root,
3278 retain_theirs_parent: false,
3279 maximum_changes,
3280 maximum_conflicts,
3281 resolutions: BTreeMap::new(),
3282 },
3283 decode_limits(target.config),
3284 WorkBudget::UNBOUNDED,
3285 &cancellation,
3286 )
3287 .await
3288 .map_err(crate::workspace::WorkspaceError::engine)?;
3289 let merged_root = match merged.value {
3290 MergeGenerationOutcome::Conflicted {
3291 conflicts,
3292 truncated,
3293 } => return Ok(WorkspaceJoinOutcome::Conflicted(conflicts, truncated)),
3294 MergeGenerationOutcome::Prepared { root, .. } => root,
3295 };
3296 let rebased_root = GenerationRoot {
3297 volume_id: target.id,
3298 root_file_id: merged_root.root_file_id,
3299 file_table: merged_root.file_table,
3300 parents: vec![GenerationId::new(source_object.digest)],
3301 required_features: merged_root.required_features,
3302 };
3303 let (candidate, _) = self
3304 .put_encoded(
3305 ObjectKind::GenerationRoot,
3306 encode_generation_root(&rebased_root)
3307 .map_err(crate::workspace::WorkspaceError::engine)?,
3308 WorkCounters::default(),
3309 WorkBudget::UNBOUNDED,
3310 &cancellation,
3311 )
3312 .await
3313 .map_err(crate::workspace::WorkspaceError::engine)?;
3314 let publication = publish_generation_async(
3315 &self.inner.objects,
3316 &self.inner.authority,
3317 PublishGenerationRequest {
3318 authority_id: volume_authority_id(target.id),
3319 volume_id: target.id,
3320 epoch: expected_head.epoch,
3321 expected: expected_head,
3322 operation_id,
3323 generation_root: candidate,
3324 },
3325 closure_limits(target.config),
3326 WorkBudget::UNBOUNDED,
3327 &cancellation,
3328 )
3329 .await
3330 .map_err(crate::workspace::WorkspaceError::engine)?;
3331 Ok(match publication.outcome {
3332 AppendOutcome::Committed(_) => {
3333 WorkspaceJoinOutcome::Applied(publication.proof.generation_id)
3334 }
3335 AppendOutcome::AlreadyCommitted(_) => {
3336 WorkspaceJoinOutcome::AlreadyApplied(publication.proof.generation_id)
3337 }
3338 AppendOutcome::Conflict { .. } => {
3339 let (actual, _, _) = target
3340 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3341 .await
3342 .map_err(crate::workspace::WorkspaceError::engine)?;
3343 WorkspaceJoinOutcome::Stale(GenerationId::new(actual.digest))
3344 }
3345 AppendOutcome::Fenced { .. } => WorkspaceJoinOutcome::Fenced,
3346 AppendOutcome::IdempotencyConflict { .. } => WorkspaceJoinOutcome::IdempotencyConflict,
3347 })
3348 }
3349
3350 #[allow(clippy::too_many_lines)]
3351 pub(crate) async fn apply_workspace_join(
3352 &self,
3353 request: WorkspaceJoinRequest<'_, A, O>,
3354 ) -> Result<WorkspaceJoinOutcome, crate::workspace::WorkspaceError> {
3355 let operation_context = request.operation_context();
3356 let WorkspaceJoinRequest {
3357 target,
3358 base,
3359 source,
3360 expected_target,
3361 expected_head,
3362 history,
3363 operation_id,
3364 permit,
3365 maximum_generations,
3366 maximum_changes,
3367 maximum_conflicts,
3368 resolutions,
3369 } = request;
3370 if maximum_generations == 0 || maximum_changes == 0 || maximum_conflicts == 0 {
3371 return Err(crate::workspace::WorkspaceError::JoinLimit);
3372 }
3373 if !Arc::ptr_eq(&self.inner, &source.workspace.volume.fs.inner)
3374 || source.workspace.volume.config != target.config
3375 {
3376 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
3377 }
3378 let cancellation = CancellationToken::new();
3379 let (current_target_object, _, _) = target
3380 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3381 .await
3382 .map_err(crate::workspace::WorkspaceError::engine)?;
3383 let current_target = GenerationId::new(current_target_object.digest);
3384 let target_object = ObjectId {
3385 kind: ObjectKind::GenerationRoot,
3386 digest: expected_target.digest(),
3387 };
3388 let base_object = ObjectId {
3389 kind: ObjectKind::GenerationRoot,
3390 digest: base.id.digest(),
3391 };
3392 let source_object = ObjectId {
3393 kind: ObjectKind::GenerationRoot,
3394 digest: source.id.digest(),
3395 };
3396 let (base_root, _) = read_generation_root(
3397 &self.inner.objects,
3398 base_object,
3399 target.config,
3400 WorkBudget::UNBOUNDED,
3401 &cancellation,
3402 )
3403 .await
3404 .map_err(crate::workspace::WorkspaceError::engine)?;
3405 let (source_root, _) = read_generation_root(
3406 &self.inner.objects,
3407 source_object,
3408 target.config,
3409 WorkBudget::UNBOUNDED,
3410 &cancellation,
3411 )
3412 .await
3413 .map_err(crate::workspace::WorkspaceError::engine)?;
3414 let (target_root, _) = read_generation_root(
3415 &self.inner.objects,
3416 target_object,
3417 target.config,
3418 WorkBudget::UNBOUNDED,
3419 &cancellation,
3420 )
3421 .await
3422 .map_err(crate::workspace::WorkspaceError::engine)?;
3423 if source_root.volume_id != source.workspace.volume.id
3424 || target_root.volume_id != target.id
3425 || base_root.root_file_id != target_root.root_file_id
3426 || source_root.root_file_id != target_root.root_file_id
3427 {
3428 return Err(crate::workspace::WorkspaceError::IncompatibleWorkspace);
3429 }
3430 let normalized_base = GenerationRoot {
3431 volume_id: target.id,
3432 root_file_id: target_root.root_file_id,
3433 file_table: base_root.file_table,
3434 parents: base_root.parents,
3435 required_features: base_root.required_features,
3436 };
3437 let normalized_source = GenerationRoot {
3438 volume_id: target.id,
3439 root_file_id: target_root.root_file_id,
3440 file_table: source_root.file_table,
3441 parents: source_root.parents,
3442 required_features: source_root.required_features,
3443 };
3444 let (normalized_base_object, _) = self
3445 .put_encoded(
3446 ObjectKind::GenerationRoot,
3447 encode_generation_root(&normalized_base)
3448 .map_err(crate::workspace::WorkspaceError::engine)?,
3449 WorkCounters::default(),
3450 WorkBudget::UNBOUNDED,
3451 &cancellation,
3452 )
3453 .await
3454 .map_err(crate::workspace::WorkspaceError::engine)?;
3455 let (normalized_source_object, _) = self
3456 .put_encoded(
3457 ObjectKind::GenerationRoot,
3458 encode_generation_root(&normalized_source)
3459 .map_err(crate::workspace::WorkspaceError::engine)?,
3460 WorkCounters::default(),
3461 WorkBudget::UNBOUNDED,
3462 &cancellation,
3463 )
3464 .await
3465 .map_err(crate::workspace::WorkspaceError::engine)?;
3466 let merged = merge_generation_async(
3467 &self.inner.objects,
3468 MergeGenerationRequest {
3469 base_generation: normalized_base_object,
3470 base: normalized_base,
3471 ours_generation: Some(target_object),
3472 ours: target_root.clone(),
3473 theirs_generation: normalized_source_object,
3474 theirs: normalized_source,
3475 retain_theirs_parent: history == crate::workspace::JoinHistory::Merge,
3476 maximum_changes,
3477 maximum_conflicts,
3478 resolutions,
3479 },
3480 decode_limits(target.config),
3481 WorkBudget::UNBOUNDED,
3482 &cancellation,
3483 )
3484 .await
3485 .map_err(crate::workspace::WorkspaceError::engine)?;
3486 let (candidate, merged_root) = match merged.value {
3487 MergeGenerationOutcome::Conflicted {
3488 conflicts,
3489 truncated,
3490 } => return Ok(WorkspaceJoinOutcome::Conflicted(conflicts, truncated)),
3491 MergeGenerationOutcome::Prepared {
3492 generation_root,
3493 root,
3494 ..
3495 } => (generation_root, root),
3496 };
3497 if merged_root.file_table == target_root.file_table {
3498 return Ok(WorkspaceJoinOutcome::NoChanges(current_target));
3499 }
3500 let publication = publish_generation_async_with_permit(
3501 &self.inner.objects,
3502 &self.inner.authority,
3503 PublishGenerationRequest {
3504 authority_id: volume_authority_id(target.id),
3505 volume_id: target.id,
3506 epoch: expected_head.epoch,
3507 expected: expected_head,
3508 operation_id,
3509 generation_root: candidate,
3510 },
3511 PublicationIntent::guarded(Some(operation_context), permit),
3512 closure_limits(target.config),
3513 WorkBudget::UNBOUNDED,
3514 &cancellation,
3515 )
3516 .await
3517 .map_err(crate::workspace::WorkspaceError::engine)?;
3518 Ok(match publication.outcome {
3519 AppendOutcome::Committed(_) => {
3520 WorkspaceJoinOutcome::Applied(publication.proof.generation_id)
3521 }
3522 AppendOutcome::AlreadyCommitted(_) => {
3523 WorkspaceJoinOutcome::AlreadyApplied(publication.proof.generation_id)
3524 }
3525 AppendOutcome::Conflict { .. } => {
3526 let (actual, _, _) = target
3527 .resolve_head_generation(WorkBudget::UNBOUNDED, &cancellation)
3528 .await
3529 .map_err(crate::workspace::WorkspaceError::engine)?;
3530 WorkspaceJoinOutcome::Stale(GenerationId::new(actual.digest))
3531 }
3532 AppendOutcome::Fenced { .. } => WorkspaceJoinOutcome::Fenced,
3533 AppendOutcome::IdempotencyConflict { .. } => WorkspaceJoinOutcome::IdempotencyConflict,
3534 })
3535 }
3536
3537 pub async fn execute_residency(
3545 &self,
3546 speculator: &crate::ResidencySpeculator,
3547 permit: crate::ResidencyPermit,
3548 budget: WorkBudget,
3549 cancellation: &CancellationToken,
3550 ) -> crate::storage::ObjectResult<u64> {
3551 crate::execute_residency(
3552 speculator,
3553 &self.inner.objects,
3554 permit,
3555 budget,
3556 cancellation,
3557 )
3558 .await
3559 }
3560
3561 pub async fn create_volume(
3568 &self,
3569 config: VolumeConfig,
3570 budget: WorkBudget,
3571 cancellation: &CancellationToken,
3572 ) -> FsResult<Volume<A, O>> {
3573 self.create_volume_with_id(VolumeId::new(), config, budget, cancellation)
3574 .await
3575 }
3576
3577 pub async fn create_volume_with_id(
3584 &self,
3585 volume_id: VolumeId,
3586 config: VolumeConfig,
3587 budget: WorkBudget,
3588 cancellation: &CancellationToken,
3589 ) -> FsResult<Volume<A, O>> {
3590 self.create_volume_with_id_operation(volume_id, config, None, budget, cancellation)
3591 .await
3592 }
3593
3594 async fn create_volume_with_id_operation(
3595 &self,
3596 volume_id: VolumeId,
3597 config: VolumeConfig,
3598 operation_id: Option<OperationId>,
3599 budget: WorkBudget,
3600 cancellation: &CancellationToken,
3601 ) -> FsResult<Volume<A, O>> {
3602 cancellation
3603 .check()
3604 .map_err(|error| OperationFailure::before_work(error.into()))?;
3605 validate_volume_capabilities(config, self.inner.capabilities)?;
3606 let (generation_root, mut work) = self
3607 .build_empty_generation(volume_id, config, budget, cancellation)
3608 .await?;
3609 let closure_limits = closure_limits(config);
3610 let remaining_budget = remaining(work, budget)?;
3611 let proof = prove_generation_closure_async(
3612 &self.inner.objects,
3613 generation_root,
3614 closure_limits,
3615 remaining_budget,
3616 cancellation,
3617 )
3618 .await
3619 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
3620 work = add(work, proof.work)?;
3621 self.publish_volume_creation(
3622 VolumeCreation {
3623 volume_id,
3624 config,
3625 generation_root,
3626 operation_id,
3627 },
3628 work,
3629 budget,
3630 cancellation,
3631 )
3632 .await
3633 }
3634
3635 pub async fn open_volume(
3643 &self,
3644 volume_id: VolumeId,
3645 budget: WorkBudget,
3646 cancellation: &CancellationToken,
3647 ) -> FsResult<Volume<A, O>> {
3648 cancellation
3649 .check()
3650 .map_err(|error| OperationFailure::before_work(error.into()))?;
3651 let (creation, work) = self
3652 .read_creation(volume_id, WorkCounters::default(), budget, cancellation)
3653 .await?;
3654 validate_volume_capabilities(creation.config, self.inner.capabilities)
3655 .map_err(|failure| OperationFailure::new(failure.error, work))?;
3656 Ok(FsReceipt {
3657 value: Volume {
3658 fs: self.clone(),
3659 id: volume_id,
3660 config: creation.config,
3661 },
3662 work,
3663 })
3664 }
3665
3666 pub async fn export_object(
3673 &self,
3674 object_id: ObjectId,
3675 maximum_bytes: u64,
3676 budget: WorkBudget,
3677 cancellation: &CancellationToken,
3678 ) -> FsResult<crate::storage::ObjectRead> {
3679 let receipt = self
3680 .inner
3681 .objects
3682 .read(object_id, maximum_bytes, budget, cancellation)
3683 .await
3684 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
3685 Ok(FsReceipt {
3686 value: receipt.value,
3687 work: receipt.work,
3688 })
3689 }
3690
3691 pub async fn import_object(
3698 &self,
3699 object_id: ObjectId,
3700 bytes: Bytes,
3701 budget: WorkBudget,
3702 cancellation: &CancellationToken,
3703 ) -> FsResult<()> {
3704 let receipt = self
3705 .inner
3706 .objects
3707 .put(object_id, bytes, budget, cancellation)
3708 .await
3709 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
3710 Ok(FsReceipt {
3711 value: (),
3712 work: receipt.work,
3713 })
3714 }
3715
3716 pub async fn export_generation_batch(
3727 &self,
3728 manifest: &GenerationExportManifest,
3729 cursor: TransferCursor,
3730 maximum_objects: u32,
3731 maximum_object_bytes: u64,
3732 budget: WorkBudget,
3733 cancellation: &CancellationToken,
3734 ) -> FsResult<GenerationTransferBatch> {
3735 export_generation_batch_async(
3736 &self.inner.objects,
3737 manifest,
3738 cursor,
3739 maximum_objects,
3740 maximum_object_bytes,
3741 budget,
3742 cancellation,
3743 )
3744 .await
3745 .map_err(|failure| OperationFailure::new(map_transfer_error(failure.error), *failure.work))
3746 }
3747
3748 pub async fn import_generation_batch(
3759 &self,
3760 manifest: &GenerationExportManifest,
3761 cursor: TransferCursor,
3762 bodies: &[Bytes],
3763 maximum_objects: u32,
3764 budget: WorkBudget,
3765 cancellation: &CancellationToken,
3766 ) -> FsResult<TransferCursor> {
3767 import_generation_batch_async(
3768 &self.inner.objects,
3769 manifest,
3770 cursor,
3771 bodies,
3772 maximum_objects,
3773 budget,
3774 cancellation,
3775 )
3776 .await
3777 .map_err(|failure| OperationFailure::new(map_transfer_error(failure.error), *failure.work))
3778 }
3779
3780 pub async fn restore_volume(
3791 &self,
3792 manifest: &GenerationExportManifest,
3793 operation_id: OperationId,
3794 budget: WorkBudget,
3795 cancellation: &CancellationToken,
3796 ) -> FsResult<Volume<A, O>> {
3797 cancellation
3798 .check()
3799 .map_err(|error| OperationFailure::before_work(error.into()))?;
3800 validate_volume_capabilities(manifest.config, self.inner.capabilities)?;
3801 let proof = authenticate_generation_export_manifest_async(
3802 &self.inner.objects,
3803 manifest,
3804 closure_limits(manifest.config),
3805 budget,
3806 cancellation,
3807 )
3808 .await
3809 .map_err(|failure| {
3810 OperationFailure::new(map_transfer_error(failure.error), *failure.work)
3811 })?;
3812 let work = proof.work;
3813 self.publish_volume_creation(
3814 VolumeCreation {
3815 volume_id: manifest.volume_id,
3816 config: manifest.config,
3817 generation_root: manifest.generation_root,
3818 operation_id: Some(operation_id),
3819 },
3820 work,
3821 budget,
3822 cancellation,
3823 )
3824 .await
3825 }
3826
3827 async fn publish_volume_creation(
3828 &self,
3829 creation: VolumeCreation,
3830 mut work: WorkCounters,
3831 budget: WorkBudget,
3832 cancellation: &CancellationToken,
3833 ) -> FsResult<Volume<A, O>> {
3834 let VolumeCreation {
3835 volume_id,
3836 config,
3837 generation_root,
3838 operation_id,
3839 } = creation;
3840 let authority_id = volume_authority_id(volume_id);
3841 let created = self
3842 .inner
3843 .authority
3844 .create_authority(
3845 authority_id,
3846 Epoch::GENESIS,
3847 remaining(work, budget)?,
3848 cancellation,
3849 )
3850 .await
3851 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
3852 work = add(work, created.work)?;
3853 let active_head = match created.value {
3854 CreateAuthorityOutcome::Created(head) | CreateAuthorityOutcome::Existing(head) => head,
3855 };
3856 let event = encode_volume_created(VolumeCreated {
3857 volume_id,
3858 config,
3859 initial_generation_root: generation_root,
3860 })
3861 .map_err(|error| OperationFailure::new(error.into(), work))?;
3862 let operation_id = if let Some(operation_id) = operation_id {
3863 operation_id
3864 } else {
3865 let (operation_id, identity_work) = derived_operation_id(volume_id);
3866 work = add(work, identity_work)?;
3867 operation_id
3868 };
3869 let (commit, encoding_work) = creation_commit(operation_id, event);
3870 work = add(work, encoding_work)?;
3871 work.verify(budget)
3872 .map_err(|error| OperationFailure::new(error.into(), work))?;
3873 let drained = self
3874 .inner
3875 .objects
3876 .flush_before_publish(remaining(work, budget)?, cancellation)
3877 .await
3878 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
3879 work = add(work, drained.work)?;
3880 let appended = self
3881 .inner
3882 .authority
3883 .compare_and_append(
3884 authority_id,
3885 active_head.epoch,
3886 Head::genesis(active_head.epoch),
3887 commit,
3888 remaining(work, budget)?,
3889 cancellation,
3890 )
3891 .await
3892 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
3893 work = add(work, appended.work)?;
3894 match appended.value {
3895 AppendOutcome::Committed(_) | AppendOutcome::AlreadyCommitted(_) => Ok(FsReceipt {
3896 value: Volume {
3897 fs: self.clone(),
3898 id: volume_id,
3899 config,
3900 },
3901 work,
3902 }),
3903 AppendOutcome::Conflict { .. }
3904 | AppendOutcome::Fenced { .. }
3905 | AppendOutcome::IdempotencyConflict { .. } => {
3906 Err(OperationFailure::new(FsError::CreationRejected, work))
3907 }
3908 }
3909 }
3910
3911 async fn read_creation(
3912 &self,
3913 volume_id: VolumeId,
3914 work: WorkCounters,
3915 budget: WorkBudget,
3916 cancellation: &CancellationToken,
3917 ) -> Result<(VolumeCreated, WorkCounters), OperationFailure<FsError>> {
3918 let records = self
3919 .inner
3920 .authority
3921 .replay(
3922 volume_authority_id(volume_id),
3923 Sequence::GENESIS,
3924 ReplayLimit {
3925 records: 1,
3926 payload_bytes: MAXIMUM_VOLUME_EVENT_BYTES,
3927 },
3928 remaining(work, budget)?,
3929 cancellation,
3930 )
3931 .await
3932 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
3933 let combined = add(work, records.work)?;
3934 let first = records
3935 .value
3936 .first()
3937 .ok_or_else(|| OperationFailure::new(FsError::EmptyAuthority, combined))?;
3938 let creation = decode_volume_created(&first.payload, MAXIMUM_VOLUME_EVENT_BYTES)
3939 .map_err(|error| OperationFailure::new(error.into(), combined))?;
3940 if creation.volume_id != volume_id {
3941 return Err(OperationFailure::new(FsError::VolumeMismatch, combined));
3942 }
3943 Ok((creation, combined))
3944 }
3945
3946 async fn build_empty_generation(
3947 &self,
3948 volume_id: VolumeId,
3949 config: VolumeConfig,
3950 budget: WorkBudget,
3951 cancellation: &CancellationToken,
3952 ) -> Result<(ObjectId, WorkCounters), OperationFailure<FsError>> {
3953 let mut work = WorkCounters::default();
3954 let metadata = self
3955 .put_encoded(
3956 ObjectKind::Metadata,
3957 encode_file_metadata(empty_metadata())
3958 .map_err(|error| OperationFailure::new(error.into(), work))?,
3959 work,
3960 budget,
3961 cancellation,
3962 )
3963 .await?;
3964 work = metadata.1;
3965 let tree = self
3966 .put_encoded(
3967 ObjectKind::TreePage,
3968 encode_tree_page(
3969 &TreePage::Leaf(Vec::new()),
3970 config.limits.maximum_directory_page_entries,
3971 )
3972 .map_err(|error| OperationFailure::new(error.into(), work))?,
3973 work,
3974 budget,
3975 cancellation,
3976 )
3977 .await?;
3978 work = tree.1;
3979 let (root_file_id, identity_work) = derived_file_id(volume_id);
3980 work = add(work, identity_work)?;
3981 let records = FileTablePage::Leaf(vec![FileRecord {
3982 file_id: root_file_id,
3983 kind: FileKind::Directory,
3984 link_count: 1,
3985 metadata: metadata.0,
3986 payload: FilePayload::Directory { entries: tree.0 },
3987 }]);
3988 work = add(
3989 work,
3990 WorkCounters {
3991 allocation_operations: 1,
3992 peak_allocation_bytes: u64::try_from(size_of::<FileRecord>()).unwrap_or(u64::MAX),
3993 ..WorkCounters::default()
3994 },
3995 )?;
3996 let encoded_file_table =
3997 encode_file_table_page(&records, config.limits.maximum_directory_page_entries)
3998 .map_err(|error| OperationFailure::new(error.into(), work))?;
3999 drop(records);
4000 let file_table = self
4001 .put_encoded(
4002 ObjectKind::FileTablePage,
4003 encoded_file_table,
4004 work,
4005 budget,
4006 cancellation,
4007 )
4008 .await?;
4009 work = file_table.1;
4010 let root = GenerationRoot {
4011 volume_id,
4012 root_file_id,
4013 file_table: file_table.0,
4014 parents: Vec::new(),
4015 required_features: 0,
4016 };
4017 self.put_encoded(
4018 ObjectKind::GenerationRoot,
4019 encode_generation_root(&root)
4020 .map_err(|error| OperationFailure::new(error.into(), work))?,
4021 work,
4022 budget,
4023 cancellation,
4024 )
4025 .await
4026 }
4027
4028 async fn put_encoded(
4029 &self,
4030 kind: ObjectKind,
4031 encoded: Vec<u8>,
4032 work: WorkCounters,
4033 budget: WorkBudget,
4034 cancellation: &CancellationToken,
4035 ) -> Result<(ObjectId, WorkCounters), OperationFailure<FsError>> {
4036 let length = u64::try_from(encoded.len()).unwrap_or(u64::MAX);
4037 let semantic = WorkCounters {
4038 bytes_encoded: length,
4039 bytes_hashed: length.saturating_add(OBJECT_DIGEST_ENVELOPE_BYTES),
4040 allocation_operations: 1,
4041 peak_allocation_bytes: u64::try_from(encoded.capacity()).unwrap_or(u64::MAX),
4042 ..WorkCounters::default()
4043 };
4044 let prospective = add(work, semantic)?;
4045 prospective
4046 .verify(budget)
4047 .map_err(|error| OperationFailure::new(error.into(), work))?;
4048 let object = ObjectId {
4049 kind,
4050 digest: object_digest(kind, &encoded),
4051 };
4052 let live_bytes = semantic.peak_allocation_bytes;
4053 let mut backend_budget = remaining(prospective, budget)?;
4054 backend_budget.peak_allocation_bytes -= live_bytes;
4057 let receipt = self
4058 .inner
4059 .objects
4060 .put(object, Bytes::from(encoded), backend_budget, cancellation)
4061 .await
4062 .map_err(|failure| {
4063 merge_simultaneous_failure(
4064 prospective,
4065 *failure.work,
4066 live_bytes,
4067 failure.error.into(),
4068 )
4069 })?;
4070 Ok((
4071 object,
4072 merge_simultaneous_work(prospective, receipt.work, live_bytes, budget)?,
4073 ))
4074 }
4075}
4076
4077impl<A: AsyncAuthorityStore, O: AsyncObjectStore> Volume<A, O> {
4078 #[must_use]
4080 pub const fn id(&self) -> VolumeId {
4081 self.id
4082 }
4083
4084 #[must_use]
4086 pub const fn config(&self) -> VolumeConfig {
4087 self.config
4088 }
4089
4090 pub async fn diff_generations(
4102 &self,
4103 before: GenerationId,
4104 after: GenerationId,
4105 maximum_changes: u32,
4106 budget: WorkBudget,
4107 cancellation: &CancellationToken,
4108 ) -> FsResult<GenerationDiff> {
4109 if maximum_changes == 0 {
4110 return Err(OperationFailure::before_work(FsError::InvalidDiff));
4111 }
4112 if before == after {
4113 return Ok(OperationReceipt {
4114 value: GenerationDiff {
4115 files: Vec::new(),
4116 bindings: Vec::new(),
4117 truncated: false,
4118 },
4119 work: WorkCounters::default(),
4120 });
4121 }
4122 let mut work = WorkCounters::default();
4123 let before_id = ObjectId {
4124 kind: ObjectKind::GenerationRoot,
4125 digest: before.digest(),
4126 };
4127 let after_id = ObjectId {
4128 kind: ObjectKind::GenerationRoot,
4129 digest: after.digest(),
4130 };
4131 let (before_root, before_work) = read_generation_root(
4132 &self.fs.inner.objects,
4133 before_id,
4134 self.config,
4135 remaining(work, budget)?,
4136 cancellation,
4137 )
4138 .await
4139 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
4140 work = add(work, before_work)?;
4141 let (after_root, after_work) = read_generation_root(
4142 &self.fs.inner.objects,
4143 after_id,
4144 self.config,
4145 remaining(work, budget)?,
4146 cancellation,
4147 )
4148 .await
4149 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
4150 work = add(work, after_work)?;
4151 if before_root.volume_id != self.id || after_root.volume_id != self.id {
4152 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
4153 }
4154 diff_generation_file_tables(
4155 &self.fs.inner.objects,
4156 before_root.file_table,
4157 after_root.file_table,
4158 maximum_changes,
4159 self.config,
4160 work,
4161 budget,
4162 cancellation,
4163 )
4164 .await
4165 }
4166
4167 async fn resolve_checkout_generation(
4170 &self,
4171 selector: GenerationSelector,
4172 mode: CheckoutMode,
4173 work: WorkCounters,
4174 budget: WorkBudget,
4175 cancellation: &CancellationToken,
4176 ) -> Result<(ObjectId, Option<Head>, WorkCounters), OperationFailure<FsError>> {
4177 match selector {
4178 GenerationSelector::Exact(generation_id) => {
4179 let mut work = work;
4180 let authority_head = if mode.access == AccessMode::ReadWrite {
4181 if mode.consistency != ConsistencyMode::TrackingSafe
4182 || mode.mutations != MutationMode::PrivateOverlay
4183 {
4184 return Err(OperationFailure::before_work(
4185 FsError::WritableCheckoutRequiresHead,
4186 ));
4187 }
4188 let resolved = self
4189 .resolve_head_generation(remaining(work, budget)?, cancellation)
4190 .await
4191 .map_err(|failure| {
4192 failure.map_with_prior_work(work, std::convert::identity)
4193 })?;
4194 work = add(work, resolved.2)?;
4195 Some(resolved.1)
4196 } else {
4197 None
4198 };
4199 Ok((
4200 ObjectId {
4201 kind: ObjectKind::GenerationRoot,
4202 digest: generation_id.digest(),
4203 },
4204 authority_head,
4205 work,
4206 ))
4207 }
4208 GenerationSelector::Head => {
4209 let resolved = self
4210 .resolve_head_generation(remaining(work, budget)?, cancellation)
4211 .await
4212 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
4213 let work = add(work, resolved.2)?;
4214 Ok((resolved.0, Some(resolved.1), work))
4215 }
4216 }
4217 }
4218
4219 pub async fn checkout(
4228 &self,
4229 selector: GenerationSelector,
4230 mode: CheckoutMode,
4231 budget: WorkBudget,
4232 cancellation: &CancellationToken,
4233 ) -> FsResult<Checkout<A, O>> {
4234 cancellation
4235 .check()
4236 .map_err(|error| OperationFailure::before_work(error.into()))?;
4237 validate_checkout(mode, self.config)?;
4238 let mut work = WorkCounters::default();
4239 let (generation_root, mut authority_head, next_work) = self
4240 .resolve_checkout_generation(selector, mode, work, budget, cancellation)
4241 .await?;
4242 work = next_work;
4243 let (root, root_work) = read_generation_root(
4244 &self.fs.inner.objects,
4245 generation_root,
4246 self.config,
4247 remaining(work, budget)?,
4248 cancellation,
4249 )
4250 .await
4251 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
4252 work = add(work, root_work)?;
4253 if root.volume_id != self.id {
4254 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
4255 }
4256 if mode.access == AccessMode::ReadWrite
4257 && self.config.concurrency == ConcurrencyMode::ExclusiveWriter
4258 {
4259 let expected = authority_head.ok_or_else(|| {
4260 OperationFailure::new(FsError::WritableCheckoutRequiresHead, work)
4261 })?;
4262 let fenced = self
4263 .fs
4264 .inner
4265 .authority
4266 .fence(
4267 volume_authority_id(self.id),
4268 expected,
4269 remaining(work, budget)?,
4270 cancellation,
4271 )
4272 .await
4273 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
4274 work = add(work, fenced.work)?;
4275 authority_head = Some(match fenced.value {
4276 FenceOutcome::Advanced(head) => head,
4277 FenceOutcome::Conflict { actual } => {
4278 return Err(OperationFailure::new(
4279 FsError::ExclusiveWriterConflict { actual },
4280 work,
4281 ));
4282 }
4283 });
4284 }
4285 let dependencies = CheckoutDependencies::new(
4286 std::iter::empty(),
4287 std::iter::empty(),
4288 self.config.limits.maximum_checkout_dependencies,
4289 )
4290 .map_err(|error| OperationFailure::new(error.into(), work))?;
4291 Ok(FsReceipt {
4292 value: Checkout {
4293 volume: self.clone(),
4294 base_generation_root: generation_root,
4295 generation_root,
4296 base_file_table: root.file_table,
4297 base_root: root.clone(),
4298 root,
4299 authority_head,
4300 authored_operation_id: None,
4301 live_operation_id: None,
4302 last_commit: None,
4303 prepared_merge_parent: None,
4304 dependencies,
4305 mode,
4306 },
4307 work,
4308 })
4309 }
4310
4311 async fn resolve_head_generation(
4312 &self,
4313 budget: WorkBudget,
4314 cancellation: &CancellationToken,
4315 ) -> Result<(ObjectId, Head, WorkCounters), OperationFailure<FsError>> {
4316 let authority_id = volume_authority_id(self.id);
4317 let head = self
4318 .fs
4319 .inner
4320 .authority
4321 .head(authority_id, budget, cancellation)
4322 .await
4323 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
4324 let mut work = head.work;
4325 if head.value.sequence == Sequence::GENESIS {
4326 return Err(OperationFailure::new(FsError::EmptyAuthority, work));
4327 }
4328 let after = Sequence::new(head.value.sequence.get().saturating_sub(1));
4329 let records = self
4330 .fs
4331 .inner
4332 .authority
4333 .replay(
4334 authority_id,
4335 after,
4336 ReplayLimit {
4337 records: 1,
4338 payload_bytes: MAXIMUM_VOLUME_EVENT_BYTES,
4339 },
4340 remaining(work, budget)?,
4341 cancellation,
4342 )
4343 .await
4344 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
4345 work = add(work, records.work)?;
4346 let latest = records
4347 .value
4348 .first()
4349 .ok_or_else(|| OperationFailure::new(FsError::InvalidAuthorityHistory, work))?;
4350 if let Ok(deleted) = decode_workspace_deleted(&latest.payload, MAXIMUM_VOLUME_EVENT_BYTES) {
4351 if deleted != self.id {
4352 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
4353 }
4354 return Err(OperationFailure::new(FsError::WorkspaceDeleted, work));
4355 }
4356 let generation_root = generation_from_record(latest, self.id, work)?;
4357 Ok((generation_root, head.value, work))
4358 }
4359}
4360
4361impl<A, O> Checkout<A, O> {
4362 #[must_use]
4364 pub const fn volume_id(&self) -> VolumeId {
4365 self.volume.id
4366 }
4367
4368 #[must_use]
4370 pub const fn volume_config(&self) -> VolumeConfig {
4371 self.volume.config
4372 }
4373
4374 #[must_use]
4376 pub const fn generation_id(&self) -> GenerationId {
4377 GenerationId::new(self.generation_root.digest)
4378 }
4379
4380 #[must_use]
4382 pub const fn root(&self) -> &GenerationRoot {
4383 &self.root
4384 }
4385
4386 #[must_use]
4388 pub const fn mode(&self) -> CheckoutMode {
4389 self.mode
4390 }
4391
4392 #[must_use]
4394 pub fn has_pending_mutations(&self) -> bool {
4395 self.root.file_table != self.base_file_table || self.prepared_merge_parent.is_some()
4396 }
4397
4398 #[must_use]
4404 pub fn snapshot_reader(&self) -> PinnedReader<A, O> {
4405 PinnedReader {
4406 volume: self.volume.clone(),
4407 generation_root: self.generation_root,
4408 root: self.root.clone(),
4409 }
4410 }
4411
4412 pub fn pinned_reader(&self) -> Result<PinnedReader<A, O>, FsError> {
4418 if self.mode.consistency != ConsistencyMode::Pinned {
4419 return Err(FsError::MutationNotAllowed);
4420 }
4421 Ok(self.snapshot_reader())
4422 }
4423
4424 #[must_use]
4427 pub fn content_stager(&self) -> ContentStager<A, O> {
4428 ContentStager {
4429 volume: self.volume.clone(),
4430 }
4431 }
4432}
4433
4434impl<A: AsyncAuthorityStore, O: AsyncObjectStore> ContentStager<A, O> {
4435 pub async fn stage<R: AsyncBlobSource>(
4437 &self,
4438 source: &mut R,
4439 maximum_source_bytes: u64,
4440 budget: WorkBudget,
4441 cancellation: &CancellationToken,
4442 ) -> FsResult<StagedContent> {
4443 stage_content_for_volume(
4444 &self.volume,
4445 source,
4446 maximum_source_bytes,
4447 budget,
4448 cancellation,
4449 )
4450 .await
4451 }
4452}
4453
4454impl<A: AsyncAuthorityStore, O: AsyncObjectStore> PinnedReader<A, O> {
4455 async fn resolve_file_records(
4456 &self,
4457 paths: &[NamespacePath],
4458 budget: WorkBudget,
4459 cancellation: &CancellationToken,
4460 ) -> FsResult<Vec<Option<FileRecord>>> {
4461 let lookup = crate::kernel::lookup_paths_async(
4462 &self.volume.fs.inner.objects,
4463 &self.root,
4464 paths,
4465 self.volume.config,
4466 budget,
4467 cancellation,
4468 )
4469 .await
4470 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::Path))?;
4471 let mut records = Vec::new();
4472 records
4473 .try_reserve_exact(lookup.entries.len())
4474 .map_err(|_| {
4475 OperationFailure::new(FsError::PendingMutationAllocationFailed, lookup.work)
4476 })?;
4477 for entry in lookup.entries {
4478 records.push(entry.record);
4479 }
4480 Ok(FsReceipt {
4481 value: records,
4482 work: lookup.work,
4483 })
4484 }
4485
4486 async fn list_directory_page(
4487 &self,
4488 request: DirectoryPageRequest,
4489 budget: WorkBudget,
4490 cancellation: &CancellationToken,
4491 ) -> FsResult<DirectoryPage> {
4492 list_directory_page_pinned(
4493 &self.volume,
4494 &self.root,
4495 &request.path,
4496 request.after.as_ref(),
4497 request.maximum_entries,
4498 budget,
4499 cancellation,
4500 )
4501 .await
4502 }
4503
4504 pub async fn list_directory_pages(
4509 &self,
4510 requests: &[DirectoryPageRequest],
4511 concurrency: usize,
4512 budget: WorkBudget,
4513 cancellation: &CancellationToken,
4514 ) -> FsResult<Vec<DirectoryPage>> {
4515 if concurrency == 0
4516 || requests.len()
4517 > usize::try_from(self.volume.config.limits.maximum_paths_per_batch)
4518 .unwrap_or(usize::MAX)
4519 {
4520 return Err(OperationFailure::before_work(FsError::Path(
4521 PathLookupError::TooManyPaths,
4522 )));
4523 }
4524 if budget != WorkBudget::UNBOUNDED {
4525 let mut work = WorkCounters::default();
4526 let mut ordered = Vec::new();
4527 ordered
4528 .try_reserve_exact(requests.len())
4529 .map_err(|_| OperationFailure::before_work(FsError::Work(WorkError::Overflow)))?;
4530 for request in requests.iter().cloned() {
4531 let receipt = self
4532 .list_directory_page(request, remaining(work, budget)?, cancellation)
4533 .await
4534 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
4535 work = add(work, receipt.work)?;
4536 ordered.push(receipt.value);
4537 }
4538 return Ok(FsReceipt {
4539 value: ordered,
4540 work,
4541 });
4542 }
4543 let results = stream::iter(
4544 requests
4545 .iter()
4546 .cloned()
4547 .enumerate()
4548 .map(|(index, request)| {
4549 let reader = self.clone();
4550 async move {
4551 (
4552 index,
4553 reader
4554 .list_directory_page(request, budget, cancellation)
4555 .await,
4556 )
4557 }
4558 }),
4559 )
4560 .buffer_unordered(concurrency.min(requests.len().max(1)))
4561 .collect::<Vec<_>>()
4562 .await;
4563 order_batch_results(results, budget)
4564 }
4565
4566 pub async fn resolve_directory_page(
4570 &self,
4571 path: &NamespacePath,
4572 after: Option<&LogicalName>,
4573 maximum_entries: u32,
4574 budget: WorkBudget,
4575 cancellation: &CancellationToken,
4576 ) -> FsResult<ResolvedDirectoryPage<A, O>> {
4577 let listing = list_directory_page_pinned(
4578 &self.volume,
4579 &self.root,
4580 path,
4581 after,
4582 maximum_entries,
4583 budget,
4584 cancellation,
4585 )
4586 .await?;
4587 let mut work = listing.work;
4588 let page = listing.value;
4589 let mut file_ids = Vec::new();
4590 file_ids
4591 .try_reserve_exact(page.entries.len())
4592 .map_err(|_| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
4593 file_ids.extend(page.entries.iter().map(|entry| entry.file_id));
4594 let records = if file_ids.is_empty() {
4595 Vec::new()
4596 } else {
4597 let receipt = lookup_file_records_async(
4598 &self.volume.fs.inner.objects,
4599 self.root.file_table,
4600 &file_ids,
4601 maximum_entries,
4602 decode_limits(self.volume.config),
4603 remaining(work, budget)?,
4604 cancellation,
4605 )
4606 .await
4607 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
4608 work = add(work, receipt.work)?;
4609 receipt.records
4610 };
4611 let mut metadata_requests = Vec::new();
4612 metadata_requests
4613 .try_reserve_exact(records.len())
4614 .map_err(|_| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
4615 for record in &records {
4616 let record = record
4617 .as_ref()
4618 .ok_or_else(|| OperationFailure::new(FsError::InvalidDirectoryRecord, work))?;
4619 metadata_requests.push(ObjectReadRequest {
4620 object_id: record.metadata,
4621 maximum_bytes: self.volume.config.limits.maximum_object_bytes,
4622 });
4623 }
4624 let metadata = if metadata_requests.is_empty() {
4625 Vec::new()
4626 } else {
4627 let receipt = self
4628 .volume
4629 .fs
4630 .inner
4631 .objects
4632 .read_many(&metadata_requests, remaining(work, budget)?, cancellation)
4633 .await
4634 .map_err(|failure| failure.map_with_prior_work(work, FsError::Object))?;
4635 work = add(work, receipt.work)?;
4636 receipt.value
4637 };
4638 let mut entries = Vec::new();
4639 entries
4640 .try_reserve_exact(page.entries.len())
4641 .map_err(|_| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
4642 for ((binding, record), metadata) in page.entries.into_iter().zip(records).zip(metadata) {
4643 let record = record
4644 .ok_or_else(|| OperationFailure::new(FsError::InvalidDirectoryRecord, work))?;
4645 if record.file_id != binding.file_id || record.kind != binding.kind {
4646 return Err(OperationFailure::new(FsError::InvalidDirectoryRecord, work));
4647 }
4648 let metadata = decode_file_metadata(&metadata, decode_limits(self.volume.config))
4649 .map_err(|error| OperationFailure::new(error.into(), work))?;
4650 let logical_bytes = record_logical_bytes(record);
4651 entries.push(ResolvedDirectoryEntry {
4652 name: binding.name,
4653 file: ResolvedFile {
4654 reader: self.clone(),
4655 record,
4656 description: FileDescription {
4657 kind: record.kind,
4658 logical_bytes,
4659 metadata,
4660 },
4661 },
4662 });
4663 }
4664 Ok(FsReceipt {
4665 value: ResolvedDirectoryPage {
4666 entries,
4667 has_more: page.has_more,
4668 },
4669 work,
4670 })
4671 }
4672
4673 async fn read_symbolic_link_record(
4674 &self,
4675 record: FileRecord,
4676 budget: WorkBudget,
4677 cancellation: &CancellationToken,
4678 ) -> FsResult<Bytes> {
4679 read_symbolic_link_record(&self.volume, record, budget, cancellation).await
4680 }
4681
4682 async fn read_record_range(
4683 &self,
4684 record: FileRecord,
4685 range: ByteRange,
4686 budget: WorkBudget,
4687 cancellation: &CancellationToken,
4688 ) -> FsResult<FileRangeRead> {
4689 if range.length > self.volume.config.limits.maximum_read_bytes {
4690 return Err(OperationFailure::before_work(FsError::FileRead(
4691 FileRangeReadError::InvalidRange,
4692 )));
4693 }
4694 let read = read_file_range_async(
4695 &self.volume.fs.inner.objects,
4696 FileRangeRequest {
4697 record,
4698 range,
4699 maximum_spans: self.volume.config.limits.maximum_directory_page_entries,
4700 limits: decode_limits(self.volume.config),
4701 budget,
4702 },
4703 cancellation,
4704 )
4705 .await
4706 .map_err(|failure| {
4707 failure.map_with_prior_work(WorkCounters::default(), FsError::FileRead)
4708 })?;
4709 Ok(FsReceipt {
4710 work: read.work,
4711 value: read,
4712 })
4713 }
4714
4715 async fn read_record_metadata(
4716 &self,
4717 record: FileRecord,
4718 budget: WorkBudget,
4719 cancellation: &CancellationToken,
4720 ) -> FsResult<FileMetadata> {
4721 let metadata = self
4722 .volume
4723 .fs
4724 .inner
4725 .objects
4726 .read(
4727 record.metadata,
4728 self.volume.config.limits.maximum_object_bytes,
4729 budget,
4730 cancellation,
4731 )
4732 .await
4733 .map_err(|failure| {
4734 failure.map_with_prior_work(WorkCounters::default(), FsError::Object)
4735 })?;
4736 let value = decode_file_metadata(&metadata.value, decode_limits(self.volume.config))
4737 .map_err(|error| OperationFailure::new(error.into(), metadata.work))?;
4738 Ok(FsReceipt {
4739 value,
4740 work: metadata.work,
4741 })
4742 }
4743
4744 async fn resolve_file(
4745 &self,
4746 path: &NamespacePath,
4747 budget: WorkBudget,
4748 cancellation: &CancellationToken,
4749 ) -> FsResult<FileRecord> {
4750 let lookup = crate::kernel::observe_path_edges_async(
4751 &self.volume.fs.inner.objects,
4752 &self.root,
4753 path,
4754 self.volume.config,
4755 budget,
4756 cancellation,
4757 )
4758 .await
4759 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::Path))?;
4760 let work = lookup.lookup.work;
4761 let record = lookup
4762 .lookup
4763 .record
4764 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
4765 Ok(FsReceipt {
4766 value: record,
4767 work,
4768 })
4769 }
4770
4771 pub async fn plan_file_extents(
4773 &self,
4774 path: &NamespacePath,
4775 range: ByteRange,
4776 maximum_spans: u32,
4777 budget: WorkBudget,
4778 cancellation: &CancellationToken,
4779 ) -> FsResult<Option<ExtentPlan>> {
4780 let resolved = self.resolve_file(path, budget, cancellation).await?;
4781 let mut work = resolved.work;
4782 let plan = self
4783 .plan_record_extents(
4784 resolved.value,
4785 range,
4786 maximum_spans,
4787 remaining(work, budget)?,
4788 cancellation,
4789 )
4790 .await
4791 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
4792 work = add(work, plan.work)?;
4793 Ok(FsReceipt {
4794 value: plan.value,
4795 work,
4796 })
4797 }
4798
4799 async fn plan_record_extents(
4800 &self,
4801 record: FileRecord,
4802 range: ByteRange,
4803 maximum_spans: u32,
4804 budget: WorkBudget,
4805 cancellation: &CancellationToken,
4806 ) -> FsResult<Option<ExtentPlan>> {
4807 if maximum_spans == 0 || range.length > self.volume.config.limits.maximum_generation_bytes {
4808 return Err(OperationFailure::before_work(FsError::FileRead(
4809 FileRangeReadError::InvalidRange,
4810 )));
4811 }
4812 let mut work = WorkCounters::default();
4813 let (logical_bytes, extents) = match record.payload {
4814 FilePayload::InlineRegular(data) => {
4815 validate_planned_file_range(
4816 u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX),
4817 range,
4818 work,
4819 )?;
4820 return Ok(FsReceipt { value: None, work });
4821 }
4822 FilePayload::Regular {
4823 logical_bytes,
4824 extents,
4825 } => (logical_bytes, extents),
4826 _ => {
4827 return Err(OperationFailure::new(
4828 FsError::FileRead(FileRangeReadError::NotRegular),
4829 work,
4830 ));
4831 }
4832 };
4833 let mut plan = plan_extent_range_async(
4834 &self.volume.fs.inner.objects,
4835 ExtentRangeRequest {
4836 root: extents,
4837 file_size: logical_bytes,
4838 range,
4839 maximum_spans,
4840 limits: decode_limits(self.volume.config),
4841 budget: remaining(work, budget)?,
4842 },
4843 cancellation,
4844 )
4845 .await
4846 .map_err(|failure| {
4847 failure.map_with_prior_work(work, |error| {
4848 FsError::FileRead(FileRangeReadError::Extent(error))
4849 })
4850 })?;
4851 work = add(work, plan.work)?;
4852 plan.work = work;
4853 Ok(FsReceipt {
4854 value: Some(plan),
4855 work,
4856 })
4857 }
4858
4859 pub async fn read_file_range(
4861 &self,
4862 path: &NamespacePath,
4863 range: ByteRange,
4864 budget: WorkBudget,
4865 cancellation: &CancellationToken,
4866 ) -> FsResult<FileRangeRead> {
4867 if range.length > self.volume.config.limits.maximum_read_bytes {
4868 return Err(OperationFailure::before_work(FsError::FileRead(
4869 FileRangeReadError::InvalidRange,
4870 )));
4871 }
4872 let resolved = self.resolve_file(path, budget, cancellation).await?;
4873 let mut work = resolved.work;
4874 let record = resolved.value;
4875 let read = read_file_range_async(
4876 &self.volume.fs.inner.objects,
4877 FileRangeRequest {
4878 record,
4879 range,
4880 maximum_spans: self.volume.config.limits.maximum_directory_page_entries,
4881 limits: decode_limits(self.volume.config),
4882 budget: remaining(work, budget)?,
4883 },
4884 cancellation,
4885 )
4886 .await
4887 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRead))?;
4888 work = add(work, read.work)?;
4889 Ok(FsReceipt { value: read, work })
4890 }
4891
4892 pub async fn read_symbolic_link(
4894 &self,
4895 path: &NamespacePath,
4896 budget: WorkBudget,
4897 cancellation: &CancellationToken,
4898 ) -> FsResult<Bytes> {
4899 let resolved = self.resolve_file(path, budget, cancellation).await?;
4900 let mut work = resolved.work;
4901 let read = self
4902 .read_symbolic_link_record(resolved.value, remaining(work, budget)?, cancellation)
4903 .await
4904 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
4905 work = add(work, read.work)?;
4906 Ok(FsReceipt {
4907 value: read.value,
4908 work,
4909 })
4910 }
4911
4912 pub async fn read_metadata(
4914 &self,
4915 path: &NamespacePath,
4916 budget: WorkBudget,
4917 cancellation: &CancellationToken,
4918 ) -> FsResult<FileMetadata> {
4919 let resolved = self.resolve_file(path, budget, cancellation).await?;
4920 let mut work = resolved.work;
4921 let metadata = self
4922 .read_record_metadata(resolved.value, remaining(work, budget)?, cancellation)
4923 .await
4924 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
4925 work = add(work, metadata.work)?;
4926 Ok(FsReceipt {
4927 value: metadata.value,
4928 work,
4929 })
4930 }
4931
4932 pub async fn describe_files(
4934 &self,
4935 paths: &[NamespacePath],
4936 budget: WorkBudget,
4937 cancellation: &CancellationToken,
4938 ) -> FsResult<Vec<Option<FileDescription>>> {
4939 let resolved = self.resolve_files(paths, budget, cancellation).await?;
4940 let descriptions = resolved
4941 .value
4942 .into_iter()
4943 .map(|file| file.map(|file| file.description))
4944 .collect();
4945 Ok(FsReceipt {
4946 value: descriptions,
4947 work: resolved.work,
4948 })
4949 }
4950
4951 pub async fn resolve_files(
4954 &self,
4955 paths: &[NamespacePath],
4956 budget: WorkBudget,
4957 cancellation: &CancellationToken,
4958 ) -> FsResult<Vec<Option<ResolvedFile<A, O>>>> {
4959 let resolved = self
4960 .resolve_file_records(paths, budget, cancellation)
4961 .await?;
4962 let mut work = resolved.work;
4963 let mut requests = Vec::new();
4964 requests
4965 .try_reserve_exact(resolved.value.len())
4966 .map_err(|_| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
4967 requests.extend(
4968 resolved
4969 .value
4970 .iter()
4971 .flatten()
4972 .map(|record| ObjectReadRequest {
4973 object_id: record.metadata,
4974 maximum_bytes: self.volume.config.limits.maximum_object_bytes,
4975 }),
4976 );
4977 let reads = if requests.is_empty() {
4978 Vec::new()
4979 } else {
4980 let receipt = self
4981 .volume
4982 .fs
4983 .inner
4984 .objects
4985 .read_many(&requests, remaining(work, budget)?, cancellation)
4986 .await
4987 .map_err(|failure| failure.map_with_prior_work(work, FsError::Object))?;
4988 work = add(work, receipt.work)?;
4989 receipt.value
4990 };
4991 let mut reads = reads.into_iter();
4992 let mut handles = Vec::new();
4993 handles
4994 .try_reserve_exact(resolved.value.len())
4995 .map_err(|_| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
4996 for record in resolved.value {
4997 let Some(record) = record else {
4998 handles.push(None);
4999 continue;
5000 };
5001 let bytes = reads
5002 .next()
5003 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), work))?;
5004 let metadata = decode_file_metadata(&bytes, decode_limits(self.volume.config))
5005 .map_err(|error| OperationFailure::new(error.into(), work))?;
5006 let logical_bytes = record_logical_bytes(record);
5007 handles.push(Some(ResolvedFile {
5008 reader: self.clone(),
5009 record,
5010 description: FileDescription {
5011 kind: record.kind,
5012 logical_bytes,
5013 metadata,
5014 },
5015 }));
5016 }
5017 Ok(FsReceipt {
5018 value: handles,
5019 work,
5020 })
5021 }
5022
5023 pub async fn read_resolved_ranges(
5026 &self,
5027 requests: &[ResolvedFileRangeReadRequest<'_, A, O>],
5028 concurrency: usize,
5029 budget: WorkBudget,
5030 cancellation: &CancellationToken,
5031 ) -> FsResult<Vec<FileRangeRead>> {
5032 if concurrency == 0
5033 || requests.len()
5034 > usize::try_from(self.volume.config.limits.maximum_paths_per_batch)
5035 .unwrap_or(usize::MAX)
5036 {
5037 return Err(OperationFailure::before_work(FsError::FileRead(
5038 FileRangeReadError::InvalidRange,
5039 )));
5040 }
5041 if budget != WorkBudget::UNBOUNDED {
5042 let mut work = WorkCounters::default();
5043 let mut reads = Vec::new();
5044 reads
5045 .try_reserve_exact(requests.len())
5046 .map_err(|_| OperationFailure::before_work(FsError::Work(WorkError::Overflow)))?;
5047 for request in requests {
5048 let read = request
5049 .file
5050 .read_range(request.range, remaining(work, budget)?, cancellation)
5051 .await
5052 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5053 work = add(work, read.work)?;
5054 reads.push(read.value);
5055 }
5056 return Ok(FsReceipt { value: reads, work });
5057 }
5058 let cancellation = cancellation.clone();
5059 let pending = requests
5060 .iter()
5061 .enumerate()
5062 .map(|(index, request)| {
5063 (
5064 index,
5065 request.file.reader.clone(),
5066 request.file.record,
5067 request.range,
5068 )
5069 })
5070 .collect::<Vec<_>>();
5071 let results = stream::iter(pending.into_iter().map(|(index, reader, record, range)| {
5072 let cancellation = cancellation.clone();
5073 async move {
5074 (
5075 index,
5076 reader
5077 .read_record_range(record, range, budget, &cancellation)
5078 .await,
5079 )
5080 }
5081 }))
5082 .buffer_unordered(concurrency.min(requests.len().max(1)))
5083 .collect::<Vec<_>>()
5084 .await;
5085 order_batch_results(results, budget)
5086 }
5087}
5088
5089fn record_logical_bytes(record: FileRecord) -> u64 {
5090 match record.payload {
5091 FilePayload::InlineRegular(bytes) => {
5092 u64::try_from(bytes.as_bytes().len()).unwrap_or(u64::MAX)
5093 }
5094 FilePayload::Regular { logical_bytes, .. } => logical_bytes,
5095 FilePayload::SymbolicLink { target_bytes, .. } => target_bytes,
5096 _ => 0,
5097 }
5098}
5099
5100async fn list_directory_page_pinned<A: AsyncAuthorityStore, O: AsyncObjectStore>(
5101 volume: &Volume<A, O>,
5102 root: &GenerationRoot,
5103 path: &NamespacePath,
5104 after: Option<&LogicalName>,
5105 maximum_entries: u32,
5106 budget: WorkBudget,
5107 cancellation: &CancellationToken,
5108) -> FsResult<DirectoryPage> {
5109 let lookup = crate::kernel::observe_path_edges_async(
5110 &volume.fs.inner.objects,
5111 root,
5112 path,
5113 volume.config,
5114 budget,
5115 cancellation,
5116 )
5117 .await
5118 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::Path))?;
5119 let mut work = lookup.lookup.work;
5120 let record = lookup
5121 .lookup
5122 .record
5123 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
5124 let FilePayload::Directory { entries } = record.payload else {
5125 return Err(OperationFailure::new(FsError::NotDirectory, work));
5126 };
5127 if record.kind != FileKind::Directory {
5128 return Err(OperationFailure::new(FsError::NotDirectory, work));
5129 }
5130 let mut page = list_tree_entries_async(
5131 &volume.fs.inner.objects,
5132 entries,
5133 after,
5134 maximum_entries,
5135 decode_limits(volume.config),
5136 remaining(work, budget)?,
5137 cancellation,
5138 )
5139 .await
5140 .map_err(|failure| failure.map_with_prior_work(work, FsError::Directory))?;
5141 work = add(work, page.work)?;
5142 page.work = work;
5143 Ok(FsReceipt { value: page, work })
5144}
5145
5146async fn read_symbolic_link_record<A: AsyncAuthorityStore, O: AsyncObjectStore>(
5147 volume: &Volume<A, O>,
5148 record: FileRecord,
5149 budget: WorkBudget,
5150 cancellation: &CancellationToken,
5151) -> FsResult<Bytes> {
5152 let FilePayload::SymbolicLink {
5153 target_bytes,
5154 target,
5155 } = record.payload
5156 else {
5157 return Err(OperationFailure::before_work(FsError::NotSymbolicLink));
5158 };
5159 if target_bytes > volume.config.limits.maximum_read_bytes {
5160 return Err(OperationFailure::before_work(FsError::FileRead(
5161 FileRangeReadError::InvalidRange,
5162 )));
5163 }
5164 if target_bytes == 0 {
5165 return Ok(FsReceipt {
5166 value: Bytes::new(),
5167 work: WorkCounters::default(),
5168 });
5169 }
5170 let read = read_blob_range_async(
5171 &volume.fs.inner.objects,
5172 target,
5173 ByteRange {
5174 offset: 0,
5175 length: target_bytes,
5176 },
5177 decode_limits(volume.config),
5178 budget,
5179 cancellation,
5180 )
5181 .await
5182 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::BlobRead))?;
5183 Ok(FsReceipt {
5184 value: read.bytes,
5185 work: read.work,
5186 })
5187}
5188
5189fn order_batch_results<T>(
5190 mut results: Vec<(usize, FsResult<T>)>,
5191 budget: WorkBudget,
5192) -> FsResult<Vec<T>> {
5193 results.sort_unstable_by_key(|(index, _)| *index);
5194 let mut work = WorkCounters::default();
5195 let mut completed = Vec::new();
5196 completed
5197 .try_reserve_exact(results.len())
5198 .map_err(|_| OperationFailure::before_work(FsError::Work(WorkError::Overflow)))?;
5199 let mut first_failure = None;
5200 for (index, result) in results {
5201 match result {
5202 Ok(receipt) => {
5203 work = add(work, receipt.work)?;
5204 completed.push((index, receipt.value));
5205 }
5206 Err(failure) => {
5207 work = add(work, *failure.work)?;
5208 if first_failure.is_none() {
5209 first_failure = Some(failure.error);
5210 }
5211 }
5212 }
5213 }
5214 work.verify(budget)
5215 .map_err(|error| OperationFailure::new(error.into(), work))?;
5216 if let Some(error) = first_failure {
5217 return Err(OperationFailure::new(error, work));
5218 }
5219 Ok(FsReceipt {
5220 value: completed.into_iter().map(|(_, value)| value).collect(),
5221 work,
5222 })
5223}
5224
5225impl<A: AsyncAuthorityStore, O: AsyncObjectStore> Checkout<A, O> {
5226 pub async fn prepare_merge(
5237 &mut self,
5238 theirs: GenerationId,
5239 maximum_changes: u32,
5240 maximum_conflicts: u32,
5241 budget: WorkBudget,
5242 cancellation: &CancellationToken,
5243 ) -> FsResult<MergePreparation> {
5244 if self.mode.access != AccessMode::ReadWrite
5245 || self.mode.mutations != MutationMode::PrivateOverlay
5246 {
5247 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
5248 }
5249 if self.prepared_merge_parent.is_some() {
5250 return Err(OperationFailure::before_work(FsError::PreparedMergePending));
5251 }
5252 if maximum_changes == 0 || maximum_conflicts == 0 {
5253 return Err(OperationFailure::before_work(FsError::MergeChangeLimit));
5254 }
5255 let theirs_id = ObjectId {
5256 kind: ObjectKind::GenerationRoot,
5257 digest: theirs.digest(),
5258 };
5259 let (current_head_root, current_head, mut work) = self
5260 .volume
5261 .resolve_head_generation(budget, cancellation)
5262 .await?;
5263 if current_head_root != theirs_id {
5264 return Err(OperationFailure::new(FsError::MergeParentNotHead, work));
5265 }
5266 let (theirs_root, theirs_work) = read_generation_root(
5267 &self.volume.fs.inner.objects,
5268 theirs_id,
5269 self.volume.config,
5270 remaining(work, budget)?,
5271 cancellation,
5272 )
5273 .await
5274 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5275 work = add(work, theirs_work)?;
5276 let merged = merge_generation_async(
5277 &self.volume.fs.inner.objects,
5278 MergeGenerationRequest {
5279 base_generation: self.base_generation_root,
5280 base: self.base_root.clone(),
5281 ours_generation: (self.root.file_table == self.base_file_table)
5282 .then_some(self.generation_root),
5283 ours: self.root.clone(),
5284 theirs_generation: theirs_id,
5285 theirs: theirs_root,
5286 retain_theirs_parent: true,
5287 maximum_changes,
5288 maximum_conflicts,
5289 resolutions: BTreeMap::new(),
5290 },
5291 decode_limits(self.volume.config),
5292 remaining(work, budget)?,
5293 cancellation,
5294 )
5295 .await
5296 .map_err(|failure| map_merge_failure(failure, work))?;
5297 work = add(work, merged.work)?;
5298 match merged.value {
5299 MergeGenerationOutcome::Prepared {
5300 generation_root,
5301 root,
5302 generation_id,
5303 } => {
5304 self.root = root;
5305 self.generation_root = generation_root;
5306 self.prepared_merge_parent = Some(theirs_id);
5307 self.authority_head = Some(current_head);
5308 Ok(OperationReceipt {
5309 value: MergePreparation::Prepared { generation_id },
5310 work,
5311 })
5312 }
5313 MergeGenerationOutcome::Conflicted {
5314 conflicts,
5315 truncated,
5316 } => Ok(OperationReceipt {
5317 value: MergePreparation::Conflicted {
5318 conflicts,
5319 truncated,
5320 },
5321 work,
5322 }),
5323 }
5324 }
5325
5326 pub async fn export_manifest(
5338 &self,
5339 budget: WorkBudget,
5340 cancellation: &CancellationToken,
5341 ) -> FsResult<GenerationExportManifest> {
5342 let checkpoint = self.checkpoint_root(budget, cancellation).await?;
5343 let generation_root = checkpoint.value;
5344 let mut work = checkpoint.work;
5345 let manifest = build_generation_export_manifest_async(
5346 &self.volume.fs.inner.objects,
5347 self.volume.id,
5348 self.volume.config,
5349 generation_root,
5350 closure_limits(self.volume.config),
5351 remaining(work, budget)?,
5352 cancellation,
5353 )
5354 .await
5355 .map_err(|failure| failure.map_with_prior_work(work, map_transfer_error))?;
5356 work = add(work, manifest.work)?;
5357 Ok(FsReceipt {
5358 value: manifest.value,
5359 work,
5360 })
5361 }
5362
5363 pub async fn checkpoint(
5375 &self,
5376 budget: WorkBudget,
5377 cancellation: &CancellationToken,
5378 ) -> FsResult<GenerationId> {
5379 let checkpoint = self.checkpoint_root(budget, cancellation).await?;
5380 Ok(FsReceipt {
5381 value: GenerationId::new(checkpoint.value.digest),
5382 work: checkpoint.work,
5383 })
5384 }
5385
5386 async fn checkpoint_root(
5387 &self,
5388 budget: WorkBudget,
5389 cancellation: &CancellationToken,
5390 ) -> FsResult<ObjectId> {
5391 if self.prepared_merge_parent.is_some() || !self.has_pending_mutations() {
5392 return Ok(FsReceipt {
5393 value: self.generation_root,
5394 work: WorkCounters::default(),
5395 });
5396 }
5397 let checkpoint = build_checkpoint_async(
5398 &self.volume.fs.inner.objects,
5399 CheckpointRequest {
5400 base: self.base_generation_root,
5401 file_table: self.root.file_table,
5402 merge_parent: None,
5403 },
5404 decode_limits(self.volume.config),
5405 budget,
5406 cancellation,
5407 )
5408 .await
5409 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
5410 Ok(FsReceipt {
5411 value: checkpoint.root,
5412 work: checkpoint.work,
5413 })
5414 }
5415
5416 pub async fn discard(
5423 &mut self,
5424 budget: WorkBudget,
5425 cancellation: &CancellationToken,
5426 ) -> FsResult<()> {
5427 if self.mode.mutations == MutationMode::None {
5428 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
5429 }
5430 let (root, work) = read_generation_root(
5431 &self.volume.fs.inner.objects,
5432 self.base_generation_root,
5433 self.volume.config,
5434 budget,
5435 cancellation,
5436 )
5437 .await?;
5438 if root.volume_id != self.volume.id {
5439 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
5440 }
5441 self.root = root;
5442 self.base_root = self.root.clone();
5443 self.base_file_table = self.root.file_table;
5444 self.generation_root = self.base_generation_root;
5445 self.live_operation_id = None;
5446 self.prepared_merge_parent = None;
5447 self.dependencies.clear();
5448 Ok(FsReceipt { value: (), work })
5449 }
5450
5451 pub async fn refresh_head(
5461 &mut self,
5462 budget: WorkBudget,
5463 cancellation: &CancellationToken,
5464 ) -> FsResult<GenerationId> {
5465 if !matches!(
5466 self.mode.consistency,
5467 ConsistencyMode::Manual | ConsistencyMode::TrackingSafe
5468 ) {
5469 return Err(OperationFailure::before_work(FsError::RefreshNotAllowed));
5470 }
5471 if self.has_pending_mutations() {
5472 return Err(OperationFailure::before_work(
5473 FsError::PendingMutationsRequireRebase,
5474 ));
5475 }
5476 let (generation_root, head, mut work) = self
5477 .volume
5478 .resolve_head_generation(budget, cancellation)
5479 .await?;
5480 if generation_root == self.generation_root {
5481 self.authority_head = Some(head);
5482 return Ok(FsReceipt {
5483 value: self.generation_id(),
5484 work,
5485 });
5486 }
5487 let (root, root_work) = read_generation_root(
5488 &self.volume.fs.inner.objects,
5489 generation_root,
5490 self.volume.config,
5491 remaining(work, budget)?,
5492 cancellation,
5493 )
5494 .await
5495 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5496 work = add(work, root_work)?;
5497 if root.volume_id != self.volume.id {
5498 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
5499 }
5500 self.base_generation_root = generation_root;
5501 self.generation_root = generation_root;
5502 self.root = root;
5503 self.base_root = self.root.clone();
5504 self.base_file_table = self.root.file_table;
5505 self.authority_head = Some(head);
5506 self.dependencies.clear();
5507 Ok(FsReceipt {
5508 value: self.generation_id(),
5509 work,
5510 })
5511 }
5512
5513 pub async fn rebase_head(
5525 &mut self,
5526 maximum_conflicts: u32,
5527 budget: WorkBudget,
5528 cancellation: &CancellationToken,
5529 ) -> FsResult<RebaseDecision> {
5530 if self.live_operation_id.is_some() {
5531 return Err(OperationFailure::before_work(FsError::PendingLiveMutation));
5532 }
5533 self.rebase_head_for_live_retry(maximum_conflicts, budget, cancellation)
5534 .await
5535 }
5536
5537 async fn rebase_head_for_live_retry(
5538 &mut self,
5539 maximum_conflicts: u32,
5540 budget: WorkBudget,
5541 cancellation: &CancellationToken,
5542 ) -> FsResult<RebaseDecision> {
5543 if self.prepared_merge_parent.is_some() {
5544 return Err(OperationFailure::before_work(FsError::PreparedMergePending));
5545 }
5546 if !matches!(
5547 self.mode.consistency,
5548 ConsistencyMode::TrackingSafe | ConsistencyMode::Manual | ConsistencyMode::Live
5549 ) {
5550 return Err(OperationFailure::before_work(FsError::RefreshNotAllowed));
5551 }
5552 let (candidate_object, candidate_head, mut work) = self
5553 .volume
5554 .resolve_head_generation(budget, cancellation)
5555 .await?;
5556 let base = GenerationId::new(self.base_generation_root.digest);
5557 let candidate = GenerationId::new(candidate_object.digest);
5558 let probe = AuthenticatedGenerationProbe::new(
5559 &self.volume.fs.inner.objects,
5560 ProbeLimits {
5561 decode: decode_limits(self.volume.config),
5562 maximum_cached_generations: 2,
5563 maximum_cached_records: self.volume.config.limits.maximum_checkout_dependencies,
5564 maximum_extent_spans: self.volume.config.limits.maximum_directory_page_entries,
5565 maximum_content_payload_bytes: self.volume.config.limits.maximum_read_bytes,
5566 maximum_directory_entries: self.volume.config.limits.maximum_directory_page_entries,
5567 },
5568 )
5569 .map_err(|error| OperationFailure::new(FsError::Rebase(RebaseError::Probe(error)), work))?;
5570 let classification = classify_rebase_async(
5571 &probe,
5572 base,
5573 candidate,
5574 &self.dependencies,
5575 maximum_conflicts,
5576 remaining(work, budget)?,
5577 cancellation,
5578 )
5579 .await
5580 .map_err(|failure| failure.map_with_prior_work(work, FsError::Rebase))?;
5581 work = add(work, classification.work)?;
5582 if matches!(classification.decision, RebaseDecision::Conflicted { .. }) {
5583 return Ok(FsReceipt {
5584 value: classification.decision,
5585 work,
5586 });
5587 }
5588 if candidate_object == self.base_generation_root {
5589 self.authority_head = Some(candidate_head);
5590 return Ok(FsReceipt {
5591 value: classification.decision,
5592 work,
5593 });
5594 }
5595 let (candidate_root, root_work) = read_generation_root(
5596 &self.volume.fs.inner.objects,
5597 candidate_object,
5598 self.volume.config,
5599 remaining(work, budget)?,
5600 cancellation,
5601 )
5602 .await
5603 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5604 work = add(work, root_work)?;
5605 if candidate_root.volume_id != self.volume.id {
5606 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
5607 }
5608 let candidate_file_table = candidate_root.file_table;
5609 let candidate_base_root = candidate_root.clone();
5610 let rebased_root = self
5611 .rebase_private_candidate(
5612 candidate_object,
5613 &candidate_root,
5614 maximum_conflicts,
5615 &mut work,
5616 budget,
5617 cancellation,
5618 )
5619 .await?;
5620 self.base_generation_root = candidate_object;
5621 self.base_file_table = candidate_file_table;
5622 self.base_root = candidate_base_root;
5623 self.generation_root = candidate_object;
5624 self.root = rebased_root;
5625 self.authority_head = Some(candidate_head);
5626 Ok(FsReceipt {
5627 value: classification.decision,
5628 work,
5629 })
5630 }
5631
5632 async fn rebase_private_candidate(
5633 &self,
5634 candidate_object: ObjectId,
5635 candidate_root: &GenerationRoot,
5636 maximum_conflicts: u32,
5637 work: &mut WorkCounters,
5638 budget: WorkBudget,
5639 cancellation: &CancellationToken,
5640 ) -> Result<GenerationRoot, OperationFailure<FsError>> {
5641 if self.root.file_table == self.base_file_table {
5642 return Ok(candidate_root.clone());
5643 }
5644 let checkpoint = self
5645 .checkpoint_root(remaining(*work, budget)?, cancellation)
5646 .await
5647 .map_err(|failure| failure.map_with_prior_work(*work, std::convert::identity))?;
5648 *work = add(*work, checkpoint.work)?;
5649 let merged = merge_generation_async(
5650 &self.volume.fs.inner.objects,
5651 MergeGenerationRequest {
5652 base_generation: self.base_generation_root,
5653 base: self.base_root.clone(),
5654 ours_generation: Some(candidate_object),
5655 ours: candidate_root.clone(),
5656 theirs_generation: checkpoint.value,
5657 theirs: self.root.clone(),
5658 retain_theirs_parent: false,
5659 maximum_changes: u32::MAX,
5660 maximum_conflicts,
5661 resolutions: BTreeMap::new(),
5662 },
5663 decode_limits(self.volume.config),
5664 remaining(*work, budget)?,
5665 cancellation,
5666 )
5667 .await
5668 .map_err(|failure| map_merge_failure(failure, *work))?;
5669 *work = add(*work, merged.work)?;
5670 match merged.value {
5671 MergeGenerationOutcome::Prepared { root, .. } => Ok(root),
5672 MergeGenerationOutcome::Conflicted { .. } => {
5673 Err(OperationFailure::new(FsError::InvalidDiff, *work))
5674 }
5675 }
5676 }
5677
5678 pub async fn refresh_live(
5686 &mut self,
5687 budget: WorkBudget,
5688 cancellation: &CancellationToken,
5689 ) -> FsResult<GenerationId> {
5690 if self.mode.consistency != ConsistencyMode::Live {
5691 return Err(OperationFailure::before_work(FsError::RefreshNotAllowed));
5692 }
5693 let synchronized = self.synchronize_live(budget, cancellation).await?;
5694 Ok(FsReceipt {
5695 value: self.generation_id(),
5696 work: synchronized.work,
5697 })
5698 }
5699
5700 async fn synchronize_live(
5701 &mut self,
5702 budget: WorkBudget,
5703 cancellation: &CancellationToken,
5704 ) -> FsResult<()> {
5705 if self.mode.consistency != ConsistencyMode::Live {
5706 return Ok(FsReceipt {
5707 value: (),
5708 work: WorkCounters::default(),
5709 });
5710 }
5711 let rebase = self
5712 .rebase_head(
5713 self.volume.config.limits.maximum_checkout_dependencies,
5714 budget,
5715 cancellation,
5716 )
5717 .await?;
5718 match rebase.value {
5719 RebaseDecision::Safe { .. } => Ok(FsReceipt {
5720 value: (),
5721 work: rebase.work,
5722 }),
5723 RebaseDecision::Conflicted {
5724 conflicts,
5725 truncated,
5726 } => Err(OperationFailure::new(
5727 FsError::LiveConflict {
5728 conflicts,
5729 truncated,
5730 },
5731 rebase.work,
5732 )),
5733 }
5734 }
5735
5736 async fn capture_mutation_dependencies(
5737 &self,
5738 operations: &[Mutation],
5739 budget: WorkBudget,
5740 cancellation: &CancellationToken,
5741 ) -> FsResult<Vec<Dependency>> {
5742 let mut work = WorkCounters::default();
5743 let mut dependencies = Vec::new();
5744 let mut exact_regions = Vec::new();
5745 exact_regions
5746 .try_reserve_exact(operations.len().saturating_mul(4))
5747 .map_err(|_| OperationFailure::before_work(FsError::PendingMutationAllocationFailed))?;
5748 for operation in operations {
5749 let Some([first_path, second_path]) = operation.paths() else {
5750 let records = self
5751 .identity_base_records(operation, remaining(work, budget)?, cancellation)
5752 .await
5753 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5754 work = add(work, records.work)?;
5755 let (first_record, second_record) = records.value;
5756 for known_region in exact_mutation_regions(operation, first_record, second_record)
5757 .into_iter()
5758 .flatten()
5759 {
5760 exact_regions.push(known_region);
5761 }
5762 continue;
5763 };
5764 let first = crate::kernel::observe_path_edges_async(
5765 &self.volume.fs.inner.objects,
5766 &self.base_root,
5767 first_path,
5768 self.volume.config,
5769 remaining(work, budget)?,
5770 cancellation,
5771 )
5772 .await
5773 .map_err(|failure| failure.map_with_prior_work(work, FsError::Path))?;
5774 work = add(work, first.lookup.work)?;
5775 dependencies.extend(first.dependencies);
5776 let second_record = if second_path == first_path {
5777 first.lookup.record
5778 } else {
5779 let second = crate::kernel::observe_path_edges_async(
5780 &self.volume.fs.inner.objects,
5781 &self.base_root,
5782 second_path,
5783 self.volume.config,
5784 remaining(work, budget)?,
5785 cancellation,
5786 )
5787 .await
5788 .map_err(|failure| failure.map_with_prior_work(work, FsError::Path))?;
5789 work = add(work, second.lookup.work)?;
5790 dependencies.extend(second.dependencies);
5791 second.lookup.record
5792 };
5793 for known_region in
5794 exact_mutation_regions(operation, first.lookup.record, second_record)
5795 .into_iter()
5796 .flatten()
5797 {
5798 exact_regions.push(known_region);
5799 }
5800 }
5801 let captured = capture_known_dependencies_async(
5802 &self.volume.fs.inner.objects,
5803 self.volume.config,
5804 exact_regions,
5805 remaining(work, budget)?,
5806 cancellation,
5807 )
5808 .await
5809 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5810 work = add(work, captured.work)?;
5811 dependencies.extend(captured.value);
5812 Ok(FsReceipt {
5813 value: dependencies,
5814 work,
5815 })
5816 }
5817
5818 async fn identity_base_records(
5819 &self,
5820 operation: &Mutation,
5821 budget: WorkBudget,
5822 cancellation: &CancellationToken,
5823 ) -> FsResult<(Option<FileRecord>, Option<FileRecord>)> {
5824 let (first_id, second_id) = match operation {
5825 Mutation::File { file_id, .. } => (Some(*file_id), None),
5826 Mutation::CloneFileRange {
5827 source_file_id,
5828 destination_file_id,
5829 ..
5830 } => (Some(*source_file_id), Some(*destination_file_id)),
5831 _ => (None, None),
5832 };
5833 let mut work = WorkCounters::default();
5834 let first_record = if let Some(file_id) = first_id {
5835 let lookup = lookup_file_record_async(
5836 &self.volume.fs.inner.objects,
5837 self.base_root.file_table,
5838 file_id,
5839 decode_limits(self.volume.config),
5840 budget,
5841 cancellation,
5842 )
5843 .await
5844 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
5845 work = add(work, lookup.work)?;
5846 lookup.record
5847 } else {
5848 None
5849 };
5850 let second_record = if second_id == first_id {
5851 first_record
5852 } else if let Some(file_id) = second_id {
5853 let lookup = lookup_file_record_async(
5854 &self.volume.fs.inner.objects,
5855 self.base_root.file_table,
5856 file_id,
5857 decode_limits(self.volume.config),
5858 remaining(work, budget)?,
5859 cancellation,
5860 )
5861 .await
5862 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
5863 work = add(work, lookup.work)?;
5864 lookup.record
5865 } else {
5866 None
5867 };
5868 Ok(FsReceipt {
5869 value: (first_record, second_record),
5870 work,
5871 })
5872 }
5873
5874 pub async fn mutate(
5884 &mut self,
5885 operations: Vec<Mutation>,
5886 budget: WorkBudget,
5887 cancellation: &CancellationToken,
5888 ) -> FsResult<()> {
5889 if self.mode.access != AccessMode::ReadWrite
5890 || !matches!(
5891 self.mode.mutations,
5892 MutationMode::PrivateOverlay | MutationMode::DirectLive
5893 )
5894 {
5895 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
5896 }
5897 self.mutate_candidate(operations, budget, cancellation)
5898 .await
5899 }
5900
5901 pub async fn mutate_live(
5915 &mut self,
5916 operations: Vec<Mutation>,
5917 operation_id: OperationId,
5918 maximum_attempts: u32,
5919 maximum_conflicts: u32,
5920 budget: WorkBudget,
5921 cancellation: &CancellationToken,
5922 ) -> FsResult<LiveMutationOutcome> {
5923 self.validate_live_operation(maximum_attempts)?;
5924 if self.has_pending_mutations() {
5925 return Err(OperationFailure::before_work(FsError::PendingLiveMutation));
5926 }
5927 let mutation = self
5928 .mutate_candidate(operations, budget, cancellation)
5929 .await?;
5930 let mut work = mutation.work;
5931 let publication = self
5932 .resume_live(
5933 operation_id,
5934 maximum_attempts,
5935 maximum_conflicts,
5936 remaining(work, budget)?,
5937 cancellation,
5938 )
5939 .await
5940 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5941 work = add(work, publication.work)?;
5942 Ok(FsReceipt {
5943 value: publication.value,
5944 work,
5945 })
5946 }
5947
5948 pub async fn apply_authored_live(
5961 &mut self,
5962 authored: Vec<AuthoredMutation>,
5963 operation_id: OperationId,
5964 maximum_attempts: u32,
5965 maximum_conflicts: u32,
5966 budget: WorkBudget,
5967 cancellation: &CancellationToken,
5968 ) -> FsResult<AuthoredLiveMutationResult> {
5969 self.validate_live_operation(maximum_attempts)?;
5970 if self.has_pending_mutations() {
5971 return Err(OperationFailure::before_work(FsError::PendingLiveMutation));
5972 }
5973 let transaction = self
5974 .apply_authored_transaction(authored, budget, cancellation)
5975 .await?;
5976 let mut work = transaction.work;
5977 let created_file_ids = transaction.value.created_file_ids;
5978 let publication = self
5979 .resume_live(
5980 operation_id,
5981 maximum_attempts,
5982 maximum_conflicts,
5983 remaining(work, budget)?,
5984 cancellation,
5985 )
5986 .await
5987 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
5988 work = add(work, publication.work)?;
5989 Ok(FsReceipt {
5990 value: AuthoredLiveMutationResult {
5991 created_file_ids,
5992 outcome: publication.value,
5993 },
5994 work,
5995 })
5996 }
5997
5998 pub async fn resume_live(
6009 &mut self,
6010 operation_id: OperationId,
6011 maximum_attempts: u32,
6012 maximum_conflicts: u32,
6013 budget: WorkBudget,
6014 cancellation: &CancellationToken,
6015 ) -> FsResult<LiveMutationOutcome> {
6016 self.validate_live_operation(maximum_attempts)?;
6017 if !self.has_pending_mutations() {
6018 return Err(OperationFailure::before_work(FsError::NoPendingMutations));
6019 }
6020 match self.live_operation_id {
6021 Some(retained) if retained != operation_id => {
6022 return Err(OperationFailure::before_work(FsError::PendingLiveMutation));
6023 }
6024 Some(_) => {}
6025 None => self.live_operation_id = Some(operation_id),
6026 }
6027 let publication = self
6028 .publish_live(
6029 operation_id,
6030 maximum_attempts,
6031 maximum_conflicts,
6032 budget,
6033 cancellation,
6034 )
6035 .await?;
6036 if matches!(
6037 publication.value,
6038 LiveMutationOutcome::Committed { .. }
6039 | LiveMutationOutcome::AlreadyCommitted { .. }
6040 | LiveMutationOutcome::IdempotencyConflict { .. }
6041 ) {
6042 self.live_operation_id = None;
6043 }
6044 Ok(publication)
6045 }
6046
6047 fn validate_live_operation(
6048 &self,
6049 maximum_attempts: u32,
6050 ) -> Result<(), OperationFailure<FsError>> {
6051 if self.mode.access != AccessMode::ReadWrite
6052 || self.mode.consistency != ConsistencyMode::Live
6053 || self.mode.mutations != MutationMode::DirectLive
6054 {
6055 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
6056 }
6057 if maximum_attempts == 0 {
6058 return Err(OperationFailure::before_work(FsError::ZeroLiveRetryLimit));
6059 }
6060 Ok(())
6061 }
6062
6063 async fn publish_live(
6064 &mut self,
6065 operation_id: OperationId,
6066 maximum_attempts: u32,
6067 maximum_conflicts: u32,
6068 budget: WorkBudget,
6069 cancellation: &CancellationToken,
6070 ) -> FsResult<LiveMutationOutcome> {
6071 let mut work = WorkCounters::default();
6072 let mut state = LiveRetryState::new(maximum_attempts)
6073 .map_err(|error| OperationFailure::before_work(error.into()))?;
6074 loop {
6075 let publication = self
6076 .publish_pending(operation_id, None, remaining(work, budget)?, cancellation)
6077 .await
6078 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6079 work = add(work, publication.work)?;
6080 let action = state
6081 .observe_publication(live_publication_observation(publication.value))
6082 .map_err(|error| OperationFailure::new(error.into(), work))?;
6083 match action {
6084 LiveRetryAction::Complete(outcome) => {
6085 return Ok(FsReceipt {
6086 value: outcome,
6087 work,
6088 });
6089 }
6090 LiveRetryAction::Publish => {
6091 return Err(OperationFailure::new(
6092 FsError::LiveRetry(LiveRetryError::InvalidTransition),
6093 work,
6094 ));
6095 }
6096 LiveRetryAction::Rebase => {
6097 let rebase = self
6098 .rebase_head_for_live_retry(
6099 maximum_conflicts,
6100 remaining(work, budget)?,
6101 cancellation,
6102 )
6103 .await
6104 .map_err(|failure| {
6105 failure.map_with_prior_work(work, std::convert::identity)
6106 })?;
6107 work = add(work, rebase.work)?;
6108 match state
6109 .observe_rebase(rebase.value)
6110 .map_err(|error| OperationFailure::new(error.into(), work))?
6111 {
6112 LiveRetryAction::Publish => {}
6113 LiveRetryAction::Complete(outcome) => {
6114 return Ok(FsReceipt {
6115 value: outcome,
6116 work,
6117 });
6118 }
6119 LiveRetryAction::Rebase => {
6120 return Err(OperationFailure::new(
6121 FsError::LiveRetry(LiveRetryError::InvalidTransition),
6122 work,
6123 ));
6124 }
6125 }
6126 }
6127 }
6128 }
6129 }
6130
6131 async fn mutate_candidate(
6132 &mut self,
6133 mut operations: Vec<Mutation>,
6134 budget: WorkBudget,
6135 cancellation: &CancellationToken,
6136 ) -> FsResult<()> {
6137 if self.live_operation_id.is_some() {
6138 return Err(OperationFailure::before_work(FsError::PendingLiveMutation));
6139 }
6140 if self.prepared_merge_parent.is_some() {
6141 return Err(OperationFailure::before_work(FsError::PreparedMergePending));
6142 }
6143 let maximum = usize::try_from(self.volume.config.limits.maximum_mutations_per_batch)
6144 .unwrap_or(usize::MAX);
6145 if operations.capacity() > maximum {
6146 return Err(OperationFailure::before_work(
6147 FsError::ExcessiveMutationCapacity,
6148 ));
6149 }
6150 let admitted =
6154 Box::pin(self.admit_mutation_names(&mut operations, budget, cancellation)).await?;
6155 let captured =
6156 Box::pin(self.capture_mutation_dependencies(&operations, budget, cancellation)).await?;
6157 let dependency_work = add(admitted.work, captured.work)?;
6158 let mutation_dependencies = captured.value;
6159 let mut next_dependencies = self.dependencies.clone();
6160 next_dependencies
6161 .extend_mutations(
6162 mutation_dependencies,
6163 self.volume.config.limits.maximum_checkout_dependencies,
6164 )
6165 .map_err(|error| OperationFailure::new(error.into(), dependency_work))?;
6166 let prior_file_table = self.root.file_table;
6167 let (receipt, _) = Box::pin(apply_generation_mutations_retaining_async(
6168 &self.volume.fs.inner.objects,
6169 &self.root,
6170 operations,
6171 self.volume.config,
6172 remaining(dependency_work, budget)?,
6173 cancellation,
6174 ))
6175 .await
6176 .map_err(|failure| failure.map_with_prior_work(dependency_work, FsError::Mutation))?;
6177 let work = add(dependency_work, receipt.work)?;
6178 if receipt.root.file_table != prior_file_table {
6179 if receipt.root.file_table == self.base_file_table {
6180 next_dependencies.clear_mutations();
6181 }
6182 self.dependencies = next_dependencies;
6183 self.root = receipt.root;
6184 }
6185 Ok(FsReceipt { value: (), work })
6186 }
6187
6188 #[allow(clippy::too_many_lines)]
6194 async fn admit_mutation_names(
6195 &mut self,
6196 operations: &mut [Mutation],
6197 budget: WorkBudget,
6198 cancellation: &CancellationToken,
6199 ) -> FsResult<()> {
6200 if self.volume.config.case_sensitivity != CaseSensitivity::ProfileFolded
6201 && self.volume.config.unicode != UnicodePolicy::RequireNfc
6202 {
6203 return Ok(FsReceipt {
6204 value: (),
6205 work: WorkCounters::default(),
6206 });
6207 }
6208
6209 let mut work = WorkCounters::default();
6210 let mut pending: Vec<(NamespacePath, FileId)> = Vec::new();
6211 let mut removed: Vec<(NamespacePath, FileId)> = Vec::new();
6212 pending
6213 .try_reserve_exact(operations.len())
6214 .map_err(|_| OperationFailure::before_work(FsError::PendingMutationAllocationFailed))?;
6215 removed
6216 .try_reserve_exact(operations.len())
6217 .map_err(|_| OperationFailure::before_work(FsError::PendingMutationAllocationFailed))?;
6218 for operation in operations.iter_mut() {
6219 cancellation
6220 .check()
6221 .map_err(|error| OperationFailure::new(error.into(), work))?;
6222 let canonicalized = Box::pin(self.canonicalize_existing_mutation(
6223 operation,
6224 &pending,
6225 remaining(work, budget)?,
6226 cancellation,
6227 ))
6228 .await
6229 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6230 work = add(work, canonicalized.work)?;
6231 let parent_canonicalized = Box::pin(self.canonicalize_new_mutation_parents(
6232 operation,
6233 &pending,
6234 remaining(work, budget)?,
6235 cancellation,
6236 ))
6237 .await
6238 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6239 work = add(work, parent_canonicalized.work)?;
6240 match operation {
6241 Mutation::Create { path, record } | Mutation::Restore { path, record } => {
6242 let allowed = removed_binding_id(path, &removed);
6243 let allowed_ids: &[FileId] = match allowed {
6244 Some(ref file_id) => std::slice::from_ref(file_id),
6245 None => &[],
6246 };
6247 let parent_pending = pending_parent_exists(
6248 path,
6249 &pending,
6250 self.volume.config.case_sensitivity,
6251 self.volume.config.limits,
6252 );
6253 let admission = Box::pin(self.admit_new_name(
6254 path,
6255 remaining(work, budget)?,
6256 cancellation,
6257 allowed_ids,
6258 parent_pending,
6259 ))
6260 .await
6261 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6262 work = add(work, admission.work)?;
6263 if self.volume.config.case_sensitivity == CaseSensitivity::ProfileFolded
6264 && pending_name_collision(path, &pending, allowed_ids)
6265 {
6266 return Err(OperationFailure::new(FsError::NameCollision, work));
6267 }
6268 pending.push((path.clone(), record.file_id));
6269 }
6270 Mutation::Link {
6271 source,
6272 destination,
6273 } => {
6274 let source_id = self
6275 .pending_or_existing_file_id(
6276 source,
6277 &pending,
6278 remaining(work, budget)?,
6279 cancellation,
6280 )
6281 .await
6282 .map_err(|failure| {
6283 failure.map_with_prior_work(work, std::convert::identity)
6284 })?;
6285 work = add(work, source_id.work)?;
6286 let allowed = removed_binding_id(destination, &removed);
6287 let allowed_ids: &[FileId] = match allowed {
6288 Some(ref file_id) => std::slice::from_ref(file_id),
6289 None => &[],
6290 };
6291 let parent_pending = pending_parent_exists(
6292 destination,
6293 &pending,
6294 self.volume.config.case_sensitivity,
6295 self.volume.config.limits,
6296 );
6297 let admission = Box::pin(self.admit_new_name(
6298 destination,
6299 remaining(work, budget)?,
6300 cancellation,
6301 allowed_ids,
6302 parent_pending,
6303 ))
6304 .await
6305 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6306 work = add(work, admission.work)?;
6307 if self.volume.config.case_sensitivity == CaseSensitivity::ProfileFolded
6308 && pending_name_collision(destination, &pending, allowed_ids)
6309 {
6310 return Err(OperationFailure::new(FsError::NameCollision, work));
6311 }
6312 pending.push((destination.clone(), source_id.value));
6313 }
6314 Mutation::Rename {
6315 source,
6316 destination,
6317 replace,
6318 } => {
6319 let source_id = self
6320 .pending_or_existing_file_id(
6321 source,
6322 &pending,
6323 remaining(work, budget)?,
6324 cancellation,
6325 )
6326 .await
6327 .map_err(|failure| {
6328 failure.map_with_prior_work(work, std::convert::identity)
6329 })?;
6330 work = add(work, source_id.work)?;
6331 let mut allowed_ids = Vec::with_capacity(2);
6332 allowed_ids.push(source_id.value);
6333 if let Some(file_id) = removed_binding_id(destination, &removed)
6334 && file_id != source_id.value
6335 {
6336 allowed_ids.push(file_id);
6337 }
6338 let parent_pending = pending_parent_exists(
6339 destination,
6340 &pending,
6341 self.volume.config.case_sensitivity,
6342 self.volume.config.limits,
6343 );
6344 let admission = Box::pin(self.admit_new_name(
6345 destination,
6346 remaining(work, budget)?,
6347 cancellation,
6348 &allowed_ids,
6349 parent_pending,
6350 ))
6351 .await
6352 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6353 work = add(work, admission.work)?;
6354 if self.volume.config.case_sensitivity == CaseSensitivity::ProfileFolded
6355 && pending_name_collision(destination, &pending, &allowed_ids)
6356 {
6357 return Err(OperationFailure::new(FsError::NameCollision, work));
6358 }
6359 pending.retain(|(path, _)| path != source);
6360 if *replace {
6361 pending.retain(|(path, _)| path != destination);
6362 }
6363 removed.push((source.clone(), source_id.value));
6364 pending.push((destination.clone(), source_id.value));
6365 }
6366 Mutation::Remove { path, .. } => {
6367 if pending_binding_id(path, &pending, self.volume.config.case_sensitivity)
6368 .is_none()
6369 {
6370 let removed_id = self
6371 .pending_or_existing_file_id(
6372 path,
6373 &pending,
6374 remaining(work, budget)?,
6375 cancellation,
6376 )
6377 .await
6378 .map_err(|failure| {
6379 failure.map_with_prior_work(work, std::convert::identity)
6380 })?;
6381 work = add(work, removed_id.work)?;
6382 removed.push((path.clone(), removed_id.value));
6383 }
6384 pending.retain(|(pending_path, _)| pending_path != path);
6385 }
6386 Mutation::SetMetadata { .. }
6387 | Mutation::Write { .. }
6388 | Mutation::ValidateRegular { .. }
6389 | Mutation::Resize { .. }
6390 | Mutation::ZeroRange { .. }
6391 | Mutation::Preallocate { .. }
6392 | Mutation::CloneRange { .. }
6393 | Mutation::File { .. }
6394 | Mutation::CloneFileRange { .. } => {}
6395 }
6396 }
6397 Ok(FsReceipt { value: (), work })
6398 }
6399
6400 async fn canonicalize_new_mutation_parents(
6401 &mut self,
6402 operation: &mut Mutation,
6403 pending: &[(NamespacePath, FileId)],
6404 budget: WorkBudget,
6405 cancellation: &CancellationToken,
6406 ) -> FsResult<()> {
6407 if self.volume.config.case_sensitivity != CaseSensitivity::ProfileFolded {
6408 return Ok(FsReceipt {
6409 value: (),
6410 work: WorkCounters::default(),
6411 });
6412 }
6413 let (Mutation::Create { path, .. }
6414 | Mutation::Link {
6415 destination: path, ..
6416 }
6417 | Mutation::Rename {
6418 destination: path, ..
6419 }) = operation
6420 else {
6421 return Ok(FsReceipt {
6422 value: (),
6423 work: WorkCounters::default(),
6424 });
6425 };
6426 let Some((parent, name)) = path.split_last() else {
6427 return Ok(FsReceipt {
6428 value: (),
6429 work: WorkCounters::default(),
6430 });
6431 };
6432 if parent.is_empty() {
6433 return Ok(FsReceipt {
6434 value: (),
6435 work: WorkCounters::default(),
6436 });
6437 }
6438 let resolved = self
6439 .canonicalize_path_components(parent, pending, budget, cancellation)
6440 .await?;
6441 let work = resolved.work;
6442 let Some(resolved_parent) = resolved.value else {
6443 return Ok(FsReceipt { value: (), work });
6444 };
6445 let mut components = resolved_parent.components().to_vec();
6446 components.push(name.clone());
6447 *path = NamespacePath::new(components, self.volume.config.limits).map_err(|error| {
6448 OperationFailure::new(FsError::Path(PathLookupError::NamespacePath(error)), work)
6449 })?;
6450 Ok(FsReceipt { value: (), work })
6451 }
6452
6453 async fn canonicalize_existing_mutation(
6454 &mut self,
6455 operation: &mut Mutation,
6456 pending: &[(NamespacePath, FileId)],
6457 budget: WorkBudget,
6458 cancellation: &CancellationToken,
6459 ) -> FsResult<()> {
6460 if self.volume.config.case_sensitivity != CaseSensitivity::ProfileFolded {
6461 return Ok(FsReceipt {
6462 value: (),
6463 work: WorkCounters::default(),
6464 });
6465 }
6466 let mut work = WorkCounters::default();
6467 let paths = match operation {
6468 Mutation::Remove { path, .. }
6469 | Mutation::SetMetadata { path, .. }
6470 | Mutation::Write { path, .. }
6471 | Mutation::ValidateRegular { path }
6472 | Mutation::Resize { path, .. }
6473 | Mutation::ZeroRange { path, .. }
6474 | Mutation::Preallocate { path, .. } => {
6475 let resolved = self
6476 .canonical_existing_path(path, pending, remaining(work, budget)?, cancellation)
6477 .await?;
6478 *path = resolved.value;
6479 work = add(work, resolved.work)?;
6480 return Ok(FsReceipt { value: (), work });
6481 }
6482 Mutation::Rename { source, .. } | Mutation::Link { source, .. } => {
6483 let resolved = self
6484 .canonical_existing_path(
6485 source,
6486 pending,
6487 remaining(work, budget)?,
6488 cancellation,
6489 )
6490 .await?;
6491 *source = resolved.value;
6492 work = add(work, resolved.work)?;
6493 return Ok(FsReceipt { value: (), work });
6494 }
6495 Mutation::CloneRange {
6496 source,
6497 destination,
6498 ..
6499 } => Some((source, destination)),
6500 Mutation::Create { .. }
6501 | Mutation::Restore { .. }
6502 | Mutation::File { .. }
6503 | Mutation::CloneFileRange { .. } => None,
6504 };
6505 if let Some((source, destination)) = paths {
6506 let resolved_source = self
6507 .canonical_existing_path(source, pending, remaining(work, budget)?, cancellation)
6508 .await?;
6509 *source = resolved_source.value;
6510 work = add(work, resolved_source.work)?;
6511 let resolved_destination = self
6512 .canonical_existing_path(
6513 destination,
6514 pending,
6515 remaining(work, budget)?,
6516 cancellation,
6517 )
6518 .await?;
6519 *destination = resolved_destination.value;
6520 work = add(work, resolved_destination.work)?;
6521 }
6522 Ok(FsReceipt { value: (), work })
6523 }
6524
6525 async fn canonical_existing_path(
6526 &mut self,
6527 path: &NamespacePath,
6528 pending: &[(NamespacePath, FileId)],
6529 budget: WorkBudget,
6530 cancellation: &CancellationToken,
6531 ) -> FsResult<NamespacePath> {
6532 if let Some((exact, _)) =
6533 pending_binding_path(path, pending, self.volume.config.case_sensitivity)
6534 {
6535 return Ok(FsReceipt {
6536 value: exact,
6537 work: WorkCounters::default(),
6538 });
6539 }
6540 let mut work = WorkCounters::default();
6541 let exact = self
6542 .lookup_no_follow_at_current(path, budget, cancellation, false)
6543 .await?;
6544 work = add(work, exact.work)?;
6545 if exact.value.record.is_some()
6546 || self.volume.config.case_sensitivity != CaseSensitivity::ProfileFolded
6547 {
6548 if exact.value.record.is_some() {
6549 return Ok(FsReceipt {
6550 value: path.clone(),
6551 work,
6552 });
6553 }
6554 return Err(OperationFailure::new(FsError::NotFound, work));
6555 }
6556
6557 if path.is_root() {
6558 return Err(OperationFailure::new(FsError::NotFound, work));
6559 }
6560 let resolved = self
6561 .canonicalize_path_components(
6562 path.components(),
6563 pending,
6564 remaining(work, budget)?,
6565 cancellation,
6566 )
6567 .await?;
6568 work = add(work, resolved.work)?;
6569 let canonical = resolved
6570 .value
6571 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
6572 Ok(FsReceipt {
6573 value: canonical,
6574 work,
6575 })
6576 }
6577
6578 async fn canonicalize_path_components(
6579 &mut self,
6580 requested_components: &[LogicalName],
6581 pending: &[(NamespacePath, FileId)],
6582 budget: WorkBudget,
6583 cancellation: &CancellationToken,
6584 ) -> FsResult<Option<NamespacePath>> {
6585 let mut work = WorkCounters::default();
6586 let mut canonical_components = Vec::new();
6587 for requested_component in requested_components {
6588 let candidate = NamespacePath::new(
6589 canonical_components
6590 .iter()
6591 .cloned()
6592 .chain(std::iter::once(requested_component.clone()))
6593 .collect(),
6594 self.volume.config.limits,
6595 )
6596 .map_err(|error| {
6597 OperationFailure::new(FsError::Path(PathLookupError::NamespacePath(error)), work)
6598 })?;
6599 if let Some((pending_path, _)) =
6600 pending_binding_path(&candidate, pending, self.volume.config.case_sensitivity)
6601 {
6602 canonical_components.push(
6603 pending_path
6604 .components()
6605 .last()
6606 .cloned()
6607 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?,
6608 );
6609 continue;
6610 }
6611 let probe = self
6612 .lookup_no_follow_at_current(
6613 &candidate,
6614 remaining(work, budget)?,
6615 cancellation,
6616 false,
6617 )
6618 .await?;
6619 work = add(work, probe.work)?;
6620 if probe.value.record.is_some() {
6621 canonical_components.push(requested_component.clone());
6622 continue;
6623 }
6624 let parent =
6625 NamespacePath::new(canonical_components.clone(), self.volume.config.limits)
6626 .map_err(|error| {
6627 OperationFailure::new(
6628 FsError::Path(PathLookupError::NamespacePath(error)),
6629 work,
6630 )
6631 })?;
6632 let siblings = self
6633 .find_case_folded_sibling(
6634 parent.components(),
6635 requested_component,
6636 remaining(work, budget)?,
6637 cancellation,
6638 &[],
6639 )
6640 .await?;
6641 work = add(work, siblings.work)?;
6642 let Some(exact_name) = siblings.value else {
6643 return Ok(FsReceipt { value: None, work });
6644 };
6645 canonical_components.push(exact_name);
6646 }
6647 let canonical = NamespacePath::new(canonical_components, self.volume.config.limits)
6648 .map_err(|error| {
6649 OperationFailure::new(FsError::Path(PathLookupError::NamespacePath(error)), work)
6650 })?;
6651 Ok(FsReceipt {
6652 value: Some(canonical),
6653 work,
6654 })
6655 }
6656
6657 async fn pending_or_existing_file_id(
6658 &mut self,
6659 path: &NamespacePath,
6660 pending: &[(NamespacePath, FileId)],
6661 budget: WorkBudget,
6662 cancellation: &CancellationToken,
6663 ) -> FsResult<FileId> {
6664 if let Some(file_id) =
6665 pending_binding_id(path, pending, self.volume.config.case_sensitivity)
6666 {
6667 return Ok(FsReceipt {
6668 value: file_id,
6669 work: WorkCounters::default(),
6670 });
6671 }
6672 let lookup = self.lookup_no_follow(path, budget, cancellation).await?;
6673 let work = lookup.work;
6674 let record = lookup
6675 .value
6676 .record
6677 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
6678 Ok(FsReceipt {
6679 value: record.file_id,
6680 work,
6681 })
6682 }
6683
6684 pub async fn apply_authored_transaction(
6696 &mut self,
6697 authored: Vec<AuthoredMutation>,
6698 budget: WorkBudget,
6699 cancellation: &CancellationToken,
6700 ) -> FsResult<AuthoredTransactionResult> {
6701 let maximum = usize::try_from(self.volume.config.limits.maximum_mutations_per_batch)
6702 .unwrap_or(usize::MAX);
6703 let operation_count = authored
6704 .iter()
6705 .try_fold(0_usize, |count, mutation| {
6706 count.checked_add(authored_mutation_operation_count(mutation))
6707 })
6708 .ok_or_else(|| OperationFailure::before_work(FsError::TooManyPendingMutations))?;
6709 if authored.len() > maximum || authored.capacity() > maximum || operation_count > maximum {
6710 return Err(OperationFailure::before_work(
6711 FsError::TooManyPendingMutations,
6712 ));
6713 }
6714 let mut work = WorkCounters::default();
6715 let mut operations = Vec::new();
6716 let mut created_file_ids = Vec::new();
6717 let mut staged_metadata = AuthoredMetadataCache::new();
6720 operations
6721 .try_reserve_exact(operation_count)
6722 .map_err(|_| OperationFailure::before_work(FsError::PendingMutationAllocationFailed))?;
6723 created_file_ids
6724 .try_reserve_exact(authored.len())
6725 .map_err(|_| OperationFailure::before_work(FsError::PendingMutationAllocationFailed))?;
6726
6727 for authored_mutation in authored {
6728 cancellation
6729 .check()
6730 .map_err(|error| OperationFailure::new(error.into(), work))?;
6731 let created = self
6732 .compile_authored_mutation(
6733 authored_mutation,
6734 &mut operations,
6735 &mut work,
6736 &mut staged_metadata,
6737 budget,
6738 cancellation,
6739 )
6740 .await?;
6741 created_file_ids.push(created);
6742 }
6743 if operations.is_empty() {
6744 return Ok(FsReceipt {
6745 value: AuthoredTransactionResult { created_file_ids },
6746 work,
6747 });
6748 }
6749 let mutation = self
6750 .mutate(operations, remaining(work, budget)?, cancellation)
6751 .await
6752 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6753 work = add(work, mutation.work)?;
6754 Ok(FsReceipt {
6755 value: AuthoredTransactionResult { created_file_ids },
6756 work,
6757 })
6758 }
6759
6760 pub(crate) async fn apply_authored_bulk_transaction(
6764 &mut self,
6765 authored: Vec<AuthoredMutation>,
6766 budget: WorkBudget,
6767 cancellation: &CancellationToken,
6768 ) -> FsResult<()> {
6769 let maximum = usize::try_from(self.volume.config.limits.maximum_mutations_per_batch)
6770 .unwrap_or(usize::MAX);
6771 if maximum == 0 {
6772 return Err(OperationFailure::before_work(
6773 FsError::TooManyPendingMutations,
6774 ));
6775 }
6776 let mut candidate = self.private_candidate();
6777 let mut work = WorkCounters::default();
6778 let mut batch = Vec::with_capacity(maximum.min(authored.len()));
6779 let mut operations = 0;
6780 for mutation in authored {
6781 let count = authored_mutation_operation_count(&mutation);
6782 if count > maximum {
6783 return Err(OperationFailure::new(
6784 FsError::TooManyPendingMutations,
6785 work,
6786 ));
6787 }
6788 if !batch.is_empty() && (batch.len() == maximum || operations + count > maximum) {
6789 let applied = candidate
6790 .apply_authored_transaction(batch, remaining(work, budget)?, cancellation)
6791 .await
6792 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6793 work = add(work, applied.work)?;
6794 batch = Vec::with_capacity(maximum.min(1024));
6795 operations = 0;
6796 }
6797 operations += count;
6798 batch.push(mutation);
6799 }
6800 if !batch.is_empty() {
6801 let applied = candidate
6802 .apply_authored_transaction(batch, remaining(work, budget)?, cancellation)
6803 .await
6804 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
6805 work = add(work, applied.work)?;
6806 }
6807 *self = candidate;
6808 Ok(FsReceipt { value: (), work })
6809 }
6810
6811 pub(crate) fn private_candidate(&self) -> Self {
6812 Self {
6813 volume: self.volume.clone(),
6814 base_generation_root: self.base_generation_root,
6815 generation_root: self.generation_root,
6816 base_file_table: self.base_file_table,
6817 base_root: self.base_root.clone(),
6818 root: self.root.clone(),
6819 authority_head: self.authority_head,
6820 authored_operation_id: self.authored_operation_id,
6821 live_operation_id: self.live_operation_id,
6822 last_commit: self.last_commit,
6823 prepared_merge_parent: self.prepared_merge_parent,
6824 dependencies: self.dependencies.clone(),
6825 mode: self.mode,
6826 }
6827 }
6828
6829 #[allow(clippy::too_many_lines)]
6830 async fn compile_authored_mutation(
6831 &self,
6832 authored: AuthoredMutation,
6833 operations: &mut Vec<Mutation>,
6834 work: &mut WorkCounters,
6835 staged_metadata: &mut AuthoredMetadataCache,
6836 budget: WorkBudget,
6837 cancellation: &CancellationToken,
6838 ) -> Result<Option<FileId>, OperationFailure<FsError>> {
6839 match authored {
6840 AuthoredMutation::Reidentify { path, file_id } => {
6841 let current = crate::kernel::lookup_path_async(
6842 &self.volume.fs.inner.objects,
6843 &self.root,
6844 &path,
6845 self.volume.config,
6846 remaining(*work, budget)?,
6847 cancellation,
6848 )
6849 .await
6850 .map_err(|failure| failure.map_with_prior_work(*work, FsError::Path))?;
6851 *work = add(*work, current.work)?;
6852 let current = current
6853 .record
6854 .ok_or_else(|| OperationFailure::new(FsError::NotFound, *work))?;
6855 if current.kind == FileKind::Directory {
6856 return Err(OperationFailure::new(FsError::InvalidSpecialKind, *work));
6857 }
6858 if current.file_id == file_id {
6859 return Ok(None);
6860 }
6861 let existing = self
6862 .lookup_file_record_by_id(file_id, remaining(*work, budget)?, cancellation)
6863 .await
6864 .map_err(|failure| {
6865 failure.map_with_prior_work(*work, std::convert::identity)
6866 })?;
6867 *work = add(*work, existing.work)?;
6868 if existing.value.is_some_and(|existing| {
6869 existing.kind != current.kind
6870 || existing.metadata != current.metadata
6871 || existing.payload != current.payload
6872 }) {
6873 return Err(OperationFailure::new(FsError::FileIdentityCollision, *work));
6874 }
6875 operations.push(Mutation::Restore {
6876 path,
6877 record: FileRecord {
6878 file_id,
6879 link_count: 1,
6880 ..current
6881 },
6882 });
6883 Ok(None)
6884 }
6885 AuthoredMutation::CreateFile {
6886 path,
6887 bytes,
6888 metadata,
6889 } => {
6890 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
6891 > self.volume.config.limits.maximum_read_bytes
6892 {
6893 return Err(OperationFailure::new(
6894 FsError::FileRead(FileRangeReadError::InvalidRange),
6895 *work,
6896 ));
6897 }
6898 let metadata = self
6899 .stage_authored_metadata(
6900 metadata,
6901 staged_metadata,
6902 remaining(*work, budget)?,
6903 cancellation,
6904 )
6905 .await
6906 .map_err(|failure| {
6907 failure.map_with_prior_work(*work, std::convert::identity)
6908 })?;
6909 *work = add(*work, metadata.work)?;
6910 let file_id = self.authored_file_id(FileKind::Regular, &path);
6911 if bytes.len() <= crate::kernel::MAXIMUM_INLINE_FILE_BYTES {
6912 operations.push(Mutation::Create {
6913 path,
6914 record: FileRecord {
6915 file_id,
6916 kind: FileKind::Regular,
6917 link_count: 1,
6918 metadata: metadata.value,
6919 payload: FilePayload::InlineRegular(
6920 InlineFileData::new(&bytes)
6921 .map_err(|error| OperationFailure::new(error.into(), *work))?,
6922 ),
6923 },
6924 });
6925 } else {
6926 let blob = self
6927 .stage_blob(bytes, remaining(*work, budget)?, cancellation)
6928 .await
6929 .map_err(|failure| {
6930 failure.map_with_prior_work(*work, std::convert::identity)
6931 })?;
6932 *work = add(*work, blob.work)?;
6933 operations.push(Mutation::Create {
6934 path: path.clone(),
6935 record: FileRecord {
6936 file_id,
6937 kind: FileKind::Regular,
6938 link_count: 1,
6939 metadata: metadata.value,
6940 payload: FilePayload::InlineRegular(
6941 InlineFileData::new(&[])
6942 .map_err(|error| OperationFailure::new(error.into(), *work))?,
6943 ),
6944 },
6945 });
6946 operations.push(Mutation::Write {
6947 path,
6948 offset: 0,
6949 length: blob.value.logical_bytes,
6950 content: blob.value.root,
6951 content_offset: 0,
6952 });
6953 }
6954 Ok(Some(file_id))
6955 }
6956 AuthoredMutation::CreateFileFromContent {
6957 path,
6958 content,
6959 metadata,
6960 file_id,
6961 } => {
6962 let metadata = self
6963 .stage_authored_metadata(
6964 metadata,
6965 staged_metadata,
6966 remaining(*work, budget)?,
6967 cancellation,
6968 )
6969 .await
6970 .map_err(|failure| {
6971 failure.map_with_prior_work(*work, std::convert::identity)
6972 })?;
6973 *work = add(*work, metadata.work)?;
6974 let file_id =
6975 file_id.unwrap_or_else(|| self.authored_file_id(FileKind::Regular, &path));
6976 operations.push(Mutation::Create {
6977 path: path.clone(),
6978 record: FileRecord {
6979 file_id,
6980 kind: FileKind::Regular,
6981 link_count: 1,
6982 metadata: metadata.value,
6983 payload: FilePayload::InlineRegular(
6984 InlineFileData::new(&[])
6985 .map_err(|error| OperationFailure::new(error.into(), *work))?,
6986 ),
6987 },
6988 });
6989 if content.logical_bytes != 0 {
6990 operations.push(Mutation::Write {
6991 path,
6992 offset: 0,
6993 length: content.logical_bytes,
6994 content: content.root,
6995 content_offset: 0,
6996 });
6997 }
6998 Ok(Some(file_id))
6999 }
7000 AuthoredMutation::CreateDirectory { path, metadata } => {
7001 let tree = self
7002 .stage_empty_tree(remaining(*work, budget)?, cancellation)
7003 .await
7004 .map_err(|failure| {
7005 failure.map_with_prior_work(*work, std::convert::identity)
7006 })?;
7007 *work = add(*work, tree.work)?;
7008 self.compile_record(
7009 operations,
7010 path,
7011 FileKind::Directory,
7012 FilePayload::Directory {
7013 entries: tree.value,
7014 },
7015 metadata,
7016 work,
7017 staged_metadata,
7018 budget,
7019 cancellation,
7020 )
7021 .await
7022 .map(Some)
7023 }
7024 AuthoredMutation::CreateSymbolicLink {
7025 path,
7026 target,
7027 metadata,
7028 } => {
7029 let blob = self
7030 .stage_blob(target, remaining(*work, budget)?, cancellation)
7031 .await
7032 .map_err(|failure| {
7033 failure.map_with_prior_work(*work, std::convert::identity)
7034 })?;
7035 *work = add(*work, blob.work)?;
7036 self.compile_record(
7037 operations,
7038 path,
7039 FileKind::SymbolicLink,
7040 FilePayload::SymbolicLink {
7041 target_bytes: blob.value.logical_bytes,
7042 target: blob.value.root,
7043 },
7044 metadata,
7045 work,
7046 staged_metadata,
7047 budget,
7048 cancellation,
7049 )
7050 .await
7051 .map(Some)
7052 }
7053 AuthoredMutation::CreateEmptySpecial {
7054 path,
7055 kind,
7056 metadata,
7057 } => {
7058 if !matches!(
7059 kind,
7060 FileKind::Fifo | FileKind::Socket | FileKind::MountBoundary
7061 ) {
7062 return Err(OperationFailure::new(FsError::InvalidSpecialKind, *work));
7063 }
7064 self.validate_profile_kind(kind, *work)?;
7065 self.compile_record(
7066 operations,
7067 path,
7068 kind,
7069 FilePayload::Empty,
7070 metadata,
7071 work,
7072 staged_metadata,
7073 budget,
7074 cancellation,
7075 )
7076 .await
7077 .map(Some)
7078 }
7079 AuthoredMutation::CreateDevice {
7080 path,
7081 kind,
7082 major,
7083 minor,
7084 metadata,
7085 } => {
7086 if !matches!(kind, FileKind::CharacterDevice | FileKind::BlockDevice) {
7087 return Err(OperationFailure::new(FsError::InvalidSpecialKind, *work));
7088 }
7089 self.validate_profile_kind(kind, *work)?;
7090 self.compile_record(
7091 operations,
7092 path,
7093 kind,
7094 FilePayload::Device { major, minor },
7095 metadata,
7096 work,
7097 staged_metadata,
7098 budget,
7099 cancellation,
7100 )
7101 .await
7102 .map(Some)
7103 }
7104 AuthoredMutation::CreateReparsePoint {
7105 path,
7106 payload,
7107 metadata,
7108 } => {
7109 self.validate_profile_kind(FileKind::ReparsePoint, *work)?;
7110 let blob = self
7111 .stage_blob(payload, remaining(*work, budget)?, cancellation)
7112 .await
7113 .map_err(|failure| {
7114 failure.map_with_prior_work(*work, std::convert::identity)
7115 })?;
7116 *work = add(*work, blob.work)?;
7117 self.compile_record(
7118 operations,
7119 path,
7120 FileKind::ReparsePoint,
7121 FilePayload::ReparsePoint {
7122 payload_bytes: blob.value.logical_bytes,
7123 payload: blob.value.root,
7124 },
7125 metadata,
7126 work,
7127 staged_metadata,
7128 budget,
7129 cancellation,
7130 )
7131 .await
7132 .map(Some)
7133 }
7134 AuthoredMutation::Remove {
7135 path,
7136 expected_file_id,
7137 } => {
7138 operations.push(Mutation::Remove {
7139 path,
7140 expected_file_id: expected_file_id
7141 .map_or(MetadataField::Unavailable, MetadataField::Value),
7142 });
7143 Ok(None)
7144 }
7145 AuthoredMutation::Rename {
7146 source,
7147 destination,
7148 replace,
7149 } => {
7150 operations.push(Mutation::Rename {
7151 source,
7152 destination,
7153 replace,
7154 });
7155 Ok(None)
7156 }
7157 AuthoredMutation::HardLink {
7158 source,
7159 destination,
7160 } => {
7161 operations.push(Mutation::Link {
7162 source,
7163 destination,
7164 });
7165 Ok(None)
7166 }
7167 AuthoredMutation::Write {
7168 path,
7169 offset,
7170 bytes,
7171 } => {
7172 if bytes.is_empty() {
7173 operations.push(Mutation::ValidateRegular { path });
7174 } else {
7175 let blob = self
7176 .stage_blob(bytes, remaining(*work, budget)?, cancellation)
7177 .await
7178 .map_err(|failure| {
7179 failure.map_with_prior_work(*work, std::convert::identity)
7180 })?;
7181 *work = add(*work, blob.work)?;
7182 operations.push(Mutation::Write {
7183 path,
7184 offset,
7185 length: blob.value.logical_bytes,
7186 content: blob.value.root,
7187 content_offset: 0,
7188 });
7189 }
7190 Ok(None)
7191 }
7192 AuthoredMutation::WriteFromContent {
7193 path,
7194 offset,
7195 content,
7196 } => {
7197 if content.logical_bytes == 0 {
7198 operations.push(Mutation::ValidateRegular { path });
7199 } else {
7200 operations.push(Mutation::Write {
7201 path,
7202 offset,
7203 length: content.logical_bytes,
7204 content: content.root,
7205 content_offset: 0,
7206 });
7207 }
7208 Ok(None)
7209 }
7210 AuthoredMutation::SetMetadata { path, metadata } => {
7211 let metadata = self
7212 .stage_authored_metadata(
7213 metadata,
7214 staged_metadata,
7215 remaining(*work, budget)?,
7216 cancellation,
7217 )
7218 .await
7219 .map_err(|failure| {
7220 failure.map_with_prior_work(*work, std::convert::identity)
7221 })?;
7222 *work = add(*work, metadata.work)?;
7223 operations.push(Mutation::SetMetadata {
7224 path,
7225 metadata: metadata.value,
7226 });
7227 Ok(None)
7228 }
7229 AuthoredMutation::Resize {
7230 path,
7231 logical_bytes,
7232 } => {
7233 operations.push(Mutation::Resize {
7234 path,
7235 logical_bytes,
7236 });
7237 Ok(None)
7238 }
7239 AuthoredMutation::ZeroRange {
7240 path,
7241 range,
7242 allocated,
7243 extend,
7244 } => {
7245 operations.push(Mutation::ZeroRange {
7246 path,
7247 offset: range.offset,
7248 length: range.length,
7249 allocated,
7250 extend,
7251 });
7252 Ok(None)
7253 }
7254 AuthoredMutation::Preallocate {
7255 path,
7256 range,
7257 keep_size,
7258 } => {
7259 operations.push(Mutation::Preallocate {
7260 path,
7261 offset: range.offset,
7262 length: range.length,
7263 keep_size,
7264 });
7265 Ok(None)
7266 }
7267 AuthoredMutation::CloneRange(request) => {
7268 operations.push(Mutation::CloneRange {
7269 source: request.source,
7270 source_offset: request.source_offset,
7271 destination: request.destination,
7272 destination_offset: request.destination_offset,
7273 length: request.length,
7274 });
7275 Ok(None)
7276 }
7277 }
7278 }
7279
7280 fn validate_profile_kind(
7281 &self,
7282 kind: FileKind,
7283 work: WorkCounters,
7284 ) -> Result<(), OperationFailure<FsError>> {
7285 if kind.is_supported_by_profile(self.volume.config.profile) {
7286 Ok(())
7287 } else {
7288 Err(OperationFailure::new(FsError::UnsupportedFileKind, work))
7289 }
7290 }
7291
7292 #[allow(clippy::too_many_arguments)]
7293 async fn compile_record(
7294 &self,
7295 operations: &mut Vec<Mutation>,
7296 path: NamespacePath,
7297 kind: FileKind,
7298 payload: FilePayload,
7299 metadata: FileMetadata,
7300 work: &mut WorkCounters,
7301 staged_metadata: &mut AuthoredMetadataCache,
7302 budget: WorkBudget,
7303 cancellation: &CancellationToken,
7304 ) -> Result<FileId, OperationFailure<FsError>> {
7305 let staged = self
7306 .stage_authored_metadata(
7307 metadata,
7308 staged_metadata,
7309 remaining(*work, budget)?,
7310 cancellation,
7311 )
7312 .await
7313 .map_err(|failure| failure.map_with_prior_work(*work, std::convert::identity))?;
7314 *work = add(*work, staged.work)?;
7315 let file_id = self.authored_file_id(kind, &path);
7316 operations.push(Mutation::Create {
7317 path,
7318 record: FileRecord {
7319 file_id,
7320 kind,
7321 link_count: 1,
7322 metadata: staged.value,
7323 payload,
7324 },
7325 });
7326 Ok(file_id)
7327 }
7328
7329 pub(crate) fn bind_authored_operation(&mut self, operation_id: OperationId) {
7330 self.authored_operation_id = Some(operation_id);
7331 }
7332
7333 fn authored_file_id(&self, kind: FileKind, path: &NamespacePath) -> FileId {
7334 let Some(operation_id) = self.authored_operation_id else {
7335 return FileId::new();
7336 };
7337 let mut hasher = blake3::Hasher::new();
7338 hasher.update(b"acyclic-fs-authored-file-id-v1\0");
7339 hasher.update(&operation_id.into_bytes());
7340 hasher.update(&[kind.tag()]);
7341 for component in path.components() {
7342 hasher.update(&[component.encoding().tag()]);
7343 hasher.update(
7344 &u64::try_from(component.as_bytes().len())
7345 .unwrap_or(u64::MAX)
7346 .to_le_bytes(),
7347 );
7348 hasher.update(component.as_bytes());
7349 }
7350 let digest = hasher.finalize();
7351 let mut bytes = [0_u8; 16];
7352 bytes.copy_from_slice(&digest.as_bytes()[..16]);
7353 FileId::from_bytes(bytes)
7354 }
7355
7356 async fn stage_metadata(
7357 &self,
7358 metadata: FileMetadata,
7359 budget: WorkBudget,
7360 cancellation: &CancellationToken,
7361 ) -> FsResult<ObjectId> {
7362 let encoded = encode_file_metadata(metadata)
7363 .map_err(|error| OperationFailure::before_work(error.into()))?;
7364 let (value, work) = self
7365 .volume
7366 .fs
7367 .put_encoded(
7368 ObjectKind::Metadata,
7369 encoded,
7370 WorkCounters::default(),
7371 budget,
7372 cancellation,
7373 )
7374 .await?;
7375 Ok(FsReceipt { value, work })
7376 }
7377
7378 async fn stage_authored_metadata(
7379 &self,
7380 metadata: FileMetadata,
7381 staged: &mut AuthoredMetadataCache,
7382 budget: WorkBudget,
7383 cancellation: &CancellationToken,
7384 ) -> FsResult<ObjectId> {
7385 cancellation
7386 .check()
7387 .map_err(|error| OperationFailure::before_work(error.into()))?;
7388 if let Some(value) = staged.get(metadata) {
7389 return Ok(FsReceipt {
7390 value,
7391 work: WorkCounters::default(),
7392 });
7393 }
7394 let receipt = self.stage_metadata(metadata, budget, cancellation).await?;
7395 staged.remember(metadata, receipt.value);
7396 Ok(receipt)
7397 }
7398
7399 async fn stage_empty_tree(
7400 &self,
7401 budget: WorkBudget,
7402 cancellation: &CancellationToken,
7403 ) -> FsResult<ObjectId> {
7404 let encoded = encode_tree_page(
7405 &TreePage::Leaf(Vec::new()),
7406 self.volume.config.limits.maximum_directory_page_entries,
7407 )
7408 .map_err(|error| OperationFailure::before_work(error.into()))?;
7409 let (value, work) = self
7410 .volume
7411 .fs
7412 .put_encoded(
7413 ObjectKind::TreePage,
7414 encoded,
7415 WorkCounters::default(),
7416 budget,
7417 cancellation,
7418 )
7419 .await?;
7420 Ok(FsReceipt { value, work })
7421 }
7422
7423 async fn stage_empty_attributes(
7424 &self,
7425 budget: WorkBudget,
7426 cancellation: &CancellationToken,
7427 ) -> FsResult<ObjectId> {
7428 let encoded = encode_attribute_page(
7429 &AttributePage::Leaf(Vec::new()),
7430 self.volume.config.limits.maximum_directory_page_entries,
7431 )
7432 .map_err(|error| OperationFailure::before_work(error.into()))?;
7433 let (value, work) = self
7434 .volume
7435 .fs
7436 .put_encoded(
7437 ObjectKind::AttributePage,
7438 encoded,
7439 WorkCounters::default(),
7440 budget,
7441 cancellation,
7442 )
7443 .await?;
7444 Ok(FsReceipt { value, work })
7445 }
7446
7447 async fn admit_new_name(
7455 &mut self,
7456 path: &NamespacePath,
7457 budget: WorkBudget,
7458 cancellation: &CancellationToken,
7459 allowed_file_ids: &[FileId],
7460 parent_pending: bool,
7461 ) -> FsResult<()> {
7462 let mut work = WorkCounters::default();
7463 let Some((parent_components, name)) = path.split_last() else {
7464 return Ok(FsReceipt { value: (), work });
7465 };
7466 if self.volume.config.unicode == UnicodePolicy::RequireNfc
7467 && name.encoding() == NameEncoding::Utf8
7468 {
7469 let is_nfc =
7470 std::str::from_utf8(name.as_bytes()).is_ok_and(unicode_normalization::is_nfc);
7471 if !is_nfc {
7472 return Err(OperationFailure::new(FsError::NonNormalizedName, work));
7473 }
7474 }
7475 if self.volume.config.case_sensitivity == CaseSensitivity::ProfileFolded && !parent_pending
7476 {
7477 let found = self
7478 .find_case_folded_sibling(
7479 parent_components,
7480 name,
7481 remaining(work, budget)?,
7482 cancellation,
7483 allowed_file_ids,
7484 )
7485 .await
7486 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7487 work = add(work, found.work)?;
7488 if found.value.is_some() {
7489 return Err(OperationFailure::new(FsError::NameCollision, work));
7490 }
7491 }
7492 Ok(FsReceipt { value: (), work })
7493 }
7494
7495 async fn find_case_folded_sibling(
7506 &mut self,
7507 parent_components: &[LogicalName],
7508 target: &LogicalName,
7509 budget: WorkBudget,
7510 cancellation: &CancellationToken,
7511 allowed_file_ids: &[FileId],
7512 ) -> FsResult<Option<LogicalName>> {
7513 let mut work = WorkCounters::default();
7514 let Ok(parent) = NamespacePath::new(parent_components.to_vec(), self.volume.config.limits)
7515 else {
7516 return Ok(FsReceipt { value: None, work });
7517 };
7518 let folded_target = target.case_fold_key();
7519 let mut after: Option<LogicalName> = None;
7520 loop {
7521 let page = Box::pin(self.list_directory(
7526 &parent,
7527 after.as_ref(),
7528 CASE_FOLD_COLLISION_SCAN_PAGE,
7529 remaining(work, budget)?,
7530 cancellation,
7531 ))
7532 .await
7533 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7534 work = add(work, page.work)?;
7535 for entry in &page.value.entries {
7536 if entry.name != *target
7537 && entry.name.case_fold_key() == folded_target
7538 && !allowed_file_ids.contains(&entry.file_id)
7539 {
7540 return Ok(FsReceipt {
7541 value: Some(entry.name.clone()),
7542 work,
7543 });
7544 }
7545 }
7546 if !page.value.has_more {
7547 break;
7548 }
7549 after = page.value.entries.last().map(|entry| entry.name.clone());
7550 }
7551 Ok(FsReceipt { value: None, work })
7552 }
7553
7554 pub async fn create_file(
7562 &mut self,
7563 path: NamespacePath,
7564 bytes: Bytes,
7565 budget: WorkBudget,
7566 cancellation: &CancellationToken,
7567 ) -> FsResult<FileId> {
7568 cancellation
7569 .check()
7570 .map_err(|error| OperationFailure::before_work(error.into()))?;
7571 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
7572 > self.volume.config.limits.maximum_read_bytes
7573 {
7574 return Err(OperationFailure::before_work(FsError::FileRead(
7575 FileRangeReadError::InvalidRange,
7576 )));
7577 }
7578 let mut work = WorkCounters::default();
7579 let metadata = self
7580 .volume
7581 .fs
7582 .put_encoded(
7583 ObjectKind::Metadata,
7584 encode_file_metadata(empty_metadata())
7585 .map_err(|error| OperationFailure::new(error.into(), work))?,
7586 work,
7587 budget,
7588 cancellation,
7589 )
7590 .await?;
7591 work = metadata.1;
7592 let file_id = FileId::new();
7593 let mut operations = Vec::new();
7594 if bytes.len() <= crate::kernel::MAXIMUM_INLINE_FILE_BYTES {
7595 operations.push(Mutation::Create {
7596 path,
7597 record: FileRecord {
7598 file_id,
7599 kind: FileKind::Regular,
7600 link_count: 1,
7601 metadata: metadata.0,
7602 payload: FilePayload::InlineRegular(
7603 InlineFileData::new(&bytes)
7604 .map_err(|error| OperationFailure::new(error.into(), work))?,
7605 ),
7606 },
7607 });
7608 } else {
7609 let blob = self
7610 .stage_blob(bytes, remaining(work, budget)?, cancellation)
7611 .await
7612 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7613 work = add(work, blob.work)?;
7614 let length = blob.value.logical_bytes;
7615 operations.push(Mutation::Create {
7616 path: path.clone(),
7617 record: FileRecord {
7618 file_id,
7619 kind: FileKind::Regular,
7620 link_count: 1,
7621 metadata: metadata.0,
7622 payload: FilePayload::InlineRegular(
7623 InlineFileData::new(&[])
7624 .map_err(|error| OperationFailure::new(error.into(), work))?,
7625 ),
7626 },
7627 });
7628 operations.push(Mutation::Write {
7629 path,
7630 offset: 0,
7631 length,
7632 content: blob.value.root,
7633 content_offset: 0,
7634 });
7635 }
7636 let mutation = self
7637 .mutate(operations, remaining(work, budget)?, cancellation)
7638 .await
7639 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7640 work = add(work, mutation.work)?;
7641 Ok(FsReceipt {
7642 value: file_id,
7643 work,
7644 })
7645 }
7646
7647 pub async fn create_directory(
7654 &mut self,
7655 path: NamespacePath,
7656 budget: WorkBudget,
7657 cancellation: &CancellationToken,
7658 ) -> FsResult<FileId> {
7659 let mut work = WorkCounters::default();
7660 let metadata = self
7661 .volume
7662 .fs
7663 .put_encoded(
7664 ObjectKind::Metadata,
7665 encode_file_metadata(empty_metadata())
7666 .map_err(|error| OperationFailure::new(error.into(), work))?,
7667 work,
7668 budget,
7669 cancellation,
7670 )
7671 .await?;
7672 work = metadata.1;
7673 let tree = self
7674 .volume
7675 .fs
7676 .put_encoded(
7677 ObjectKind::TreePage,
7678 encode_tree_page(
7679 &TreePage::Leaf(Vec::new()),
7680 self.volume.config.limits.maximum_directory_page_entries,
7681 )
7682 .map_err(|error| OperationFailure::new(error.into(), work))?,
7683 work,
7684 budget,
7685 cancellation,
7686 )
7687 .await?;
7688 work = tree.1;
7689 let file_id = FileId::new();
7690 let mutation = self
7691 .mutate(
7692 vec![Mutation::Create {
7693 path,
7694 record: FileRecord {
7695 file_id,
7696 kind: FileKind::Directory,
7697 link_count: 1,
7698 metadata: metadata.0,
7699 payload: FilePayload::Directory { entries: tree.0 },
7700 },
7701 }],
7702 remaining(work, budget)?,
7703 cancellation,
7704 )
7705 .await
7706 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7707 work = add(work, mutation.work)?;
7708 Ok(FsReceipt {
7709 value: file_id,
7710 work,
7711 })
7712 }
7713
7714 pub async fn create_symbolic_link(
7721 &mut self,
7722 path: NamespacePath,
7723 target: Bytes,
7724 budget: WorkBudget,
7725 cancellation: &CancellationToken,
7726 ) -> FsResult<FileId> {
7727 let blob = self.stage_blob(target, budget, cancellation).await?;
7728 let mut work = blob.work;
7729 let created = self
7730 .create_record(
7731 path,
7732 FileKind::SymbolicLink,
7733 FilePayload::SymbolicLink {
7734 target_bytes: blob.value.logical_bytes,
7735 target: blob.value.root,
7736 },
7737 remaining(work, budget)?,
7738 cancellation,
7739 )
7740 .await
7741 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7742 work = add(work, created.work)?;
7743 Ok(FsReceipt {
7744 value: created.value,
7745 work,
7746 })
7747 }
7748
7749 pub async fn read_symbolic_link(
7756 &mut self,
7757 path: &NamespacePath,
7758 budget: WorkBudget,
7759 cancellation: &CancellationToken,
7760 ) -> FsResult<Bytes> {
7761 let lookup = self.lookup_no_follow(path, budget, cancellation).await?;
7762 let mut work = lookup.work;
7763 let record = lookup
7764 .value
7765 .record
7766 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
7767 let read =
7768 read_symbolic_link_record(&self.volume, record, remaining(work, budget)?, cancellation)
7769 .await
7770 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7771 work = add(work, read.work)?;
7772 Ok(FsReceipt {
7773 value: read.value,
7774 work,
7775 })
7776 }
7777
7778 pub async fn create_empty_special(
7785 &mut self,
7786 path: NamespacePath,
7787 kind: FileKind,
7788 budget: WorkBudget,
7789 cancellation: &CancellationToken,
7790 ) -> FsResult<FileId> {
7791 if !matches!(
7792 kind,
7793 FileKind::Fifo | FileKind::Socket | FileKind::MountBoundary
7794 ) {
7795 return Err(OperationFailure::before_work(FsError::InvalidSpecialKind));
7796 }
7797 self.validate_profile_kind(kind, WorkCounters::default())?;
7798 self.create_record(path, kind, FilePayload::Empty, budget, cancellation)
7799 .await
7800 }
7801
7802 pub async fn create_device(
7809 &mut self,
7810 path: NamespacePath,
7811 kind: FileKind,
7812 major: u32,
7813 minor: u32,
7814 budget: WorkBudget,
7815 cancellation: &CancellationToken,
7816 ) -> FsResult<FileId> {
7817 if !matches!(kind, FileKind::CharacterDevice | FileKind::BlockDevice) {
7818 return Err(OperationFailure::before_work(FsError::InvalidSpecialKind));
7819 }
7820 self.validate_profile_kind(kind, WorkCounters::default())?;
7821 self.create_record(
7822 path,
7823 kind,
7824 FilePayload::Device { major, minor },
7825 budget,
7826 cancellation,
7827 )
7828 .await
7829 }
7830
7831 pub async fn create_reparse_point(
7838 &mut self,
7839 path: NamespacePath,
7840 payload: Bytes,
7841 budget: WorkBudget,
7842 cancellation: &CancellationToken,
7843 ) -> FsResult<FileId> {
7844 self.validate_profile_kind(FileKind::ReparsePoint, WorkCounters::default())?;
7845 let blob = self.stage_blob(payload, budget, cancellation).await?;
7846 let mut work = blob.work;
7847 let created = self
7848 .create_record(
7849 path,
7850 FileKind::ReparsePoint,
7851 FilePayload::ReparsePoint {
7852 payload_bytes: blob.value.logical_bytes,
7853 payload: blob.value.root,
7854 },
7855 remaining(work, budget)?,
7856 cancellation,
7857 )
7858 .await
7859 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7860 work = add(work, created.work)?;
7861 Ok(FsReceipt {
7862 value: created.value,
7863 work,
7864 })
7865 }
7866
7867 pub async fn read_reparse_point(
7874 &mut self,
7875 path: &NamespacePath,
7876 budget: WorkBudget,
7877 cancellation: &CancellationToken,
7878 ) -> FsResult<Bytes> {
7879 let lookup = self.lookup_no_follow(path, budget, cancellation).await?;
7880 let mut work = lookup.work;
7881 let record = lookup
7882 .value
7883 .record
7884 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
7885 let FilePayload::ReparsePoint {
7886 payload_bytes,
7887 payload,
7888 } = record.payload
7889 else {
7890 return Err(OperationFailure::new(FsError::NotReparsePoint, work));
7891 };
7892 if payload_bytes > self.volume.config.limits.maximum_read_bytes {
7893 return Err(OperationFailure::new(
7894 FsError::FileRead(FileRangeReadError::InvalidRange),
7895 work,
7896 ));
7897 }
7898 if payload_bytes == 0 {
7899 return Ok(FsReceipt {
7900 value: Bytes::new(),
7901 work,
7902 });
7903 }
7904 let read = read_blob_range_async(
7905 &self.volume.fs.inner.objects,
7906 payload,
7907 ByteRange {
7908 offset: 0,
7909 length: payload_bytes,
7910 },
7911 decode_limits(self.volume.config),
7912 remaining(work, budget)?,
7913 cancellation,
7914 )
7915 .await
7916 .map_err(|failure| failure.map_with_prior_work(work, FsError::BlobRead))?;
7917 work = add(work, read.work)?;
7918 Ok(FsReceipt {
7919 value: read.bytes,
7920 work,
7921 })
7922 }
7923
7924 async fn create_record(
7925 &mut self,
7926 path: NamespacePath,
7927 kind: FileKind,
7928 payload: FilePayload,
7929 budget: WorkBudget,
7930 cancellation: &CancellationToken,
7931 ) -> FsResult<FileId> {
7932 let mut work = WorkCounters::default();
7933 let metadata = self
7934 .volume
7935 .fs
7936 .put_encoded(
7937 ObjectKind::Metadata,
7938 encode_file_metadata(empty_metadata())
7939 .map_err(|error| OperationFailure::new(error.into(), work))?,
7940 work,
7941 budget,
7942 cancellation,
7943 )
7944 .await?;
7945 work = metadata.1;
7946 let file_id = FileId::new();
7947 let mutation = self
7948 .mutate(
7949 vec![Mutation::Create {
7950 path,
7951 record: FileRecord {
7952 file_id,
7953 kind,
7954 link_count: 1,
7955 metadata: metadata.0,
7956 payload,
7957 },
7958 }],
7959 remaining(work, budget)?,
7960 cancellation,
7961 )
7962 .await
7963 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
7964 work = add(work, mutation.work)?;
7965 Ok(FsReceipt {
7966 value: file_id,
7967 work,
7968 })
7969 }
7970
7971 pub async fn write_file(
7978 &mut self,
7979 path: NamespacePath,
7980 offset: u64,
7981 bytes: Bytes,
7982 budget: WorkBudget,
7983 cancellation: &CancellationToken,
7984 ) -> FsResult<()> {
7985 if bytes.is_empty() {
7986 return self
7987 .mutate(
7988 vec![Mutation::ValidateRegular { path }],
7989 budget,
7990 cancellation,
7991 )
7992 .await;
7993 }
7994 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
7995 > self.volume.config.limits.maximum_read_bytes
7996 {
7997 return Err(OperationFailure::before_work(FsError::FileRead(
7998 FileRangeReadError::InvalidRange,
7999 )));
8000 }
8001 let blob = self.stage_blob(bytes, budget, cancellation).await?;
8002 let mut work = blob.work;
8003 let mutation = self
8004 .mutate(
8005 vec![Mutation::Write {
8006 path,
8007 offset,
8008 length: blob.value.logical_bytes,
8009 content: blob.value.root,
8010 content_offset: 0,
8011 }],
8012 remaining(work, budget)?,
8013 cancellation,
8014 )
8015 .await
8016 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8017 work = add(work, mutation.work)?;
8018 Ok(FsReceipt { value: (), work })
8019 }
8020
8021 pub async fn detach_regular_file(
8032 &mut self,
8033 path: &NamespacePath,
8034 budget: WorkBudget,
8035 cancellation: &CancellationToken,
8036 ) -> FsResult<DetachedFile<A, O>> {
8037 let lookup = self
8038 .lookup_no_follow_observed(path, budget, cancellation, true)
8039 .await?;
8040 let record = lookup
8041 .value
8042 .record
8043 .ok_or_else(|| OperationFailure::new(FsError::NotFound, lookup.work))?;
8044 if record.kind != FileKind::Regular {
8045 return Err(OperationFailure::new(
8046 FsError::FileRead(FileRangeReadError::NotRegular),
8047 lookup.work,
8048 ));
8049 }
8050 Ok(FsReceipt {
8051 value: DetachedFile {
8052 volume: self.volume.clone(),
8053 record,
8054 },
8055 work: lookup.work,
8056 })
8057 }
8058
8059 #[cfg(feature = "native-mount")]
8060 pub(crate) fn detached_from_record(&self, record: FileRecord) -> DetachedFile<A, O> {
8061 DetachedFile::from_record(self.volume.clone(), record)
8062 }
8063
8064 pub async fn read_file_record_by_id(
8074 &mut self,
8075 file_id: FileId,
8076 budget: WorkBudget,
8077 cancellation: &CancellationToken,
8078 ) -> FsResult<FileRecord> {
8079 let lookup = self
8080 .lookup_file_record_by_id(file_id, budget, cancellation)
8081 .await?;
8082 let mut work = lookup.work;
8083 let record = lookup
8084 .value
8085 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8086 if self.mode.consistency != ConsistencyMode::Pinned {
8087 work = self
8088 .observe_identity_region(
8089 DependencyRegion::FileRecord(file_id),
8090 work,
8091 budget,
8092 cancellation,
8093 )
8094 .await?;
8095 }
8096 Ok(FsReceipt {
8097 value: record,
8098 work,
8099 })
8100 }
8101
8102 pub async fn read_file_range_by_id(
8109 &mut self,
8110 file_id: FileId,
8111 range: ByteRange,
8112 budget: WorkBudget,
8113 cancellation: &CancellationToken,
8114 ) -> FsResult<FileRangeRead> {
8115 self.read_file_by_id_range(file_id, range, false, budget, cancellation)
8116 .await
8117 }
8118
8119 pub async fn read_file_up_to_by_id(
8126 &mut self,
8127 file_id: FileId,
8128 offset: u64,
8129 maximum_bytes: u32,
8130 budget: WorkBudget,
8131 cancellation: &CancellationToken,
8132 ) -> FsResult<FileRangeRead> {
8133 self.read_file_by_id_range(
8134 file_id,
8135 ByteRange {
8136 offset,
8137 length: u64::from(maximum_bytes),
8138 },
8139 true,
8140 budget,
8141 cancellation,
8142 )
8143 .await
8144 }
8145
8146 async fn read_file_by_id_range(
8147 &mut self,
8148 file_id: FileId,
8149 mut range: ByteRange,
8150 clip_to_eof: bool,
8151 budget: WorkBudget,
8152 cancellation: &CancellationToken,
8153 ) -> FsResult<FileRangeRead> {
8154 let requested_range = range;
8155 if range.length > self.volume.config.limits.maximum_read_bytes {
8156 return Err(OperationFailure::before_work(FsError::FileRead(
8157 FileRangeReadError::InvalidRange,
8158 )));
8159 }
8160 let lookup = self
8161 .lookup_file_record_by_id(file_id, budget, cancellation)
8162 .await?;
8163 let mut work = lookup.work;
8164 let record = lookup
8165 .value
8166 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8167 if clip_to_eof {
8168 if record.kind != FileKind::Regular {
8169 return Err(OperationFailure::new(
8170 FsError::FileRead(FileRangeReadError::NotRegular),
8171 work,
8172 ));
8173 }
8174 let logical_bytes = match record.payload {
8175 FilePayload::InlineRegular(data) => {
8176 u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX)
8177 }
8178 FilePayload::Regular { logical_bytes, .. } => logical_bytes,
8179 _ => {
8180 return Err(OperationFailure::new(
8181 FsError::FileRead(FileRangeReadError::NotRegular),
8182 work,
8183 ));
8184 }
8185 };
8186 range.length = range.length.min(logical_bytes.saturating_sub(range.offset));
8187 if range.length == 0 {
8188 if self.mode.consistency != ConsistencyMode::Pinned && requested_range.length != 0 {
8189 work = self
8190 .observe_base_regular_range(
8191 file_id,
8192 requested_range,
8193 work,
8194 budget,
8195 cancellation,
8196 )
8197 .await?;
8198 }
8199 return Ok(FsReceipt {
8200 value: FileRangeRead {
8201 bytes: Bytes::new(),
8202 work,
8203 },
8204 work,
8205 });
8206 }
8207 }
8208 let mut read = read_file_range_async(
8209 &self.volume.fs.inner.objects,
8210 FileRangeRequest {
8211 record,
8212 range,
8213 maximum_spans: self.volume.config.limits.maximum_directory_page_entries,
8214 limits: decode_limits(self.volume.config),
8215 budget: remaining(work, budget)?,
8216 },
8217 cancellation,
8218 )
8219 .await
8220 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRead))?;
8221 work = add(work, read.work)?;
8222 if self.mode.consistency != ConsistencyMode::Pinned && requested_range.length != 0 {
8223 work = self
8224 .observe_base_regular_range(file_id, requested_range, work, budget, cancellation)
8225 .await?;
8226 }
8227 read.work = work;
8228 Ok(FsReceipt { value: read, work })
8229 }
8230
8231 pub async fn plan_file_extents_by_id(
8243 &mut self,
8244 file_id: FileId,
8245 range: ByteRange,
8246 maximum_spans: u32,
8247 budget: WorkBudget,
8248 cancellation: &CancellationToken,
8249 ) -> FsResult<Option<ExtentPlan>> {
8250 if maximum_spans == 0 || range.length > self.volume.config.limits.maximum_generation_bytes {
8251 return Err(OperationFailure::before_work(FsError::FileRead(
8252 FileRangeReadError::InvalidRange,
8253 )));
8254 }
8255 let lookup = self
8256 .lookup_file_record_by_id(file_id, budget, cancellation)
8257 .await?;
8258 let mut work = lookup.work;
8259 let record = lookup
8260 .value
8261 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8262 let (logical_bytes, extents) = match record.payload {
8263 FilePayload::InlineRegular(data) => {
8264 let logical_bytes = u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX);
8265 validate_planned_file_range(logical_bytes, range, work)?;
8266 if self.mode.consistency != ConsistencyMode::Pinned && range.length != 0 {
8267 work = self
8268 .observe_base_regular_range(file_id, range, work, budget, cancellation)
8269 .await?;
8270 }
8271 return Ok(FsReceipt { value: None, work });
8272 }
8273 FilePayload::Regular {
8274 logical_bytes,
8275 extents,
8276 } => (logical_bytes, extents),
8277 FilePayload::Directory { .. }
8278 | FilePayload::SymbolicLink { .. }
8279 | FilePayload::Device { .. }
8280 | FilePayload::Empty
8281 | FilePayload::ReparsePoint { .. } => {
8282 return Err(OperationFailure::new(
8283 FsError::FileRead(FileRangeReadError::NotRegular),
8284 work,
8285 ));
8286 }
8287 };
8288 let mut plan = plan_extent_range_async(
8289 &self.volume.fs.inner.objects,
8290 ExtentRangeRequest {
8291 root: extents,
8292 file_size: logical_bytes,
8293 range,
8294 maximum_spans,
8295 limits: decode_limits(self.volume.config),
8296 budget: remaining(work, budget)?,
8297 },
8298 cancellation,
8299 )
8300 .await
8301 .map_err(|failure| {
8302 failure.map_with_prior_work(work, |error| {
8303 FsError::FileRead(FileRangeReadError::Extent(error))
8304 })
8305 })?;
8306 work = add(work, plan.work)?;
8307 if self.mode.consistency != ConsistencyMode::Pinned && range.length != 0 {
8308 work = self
8309 .observe_base_regular_range(file_id, range, work, budget, cancellation)
8310 .await?;
8311 }
8312 plan.work = work;
8313 Ok(FsReceipt {
8314 value: Some(plan),
8315 work,
8316 })
8317 }
8318
8319 pub async fn seek_file_extent_by_id(
8326 &mut self,
8327 file_id: FileId,
8328 offset: u64,
8329 target: ExtentSeekTarget,
8330 budget: WorkBudget,
8331 cancellation: &CancellationToken,
8332 ) -> FsResult<Option<u64>> {
8333 let lookup = self
8334 .lookup_file_record_by_id(file_id, budget, cancellation)
8335 .await?;
8336 let mut work = lookup.work;
8337 let record = lookup
8338 .value
8339 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8340 let value = match record.payload {
8341 FilePayload::InlineRegular(data) => {
8342 let logical_bytes = u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX);
8343 if offset > logical_bytes {
8344 None
8345 } else {
8346 match target {
8347 ExtentSeekTarget::Data => (offset < logical_bytes).then_some(offset),
8348 ExtentSeekTarget::Hole => Some(logical_bytes),
8349 }
8350 }
8351 }
8352 FilePayload::Regular {
8353 logical_bytes,
8354 extents,
8355 } => {
8356 if offset > logical_bytes {
8357 None
8358 } else {
8359 let receipt = seek_extent_async(
8360 &self.volume.fs.inner.objects,
8361 ExtentSeekRequest {
8362 root: extents,
8363 file_size: logical_bytes,
8364 offset,
8365 target,
8366 limits: decode_limits(self.volume.config),
8367 budget: remaining(work, budget)?,
8368 },
8369 cancellation,
8370 )
8371 .await
8372 .map_err(|failure| {
8373 failure.map_with_prior_work(work, |error| {
8374 FsError::FileRead(FileRangeReadError::Extent(error))
8375 })
8376 })?;
8377 work = add(work, receipt.work)?;
8378 receipt.value
8379 }
8380 }
8381 FilePayload::Directory { .. }
8382 | FilePayload::SymbolicLink { .. }
8383 | FilePayload::Empty
8384 | FilePayload::Device { .. }
8385 | FilePayload::ReparsePoint { .. } => {
8386 return Err(OperationFailure::new(
8387 FsError::FileRead(FileRangeReadError::NotRegular),
8388 work,
8389 ));
8390 }
8391 };
8392 if self.mode.consistency != ConsistencyMode::Pinned {
8393 work = self
8394 .observe_identity_region(
8395 DependencyRegion::SparseSeek {
8396 file_id,
8397 offset,
8398 target,
8399 },
8400 work,
8401 budget,
8402 cancellation,
8403 )
8404 .await?;
8405 }
8406 Ok(FsReceipt { value, work })
8407 }
8408
8409 pub async fn read_metadata_by_id(
8416 &mut self,
8417 file_id: FileId,
8418 budget: WorkBudget,
8419 cancellation: &CancellationToken,
8420 ) -> FsResult<FileMetadata> {
8421 let lookup = self
8422 .lookup_file_record_by_id(file_id, budget, cancellation)
8423 .await?;
8424 let mut work = lookup.work;
8425 let record = lookup
8426 .value
8427 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8428 let metadata = self
8429 .volume
8430 .fs
8431 .inner
8432 .objects
8433 .read(
8434 record.metadata,
8435 self.volume.config.limits.maximum_object_bytes,
8436 remaining(work, budget)?,
8437 cancellation,
8438 )
8439 .await
8440 .map_err(|failure| failure.map_with_prior_work(work, FsError::Object))?;
8441 work = add(work, metadata.work)?;
8442 let value = decode_file_metadata(&metadata.value, decode_limits(self.volume.config))
8443 .map_err(|error| OperationFailure::new(error.into(), work))?;
8444 if self.mode.consistency != ConsistencyMode::Pinned {
8445 work = self
8446 .observe_base_metadata_ids(
8447 std::slice::from_ref(&file_id),
8448 work,
8449 budget,
8450 cancellation,
8451 )
8452 .await?;
8453 }
8454 Ok(FsReceipt { value, work })
8455 }
8456
8457 pub async fn read_named_attribute_by_id(
8464 &mut self,
8465 file_id: FileId,
8466 name: &AttributeName,
8467 budget: WorkBudget,
8468 cancellation: &CancellationToken,
8469 ) -> FsResult<Option<Bytes>> {
8470 let metadata = self
8471 .read_metadata_by_id(file_id, budget, cancellation)
8472 .await?;
8473 let mut work = metadata.work;
8474 let MetadataField::Value(root) = metadata.value.named_attributes else {
8475 return Ok(FsReceipt { value: None, work });
8476 };
8477 let lookup = lookup_attribute_async(
8478 &self.volume.fs.inner.objects,
8479 root,
8480 name,
8481 decode_limits(self.volume.config),
8482 remaining(work, budget)?,
8483 cancellation,
8484 )
8485 .await
8486 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
8487 work = add(work, lookup.work)?;
8488 let Some(entry) = lookup.entry else {
8489 return Ok(FsReceipt { value: None, work });
8490 };
8491 if entry.value_bytes > self.volume.config.limits.maximum_read_bytes {
8492 return Err(OperationFailure::new(
8493 FsError::FileRead(FileRangeReadError::InvalidRange),
8494 work,
8495 ));
8496 }
8497 let read = read_blob_range_async(
8498 &self.volume.fs.inner.objects,
8499 entry.value,
8500 ByteRange {
8501 offset: 0,
8502 length: entry.value_bytes,
8503 },
8504 decode_limits(self.volume.config),
8505 remaining(work, budget)?,
8506 cancellation,
8507 )
8508 .await
8509 .map_err(|failure| failure.map_with_prior_work(work, FsError::BlobRead))?;
8510 work = add(work, read.work)?;
8511 Ok(FsReceipt {
8512 value: Some(read.bytes),
8513 work,
8514 })
8515 }
8516
8517 pub async fn list_named_attributes_by_id(
8524 &mut self,
8525 file_id: FileId,
8526 after: Option<&AttributeName>,
8527 maximum_entries: u32,
8528 budget: WorkBudget,
8529 cancellation: &CancellationToken,
8530 ) -> FsResult<AttributeListing> {
8531 let metadata = self
8532 .read_metadata_by_id(file_id, budget, cancellation)
8533 .await?;
8534 let mut work = metadata.work;
8535 let MetadataField::Value(root) = metadata.value.named_attributes else {
8536 let value = AttributeListing {
8537 entries: Vec::new(),
8538 has_more: false,
8539 next_residency: None,
8540 work,
8541 };
8542 return Ok(FsReceipt { value, work });
8543 };
8544 let mut listing = list_attributes_async(
8545 &self.volume.fs.inner.objects,
8546 root,
8547 after,
8548 maximum_entries,
8549 decode_limits(self.volume.config),
8550 remaining(work, budget)?,
8551 cancellation,
8552 )
8553 .await
8554 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeList))?;
8555 work = add(work, listing.work)?;
8556 listing.work = work;
8557 Ok(FsReceipt {
8558 value: listing,
8559 work,
8560 })
8561 }
8562
8563 pub async fn write_named_attribute_by_id(
8570 &mut self,
8571 file_id: FileId,
8572 name: AttributeName,
8573 value: Bytes,
8574 mode: NamedAttributeWriteMode,
8575 budget: WorkBudget,
8576 cancellation: &CancellationToken,
8577 ) -> FsResult<()> {
8578 let metadata = self
8579 .read_metadata_by_id(file_id, budget, cancellation)
8580 .await?;
8581 let mut work = metadata.work;
8582 let mut metadata_value = metadata.value;
8583 let root = match metadata_value.named_attributes {
8584 MetadataField::Unavailable => {
8585 let empty = self
8586 .stage_empty_attributes(remaining(work, budget)?, cancellation)
8587 .await
8588 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8589 work = add(work, empty.work)?;
8590 empty.value
8591 }
8592 MetadataField::Value(root) => root,
8593 };
8594 let current = lookup_attribute_async(
8595 &self.volume.fs.inner.objects,
8596 root,
8597 &name,
8598 decode_limits(self.volume.config),
8599 remaining(work, budget)?,
8600 cancellation,
8601 )
8602 .await
8603 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
8604 work = add(work, current.work)?;
8605 match (mode, current.entry.as_ref()) {
8606 (NamedAttributeWriteMode::Create, Some(_))
8607 | (NamedAttributeWriteMode::Replace, None) => {
8608 return Err(OperationFailure::new(FsError::CreationRejected, work));
8609 }
8610 _ => {}
8611 }
8612 let blob = self
8613 .stage_blob(value, remaining(work, budget)?, cancellation)
8614 .await
8615 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8616 work = add(work, blob.work)?;
8617 let replacement = AttributeEntry {
8618 name,
8619 value_bytes: blob.value.logical_bytes,
8620 value: blob.value.root,
8621 };
8622 let mutation = match current.entry {
8623 None => AttributeMutation::Insert(replacement),
8624 Some(expected) => AttributeMutation::Replace {
8625 expected,
8626 replacement,
8627 },
8628 };
8629 let changed = apply_attribute_mutations_async(
8630 &self.volume.fs.inner.objects,
8631 root,
8632 vec![mutation],
8633 1,
8634 decode_limits(self.volume.config),
8635 remaining(work, budget)?,
8636 cancellation,
8637 )
8638 .await
8639 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
8640 work = add(work, changed.work)?;
8641 metadata_value.named_attributes = MetadataField::Value(changed.root);
8642 let changed_metadata = self
8643 .set_metadata_by_id(
8644 file_id,
8645 metadata_value,
8646 remaining(work, budget)?,
8647 cancellation,
8648 )
8649 .await
8650 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8651 work = add(work, changed_metadata.work)?;
8652 Ok(FsReceipt { value: (), work })
8653 }
8654
8655 pub async fn remove_named_attribute_by_id(
8662 &mut self,
8663 file_id: FileId,
8664 name: AttributeName,
8665 budget: WorkBudget,
8666 cancellation: &CancellationToken,
8667 ) -> FsResult<()> {
8668 let metadata = self
8669 .read_metadata_by_id(file_id, budget, cancellation)
8670 .await?;
8671 let mut work = metadata.work;
8672 let mut metadata_value = metadata.value;
8673 let MetadataField::Value(root) = metadata_value.named_attributes else {
8674 return Err(OperationFailure::new(FsError::NotFound, work));
8675 };
8676 let current = lookup_attribute_async(
8677 &self.volume.fs.inner.objects,
8678 root,
8679 &name,
8680 decode_limits(self.volume.config),
8681 remaining(work, budget)?,
8682 cancellation,
8683 )
8684 .await
8685 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
8686 work = add(work, current.work)?;
8687 let expected = current
8688 .entry
8689 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
8690 let changed = apply_attribute_mutations_async(
8691 &self.volume.fs.inner.objects,
8692 root,
8693 vec![AttributeMutation::Remove {
8694 name,
8695 expected: Some(expected),
8696 }],
8697 1,
8698 decode_limits(self.volume.config),
8699 remaining(work, budget)?,
8700 cancellation,
8701 )
8702 .await
8703 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
8704 work = add(work, changed.work)?;
8705 metadata_value.named_attributes = MetadataField::Value(changed.root);
8706 let changed_metadata = self
8707 .set_metadata_by_id(
8708 file_id,
8709 metadata_value,
8710 remaining(work, budget)?,
8711 cancellation,
8712 )
8713 .await
8714 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8715 work = add(work, changed_metadata.work)?;
8716 Ok(FsReceipt { value: (), work })
8717 }
8718
8719 pub async fn write_file_by_id(
8726 &mut self,
8727 file_id: FileId,
8728 offset: u64,
8729 bytes: Bytes,
8730 budget: WorkBudget,
8731 cancellation: &CancellationToken,
8732 ) -> FsResult<()> {
8733 if bytes.is_empty() {
8734 return self
8735 .mutate(
8736 vec![Mutation::File {
8737 file_id,
8738 mutation: FileMutation::ValidateRegular,
8739 }],
8740 budget,
8741 cancellation,
8742 )
8743 .await;
8744 }
8745 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
8746 > self.volume.config.limits.maximum_read_bytes
8747 {
8748 return Err(OperationFailure::before_work(FsError::FileRead(
8749 FileRangeReadError::InvalidRange,
8750 )));
8751 }
8752 let blob = self.stage_blob(bytes, budget, cancellation).await?;
8753 let mut work = blob.work;
8754 let mutation = self
8755 .mutate(
8756 vec![Mutation::File {
8757 file_id,
8758 mutation: FileMutation::Write {
8759 offset,
8760 length: blob.value.logical_bytes,
8761 content: blob.value.root,
8762 content_offset: 0,
8763 },
8764 }],
8765 remaining(work, budget)?,
8766 cancellation,
8767 )
8768 .await
8769 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8770 work = add(work, mutation.work)?;
8771 Ok(FsReceipt { value: (), work })
8772 }
8773
8774 pub async fn set_metadata_by_id(
8781 &mut self,
8782 file_id: FileId,
8783 metadata: FileMetadata,
8784 budget: WorkBudget,
8785 cancellation: &CancellationToken,
8786 ) -> FsResult<()> {
8787 let encoded = encode_file_metadata(metadata)
8788 .map_err(|error| OperationFailure::before_work(error.into()))?;
8789 let (metadata, mut work) = self
8790 .volume
8791 .fs
8792 .put_encoded(
8793 ObjectKind::Metadata,
8794 encoded,
8795 WorkCounters::default(),
8796 budget,
8797 cancellation,
8798 )
8799 .await?;
8800 let mutation = self
8801 .mutate(
8802 vec![Mutation::File {
8803 file_id,
8804 mutation: FileMutation::SetMetadata { metadata },
8805 }],
8806 remaining(work, budget)?,
8807 cancellation,
8808 )
8809 .await
8810 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8811 work = add(work, mutation.work)?;
8812 Ok(FsReceipt { value: (), work })
8813 }
8814
8815 pub async fn set_attributes_by_id(
8824 &mut self,
8825 file_id: FileId,
8826 metadata: FileMetadata,
8827 logical_bytes: Option<u64>,
8828 budget: WorkBudget,
8829 cancellation: &CancellationToken,
8830 ) -> FsResult<()> {
8831 let encoded = encode_file_metadata(metadata)
8832 .map_err(|error| OperationFailure::before_work(error.into()))?;
8833 let (metadata, mut work) = self
8834 .volume
8835 .fs
8836 .put_encoded(
8837 ObjectKind::Metadata,
8838 encoded,
8839 WorkCounters::default(),
8840 budget,
8841 cancellation,
8842 )
8843 .await?;
8844 let mut operations = vec![Mutation::File {
8845 file_id,
8846 mutation: FileMutation::SetMetadata { metadata },
8847 }];
8848 if let Some(logical_bytes) = logical_bytes {
8849 operations.push(Mutation::File {
8850 file_id,
8851 mutation: FileMutation::Resize { logical_bytes },
8852 });
8853 }
8854 let mutation = self
8855 .mutate(operations, remaining(work, budget)?, cancellation)
8856 .await
8857 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
8858 work = add(work, mutation.work)?;
8859 Ok(FsReceipt { value: (), work })
8860 }
8861
8862 pub async fn resize_file_by_id(
8868 &mut self,
8869 file_id: FileId,
8870 logical_bytes: u64,
8871 budget: WorkBudget,
8872 cancellation: &CancellationToken,
8873 ) -> FsResult<()> {
8874 self.mutate(
8875 vec![Mutation::File {
8876 file_id,
8877 mutation: FileMutation::Resize { logical_bytes },
8878 }],
8879 budget,
8880 cancellation,
8881 )
8882 .await
8883 }
8884
8885 pub async fn zero_file_range_by_id(
8891 &mut self,
8892 file_id: FileId,
8893 range: ByteRange,
8894 allocated: bool,
8895 extend: bool,
8896 budget: WorkBudget,
8897 cancellation: &CancellationToken,
8898 ) -> FsResult<()> {
8899 self.mutate(
8900 vec![Mutation::File {
8901 file_id,
8902 mutation: FileMutation::ZeroRange {
8903 offset: range.offset,
8904 length: range.length,
8905 allocated,
8906 extend,
8907 },
8908 }],
8909 budget,
8910 cancellation,
8911 )
8912 .await
8913 }
8914
8915 pub async fn preallocate_file_by_id(
8921 &mut self,
8922 file_id: FileId,
8923 range: ByteRange,
8924 keep_size: bool,
8925 budget: WorkBudget,
8926 cancellation: &CancellationToken,
8927 ) -> FsResult<()> {
8928 self.mutate(
8929 vec![Mutation::File {
8930 file_id,
8931 mutation: FileMutation::Preallocate {
8932 offset: range.offset,
8933 length: range.length,
8934 keep_size,
8935 },
8936 }],
8937 budget,
8938 cancellation,
8939 )
8940 .await
8941 }
8942
8943 #[allow(clippy::too_many_arguments)]
8949 pub async fn clone_file_range_by_id(
8950 &mut self,
8951 source_file_id: FileId,
8952 source_offset: u64,
8953 destination_file_id: FileId,
8954 destination_offset: u64,
8955 length: u64,
8956 budget: WorkBudget,
8957 cancellation: &CancellationToken,
8958 ) -> FsResult<()> {
8959 self.mutate(
8960 vec![Mutation::CloneFileRange {
8961 source_file_id,
8962 source_offset,
8963 destination_file_id,
8964 destination_offset,
8965 length,
8966 }],
8967 budget,
8968 cancellation,
8969 )
8970 .await
8971 }
8972
8973 pub async fn remove(
8979 &mut self,
8980 path: NamespacePath,
8981 expected_file_id: Option<FileId>,
8982 budget: WorkBudget,
8983 cancellation: &CancellationToken,
8984 ) -> FsResult<()> {
8985 self.mutate(
8986 vec![Mutation::Remove {
8987 path,
8988 expected_file_id: expected_file_id
8989 .map_or(MetadataField::Unavailable, MetadataField::Value),
8990 }],
8991 budget,
8992 cancellation,
8993 )
8994 .await
8995 }
8996
8997 pub async fn rename(
9003 &mut self,
9004 source: NamespacePath,
9005 destination: NamespacePath,
9006 replace: bool,
9007 budget: WorkBudget,
9008 cancellation: &CancellationToken,
9009 ) -> FsResult<()> {
9010 self.mutate(
9011 vec![Mutation::Rename {
9012 source,
9013 destination,
9014 replace,
9015 }],
9016 budget,
9017 cancellation,
9018 )
9019 .await
9020 }
9021
9022 pub async fn hard_link(
9028 &mut self,
9029 source: NamespacePath,
9030 destination: NamespacePath,
9031 budget: WorkBudget,
9032 cancellation: &CancellationToken,
9033 ) -> FsResult<()> {
9034 self.mutate(
9035 vec![Mutation::Link {
9036 source,
9037 destination,
9038 }],
9039 budget,
9040 cancellation,
9041 )
9042 .await
9043 }
9044
9045 pub async fn set_metadata(
9055 &mut self,
9056 path: NamespacePath,
9057 metadata: FileMetadata,
9058 budget: WorkBudget,
9059 cancellation: &CancellationToken,
9060 ) -> FsResult<()> {
9061 let mut work = WorkCounters::default();
9062 let encoded = encode_file_metadata(metadata)
9063 .map_err(|error| OperationFailure::new(error.into(), work))?;
9064 let stored = self
9065 .volume
9066 .fs
9067 .put_encoded(ObjectKind::Metadata, encoded, work, budget, cancellation)
9068 .await?;
9069 work = stored.1;
9070 let mutation = self
9071 .mutate(
9072 vec![Mutation::SetMetadata {
9073 path,
9074 metadata: stored.0,
9075 }],
9076 remaining(work, budget)?,
9077 cancellation,
9078 )
9079 .await
9080 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
9081 work = add(work, mutation.work)?;
9082 Ok(FsReceipt { value: (), work })
9083 }
9084
9085 pub async fn set_attributes(
9095 &mut self,
9096 path: NamespacePath,
9097 metadata: FileMetadata,
9098 logical_bytes: Option<u64>,
9099 budget: WorkBudget,
9100 cancellation: &CancellationToken,
9101 ) -> FsResult<()> {
9102 let lookup = self
9103 .lookup_no_follow_observed(&path, budget, cancellation, true)
9104 .await?;
9105 let mut work = lookup.work;
9106 let file_id = lookup
9107 .value
9108 .record
9109 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?
9110 .file_id;
9111 let mutation = self
9112 .set_attributes_by_id(
9113 file_id,
9114 metadata,
9115 logical_bytes,
9116 remaining(work, budget)?,
9117 cancellation,
9118 )
9119 .await
9120 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
9121 work = add(work, mutation.work)?;
9122 Ok(FsReceipt { value: (), work })
9123 }
9124
9125 pub async fn resize_file(
9131 &mut self,
9132 path: NamespacePath,
9133 logical_bytes: u64,
9134 budget: WorkBudget,
9135 cancellation: &CancellationToken,
9136 ) -> FsResult<()> {
9137 self.mutate(
9138 vec![Mutation::Resize {
9139 path,
9140 logical_bytes,
9141 }],
9142 budget,
9143 cancellation,
9144 )
9145 .await
9146 }
9147
9148 pub async fn zero_file_range(
9154 &mut self,
9155 path: NamespacePath,
9156 range: ByteRange,
9157 allocated: bool,
9158 extend: bool,
9159 budget: WorkBudget,
9160 cancellation: &CancellationToken,
9161 ) -> FsResult<()> {
9162 self.mutate(
9163 vec![Mutation::ZeroRange {
9164 path,
9165 offset: range.offset,
9166 length: range.length,
9167 allocated,
9168 extend,
9169 }],
9170 budget,
9171 cancellation,
9172 )
9173 .await
9174 }
9175
9176 pub async fn preallocate_file(
9187 &mut self,
9188 path: NamespacePath,
9189 range: ByteRange,
9190 keep_size: bool,
9191 budget: WorkBudget,
9192 cancellation: &CancellationToken,
9193 ) -> FsResult<()> {
9194 self.mutate(
9195 vec![Mutation::Preallocate {
9196 path,
9197 offset: range.offset,
9198 length: range.length,
9199 keep_size,
9200 }],
9201 budget,
9202 cancellation,
9203 )
9204 .await
9205 }
9206
9207 pub async fn clone_file_range(
9213 &mut self,
9214 request: FileCloneRequest,
9215 budget: WorkBudget,
9216 cancellation: &CancellationToken,
9217 ) -> FsResult<()> {
9218 self.mutate(
9219 vec![Mutation::CloneRange {
9220 source: request.source,
9221 source_offset: request.source_offset,
9222 destination: request.destination,
9223 destination_offset: request.destination_offset,
9224 length: request.length,
9225 }],
9226 budget,
9227 cancellation,
9228 )
9229 .await
9230 }
9231
9232 pub async fn stage_content<R: AsyncBlobSource>(
9245 &self,
9246 source: &mut R,
9247 maximum_source_bytes: u64,
9248 budget: WorkBudget,
9249 cancellation: &CancellationToken,
9250 ) -> FsResult<StagedContent> {
9251 stage_content_for_volume(
9252 &self.volume,
9253 source,
9254 maximum_source_bytes,
9255 budget,
9256 cancellation,
9257 )
9258 .await
9259 }
9260
9261 async fn stage_blob(
9262 &self,
9263 bytes: Bytes,
9264 budget: WorkBudget,
9265 cancellation: &CancellationToken,
9266 ) -> FsResult<crate::kernel::BlobBuild> {
9267 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
9268 > self.volume.config.limits.maximum_read_bytes
9269 {
9270 return Err(OperationFailure::before_work(FsError::FileRead(
9271 FileRangeReadError::InvalidRange,
9272 )));
9273 }
9274 let maximum_blob_bytes = u64::try_from(bytes.len()).unwrap_or(u64::MAX).max(1);
9275 let mut source = std::io::Cursor::new(bytes);
9276 self.stage_blob_source(&mut source, maximum_blob_bytes, budget, cancellation)
9277 .await
9278 }
9279
9280 async fn stage_blob_source<R: AsyncBlobSource>(
9281 &self,
9282 source: &mut R,
9283 maximum_blob_bytes: u64,
9284 budget: WorkBudget,
9285 cancellation: &CancellationToken,
9286 ) -> FsResult<crate::kernel::BlobBuild> {
9287 let staged = stage_content_for_volume(
9288 &self.volume,
9289 source,
9290 maximum_blob_bytes,
9291 budget,
9292 cancellation,
9293 )
9294 .await?;
9295 Ok(FsReceipt {
9296 work: staged.work,
9297 value: crate::kernel::BlobBuild {
9298 root: staged.value.root,
9299 logical_bytes: staged.value.logical_bytes,
9300 work: staged.work,
9301 },
9302 })
9303 }
9304
9305 pub async fn commit(
9316 &mut self,
9317 operation_id: OperationId,
9318 budget: WorkBudget,
9319 cancellation: &CancellationToken,
9320 ) -> FsResult<CheckoutCommitOutcome> {
9321 if self.mode.access != AccessMode::ReadWrite
9322 || self.mode.mutations != MutationMode::PrivateOverlay
9323 {
9324 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
9325 }
9326 self.publish_pending(operation_id, None, budget, cancellation)
9327 .await
9328 }
9329
9330 pub async fn commit_with_permit(
9333 &mut self,
9334 operation_id: OperationId,
9335 permit: PublicationPermit,
9336 budget: WorkBudget,
9337 cancellation: &CancellationToken,
9338 ) -> FsResult<CheckoutCommitOutcome> {
9339 if self.mode.access != AccessMode::ReadWrite
9340 || self.mode.mutations != MutationMode::PrivateOverlay
9341 {
9342 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
9343 }
9344 self.publish_pending_with_permit(operation_id, None, permit, budget, cancellation)
9345 .await
9346 }
9347
9348 #[cfg(feature = "native-mount")]
9351 pub(crate) async fn commit_with_permit_even_if_clean(
9352 &mut self,
9353 operation_id: OperationId,
9354 permit: PublicationPermit,
9355 budget: WorkBudget,
9356 cancellation: &CancellationToken,
9357 ) -> FsResult<CheckoutCommitOutcome> {
9358 if self.mode.access != AccessMode::ReadWrite
9359 || self.mode.mutations != MutationMode::PrivateOverlay
9360 {
9361 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
9362 }
9363 let expected = self
9364 .authority_head
9365 .ok_or_else(|| OperationFailure::before_work(FsError::WritableCheckoutRequiresHead))?;
9366 self.publish_pending_against(operation_id, None, permit, expected, budget, cancellation)
9367 .await
9368 }
9369
9370 #[cfg(all(feature = "native-watch", not(target_arch = "wasm32")))]
9371 pub(crate) async fn commit_with_context(
9372 &mut self,
9373 operation_id: OperationId,
9374 operation_context: Digest,
9375 budget: WorkBudget,
9376 cancellation: &CancellationToken,
9377 ) -> FsResult<CheckoutCommitOutcome> {
9378 if self.mode.access != AccessMode::ReadWrite
9379 || self.mode.mutations != MutationMode::PrivateOverlay
9380 {
9381 return Err(OperationFailure::before_work(FsError::MutationNotAllowed));
9382 }
9383 let expected = self
9384 .authority_head
9385 .ok_or_else(|| OperationFailure::before_work(FsError::WritableCheckoutRequiresHead))?;
9386 self.publish_pending_against(
9389 operation_id,
9390 Some(operation_context),
9391 PublicationPermit::Unrestricted,
9392 expected,
9393 budget,
9394 cancellation,
9395 )
9396 .await
9397 }
9398
9399 async fn publish_pending(
9400 &mut self,
9401 operation_id: OperationId,
9402 operation_context: Option<Digest>,
9403 budget: WorkBudget,
9404 cancellation: &CancellationToken,
9405 ) -> FsResult<CheckoutCommitOutcome> {
9406 self.publish_pending_with_permit(
9407 operation_id,
9408 operation_context,
9409 PublicationPermit::Unrestricted,
9410 budget,
9411 cancellation,
9412 )
9413 .await
9414 }
9415
9416 async fn publish_pending_with_permit(
9417 &mut self,
9418 operation_id: OperationId,
9419 operation_context: Option<Digest>,
9420 permit: PublicationPermit,
9421 budget: WorkBudget,
9422 cancellation: &CancellationToken,
9423 ) -> FsResult<CheckoutCommitOutcome> {
9424 if !self.has_pending_mutations() && permit == PublicationPermit::Unrestricted {
9425 return self
9426 .resolve_clean_commit(operation_id, budget, cancellation)
9427 .await;
9428 }
9429 let expected = self
9430 .authority_head
9431 .ok_or_else(|| OperationFailure::before_work(FsError::WritableCheckoutRequiresHead))?;
9432 self.publish_pending_against(
9433 operation_id,
9434 operation_context,
9435 permit,
9436 expected,
9437 budget,
9438 cancellation,
9439 )
9440 .await
9441 }
9442
9443 async fn publish_pending_against(
9444 &mut self,
9445 operation_id: OperationId,
9446 operation_context: Option<Digest>,
9447 permit: PublicationPermit,
9448 expected: Head,
9449 budget: WorkBudget,
9450 cancellation: &CancellationToken,
9451 ) -> FsResult<CheckoutCommitOutcome> {
9452 cancellation
9453 .check()
9454 .map_err(|error| OperationFailure::before_work(error.into()))?;
9455 let checkpoint = self.checkpoint_root(budget, cancellation).await?;
9456 let checkpoint_root = checkpoint.value;
9457 let mut work = checkpoint.work;
9458 let request = PublishGenerationRequest {
9459 authority_id: volume_authority_id(self.volume.id),
9460 volume_id: self.volume.id,
9461 epoch: expected.epoch,
9462 expected,
9463 operation_id,
9464 generation_root: checkpoint_root,
9465 };
9466 let publication = if permit != PublicationPermit::Unrestricted {
9467 publish_generation_async_with_permit(
9468 &self.volume.fs.inner.objects,
9469 &self.volume.fs.inner.authority,
9470 request,
9471 PublicationIntent {
9472 operation_context,
9473 permit,
9474 },
9475 closure_limits(self.volume.config),
9476 remaining(work, budget)?,
9477 cancellation,
9478 )
9479 .await
9480 } else if let Some(context) = operation_context {
9481 publish_generation_async_with_context(
9482 &self.volume.fs.inner.objects,
9483 &self.volume.fs.inner.authority,
9484 request,
9485 context,
9486 closure_limits(self.volume.config),
9487 remaining(work, budget)?,
9488 cancellation,
9489 )
9490 .await
9491 } else {
9492 publish_generation_async(
9493 &self.volume.fs.inner.objects,
9494 &self.volume.fs.inner.authority,
9495 request,
9496 closure_limits(self.volume.config),
9497 remaining(work, budget)?,
9498 cancellation,
9499 )
9500 .await
9501 }
9502 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
9503 work = add(work, publication.work)?;
9504 let outcome = self.apply_publication(checkpoint_root, operation_id, publication);
9505 Ok(FsReceipt {
9506 value: outcome,
9507 work,
9508 })
9509 }
9510
9511 fn apply_publication(
9512 &mut self,
9513 checkpoint_root: ObjectId,
9514 operation_id: OperationId,
9515 publication: PublicationReceipt,
9516 ) -> CheckoutCommitOutcome {
9517 let generation_id = publication.proof.generation_id;
9518 match publication.outcome {
9519 AppendOutcome::Committed(commit) => {
9520 let head = durable_head(&commit);
9521 self.base_generation_root = checkpoint_root;
9522 self.generation_root = checkpoint_root;
9523 self.root = publication.proof.root;
9524 self.base_root = self.root.clone();
9525 self.base_file_table = self.root.file_table;
9526 self.authority_head = Some(head);
9527 self.prepared_merge_parent = None;
9528 self.dependencies.clear();
9529 self.last_commit = Some(LastCommit {
9530 operation_id,
9531 generation_id,
9532 head,
9533 });
9534 CheckoutCommitOutcome::Committed {
9535 generation_id,
9536 head,
9537 }
9538 }
9539 AppendOutcome::AlreadyCommitted(commit) => {
9540 let head = durable_head(&commit);
9541 self.base_generation_root = checkpoint_root;
9542 self.generation_root = checkpoint_root;
9543 self.root = publication.proof.root;
9544 self.base_root = self.root.clone();
9545 self.base_file_table = self.root.file_table;
9546 self.authority_head = Some(head);
9547 self.prepared_merge_parent = None;
9548 self.dependencies.clear();
9549 self.last_commit = Some(LastCommit {
9550 operation_id,
9551 generation_id,
9552 head,
9553 });
9554 CheckoutCommitOutcome::AlreadyCommitted {
9555 generation_id,
9556 head,
9557 }
9558 }
9559 AppendOutcome::Conflict { actual } => CheckoutCommitOutcome::Conflict { actual },
9560 AppendOutcome::Fenced { actual_epoch } => {
9561 CheckoutCommitOutcome::Fenced { actual_epoch }
9562 }
9563 AppendOutcome::IdempotencyConflict {
9564 committed_fingerprint,
9565 } => CheckoutCommitOutcome::IdempotencyConflict {
9566 committed_fingerprint,
9567 },
9568 }
9569 }
9570
9571 pub(crate) async fn retry_stale_commit(
9572 &mut self,
9573 operation_id: OperationId,
9574 permit: PublicationPermit,
9575 budget: WorkBudget,
9576 cancellation: &CancellationToken,
9577 ) -> FsResult<Option<CheckoutCommitOutcome>> {
9578 cancellation
9579 .check()
9580 .map_err(|error| OperationFailure::before_work(error.into()))?;
9581 let resolved = self
9582 .volume
9583 .fs
9584 .inner
9585 .authority
9586 .find_operation(
9587 volume_authority_id(self.volume.id),
9588 operation_id,
9589 budget,
9590 cancellation,
9591 )
9592 .await
9593 .map_err(|failure| {
9594 OperationFailure::new(FsError::Authority(failure.error), *failure.work)
9595 })?;
9596 let Some(commit) = resolved.value else {
9597 return Ok(FsReceipt {
9598 value: None,
9599 work: resolved.work,
9600 });
9601 };
9602 let previous_sequence = commit.sequence.get().checked_sub(1).ok_or_else(|| {
9603 OperationFailure::new(FsError::InvalidAuthorityHistory, resolved.work)
9604 })?;
9605 let expected = Head {
9606 epoch: commit.epoch,
9607 sequence: Sequence::new(previous_sequence),
9608 digest: commit.previous_digest,
9609 };
9610 let retried = self
9611 .publish_pending_against(
9612 operation_id,
9613 None,
9614 permit,
9615 expected,
9616 remaining(resolved.work, budget)?,
9617 cancellation,
9618 )
9619 .await
9620 .map_err(|failure| {
9621 failure.map_with_prior_work(resolved.work, std::convert::identity)
9622 })?;
9623 Ok(FsReceipt {
9624 value: Some(retried.value),
9625 work: add(resolved.work, retried.work)?,
9626 })
9627 }
9628
9629 async fn resolve_clean_commit(
9630 &self,
9631 operation_id: OperationId,
9632 budget: WorkBudget,
9633 cancellation: &CancellationToken,
9634 ) -> FsResult<CheckoutCommitOutcome> {
9635 if let Some(last) = self.last_commit {
9636 if last.operation_id != operation_id {
9637 return Err(OperationFailure::before_work(FsError::NoPendingMutations));
9638 }
9639 if last.generation_id == GenerationId::new(self.generation_root.digest) {
9640 return Ok(FsReceipt {
9641 value: CheckoutCommitOutcome::AlreadyCommitted {
9642 generation_id: last.generation_id,
9643 head: last.head,
9644 },
9645 work: WorkCounters::default(),
9646 });
9647 }
9648 }
9649 cancellation
9650 .check()
9651 .map_err(|error| OperationFailure::before_work(error.into()))?;
9652 let head = self
9653 .authority_head
9654 .ok_or_else(|| OperationFailure::before_work(FsError::WritableCheckoutRequiresHead))?;
9655 let resolved = self
9656 .volume
9657 .fs
9658 .inner
9659 .authority
9660 .find_operation(
9661 volume_authority_id(self.volume.id),
9662 operation_id,
9663 budget,
9664 cancellation,
9665 )
9666 .await
9667 .map_err(|failure| {
9668 OperationFailure::new(FsError::Authority(failure.error), *failure.work)
9669 })?;
9670 let Some(commit) = resolved.value else {
9671 return Err(OperationFailure::new(
9672 FsError::NoPendingMutations,
9673 resolved.work,
9674 ));
9675 };
9676 let generation_root = generation_from_record(&commit, self.volume.id, resolved.work)?;
9677 let value = if generation_root == self.generation_root {
9678 CheckoutCommitOutcome::AlreadyCommitted {
9679 generation_id: GenerationId::new(generation_root.digest),
9680 head,
9681 }
9682 } else {
9683 CheckoutCommitOutcome::IdempotencyConflict {
9684 committed_fingerprint: commit.fingerprint,
9685 }
9686 };
9687 Ok(FsReceipt {
9688 value,
9689 work: resolved.work,
9690 })
9691 }
9692
9693 pub async fn lookup_no_follow(
9701 &mut self,
9702 path: &NamespacePath,
9703 budget: WorkBudget,
9704 cancellation: &CancellationToken,
9705 ) -> FsResult<PathLookup> {
9706 self.lookup_no_follow_observed(path, budget, cancellation, true)
9707 .await
9708 }
9709
9710 pub async fn lookup_no_follow_with_metadata(
9718 &mut self,
9719 path: &NamespacePath,
9720 budget: WorkBudget,
9721 cancellation: &CancellationToken,
9722 ) -> FsResult<Option<PathMetadataLookup>> {
9723 let lookup = self
9724 .lookup_no_follow_observed(path, budget, cancellation, false)
9725 .await?;
9726 let mut work = lookup.work;
9727 let Some(record) = lookup.value.record else {
9728 return Ok(FsReceipt { value: None, work });
9729 };
9730 let metadata = self
9731 .volume
9732 .fs
9733 .inner
9734 .objects
9735 .read(
9736 record.metadata,
9737 self.volume.config.limits.maximum_object_bytes,
9738 remaining(work, budget)?,
9739 cancellation,
9740 )
9741 .await
9742 .map_err(|failure| failure.map_with_prior_work(work, FsError::Object))?;
9743 work = add(work, metadata.work)?;
9744 let metadata = decode_file_metadata(&metadata.value, decode_limits(self.volume.config))
9745 .map_err(|error| OperationFailure::new(error.into(), work))?;
9746 if self.mode.consistency != ConsistencyMode::Pinned {
9747 work = self
9748 .observe_base_metadata_ids(
9749 std::slice::from_ref(&record.file_id),
9750 work,
9751 budget,
9752 cancellation,
9753 )
9754 .await?;
9755 }
9756 Ok(FsReceipt {
9757 value: Some(PathMetadataLookup { record, metadata }),
9758 work,
9759 })
9760 }
9761
9762 async fn lookup_no_follow_observed(
9763 &mut self,
9764 path: &NamespacePath,
9765 budget: WorkBudget,
9766 cancellation: &CancellationToken,
9767 capture_terminal: bool,
9768 ) -> FsResult<PathLookup> {
9769 let synchronized = self.synchronize_live(budget, cancellation).await?;
9770 let prior = synchronized.work;
9771 let mut lookup = Box::pin(self.lookup_no_follow_at_current(
9772 path,
9773 remaining(prior, budget)?,
9774 cancellation,
9775 capture_terminal,
9776 ))
9777 .await
9778 .map_err(|failure| failure.map_with_prior_work(prior, std::convert::identity))?;
9779 let mut total = add(prior, lookup.work)?;
9780 if lookup.value.record.is_none()
9781 && self.volume.config.case_sensitivity == CaseSensitivity::ProfileFolded
9782 {
9783 let folded = Box::pin(self.resolve_profile_folded_path(
9788 path,
9789 remaining(total, budget)?,
9790 cancellation,
9791 capture_terminal,
9792 ))
9793 .await
9794 .map_err(|failure| failure.map_with_prior_work(total, std::convert::identity))?;
9795 total = add(total, folded.work)?;
9796 if let Some(resolved) = folded.value {
9797 lookup.value = resolved;
9798 }
9799 }
9800 lookup.work = total;
9801 lookup.value.work = total;
9802 Ok(lookup)
9803 }
9804
9805 async fn resolve_profile_folded_path(
9811 &mut self,
9812 path: &NamespacePath,
9813 budget: WorkBudget,
9814 cancellation: &CancellationToken,
9815 capture_terminal: bool,
9816 ) -> FsResult<Option<PathLookup>> {
9817 let mut work = WorkCounters::default();
9818 if path.is_root() {
9819 return Ok(FsReceipt { value: None, work });
9820 }
9821 let resolved = Box::pin(self.canonicalize_path_components(
9822 path.components(),
9823 &[],
9824 budget,
9825 cancellation,
9826 ))
9827 .await
9828 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
9829 work = add(work, resolved.work)?;
9830 let Some(corrected) = resolved.value else {
9831 return Ok(FsReceipt { value: None, work });
9832 };
9833 let retried = self
9834 .lookup_no_follow_at_current(
9835 &corrected,
9836 remaining(work, budget)?,
9837 cancellation,
9838 capture_terminal,
9839 )
9840 .await
9841 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
9842 work = add(work, retried.work)?;
9843 Ok(FsReceipt {
9844 value: Some(retried.value),
9845 work,
9846 })
9847 }
9848
9849 async fn lookup_no_follow_at_current(
9850 &mut self,
9851 path: &NamespacePath,
9852 budget: WorkBudget,
9853 cancellation: &CancellationToken,
9854 capture_terminal: bool,
9855 ) -> FsResult<PathLookup> {
9856 let lookup = if self.mode.consistency == ConsistencyMode::Pinned {
9857 crate::kernel::lookup_path_async(
9858 &self.volume.fs.inner.objects,
9859 &self.root,
9860 path,
9861 self.volume.config,
9862 budget,
9863 cancellation,
9864 )
9865 .await
9866 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?
9867 } else {
9868 let candidate = if self.has_pending_mutations() {
9869 Some(
9870 crate::kernel::lookup_path_async(
9871 &self.volume.fs.inner.objects,
9872 &self.root,
9873 path,
9874 self.volume.config,
9875 budget,
9876 cancellation,
9877 )
9878 .await
9879 .map_err(|failure| {
9880 OperationFailure::new(failure.error.into(), *failure.work)
9881 })?,
9882 )
9883 } else {
9884 None
9885 };
9886 let prior = candidate
9887 .as_ref()
9888 .map_or(WorkCounters::default(), |value| value.work);
9889 let observed = self
9890 .observe_base_for_candidate(
9891 path,
9892 prior,
9893 candidate.is_some(),
9894 capture_terminal,
9895 budget,
9896 cancellation,
9897 )
9898 .await?;
9899 let combined = observed.work;
9900 self.dependencies
9901 .extend_observations(
9902 observed.value.dependencies,
9903 self.volume.config.limits.maximum_checkout_dependencies,
9904 )
9905 .map_err(|error| OperationFailure::new(error.into(), combined))?;
9906 candidate.map_or(
9907 PathLookup {
9908 work: combined,
9909 ..observed.value.lookup
9910 },
9911 |value| PathLookup {
9912 work: combined,
9913 ..value
9914 },
9915 )
9916 };
9917 Ok(FsReceipt {
9918 work: lookup.work,
9919 value: lookup,
9920 })
9921 }
9922
9923 async fn observe_base_for_candidate(
9924 &self,
9925 path: &NamespacePath,
9926 prior: WorkCounters,
9927 has_candidate: bool,
9928 capture_terminal: bool,
9929 budget: WorkBudget,
9930 cancellation: &CancellationToken,
9931 ) -> FsResult<crate::kernel::ObservedPathLookup> {
9932 let observed = if capture_terminal {
9933 crate::kernel::observe_path_async(
9934 &self.volume.fs.inner.objects,
9935 &self.base_root,
9936 path,
9937 self.volume.config,
9938 remaining(prior, budget)?,
9939 cancellation,
9940 )
9941 .await
9942 } else {
9943 crate::kernel::observe_path_edges_async(
9944 &self.volume.fs.inner.objects,
9945 &self.base_root,
9946 path,
9947 self.volume.config,
9948 remaining(prior, budget)?,
9949 cancellation,
9950 )
9951 .await
9952 };
9953 let (observed, observation_prior) = match observed {
9954 Ok(observed) => (observed, prior),
9955 Err(failure)
9956 if has_candidate && matches!(failure.error, PathLookupError::NotDirectory) =>
9957 {
9958 let consumed = add(prior, *failure.work)?;
9961 let observed = crate::kernel::observe_path_edges_async(
9962 &self.volume.fs.inner.objects,
9963 &self.base_root,
9964 path,
9965 self.volume.config,
9966 remaining(consumed, budget)?,
9967 cancellation,
9968 )
9969 .await
9970 .map_err(|failure| failure.map_with_prior_work(consumed, FsError::Path))?;
9971 (observed, consumed)
9972 }
9973 Err(failure) => return Err(failure.map_with_prior_work(prior, FsError::Path)),
9974 };
9975 Ok(FsReceipt {
9976 work: add(observation_prior, observed.lookup.work)?,
9977 value: observed,
9978 })
9979 }
9980
9981 pub async fn lookup_batch_no_follow(
9991 &mut self,
9992 paths: &[NamespacePath],
9993 budget: WorkBudget,
9994 cancellation: &CancellationToken,
9995 ) -> FsResult<PathBatchLookup> {
9996 let synchronized = self.synchronize_live(budget, cancellation).await?;
9997 let prior = synchronized.work;
9998 let mut lookup = crate::kernel::lookup_paths_async(
9999 &self.volume.fs.inner.objects,
10000 &self.root,
10001 paths,
10002 self.volume.config,
10003 remaining(prior, budget)?,
10004 cancellation,
10005 )
10006 .await
10007 .map_err(|failure| failure.map_with_prior_work(prior, FsError::Path))?;
10008 lookup.work = add(prior, lookup.work)?;
10009 if self.mode.consistency != ConsistencyMode::Pinned {
10010 let mut work = lookup.work;
10011 for path in paths {
10012 let observed = crate::kernel::observe_path_async(
10013 &self.volume.fs.inner.objects,
10014 &self.base_root,
10015 path,
10016 self.volume.config,
10017 remaining(work, budget)?,
10018 cancellation,
10019 )
10020 .await
10021 .map_err(|failure| failure.map_with_prior_work(work, FsError::Path))?;
10022 work = add(work, observed.lookup.work)?;
10023 self.dependencies
10024 .extend_observations(
10025 observed.dependencies,
10026 self.volume.config.limits.maximum_checkout_dependencies,
10027 )
10028 .map_err(|error| OperationFailure::new(error.into(), work))?;
10029 }
10030 lookup.work = work;
10031 }
10032 Ok(FsReceipt {
10033 work: lookup.work,
10034 value: lookup,
10035 })
10036 }
10037
10038 pub async fn list_directory(
10048 &mut self,
10049 path: &NamespacePath,
10050 after: Option<&LogicalName>,
10051 maximum_entries: u32,
10052 budget: WorkBudget,
10053 cancellation: &CancellationToken,
10054 ) -> FsResult<DirectoryPage> {
10055 self.list_directory_with_bound(path, after, false, maximum_entries, budget, cancellation)
10056 .await
10057 }
10058
10059 async fn list_directory_with_bound(
10060 &mut self,
10061 path: &NamespacePath,
10062 after: Option<&LogicalName>,
10063 inclusive: bool,
10064 maximum_entries: u32,
10065 budget: WorkBudget,
10066 cancellation: &CancellationToken,
10067 ) -> FsResult<DirectoryPage> {
10068 let lookup = self
10069 .lookup_no_follow_observed(path, budget, cancellation, false)
10070 .await?;
10071 let mut work = lookup.work;
10072 let record = lookup
10073 .value
10074 .record
10075 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
10076 let FilePayload::Directory { entries } = record.payload else {
10077 return Err(OperationFailure::new(FsError::NotDirectory, work));
10078 };
10079 if record.kind != FileKind::Directory {
10080 return Err(OperationFailure::new(FsError::NotDirectory, work));
10081 }
10082 let mut page = if let Some(at) = after.filter(|_| inclusive) {
10083 list_tree_entries_at_or_after_async(
10084 &self.volume.fs.inner.objects,
10085 entries,
10086 at,
10087 maximum_entries,
10088 decode_limits(self.volume.config),
10089 remaining(work, budget)?,
10090 cancellation,
10091 )
10092 .await
10093 } else {
10094 list_tree_entries_async(
10095 &self.volume.fs.inner.objects,
10096 entries,
10097 after,
10098 maximum_entries,
10099 decode_limits(self.volume.config),
10100 remaining(work, budget)?,
10101 cancellation,
10102 )
10103 .await
10104 }
10105 .map_err(|failure| failure.map_with_prior_work(work, FsError::Directory))?;
10106 work = add(work, page.work)?;
10107 if self.mode.consistency != ConsistencyMode::Pinned {
10108 let region = DependencyRegion::DirectoryRange {
10109 directory_id: record.file_id,
10110 after: after.cloned(),
10111 maximum_entries,
10112 };
10113 let probe = AuthenticatedGenerationProbe::new(
10114 &self.volume.fs.inner.objects,
10115 probe_limits(self.volume.config),
10116 )
10117 .map_err(|error| OperationFailure::new(error.into(), work))?;
10118 let state = probe
10119 .probe_async(
10120 GenerationId::new(self.base_generation_root.digest),
10121 ®ion,
10122 remaining(work, budget)?,
10123 cancellation,
10124 )
10125 .await
10126 .map_err(|failure| failure.map_with_prior_work(work, FsError::Probe))?;
10127 work = add(work, state.work)?;
10128 self.dependencies
10129 .extend_observations(
10130 vec![Dependency {
10131 region,
10132 expected: state.value,
10133 }],
10134 self.volume.config.limits.maximum_checkout_dependencies,
10135 )
10136 .map_err(|error| OperationFailure::new(error.into(), work))?;
10137 }
10138 page.work = work;
10139 Ok(FsReceipt { value: page, work })
10140 }
10141
10142 pub async fn list_directory_records(
10154 &mut self,
10155 path: &NamespacePath,
10156 after: Option<&LogicalName>,
10157 maximum_entries: u32,
10158 budget: WorkBudget,
10159 cancellation: &CancellationToken,
10160 ) -> FsResult<DirectoryRecordPage> {
10161 self.list_directory_records_with_bound(
10162 path,
10163 after,
10164 false,
10165 maximum_entries,
10166 budget,
10167 cancellation,
10168 )
10169 .await
10170 }
10171
10172 pub(crate) async fn list_directory_records_at_or_after(
10173 &mut self,
10174 path: &NamespacePath,
10175 at: &LogicalName,
10176 maximum_entries: u32,
10177 budget: WorkBudget,
10178 cancellation: &CancellationToken,
10179 ) -> FsResult<DirectoryRecordPage> {
10180 if self.mode.consistency != ConsistencyMode::Pinned {
10181 return Err(OperationFailure::before_work(FsError::UnsupportedCheckout));
10182 }
10183 self.list_directory_records_with_bound(
10184 path,
10185 Some(at),
10186 true,
10187 maximum_entries,
10188 budget,
10189 cancellation,
10190 )
10191 .await
10192 }
10193
10194 async fn list_directory_records_with_bound(
10195 &mut self,
10196 path: &NamespacePath,
10197 after: Option<&LogicalName>,
10198 inclusive: bool,
10199 maximum_entries: u32,
10200 budget: WorkBudget,
10201 cancellation: &CancellationToken,
10202 ) -> FsResult<DirectoryRecordPage> {
10203 let listing = self
10204 .list_directory_with_bound(
10205 path,
10206 after,
10207 inclusive,
10208 maximum_entries,
10209 budget,
10210 cancellation,
10211 )
10212 .await?;
10213 let mut work = listing.work;
10214 let page = listing.value;
10215 if page.entries.is_empty() {
10216 return Ok(FsReceipt {
10217 value: DirectoryRecordPage {
10218 entries: Vec::new(),
10219 has_more: page.has_more,
10220 },
10221 work,
10222 });
10223 }
10224 let retained = u64::try_from(page.entries.len())
10225 .unwrap_or(u64::MAX)
10226 .saturating_mul(u64::try_from(size_of::<FileId>()).unwrap_or(u64::MAX));
10227 work = add(
10228 work,
10229 WorkCounters {
10230 allocation_operations: 1,
10231 peak_allocation_bytes: retained,
10232 ..WorkCounters::default()
10233 },
10234 )?;
10235 work.verify(budget)
10236 .map_err(|error| OperationFailure::new(error.into(), work))?;
10237 let mut file_ids = Vec::new();
10238 file_ids
10239 .try_reserve_exact(page.entries.len())
10240 .map_err(|_| OperationFailure::new(FsError::PendingMutationAllocationFailed, work))?;
10241 file_ids.extend(page.entries.iter().map(|entry| entry.file_id));
10242 let records = lookup_file_records_async(
10243 &self.volume.fs.inner.objects,
10244 self.root.file_table,
10245 &file_ids,
10246 maximum_entries,
10247 decode_limits(self.volume.config),
10248 remaining(work, budget)?,
10249 cancellation,
10250 )
10251 .await
10252 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
10253 work = add(work, records.work)?;
10254 drop(file_ids);
10255 let mut named_records = Vec::new();
10256 named_records
10257 .try_reserve_exact(page.entries.len())
10258 .map_err(|_| OperationFailure::new(FsError::PendingMutationAllocationFailed, work))?;
10259 for (binding, record) in page.entries.into_iter().zip(records.records) {
10260 let record = record
10261 .ok_or_else(|| OperationFailure::new(FsError::InvalidDirectoryRecord, work))?;
10262 if record.file_id != binding.file_id || record.kind != binding.kind {
10263 return Err(OperationFailure::new(FsError::InvalidDirectoryRecord, work));
10264 }
10265 named_records.push((binding.name, record));
10266 }
10267 self.attach_directory_metadata(named_records, page.has_more, work, budget, cancellation)
10268 .await
10269 }
10270
10271 async fn attach_directory_metadata(
10272 &mut self,
10273 named_records: Vec<(LogicalName, FileRecord)>,
10274 has_more: bool,
10275 mut work: WorkCounters,
10276 budget: WorkBudget,
10277 cancellation: &CancellationToken,
10278 ) -> FsResult<DirectoryRecordPage> {
10279 let mut requests = Vec::new();
10280 requests
10281 .try_reserve_exact(named_records.len())
10282 .map_err(|_| OperationFailure::new(FsError::PendingMutationAllocationFailed, work))?;
10283 requests.extend(named_records.iter().map(|(_, record)| ObjectReadRequest {
10284 object_id: record.metadata,
10285 maximum_bytes: self.volume.config.limits.maximum_object_bytes,
10286 }));
10287 let metadata_reads = self
10288 .volume
10289 .fs
10290 .inner
10291 .objects
10292 .read_many(&requests, remaining(work, budget)?, cancellation)
10293 .await
10294 .map_err(|failure| failure.map_with_prior_work(work, FsError::Object))?;
10295 work = add(work, metadata_reads.work)?;
10296 let mut entries = Vec::new();
10297 entries
10298 .try_reserve_exact(named_records.len())
10299 .map_err(|_| OperationFailure::new(FsError::PendingMutationAllocationFailed, work))?;
10300 for ((name, record), metadata) in named_records.into_iter().zip(metadata_reads.value) {
10301 let metadata = decode_file_metadata(&metadata, decode_limits(self.volume.config))
10302 .map_err(|error| OperationFailure::new(error.into(), work))?;
10303 entries.push(DirectoryRecordEntry {
10304 name,
10305 record,
10306 metadata,
10307 });
10308 }
10309 if self.mode.consistency != ConsistencyMode::Pinned && !entries.is_empty() {
10310 let mut file_ids = Vec::new();
10311 file_ids.try_reserve_exact(entries.len()).map_err(|_| {
10312 OperationFailure::new(FsError::PendingMutationAllocationFailed, work)
10313 })?;
10314 file_ids.extend(entries.iter().map(|entry| entry.record.file_id));
10315 work = self
10316 .observe_base_metadata_ids(&file_ids, work, budget, cancellation)
10317 .await?;
10318 }
10319 Ok(FsReceipt {
10320 value: DirectoryRecordPage { entries, has_more },
10321 work,
10322 })
10323 }
10324
10325 pub async fn read_metadata(
10335 &mut self,
10336 path: &NamespacePath,
10337 budget: WorkBudget,
10338 cancellation: &CancellationToken,
10339 ) -> FsResult<FileMetadata> {
10340 let lookup = self
10341 .lookup_no_follow_with_metadata(path, budget, cancellation)
10342 .await?;
10343 let value = lookup
10344 .value
10345 .ok_or_else(|| OperationFailure::new(FsError::NotFound, lookup.work))?
10346 .metadata;
10347 Ok(FsReceipt {
10348 value,
10349 work: lookup.work,
10350 })
10351 }
10352
10353 pub async fn read_named_attribute(
10361 &mut self,
10362 path: &NamespacePath,
10363 name: &AttributeName,
10364 budget: WorkBudget,
10365 cancellation: &CancellationToken,
10366 ) -> FsResult<Option<Bytes>> {
10367 let metadata = self.read_metadata(path, budget, cancellation).await?;
10368 let mut work = metadata.work;
10369 let MetadataField::Value(root) = metadata.value.named_attributes else {
10370 return Ok(FsReceipt { value: None, work });
10371 };
10372 let lookup = lookup_attribute_async(
10373 &self.volume.fs.inner.objects,
10374 root,
10375 name,
10376 decode_limits(self.volume.config),
10377 remaining(work, budget)?,
10378 cancellation,
10379 )
10380 .await
10381 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
10382 work = add(work, lookup.work)?;
10383 let Some(entry) = lookup.entry else {
10384 return Ok(FsReceipt { value: None, work });
10385 };
10386 if entry.value_bytes > self.volume.config.limits.maximum_read_bytes {
10387 return Err(OperationFailure::new(
10388 FsError::FileRead(FileRangeReadError::InvalidRange),
10389 work,
10390 ));
10391 }
10392 let read = read_blob_range_async(
10393 &self.volume.fs.inner.objects,
10394 entry.value,
10395 ByteRange {
10396 offset: 0,
10397 length: entry.value_bytes,
10398 },
10399 decode_limits(self.volume.config),
10400 remaining(work, budget)?,
10401 cancellation,
10402 )
10403 .await
10404 .map_err(|failure| failure.map_with_prior_work(work, FsError::BlobRead))?;
10405 work = add(work, read.work)?;
10406 Ok(FsReceipt {
10407 value: Some(read.bytes),
10408 work,
10409 })
10410 }
10411
10412 pub async fn list_named_attributes(
10420 &mut self,
10421 path: &NamespacePath,
10422 after: Option<&AttributeName>,
10423 maximum_entries: u32,
10424 budget: WorkBudget,
10425 cancellation: &CancellationToken,
10426 ) -> FsResult<AttributeListing> {
10427 let metadata = self.read_metadata(path, budget, cancellation).await?;
10428 let mut work = metadata.work;
10429 let MetadataField::Value(root) = metadata.value.named_attributes else {
10430 return Ok(FsReceipt {
10431 value: AttributeListing {
10432 entries: Vec::new(),
10433 has_more: false,
10434 next_residency: None,
10435 work,
10436 },
10437 work,
10438 });
10439 };
10440 let mut listing = list_attributes_async(
10441 &self.volume.fs.inner.objects,
10442 root,
10443 after,
10444 maximum_entries,
10445 decode_limits(self.volume.config),
10446 remaining(work, budget)?,
10447 cancellation,
10448 )
10449 .await
10450 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeList))?;
10451 work = add(work, listing.work)?;
10452 listing.work = work;
10453 Ok(FsReceipt {
10454 value: listing,
10455 work,
10456 })
10457 }
10458
10459 pub async fn write_named_attribute(
10470 &mut self,
10471 path: NamespacePath,
10472 name: AttributeName,
10473 value: Bytes,
10474 mode: NamedAttributeWriteMode,
10475 budget: WorkBudget,
10476 cancellation: &CancellationToken,
10477 ) -> FsResult<()> {
10478 let metadata = self.read_metadata(&path, budget, cancellation).await?;
10479 let mut work = metadata.work;
10480 let mut metadata_value = metadata.value;
10481 let root = match metadata_value.named_attributes {
10482 MetadataField::Unavailable => {
10483 let empty = self
10484 .stage_empty_attributes(remaining(work, budget)?, cancellation)
10485 .await
10486 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
10487 work = add(work, empty.work)?;
10488 empty.value
10489 }
10490 MetadataField::Value(root) => root,
10491 };
10492 let current = lookup_attribute_async(
10493 &self.volume.fs.inner.objects,
10494 root,
10495 &name,
10496 decode_limits(self.volume.config),
10497 remaining(work, budget)?,
10498 cancellation,
10499 )
10500 .await
10501 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
10502 work = add(work, current.work)?;
10503 match (mode, current.entry.as_ref()) {
10504 (NamedAttributeWriteMode::Create, Some(_))
10505 | (NamedAttributeWriteMode::Replace, None) => {
10506 return Err(OperationFailure::new(FsError::CreationRejected, work));
10507 }
10508 _ => {}
10509 }
10510 let blob = self
10511 .stage_blob(value, remaining(work, budget)?, cancellation)
10512 .await
10513 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
10514 work = add(work, blob.work)?;
10515 let replacement = AttributeEntry {
10516 name,
10517 value_bytes: blob.value.logical_bytes,
10518 value: blob.value.root,
10519 };
10520 let mutation = match current.entry {
10521 None => AttributeMutation::Insert(replacement),
10522 Some(expected) => AttributeMutation::Replace {
10523 expected,
10524 replacement,
10525 },
10526 };
10527 let changed = apply_attribute_mutations_async(
10528 &self.volume.fs.inner.objects,
10529 root,
10530 vec![mutation],
10531 1,
10532 decode_limits(self.volume.config),
10533 remaining(work, budget)?,
10534 cancellation,
10535 )
10536 .await
10537 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
10538 work = add(work, changed.work)?;
10539 metadata_value.named_attributes = MetadataField::Value(changed.root);
10540 let changed_metadata = self
10541 .set_metadata(path, metadata_value, remaining(work, budget)?, cancellation)
10542 .await
10543 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
10544 work = add(work, changed_metadata.work)?;
10545 Ok(FsReceipt { value: (), work })
10546 }
10547
10548 pub async fn remove_named_attribute(
10555 &mut self,
10556 path: NamespacePath,
10557 name: AttributeName,
10558 budget: WorkBudget,
10559 cancellation: &CancellationToken,
10560 ) -> FsResult<()> {
10561 let metadata = self.read_metadata(&path, budget, cancellation).await?;
10562 let mut work = metadata.work;
10563 let mut metadata_value = metadata.value;
10564 let MetadataField::Value(root) = metadata_value.named_attributes else {
10565 return Err(OperationFailure::new(FsError::NotFound, work));
10566 };
10567 let current = lookup_attribute_async(
10568 &self.volume.fs.inner.objects,
10569 root,
10570 &name,
10571 decode_limits(self.volume.config),
10572 remaining(work, budget)?,
10573 cancellation,
10574 )
10575 .await
10576 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
10577 work = add(work, current.work)?;
10578 let expected = current
10579 .entry
10580 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
10581 let changed = apply_attribute_mutations_async(
10582 &self.volume.fs.inner.objects,
10583 root,
10584 vec![AttributeMutation::Remove {
10585 name,
10586 expected: Some(expected),
10587 }],
10588 1,
10589 decode_limits(self.volume.config),
10590 remaining(work, budget)?,
10591 cancellation,
10592 )
10593 .await
10594 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
10595 work = add(work, changed.work)?;
10596 metadata_value.named_attributes = MetadataField::Value(changed.root);
10597 let changed_metadata = self
10598 .set_metadata(path, metadata_value, remaining(work, budget)?, cancellation)
10599 .await
10600 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
10601 work = add(work, changed_metadata.work)?;
10602 Ok(FsReceipt { value: (), work })
10603 }
10604
10605 pub async fn plan_file_extents(
10618 &mut self,
10619 path: &NamespacePath,
10620 range: ByteRange,
10621 maximum_spans: u32,
10622 budget: WorkBudget,
10623 cancellation: &CancellationToken,
10624 ) -> FsResult<Option<ExtentPlan>> {
10625 if maximum_spans == 0 || range.length > self.volume.config.limits.maximum_generation_bytes {
10626 return Err(OperationFailure::before_work(FsError::FileRead(
10627 FileRangeReadError::InvalidRange,
10628 )));
10629 }
10630 let lookup = self
10631 .lookup_no_follow_observed(path, budget, cancellation, false)
10632 .await?;
10633 let mut work = lookup.work;
10634 let record = lookup
10635 .value
10636 .record
10637 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
10638 let (logical_bytes, extents) = match record.payload {
10639 FilePayload::InlineRegular(data) => {
10640 let logical_bytes = u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX);
10641 validate_planned_file_range(logical_bytes, range, work)?;
10642 if self.mode.consistency != ConsistencyMode::Pinned && range.length != 0 {
10643 work = self
10644 .observe_base_regular_range(
10645 record.file_id,
10646 range,
10647 work,
10648 budget,
10649 cancellation,
10650 )
10651 .await?;
10652 }
10653 return Ok(FsReceipt { value: None, work });
10654 }
10655 FilePayload::Regular {
10656 logical_bytes,
10657 extents,
10658 } => (logical_bytes, extents),
10659 FilePayload::Directory { .. }
10660 | FilePayload::SymbolicLink { .. }
10661 | FilePayload::Device { .. }
10662 | FilePayload::Empty
10663 | FilePayload::ReparsePoint { .. } => {
10664 return Err(OperationFailure::new(
10665 FsError::FileRead(FileRangeReadError::NotRegular),
10666 work,
10667 ));
10668 }
10669 };
10670 let mut plan = plan_extent_range_async(
10671 &self.volume.fs.inner.objects,
10672 ExtentRangeRequest {
10673 root: extents,
10674 file_size: logical_bytes,
10675 range,
10676 maximum_spans,
10677 limits: decode_limits(self.volume.config),
10678 budget: remaining(work, budget)?,
10679 },
10680 cancellation,
10681 )
10682 .await
10683 .map_err(|failure| {
10684 failure.map_with_prior_work(work, |error| {
10685 FsError::FileRead(FileRangeReadError::Extent(error))
10686 })
10687 })?;
10688 work = add(work, plan.work)?;
10689 if self.mode.consistency != ConsistencyMode::Pinned && range.length != 0 {
10690 work = self
10691 .observe_base_regular_range(record.file_id, range, work, budget, cancellation)
10692 .await?;
10693 }
10694 plan.work = work;
10695 Ok(FsReceipt {
10696 value: Some(plan),
10697 work,
10698 })
10699 }
10700
10701 pub async fn seek_file_extent(
10713 &mut self,
10714 path: &NamespacePath,
10715 offset: u64,
10716 target: ExtentSeekTarget,
10717 budget: WorkBudget,
10718 cancellation: &CancellationToken,
10719 ) -> FsResult<Option<u64>> {
10720 let lookup = self
10721 .lookup_no_follow_observed(path, budget, cancellation, false)
10722 .await?;
10723 let mut work = lookup.work;
10724 let record = lookup
10725 .value
10726 .record
10727 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
10728 let value = match record.payload {
10729 FilePayload::InlineRegular(data) => {
10730 let logical_bytes = u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX);
10731 if offset > logical_bytes {
10732 None
10733 } else {
10734 match target {
10735 ExtentSeekTarget::Data => (offset < logical_bytes).then_some(offset),
10736 ExtentSeekTarget::Hole => Some(logical_bytes),
10737 }
10738 }
10739 }
10740 FilePayload::Regular {
10741 logical_bytes,
10742 extents,
10743 } => {
10744 if offset > logical_bytes {
10745 None
10746 } else {
10747 let receipt = seek_extent_async(
10748 &self.volume.fs.inner.objects,
10749 ExtentSeekRequest {
10750 root: extents,
10751 file_size: logical_bytes,
10752 offset,
10753 target,
10754 limits: decode_limits(self.volume.config),
10755 budget: remaining(work, budget)?,
10756 },
10757 cancellation,
10758 )
10759 .await
10760 .map_err(|failure| {
10761 failure.map_with_prior_work(work, |error| {
10762 FsError::FileRead(FileRangeReadError::Extent(error))
10763 })
10764 })?;
10765 work = add(work, receipt.work)?;
10766 receipt.value
10767 }
10768 }
10769 FilePayload::Directory { .. }
10770 | FilePayload::SymbolicLink { .. }
10771 | FilePayload::Empty
10772 | FilePayload::Device { .. }
10773 | FilePayload::ReparsePoint { .. } => {
10774 return Err(OperationFailure::new(
10775 FsError::FileRead(FileRangeReadError::NotRegular),
10776 work,
10777 ));
10778 }
10779 };
10780 if self.mode.consistency != ConsistencyMode::Pinned {
10781 let region = DependencyRegion::SparseSeek {
10782 file_id: record.file_id,
10783 offset,
10784 target,
10785 };
10786 let probe = AuthenticatedGenerationProbe::new(
10787 &self.volume.fs.inner.objects,
10788 probe_limits(self.volume.config),
10789 )
10790 .map_err(|error| OperationFailure::new(error.into(), work))?;
10791 let state = probe
10792 .probe_async(
10793 GenerationId::new(self.base_generation_root.digest),
10794 ®ion,
10795 remaining(work, budget)?,
10796 cancellation,
10797 )
10798 .await
10799 .map_err(|failure| failure.map_with_prior_work(work, FsError::Probe))?;
10800 work = add(work, state.work)?;
10801 self.dependencies
10802 .extend_observations(
10803 vec![Dependency {
10804 region,
10805 expected: state.value,
10806 }],
10807 self.volume.config.limits.maximum_checkout_dependencies,
10808 )
10809 .map_err(|error| OperationFailure::new(error.into(), work))?;
10810 }
10811 Ok(FsReceipt { value, work })
10812 }
10813
10814 pub async fn read_file_range(
10822 &mut self,
10823 path: &NamespacePath,
10824 range: ByteRange,
10825 budget: WorkBudget,
10826 cancellation: &CancellationToken,
10827 ) -> FsResult<FileRangeRead> {
10828 if range.length > self.volume.config.limits.maximum_read_bytes {
10829 return Err(OperationFailure::before_work(FsError::FileRead(
10830 FileRangeReadError::InvalidRange,
10831 )));
10832 }
10833 let lookup = self
10834 .lookup_no_follow_observed(path, budget, cancellation, false)
10835 .await?;
10836 let mut work = lookup.work;
10837 let record = lookup
10838 .value
10839 .record
10840 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
10841 let mut read = read_file_range_async(
10842 &self.volume.fs.inner.objects,
10843 FileRangeRequest {
10844 record,
10845 range,
10846 maximum_spans: self.volume.config.limits.maximum_directory_page_entries,
10847 limits: decode_limits(self.volume.config),
10848 budget: remaining(work, budget)?,
10849 },
10850 cancellation,
10851 )
10852 .await
10853 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRead))?;
10854 work = add(work, read.work)?;
10855 if self.mode.consistency != ConsistencyMode::Pinned && range.length != 0 {
10856 work = self
10857 .observe_base_regular_range(record.file_id, range, work, budget, cancellation)
10858 .await?;
10859 }
10860 read.work = work;
10861 Ok(FsReceipt { value: read, work })
10862 }
10863 async fn lookup_file_record_by_id(
10864 &self,
10865 file_id: FileId,
10866 budget: WorkBudget,
10867 cancellation: &CancellationToken,
10868 ) -> FsResult<Option<FileRecord>> {
10869 let lookup = lookup_file_record_async(
10870 &self.volume.fs.inner.objects,
10871 self.root.file_table,
10872 file_id,
10873 decode_limits(self.volume.config),
10874 budget,
10875 cancellation,
10876 )
10877 .await
10878 .map_err(|failure| {
10879 OperationFailure::new(FsError::FileRecord(failure.error), *failure.work)
10880 })?;
10881 Ok(FsReceipt {
10882 value: lookup.record,
10883 work: lookup.work,
10884 })
10885 }
10886
10887 async fn observe_identity_region(
10888 &mut self,
10889 region: DependencyRegion,
10890 work: WorkCounters,
10891 budget: WorkBudget,
10892 cancellation: &CancellationToken,
10893 ) -> Result<WorkCounters, OperationFailure<FsError>> {
10894 self.observe_identity_regions(vec![region], work, budget, cancellation)
10895 .await
10896 }
10897
10898 async fn observe_identity_regions(
10899 &mut self,
10900 regions: Vec<DependencyRegion>,
10901 mut work: WorkCounters,
10902 budget: WorkBudget,
10903 cancellation: &CancellationToken,
10904 ) -> Result<WorkCounters, OperationFailure<FsError>> {
10905 let captured = capture_dependencies_async(
10906 &self.volume.fs.inner.objects,
10907 &self.base_generation_root,
10908 self.volume.config,
10909 regions,
10910 remaining(work, budget)?,
10911 cancellation,
10912 )
10913 .await
10914 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
10915 work = add(work, captured.work)?;
10916 self.dependencies
10917 .extend_observations(
10918 captured.value,
10919 self.volume.config.limits.maximum_checkout_dependencies,
10920 )
10921 .map_err(|error| OperationFailure::new(error.into(), work))?;
10922 Ok(work)
10923 }
10924
10925 async fn observe_base_regular_range(
10926 &mut self,
10927 file_id: FileId,
10928 range: ByteRange,
10929 mut work: WorkCounters,
10930 budget: WorkBudget,
10931 cancellation: &CancellationToken,
10932 ) -> Result<WorkCounters, OperationFailure<FsError>> {
10933 let lookup = lookup_file_record_async(
10934 &self.volume.fs.inner.objects,
10935 self.base_root.file_table,
10936 file_id,
10937 decode_limits(self.volume.config),
10938 remaining(work, budget)?,
10939 cancellation,
10940 )
10941 .await
10942 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
10943 work = add(work, lookup.work)?;
10944 let regions = regular_range_regions(lookup.record, range.offset, range.length)
10945 .into_iter()
10946 .flatten()
10947 .collect();
10948 self.observe_identity_regions(regions, work, budget, cancellation)
10949 .await
10950 }
10951
10952 async fn observe_base_metadata_ids(
10953 &mut self,
10954 file_ids: &[FileId],
10955 mut work: WorkCounters,
10956 budget: WorkBudget,
10957 cancellation: &CancellationToken,
10958 ) -> Result<WorkCounters, OperationFailure<FsError>> {
10959 if file_ids.is_empty() {
10960 return Ok(work);
10961 }
10962 let maximum = u32::try_from(file_ids.len()).unwrap_or(u32::MAX);
10963 let records = lookup_file_records_async(
10964 &self.volume.fs.inner.objects,
10965 self.base_root.file_table,
10966 file_ids,
10967 maximum,
10968 decode_limits(self.volume.config),
10969 remaining(work, budget)?,
10970 cancellation,
10971 )
10972 .await
10973 .map_err(|failure| failure.map_with_prior_work(work, FsError::FileRecord))?;
10974 work = add(work, records.work)?;
10975 let mut dependencies = Vec::new();
10976 dependencies
10977 .try_reserve_exact(file_ids.len())
10978 .map_err(|_| OperationFailure::new(FsError::PendingMutationAllocationFailed, work))?;
10979 dependencies.extend(
10980 file_ids
10981 .iter()
10982 .zip(records.records)
10983 .map(|(file_id, record)| Dependency {
10984 region: DependencyRegion::Metadata(*file_id),
10985 expected: record.map_or(DependencyState::Absent, |record| {
10986 DependencyState::Present(record.metadata.digest)
10987 }),
10988 }),
10989 );
10990 self.dependencies
10991 .extend_observations(
10992 dependencies,
10993 self.volume.config.limits.maximum_checkout_dependencies,
10994 )
10995 .map_err(|error| OperationFailure::new(error.into(), work))?;
10996 Ok(work)
10997 }
10998}
10999
11000async fn stage_content_for_volume<
11001 A: AsyncAuthorityStore,
11002 O: AsyncObjectStore,
11003 R: AsyncBlobSource,
11004>(
11005 volume: &Volume<A, O>,
11006 source: &mut R,
11007 maximum_source_bytes: u64,
11008 budget: WorkBudget,
11009 cancellation: &CancellationToken,
11010) -> FsResult<StagedContent> {
11011 if maximum_source_bytes == 0
11012 || maximum_source_bytes > volume.config.limits.maximum_generation_bytes
11013 {
11014 return Err(OperationFailure::before_work(FsError::FileRead(
11015 FileRangeReadError::InvalidRange,
11016 )));
11017 }
11018 let chunk_bytes = u32::try_from(volume.config.limits.maximum_object_bytes.min(1024 * 1024))
11019 .unwrap_or(u32::MAX)
11020 .max(1);
11021 let page_bytes = u32::try_from(volume.config.limits.maximum_object_bytes)
11022 .unwrap_or(u32::MAX)
11023 .max(1);
11024 let blob = build_blob_async(
11025 &volume.fs.inner.objects,
11026 source,
11027 BlobBuildOptions {
11028 chunk_bytes,
11029 page_items: volume.config.limits.maximum_directory_page_entries,
11030 page_bytes,
11031 maximum_blob_bytes: maximum_source_bytes,
11032 },
11033 budget,
11034 cancellation,
11035 )
11036 .await
11037 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
11038 Ok(FsReceipt {
11039 value: StagedContent {
11040 root: blob.root,
11041 logical_bytes: blob.logical_bytes,
11042 },
11043 work: blob.work,
11044 })
11045}
11046
11047impl<A: AsyncAuthorityStore, O: AsyncObjectStore> DetachedFile<A, O> {
11048 pub(crate) fn from_record(volume: Volume<A, O>, record: FileRecord) -> Self {
11049 Self { volume, record }
11050 }
11051
11052 #[cfg(feature = "native-mount")]
11053 pub(crate) const fn record(&self) -> FileRecord {
11054 self.record
11055 }
11056
11057 pub(crate) async fn read_symbolic_link(
11058 &self,
11059 budget: WorkBudget,
11060 cancellation: &CancellationToken,
11061 ) -> FsResult<Bytes> {
11062 read_symbolic_link_record(&self.volume, self.record, budget, cancellation).await
11063 }
11064 #[must_use]
11066 pub const fn file_id(&self) -> FileId {
11067 self.record.file_id
11068 }
11069
11070 #[must_use]
11072 pub fn logical_bytes(&self) -> u64 {
11073 match self.record.payload {
11074 FilePayload::InlineRegular(data) => {
11075 u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX)
11076 }
11077 FilePayload::Regular { logical_bytes, .. } => logical_bytes,
11078 _ => 0,
11079 }
11080 }
11081
11082 pub async fn seek(
11089 &self,
11090 offset: u64,
11091 target: ExtentSeekTarget,
11092 budget: WorkBudget,
11093 cancellation: &CancellationToken,
11094 ) -> FsResult<Option<u64>> {
11095 let value = match self.record.payload {
11096 FilePayload::InlineRegular(data) => {
11097 let logical_bytes = u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX);
11098 if offset > logical_bytes {
11099 None
11100 } else {
11101 match target {
11102 ExtentSeekTarget::Data => (offset < logical_bytes).then_some(offset),
11103 ExtentSeekTarget::Hole => Some(logical_bytes),
11104 }
11105 }
11106 }
11107 FilePayload::Regular {
11108 logical_bytes,
11109 extents,
11110 } => {
11111 if offset > logical_bytes {
11112 None
11113 } else {
11114 return seek_extent_async(
11115 &self.volume.fs.inner.objects,
11116 ExtentSeekRequest {
11117 root: extents,
11118 file_size: logical_bytes,
11119 offset,
11120 target,
11121 limits: decode_limits(self.volume.config),
11122 budget,
11123 },
11124 cancellation,
11125 )
11126 .await
11127 .map_err(|failure| {
11128 OperationFailure::new(
11129 FsError::FileRead(FileRangeReadError::Extent(failure.error)),
11130 *failure.work,
11131 )
11132 });
11133 }
11134 }
11135 FilePayload::Directory { .. }
11136 | FilePayload::SymbolicLink { .. }
11137 | FilePayload::Empty
11138 | FilePayload::Device { .. }
11139 | FilePayload::ReparsePoint { .. } => {
11140 return Err(OperationFailure::before_work(FsError::FileRead(
11141 FileRangeReadError::NotRegular,
11142 )));
11143 }
11144 };
11145 Ok(FsReceipt {
11146 value,
11147 work: WorkCounters::default(),
11148 })
11149 }
11150
11151 pub async fn set_metadata(
11159 &mut self,
11160 metadata: FileMetadata,
11161 budget: WorkBudget,
11162 cancellation: &CancellationToken,
11163 ) -> FsResult<()> {
11164 let encoded = encode_file_metadata(metadata)
11165 .map_err(|error| OperationFailure::before_work(error.into()))?;
11166 let (metadata, work) = self
11167 .volume
11168 .fs
11169 .put_encoded(
11170 ObjectKind::Metadata,
11171 encoded,
11172 WorkCounters::default(),
11173 budget,
11174 cancellation,
11175 )
11176 .await?;
11177 self.record.metadata = metadata;
11178 Ok(FsReceipt { value: (), work })
11179 }
11180
11181 pub async fn set_attributes(
11193 &mut self,
11194 metadata: FileMetadata,
11195 logical_bytes: Option<u64>,
11196 budget: WorkBudget,
11197 cancellation: &CancellationToken,
11198 ) -> FsResult<()> {
11199 let encoded = encode_file_metadata(metadata)
11200 .map_err(|error| OperationFailure::before_work(error.into()))?;
11201 let (metadata_id, mut work) = self
11202 .volume
11203 .fs
11204 .put_encoded(
11205 ObjectKind::Metadata,
11206 encoded,
11207 WorkCounters::default(),
11208 budget,
11209 cancellation,
11210 )
11211 .await?;
11212 let payload = if let Some(logical_bytes) = logical_bytes {
11213 let resized = apply_regular_mutation_async(
11214 &self.volume.fs.inner.objects,
11215 self.record.payload,
11216 RegularMutation::Resize { logical_bytes },
11217 self.volume.config,
11218 remaining(work, budget)?,
11219 cancellation,
11220 )
11221 .await
11222 .map_err(|failure| failure.map_with_prior_work(work, FsError::DetachedMutation))?;
11223 work = add(work, resized.work)?;
11224 resized.payload
11225 } else {
11226 self.record.payload
11227 };
11228 self.record.metadata = metadata_id;
11229 self.record.payload = payload;
11230 Ok(FsReceipt { value: (), work })
11231 }
11232
11233 pub async fn read_named_attribute(
11240 &self,
11241 name: &AttributeName,
11242 budget: WorkBudget,
11243 cancellation: &CancellationToken,
11244 ) -> FsResult<Option<Bytes>> {
11245 let metadata = self.read_metadata(budget, cancellation).await?;
11246 let mut work = metadata.work;
11247 let MetadataField::Value(root) = metadata.value.named_attributes else {
11248 return Ok(FsReceipt { value: None, work });
11249 };
11250 let lookup = lookup_attribute_async(
11251 &self.volume.fs.inner.objects,
11252 root,
11253 name,
11254 decode_limits(self.volume.config),
11255 remaining(work, budget)?,
11256 cancellation,
11257 )
11258 .await
11259 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
11260 work = add(work, lookup.work)?;
11261 let Some(entry) = lookup.entry else {
11262 return Ok(FsReceipt { value: None, work });
11263 };
11264 if entry.value_bytes > self.volume.config.limits.maximum_read_bytes {
11265 return Err(OperationFailure::new(
11266 FsError::FileRead(FileRangeReadError::InvalidRange),
11267 work,
11268 ));
11269 }
11270 let read = read_blob_range_async(
11271 &self.volume.fs.inner.objects,
11272 entry.value,
11273 ByteRange {
11274 offset: 0,
11275 length: entry.value_bytes,
11276 },
11277 decode_limits(self.volume.config),
11278 remaining(work, budget)?,
11279 cancellation,
11280 )
11281 .await
11282 .map_err(|failure| failure.map_with_prior_work(work, FsError::BlobRead))?;
11283 work = add(work, read.work)?;
11284 Ok(FsReceipt {
11285 value: Some(read.bytes),
11286 work,
11287 })
11288 }
11289
11290 pub async fn list_named_attributes(
11297 &self,
11298 after: Option<&AttributeName>,
11299 maximum_entries: u32,
11300 budget: WorkBudget,
11301 cancellation: &CancellationToken,
11302 ) -> FsResult<AttributeListing> {
11303 let metadata = self.read_metadata(budget, cancellation).await?;
11304 let mut work = metadata.work;
11305 let MetadataField::Value(root) = metadata.value.named_attributes else {
11306 let value = AttributeListing {
11307 entries: Vec::new(),
11308 has_more: false,
11309 next_residency: None,
11310 work,
11311 };
11312 return Ok(FsReceipt { value, work });
11313 };
11314 let mut listing = list_attributes_async(
11315 &self.volume.fs.inner.objects,
11316 root,
11317 after,
11318 maximum_entries,
11319 decode_limits(self.volume.config),
11320 remaining(work, budget)?,
11321 cancellation,
11322 )
11323 .await
11324 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeList))?;
11325 work = add(work, listing.work)?;
11326 listing.work = work;
11327 Ok(FsReceipt {
11328 value: listing,
11329 work,
11330 })
11331 }
11332
11333 pub async fn write_named_attribute(
11340 &mut self,
11341 name: AttributeName,
11342 value: Bytes,
11343 mode: NamedAttributeWriteMode,
11344 budget: WorkBudget,
11345 cancellation: &CancellationToken,
11346 ) -> FsResult<FileMetadata> {
11347 let metadata = self.read_metadata(budget, cancellation).await?;
11348 let mut work = metadata.work;
11349 let mut metadata_value = metadata.value;
11350 let root = match metadata_value.named_attributes {
11351 MetadataField::Unavailable => {
11352 let encoded = encode_attribute_page(
11353 &AttributePage::Leaf(Vec::new()),
11354 self.volume.config.limits.maximum_directory_page_entries,
11355 )
11356 .map_err(|error| OperationFailure::new(error.into(), work))?;
11357 let stored = self
11358 .volume
11359 .fs
11360 .put_encoded(
11361 ObjectKind::AttributePage,
11362 encoded,
11363 work,
11364 budget,
11365 cancellation,
11366 )
11367 .await?;
11368 work = stored.1;
11369 stored.0
11370 }
11371 MetadataField::Value(root) => root,
11372 };
11373 let current = lookup_attribute_async(
11374 &self.volume.fs.inner.objects,
11375 root,
11376 &name,
11377 decode_limits(self.volume.config),
11378 remaining(work, budget)?,
11379 cancellation,
11380 )
11381 .await
11382 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
11383 work = add(work, current.work)?;
11384 match (mode, current.entry.as_ref()) {
11385 (NamedAttributeWriteMode::Create, Some(_))
11386 | (NamedAttributeWriteMode::Replace, None) => {
11387 return Err(OperationFailure::new(FsError::CreationRejected, work));
11388 }
11389 _ => {}
11390 }
11391 let blob = self
11392 .stage_blob(value, remaining(work, budget)?, cancellation)
11393 .await
11394 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
11395 work = add(work, blob.work)?;
11396 let replacement = AttributeEntry {
11397 name,
11398 value_bytes: blob.value.logical_bytes,
11399 value: blob.value.root,
11400 };
11401 let mutation = match current.entry {
11402 None => AttributeMutation::Insert(replacement),
11403 Some(expected) => AttributeMutation::Replace {
11404 expected,
11405 replacement,
11406 },
11407 };
11408 let changed = apply_attribute_mutations_async(
11409 &self.volume.fs.inner.objects,
11410 root,
11411 vec![mutation],
11412 1,
11413 decode_limits(self.volume.config),
11414 remaining(work, budget)?,
11415 cancellation,
11416 )
11417 .await
11418 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
11419 work = add(work, changed.work)?;
11420 metadata_value.named_attributes = MetadataField::Value(changed.root);
11421 let metadata = self
11422 .set_metadata(metadata_value, remaining(work, budget)?, cancellation)
11423 .await
11424 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
11425 work = add(work, metadata.work)?;
11426 Ok(FsReceipt {
11427 value: metadata_value,
11428 work,
11429 })
11430 }
11431
11432 pub async fn remove_named_attribute(
11439 &mut self,
11440 name: AttributeName,
11441 budget: WorkBudget,
11442 cancellation: &CancellationToken,
11443 ) -> FsResult<FileMetadata> {
11444 let metadata = self.read_metadata(budget, cancellation).await?;
11445 let mut work = metadata.work;
11446 let mut metadata_value = metadata.value;
11447 let MetadataField::Value(root) = metadata_value.named_attributes else {
11448 return Err(OperationFailure::new(FsError::NotFound, work));
11449 };
11450 let current = lookup_attribute_async(
11451 &self.volume.fs.inner.objects,
11452 root,
11453 &name,
11454 decode_limits(self.volume.config),
11455 remaining(work, budget)?,
11456 cancellation,
11457 )
11458 .await
11459 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeLookup))?;
11460 work = add(work, current.work)?;
11461 let expected = current
11462 .entry
11463 .ok_or_else(|| OperationFailure::new(FsError::NotFound, work))?;
11464 let changed = apply_attribute_mutations_async(
11465 &self.volume.fs.inner.objects,
11466 root,
11467 vec![AttributeMutation::Remove {
11468 name,
11469 expected: Some(expected),
11470 }],
11471 1,
11472 decode_limits(self.volume.config),
11473 remaining(work, budget)?,
11474 cancellation,
11475 )
11476 .await
11477 .map_err(|failure| failure.map_with_prior_work(work, FsError::AttributeMutation))?;
11478 work = add(work, changed.work)?;
11479 metadata_value.named_attributes = MetadataField::Value(changed.root);
11480 let metadata = self
11481 .set_metadata(metadata_value, remaining(work, budget)?, cancellation)
11482 .await
11483 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
11484 work = add(work, metadata.work)?;
11485 Ok(FsReceipt {
11486 value: metadata_value,
11487 work,
11488 })
11489 }
11490
11491 pub async fn read_metadata(
11498 &self,
11499 budget: WorkBudget,
11500 cancellation: &CancellationToken,
11501 ) -> FsResult<FileMetadata> {
11502 let metadata = self
11503 .volume
11504 .fs
11505 .inner
11506 .objects
11507 .read(
11508 self.record.metadata,
11509 self.volume.config.limits.maximum_object_bytes,
11510 budget,
11511 cancellation,
11512 )
11513 .await
11514 .map_err(|failure| {
11515 OperationFailure::new(FsError::Object(failure.error), *failure.work)
11516 })?;
11517 let value = decode_file_metadata(&metadata.value, decode_limits(self.volume.config))
11518 .map_err(|error| OperationFailure::new(error.into(), metadata.work))?;
11519 Ok(FsReceipt {
11520 value,
11521 work: metadata.work,
11522 })
11523 }
11524
11525 async fn stage_blob(
11526 &self,
11527 bytes: Bytes,
11528 budget: WorkBudget,
11529 cancellation: &CancellationToken,
11530 ) -> FsResult<crate::kernel::BlobBuild> {
11531 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
11532 > self.volume.config.limits.maximum_read_bytes
11533 {
11534 return Err(OperationFailure::before_work(FsError::FileRead(
11535 FileRangeReadError::InvalidRange,
11536 )));
11537 }
11538 let maximum_blob_bytes = u64::try_from(bytes.len()).unwrap_or(u64::MAX).max(1);
11539 let mut source = std::io::Cursor::new(bytes);
11540 let staged = stage_content_for_volume(
11541 &self.volume,
11542 &mut source,
11543 maximum_blob_bytes,
11544 budget,
11545 cancellation,
11546 )
11547 .await?;
11548 Ok(FsReceipt {
11549 work: staged.work,
11550 value: crate::kernel::BlobBuild {
11551 root: staged.value.root,
11552 logical_bytes: staged.value.logical_bytes,
11553 work: staged.work,
11554 },
11555 })
11556 }
11557
11558 pub async fn read_range(
11565 &self,
11566 range: ByteRange,
11567 budget: WorkBudget,
11568 cancellation: &CancellationToken,
11569 ) -> FsResult<FileRangeRead> {
11570 if range.length > self.volume.config.limits.maximum_read_bytes {
11571 return Err(OperationFailure::before_work(FsError::FileRead(
11572 FileRangeReadError::InvalidRange,
11573 )));
11574 }
11575 let read = read_file_range_async(
11576 &self.volume.fs.inner.objects,
11577 FileRangeRequest {
11578 record: self.record,
11579 range,
11580 maximum_spans: self.volume.config.limits.maximum_mutations_per_batch,
11581 limits: decode_limits(self.volume.config),
11582 budget,
11583 },
11584 cancellation,
11585 )
11586 .await
11587 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
11588 Ok(FsReceipt {
11589 work: read.work,
11590 value: read,
11591 })
11592 }
11593
11594 pub async fn write_range(
11601 &mut self,
11602 offset: u64,
11603 bytes: Bytes,
11604 budget: WorkBudget,
11605 cancellation: &CancellationToken,
11606 ) -> FsResult<()> {
11607 if bytes.is_empty() {
11608 return Ok(FsReceipt {
11609 value: (),
11610 work: WorkCounters::default(),
11611 });
11612 }
11613 if u64::try_from(bytes.len()).unwrap_or(u64::MAX)
11614 > self.volume.config.limits.maximum_read_bytes
11615 {
11616 return Err(OperationFailure::before_work(FsError::FileRead(
11617 FileRangeReadError::InvalidRange,
11618 )));
11619 }
11620 let maximum_blob_bytes = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
11621 let mut source = std::io::Cursor::new(bytes);
11622 let blob = stage_content_for_volume(
11623 &self.volume,
11624 &mut source,
11625 maximum_blob_bytes,
11626 budget,
11627 cancellation,
11628 )
11629 .await?;
11630 let mut work = blob.work;
11631 let mutation = self
11632 .mutate_regular(
11633 RegularMutation::Write {
11634 offset,
11635 length: blob.value.logical_bytes,
11636 content: blob.value.root,
11637 content_offset: 0,
11638 },
11639 remaining(work, budget)?,
11640 cancellation,
11641 )
11642 .await
11643 .map_err(|failure| failure.map_with_prior_work(work, std::convert::identity))?;
11644 work = add(work, mutation.work)?;
11645 Ok(FsReceipt { value: (), work })
11646 }
11647
11648 pub async fn resize(
11654 &mut self,
11655 logical_bytes: u64,
11656 budget: WorkBudget,
11657 cancellation: &CancellationToken,
11658 ) -> FsResult<()> {
11659 self.mutate_regular(
11660 RegularMutation::Resize { logical_bytes },
11661 budget,
11662 cancellation,
11663 )
11664 .await
11665 }
11666
11667 pub async fn zero_range(
11673 &mut self,
11674 range: ByteRange,
11675 allocated: bool,
11676 extend: bool,
11677 budget: WorkBudget,
11678 cancellation: &CancellationToken,
11679 ) -> FsResult<()> {
11680 self.mutate_regular(
11681 RegularMutation::ZeroRange {
11682 offset: range.offset,
11683 length: range.length,
11684 allocated,
11685 extend,
11686 },
11687 budget,
11688 cancellation,
11689 )
11690 .await
11691 }
11692
11693 pub async fn preallocate(
11700 &mut self,
11701 range: ByteRange,
11702 keep_size: bool,
11703 budget: WorkBudget,
11704 cancellation: &CancellationToken,
11705 ) -> FsResult<()> {
11706 self.mutate_regular(
11707 RegularMutation::Preallocate {
11708 offset: range.offset,
11709 length: range.length,
11710 keep_size,
11711 },
11712 budget,
11713 cancellation,
11714 )
11715 .await
11716 }
11717
11718 async fn mutate_regular(
11719 &mut self,
11720 mutation: RegularMutation,
11721 budget: WorkBudget,
11722 cancellation: &CancellationToken,
11723 ) -> FsResult<()> {
11724 let receipt = apply_regular_mutation_async(
11725 &self.volume.fs.inner.objects,
11726 self.record.payload,
11727 mutation,
11728 self.volume.config,
11729 budget,
11730 cancellation,
11731 )
11732 .await
11733 .map_err(|failure| {
11734 OperationFailure::new(FsError::DetachedMutation(failure.error), *failure.work)
11735 })?;
11736 self.record.payload = receipt.payload;
11737 Ok(FsReceipt {
11738 value: (),
11739 work: receipt.work,
11740 })
11741 }
11742}
11743
11744pub(crate) async fn read_generation_root<O: AsyncObjectStore>(
11745 objects: &O,
11746 generation_root: ObjectId,
11747 config: VolumeConfig,
11748 budget: WorkBudget,
11749 cancellation: &CancellationToken,
11750) -> Result<(GenerationRoot, WorkCounters), OperationFailure<FsError>> {
11751 cancellation
11752 .check()
11753 .map_err(|error| OperationFailure::before_work(error.into()))?;
11754 let receipt = objects
11755 .read(
11756 generation_root,
11757 MAXIMUM_GENERATION_ROOT_BYTES.min(config.limits.maximum_object_bytes),
11758 budget,
11759 cancellation,
11760 )
11761 .await
11762 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
11763 let retained_bytes = match receipt.value.retention {
11764 ObjectReadRetention::Shared => 0,
11765 ObjectReadRetention::Owned { logical_bytes } => logical_bytes,
11766 };
11767 let decode = decode_limits(config);
11768 let parent_count = generation_root_parent_count(&receipt.value, decode)
11769 .map_err(|error| OperationFailure::new(error.into(), receipt.work))?;
11770 let parent_bytes = parent_count
11771 .checked_mul(size_of::<GenerationId>())
11772 .map(crate::foundation::usize_to_u64)
11773 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), receipt.work))?;
11774 let simultaneous = retained_bytes
11775 .checked_add(parent_bytes)
11776 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), receipt.work))?;
11777 let mut work = receipt
11778 .work
11779 .checked_add(WorkCounters {
11780 allocation_operations: u64::from(parent_count != 0),
11781 bytes_copied: parent_bytes,
11782 ..WorkCounters::default()
11783 })
11784 .map_err(|error| OperationFailure::new(error.into(), receipt.work))?;
11785 work.peak_allocation_bytes = work.peak_allocation_bytes.max(simultaneous);
11786 work.verify(budget)
11787 .map_err(|error| OperationFailure::new(error.into(), receipt.work))?;
11788 let root = crate::kernel::decode_generation_root(&receipt.value, decode)
11789 .map_err(|error| OperationFailure::new(error.into(), work))?;
11790 Ok((root, work))
11791}
11792
11793fn exact_mutation_regions(
11794 mutation: &Mutation,
11795 first: Option<FileRecord>,
11796 second: Option<FileRecord>,
11797) -> [Option<(Option<FileRecord>, DependencyRegion)>; 4] {
11798 let mut regions = [None, None, None, None];
11799 match mutation {
11800 Mutation::Create { .. } | Mutation::Link { .. } => {}
11801 Mutation::Restore { .. } => {
11802 regions[0] =
11803 first.map(|record| (Some(record), DependencyRegion::FileRecord(record.file_id)));
11804 }
11805 Mutation::Remove { .. } => {
11806 regions[0] =
11807 first.map(|record| (Some(record), DependencyRegion::FileRecord(record.file_id)));
11808 }
11809 Mutation::Rename { .. } => {
11810 regions[0] =
11811 second.map(|record| (Some(record), DependencyRegion::FileRecord(record.file_id)));
11812 }
11813 Mutation::SetMetadata { .. } => {
11814 regions[0] =
11815 first.map(|record| (Some(record), DependencyRegion::Metadata(record.file_id)));
11816 }
11817 Mutation::Write { offset, length, .. } | Mutation::ZeroRange { offset, length, .. } => {
11818 let [content, file_length] = regular_range_regions(first, *offset, *length);
11819 regions[0] = content.map(|region| (first, region));
11820 regions[1] = file_length.map(|region| (first, region));
11821 }
11822 Mutation::ValidateRegular { .. } => {
11823 regions[0] =
11824 first.map(|record| (Some(record), DependencyRegion::FileRecord(record.file_id)));
11825 }
11826 Mutation::Preallocate { .. } => {
11827 regions[0] =
11828 first.map(|record| (Some(record), DependencyRegion::FileLength(record.file_id)));
11829 }
11830 Mutation::Resize { .. } => {
11831 regions[0] =
11835 first.map(|record| (Some(record), DependencyRegion::FileRecord(record.file_id)));
11836 }
11837 Mutation::CloneRange {
11838 source_offset,
11839 destination_offset,
11840 length,
11841 ..
11842 } => {
11843 let [source_content, source_length] =
11844 regular_range_regions(first, *source_offset, *length);
11845 let [destination_content, destination_length] =
11846 regular_range_regions(second, *destination_offset, *length);
11847 regions[0] = source_content.map(|region| (first, region));
11848 regions[1] = source_length.map(|region| (first, region));
11849 regions[2] = destination_content.map(|region| (second, region));
11850 regions[3] = destination_length.map(|region| (second, region));
11851 }
11852 Mutation::File { file_id, mutation } => match mutation {
11853 FileMutation::SetMetadata { .. } => {
11854 regions[0] = Some((first, DependencyRegion::Metadata(*file_id)));
11855 }
11856 FileMutation::Write { offset, length, .. }
11857 | FileMutation::ZeroRange { offset, length, .. } => {
11858 let [content, file_length] = regular_range_regions(first, *offset, *length);
11859 regions[0] = content.map(|region| (first, region));
11860 regions[1] = file_length.map(|region| (first, region));
11861 }
11862 FileMutation::Preallocate { .. } => {
11863 regions[0] = Some((first, DependencyRegion::FileLength(*file_id)));
11864 }
11865 FileMutation::ReplaceRecord { .. }
11866 | FileMutation::ValidateRegular
11867 | FileMutation::Resize { .. } => {
11868 regions[0] = Some((first, DependencyRegion::FileRecord(*file_id)));
11869 }
11870 },
11871 Mutation::CloneFileRange {
11872 source_file_id,
11873 source_offset,
11874 destination_file_id,
11875 destination_offset,
11876 length,
11877 } => {
11878 let [source_content, source_length] =
11879 regular_range_regions(first, *source_offset, *length);
11880 let [destination_content, destination_length] =
11881 regular_range_regions(second, *destination_offset, *length);
11882 regions[0] = source_content.map(|region| (first, region));
11883 regions[1] = source_length.map(|region| (first, region));
11884 regions[2] = destination_content.map(|region| (second, region));
11885 regions[3] = destination_length.map(|region| (second, region));
11886 debug_assert!(first.is_none_or(|record| record.file_id == *source_file_id));
11887 debug_assert!(second.is_none_or(|record| record.file_id == *destination_file_id));
11888 }
11889 }
11890 regions
11891}
11892
11893fn regular_range_regions(
11894 record: Option<FileRecord>,
11895 offset: u64,
11896 length: u64,
11897) -> [Option<DependencyRegion>; 2] {
11898 let Some(record) = record else {
11899 return [None, None];
11900 };
11901 let logical_bytes = match record.payload {
11902 FilePayload::InlineRegular(data) => {
11903 u64::try_from(data.as_bytes().len()).unwrap_or(u64::MAX)
11904 }
11905 FilePayload::Regular { logical_bytes, .. } => logical_bytes,
11906 FilePayload::Directory { .. }
11907 | FilePayload::SymbolicLink { .. }
11908 | FilePayload::Device { .. }
11909 | FilePayload::Empty
11910 | FilePayload::ReparsePoint { .. } => {
11911 return [Some(DependencyRegion::FileRecord(record.file_id)), None];
11912 }
11913 };
11914 let overlap = logical_bytes.saturating_sub(offset).min(length);
11915 let content = (overlap != 0).then_some(DependencyRegion::ContentRange {
11916 file_id: record.file_id,
11917 offset,
11918 length: overlap,
11919 });
11920 let extends = offset
11921 .checked_add(length)
11922 .is_none_or(|end| end > logical_bytes);
11923 let file_length = extends.then_some(DependencyRegion::FileLength(record.file_id));
11924 [content, file_length]
11925}
11926
11927fn validate_planned_file_range(
11928 logical_bytes: u64,
11929 range: ByteRange,
11930 work: WorkCounters,
11931) -> Result<(), OperationFailure<FsError>> {
11932 let end = range.offset.checked_add(range.length).ok_or_else(|| {
11933 OperationFailure::new(FsError::FileRead(FileRangeReadError::InvalidRange), work)
11934 })?;
11935 if end > logical_bytes {
11936 return Err(OperationFailure::new(
11937 FsError::FileRead(FileRangeReadError::InvalidRange),
11938 work,
11939 ));
11940 }
11941 Ok(())
11942}
11943
11944async fn capture_dependencies_async<O: AsyncObjectStore>(
11945 objects: &O,
11946 generation_root: &ObjectId,
11947 config: VolumeConfig,
11948 regions: Vec<DependencyRegion>,
11949 budget: WorkBudget,
11950 cancellation: &CancellationToken,
11951) -> FsResult<Vec<Dependency>> {
11952 let probe = AuthenticatedGenerationProbe::new(objects, probe_limits(config))
11953 .map_err(|error| OperationFailure::before_work(error.into()))?;
11954 probe
11955 .capture_many_async(
11956 GenerationId::new(generation_root.digest),
11957 regions,
11958 config.limits.maximum_checkout_dependencies,
11959 budget,
11960 cancellation,
11961 )
11962 .await
11963 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::Probe))
11964}
11965
11966async fn capture_known_dependencies_async<O: AsyncObjectStore>(
11967 objects: &O,
11968 config: VolumeConfig,
11969 regions: Vec<(Option<FileRecord>, DependencyRegion)>,
11970 budget: WorkBudget,
11971 cancellation: &CancellationToken,
11972) -> FsResult<Vec<Dependency>> {
11973 let probe = AuthenticatedGenerationProbe::new(objects, probe_limits(config))
11974 .map_err(|error| OperationFailure::before_work(error.into()))?;
11975 probe
11976 .capture_records_many_async(
11977 regions,
11978 config.limits.maximum_checkout_dependencies,
11979 budget,
11980 cancellation,
11981 )
11982 .await
11983 .map_err(|failure| failure.map_with_prior_work(WorkCounters::default(), FsError::Probe))
11984}
11985
11986fn generation_from_record(
11987 record: &DurableCommit,
11988 volume_id: VolumeId,
11989 work: WorkCounters,
11990) -> Result<ObjectId, OperationFailure<FsError>> {
11991 if record.sequence == Sequence::new(1) {
11992 let creation = decode_volume_created(&record.payload, MAXIMUM_VOLUME_EVENT_BYTES)
11993 .map_err(|error| OperationFailure::new(error.into(), work))?;
11994 if creation.volume_id != volume_id {
11995 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
11996 }
11997 Ok(creation.initial_generation_root)
11998 } else {
11999 let publication = decode_published_generation(&record.payload, MAXIMUM_VOLUME_EVENT_BYTES)
12000 .map_err(|error| OperationFailure::new(error.into(), work))?;
12001 if publication.volume_id != volume_id {
12002 return Err(OperationFailure::new(FsError::VolumeMismatch, work));
12003 }
12004 Ok(publication.generation_root)
12005 }
12006}
12007
12008#[allow(clippy::too_many_arguments)]
12009async fn diff_generation_file_tables<O: AsyncObjectStore>(
12010 objects: &O,
12011 before: ObjectId,
12012 after: ObjectId,
12013 maximum_changes: u32,
12014 config: VolumeConfig,
12015 mut work: WorkCounters,
12016 budget: WorkBudget,
12017 cancellation: &CancellationToken,
12018) -> FsResult<GenerationDiff> {
12019 let records = diff_file_records_async(
12020 objects,
12021 Some(before),
12022 Some(after),
12023 maximum_changes,
12024 decode_limits(config),
12025 remaining(work, budget)?,
12026 cancellation,
12027 )
12028 .await
12029 .map_err(|failure| map_diff_failure(failure, work))?;
12030 work = add(work, records.work)?;
12031 let maximum = usize::try_from(maximum_changes).unwrap_or(usize::MAX);
12032 let mut files = Vec::new();
12033 files
12034 .try_reserve(records.changes.len())
12035 .map_err(|_| OperationFailure::new(FsError::DiffAllocationFailed, work))?;
12036 let mut bindings = Vec::new();
12037 let mut truncated = records.truncated;
12038 for change in records.changes {
12039 if files.len() + bindings.len() >= maximum {
12040 truncated = true;
12041 break;
12042 }
12043 let before_entries = directory_entries(change.before);
12044 let after_entries = directory_entries(change.after);
12045 files.push(FileRecordChange {
12046 file_id: change.key,
12047 before: change.before,
12048 after: change.after,
12049 });
12050 if before_entries == after_entries {
12051 continue;
12052 }
12053 let remaining_changes = maximum.saturating_sub(files.len() + bindings.len());
12054 if remaining_changes == 0 {
12055 truncated = true;
12056 continue;
12057 }
12058 let entries = diff_tree_entries_async(
12059 objects,
12060 before_entries,
12061 after_entries,
12062 u32::try_from(remaining_changes).unwrap_or(u32::MAX),
12063 decode_limits(config),
12064 remaining(work, budget)?,
12065 cancellation,
12066 )
12067 .await
12068 .map_err(|failure| map_diff_failure(failure, work))?;
12069 work = add(work, entries.work)?;
12070 truncated |= entries.truncated;
12071 bindings.extend(
12072 entries
12073 .changes
12074 .into_iter()
12075 .map(|entry| DirectoryBindingChange {
12076 directory_id: change.key,
12077 name: entry.key,
12078 before: entry.before,
12079 after: entry.after,
12080 }),
12081 );
12082 }
12083 Ok(OperationReceipt {
12084 value: GenerationDiff {
12085 files,
12086 bindings,
12087 truncated,
12088 },
12089 work,
12090 })
12091}
12092
12093async fn collect_generation_ancestors<O: AsyncObjectStore>(
12094 objects: &O,
12095 start: GenerationId,
12096 config: VolumeConfig,
12097 maximum_generations: u32,
12098 cancellation: &CancellationToken,
12099) -> Result<(BTreeMap<GenerationId, Option<VolumeId>>, bool), crate::workspace::WorkspaceError> {
12100 let maximum = usize::try_from(maximum_generations).unwrap_or(usize::MAX);
12101 let mut visited = BTreeMap::new();
12102 let mut pending = VecDeque::from([start]);
12103 while let Some(generation) = pending.pop_front() {
12104 if visited.contains_key(&generation) {
12105 continue;
12106 }
12107 if visited.len() == maximum {
12108 for frontier in pending {
12109 visited.entry(frontier).or_insert(None);
12110 }
12111 visited.entry(generation).or_insert(None);
12112 return Ok((visited, true));
12113 }
12114 let (root, _) = read_generation_root(
12115 objects,
12116 ObjectId {
12117 kind: ObjectKind::GenerationRoot,
12118 digest: generation.digest(),
12119 },
12120 config,
12121 WorkBudget::UNBOUNDED,
12122 cancellation,
12123 )
12124 .await
12125 .map_err(crate::workspace::WorkspaceError::engine)?;
12126 visited.insert(generation, Some(root.volume_id));
12127 pending.extend(root.parents);
12128 }
12129 Ok((visited, false))
12130}
12131
12132async fn find_first_generation_ancestor<O: AsyncObjectStore>(
12133 objects: &O,
12134 start: GenerationId,
12135 config: VolumeConfig,
12136 maximum_generations: u32,
12137 candidates: &BTreeMap<GenerationId, Option<VolumeId>>,
12138 candidates_truncated: bool,
12139 cancellation: &CancellationToken,
12140) -> Result<WorkspaceCommonAncestor, crate::workspace::WorkspaceError> {
12141 let maximum = usize::try_from(maximum_generations).unwrap_or(usize::MAX);
12142 let mut visited = BTreeSet::new();
12143 let mut pending = VecDeque::from([start]);
12144 while let Some(generation) = pending.pop_front() {
12145 if let Some(Some(volume_id)) = candidates.get(&generation) {
12146 return Ok(WorkspaceCommonAncestor {
12147 id: generation,
12148 volume_id: *volume_id,
12149 });
12150 }
12151 let is_candidate_frontier = matches!(candidates.get(&generation), Some(None));
12152 if visited.contains(&generation) {
12153 continue;
12154 }
12155 if visited.len() == maximum {
12156 return Err(crate::workspace::WorkspaceError::LineageLimit);
12157 }
12158 let (root, _) = read_generation_root(
12159 objects,
12160 ObjectId {
12161 kind: ObjectKind::GenerationRoot,
12162 digest: generation.digest(),
12163 },
12164 config,
12165 WorkBudget::UNBOUNDED,
12166 cancellation,
12167 )
12168 .await
12169 .map_err(crate::workspace::WorkspaceError::engine)?;
12170 visited.insert(generation);
12171 if is_candidate_frontier {
12172 return Ok(WorkspaceCommonAncestor {
12173 id: generation,
12174 volume_id: root.volume_id,
12175 });
12176 }
12177 pending.extend(root.parents);
12178 }
12179 if candidates_truncated {
12180 Err(crate::workspace::WorkspaceError::LineageLimit)
12181 } else {
12182 Err(crate::workspace::WorkspaceError::NoCommonAncestor)
12183 }
12184}
12185
12186fn durable_head(commit: &DurableCommit) -> Head {
12187 Head {
12188 epoch: commit.epoch,
12189 sequence: commit.sequence,
12190 digest: commit.digest,
12191 }
12192}
12193
12194fn validate_volume_capabilities(
12195 config: VolumeConfig,
12196 capabilities: EmbeddedCapabilities,
12197) -> Result<(), OperationFailure<FsError>> {
12198 config
12199 .validate()
12200 .map_err(|error| OperationFailure::before_work(error.into()))?;
12201 if config.lifecycle == Lifecycle::Durable && !capabilities.durable {
12202 return Err(OperationFailure::before_work(
12203 FsError::UnsupportedDurability,
12204 ));
12205 }
12206 Ok(())
12207}
12208
12209fn validate_checkout(
12210 mode: CheckoutMode,
12211 config: VolumeConfig,
12212) -> Result<(), OperationFailure<FsError>> {
12213 mode.validate()
12214 .map_err(|error| OperationFailure::before_work(error.into()))?;
12215 match (mode.access, mode.consistency, mode.mutations) {
12216 (
12217 AccessMode::ReadOnly,
12218 ConsistencyMode::Pinned
12219 | ConsistencyMode::Manual
12220 | ConsistencyMode::TrackingSafe
12221 | ConsistencyMode::Live,
12222 MutationMode::None,
12223 )
12224 | (
12225 AccessMode::ReadWrite,
12226 ConsistencyMode::Pinned
12227 | ConsistencyMode::Manual
12228 | ConsistencyMode::TrackingSafe
12229 | ConsistencyMode::Live,
12230 MutationMode::PrivateOverlay,
12231 ) => Ok(()),
12232 (AccessMode::ReadWrite, ConsistencyMode::Live, MutationMode::DirectLive)
12233 if config.concurrency == ConcurrencyMode::SerializedAuthority =>
12234 {
12235 Ok(())
12236 }
12237 (AccessMode::ReadWrite, ConsistencyMode::Live, MutationMode::DirectLive) => Err(
12238 OperationFailure::before_work(FsError::LiveRequiresSerializedAuthority),
12239 ),
12240 _ => Err(OperationFailure::before_work(FsError::UnsupportedCheckout)),
12241 }
12242}
12243
12244fn closure_limits(config: VolumeConfig) -> ClosureLimits {
12245 ClosureLimits {
12246 decode: decode_limits(config),
12247 maximum_objects: config.limits.maximum_objects_per_generation,
12248 maximum_files: config.limits.maximum_files_per_generation,
12249 maximum_object_bytes: config.limits.maximum_generation_bytes,
12250 profile: config.profile,
12251 symbolic_links: config.symbolic_links,
12252 hard_links: config.hard_links,
12253 sparse_files: config.sparse_files,
12254 }
12255}
12256
12257fn decode_limits(config: VolumeConfig) -> DecodeLimits {
12258 DecodeLimits {
12259 maximum_object_bytes: config.limits.maximum_object_bytes,
12260 maximum_name_bytes: config.limits.maximum_component_bytes,
12261 maximum_page_items: config.limits.maximum_directory_page_entries,
12262 maximum_page_bytes: u32::try_from(config.limits.maximum_object_bytes).unwrap_or(u32::MAX),
12263 maximum_page_height: config.limits.maximum_page_height,
12264 maximum_visited_pages: u32::try_from(config.limits.maximum_objects_per_generation)
12265 .unwrap_or(u32::MAX),
12266 }
12267}
12268
12269fn directory_entries(record: Option<FileRecord>) -> Option<ObjectId> {
12270 let record = record?;
12271 match record.payload {
12272 FilePayload::Directory { entries } => Some(entries),
12273 _ => None,
12274 }
12275}
12276
12277fn map_diff_failure(
12278 failure: OperationFailure<PersistentDiffError>,
12279 prior: WorkCounters,
12280) -> OperationFailure<FsError> {
12281 failure.map_with_prior_work(prior, |error| match error {
12282 PersistentDiffError::WrongRootKind | PersistentDiffError::InvalidLimit => {
12283 FsError::InvalidDiff
12284 }
12285 PersistentDiffError::AllocationFailed => FsError::DiffAllocationFailed,
12286 PersistentDiffError::Storage(error) => FsError::Object(error),
12287 PersistentDiffError::Decode(error) => FsError::Decode(error),
12288 PersistentDiffError::Work(error) => FsError::Work(error),
12289 PersistentDiffError::Cancelled => FsError::Cancelled(CancellationError),
12290 })
12291}
12292
12293fn map_merge_failure(
12294 failure: OperationFailure<MergeGenerationError>,
12295 prior: WorkCounters,
12296) -> OperationFailure<FsError> {
12297 failure.map_with_prior_work(prior, |error| match error {
12298 MergeGenerationError::InvalidDiff => FsError::InvalidDiff,
12299 MergeGenerationError::ChangeLimit => FsError::MergeChangeLimit,
12300 MergeGenerationError::AllocationFailed => FsError::DiffAllocationFailed,
12301 MergeGenerationError::Object(error) => FsError::Object(error),
12302 MergeGenerationError::Decode(error) => FsError::Decode(error),
12303 MergeGenerationError::FileTable(error) => FsError::MergeFileTable(error),
12304 MergeGenerationError::Tree(error) => FsError::MergeTree(error),
12305 MergeGenerationError::ExtentRead(error) => {
12306 FsError::FileRead(FileRangeReadError::Extent(error))
12307 }
12308 MergeGenerationError::ExtentMutation(error) => {
12309 FsError::DetachedMutation(RegularMutationError::Extent(error))
12310 }
12311 MergeGenerationError::Checkpoint(error) => FsError::Checkpoint(error),
12312 MergeGenerationError::Cancelled(error) => FsError::Cancelled(error),
12313 MergeGenerationError::Work(error) => FsError::Work(error),
12314 })
12315}
12316
12317const fn live_publication_observation(
12318 outcome: CheckoutCommitOutcome,
12319) -> LivePublicationObservation {
12320 match outcome {
12321 CheckoutCommitOutcome::Committed {
12322 generation_id,
12323 head,
12324 } => LivePublicationObservation::Committed {
12325 generation_id,
12326 head,
12327 },
12328 CheckoutCommitOutcome::AlreadyCommitted {
12329 generation_id,
12330 head,
12331 } => LivePublicationObservation::AlreadyCommitted {
12332 generation_id,
12333 head,
12334 },
12335 CheckoutCommitOutcome::Conflict { actual } => {
12336 LivePublicationObservation::Conflict { actual }
12337 }
12338 CheckoutCommitOutcome::Fenced { actual_epoch } => {
12339 LivePublicationObservation::Fenced { actual_epoch }
12340 }
12341 CheckoutCommitOutcome::IdempotencyConflict {
12342 committed_fingerprint,
12343 } => LivePublicationObservation::IdempotencyConflict {
12344 committed_fingerprint,
12345 },
12346 }
12347}
12348
12349fn probe_limits(config: VolumeConfig) -> ProbeLimits {
12350 ProbeLimits {
12351 decode: decode_limits(config),
12352 maximum_cached_generations: 2,
12353 maximum_cached_records: config.limits.maximum_checkout_dependencies,
12354 maximum_extent_spans: config.limits.maximum_directory_page_entries,
12355 maximum_content_payload_bytes: config.limits.maximum_read_bytes,
12356 maximum_directory_entries: config.limits.maximum_directory_page_entries,
12357 }
12358}
12359
12360fn empty_metadata() -> FileMetadata {
12361 FileMetadata::default()
12362}
12363
12364fn derived_file_id(volume_id: VolumeId) -> (FileId, WorkCounters) {
12365 let digest = derived_digest(ROOT_FILE_DOMAIN, volume_id);
12366 let mut bytes = [0_u8; 16];
12367 bytes.copy_from_slice(&digest.as_bytes()[..16]);
12368 (
12369 FileId::from_bytes(bytes),
12370 identity_hash_work(ROOT_FILE_DOMAIN),
12371 )
12372}
12373
12374fn derived_operation_id(volume_id: VolumeId) -> (OperationId, WorkCounters) {
12375 let digest = derived_digest(CREATION_OPERATION_DOMAIN, volume_id);
12376 let mut bytes = [0_u8; 16];
12377 bytes.copy_from_slice(&digest.as_bytes()[..16]);
12378 (
12379 OperationId::from_bytes(bytes),
12380 identity_hash_work(CREATION_OPERATION_DOMAIN),
12381 )
12382}
12383
12384fn derived_digest(domain: &[u8], volume_id: VolumeId) -> Digest {
12385 let mut hasher = blake3::Hasher::new();
12386 hasher.update(domain);
12387 hasher.update(&volume_id.into_bytes());
12388 Digest::from_bytes(*hasher.finalize().as_bytes())
12389}
12390
12391fn identity_hash_work(domain: &[u8]) -> WorkCounters {
12392 WorkCounters {
12393 bytes_hashed: u64::try_from(domain.len()).unwrap_or(u64::MAX) + 16,
12394 ..WorkCounters::default()
12395 }
12396}
12397
12398fn creation_commit(operation_id: OperationId, payload: Vec<u8>) -> (ProposedCommit, WorkCounters) {
12399 let mut hasher = blake3::Hasher::new();
12400 hasher.update(CREATION_FINGERPRINT_DOMAIN);
12401 hasher.update(&payload);
12402 let length = u64::try_from(payload.len()).unwrap_or(u64::MAX);
12403 let capacity = u64::try_from(payload.capacity()).unwrap_or(u64::MAX);
12404 (
12405 ProposedCommit {
12406 operation_id,
12407 fingerprint: Digest::from_bytes(*hasher.finalize().as_bytes()),
12408 payload: Bytes::from(payload),
12409 },
12410 WorkCounters {
12411 bytes_encoded: length,
12412 bytes_hashed: u64::try_from(CREATION_FINGERPRINT_DOMAIN.len())
12413 .unwrap_or(u64::MAX)
12414 .saturating_add(length),
12415 allocation_operations: 1,
12416 peak_allocation_bytes: capacity,
12417 ..WorkCounters::default()
12418 },
12419 )
12420}
12421
12422fn add(prior: WorkCounters, next: WorkCounters) -> Result<WorkCounters, OperationFailure<FsError>> {
12423 prior
12424 .checked_add(next)
12425 .map_err(|error| OperationFailure::new(error.into(), prior))
12426}
12427
12428fn map_transfer_error(error: GenerationTransferError) -> FsError {
12429 match error {
12430 GenerationTransferError::Cancelled(error) => FsError::Cancelled(error),
12431 GenerationTransferError::Manifest(_) | GenerationTransferError::ManifestMismatch => {
12432 FsError::InvalidExportManifest
12433 }
12434 GenerationTransferError::Closure(error) => FsError::Closure(error),
12435 GenerationTransferError::Object(error) => FsError::Object(error),
12436 GenerationTransferError::Work(error) => FsError::Work(error),
12437 error @ (GenerationTransferError::InvalidCursor
12438 | GenerationTransferError::EmptyBatch
12439 | GenerationTransferError::TooManyObjects
12440 | GenerationTransferError::AllocationFailed) => FsError::Transfer(error),
12441 }
12442}
12443
12444#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
12445fn object_vec_bytes(objects: &Vec<ObjectId>) -> u64 {
12446 u64::try_from(objects.capacity())
12447 .unwrap_or(u64::MAX)
12448 .saturating_mul(u64::try_from(size_of::<ObjectId>()).unwrap_or(u64::MAX))
12449}
12450
12451#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
12452fn object_slice_bytes(objects: &[ObjectId]) -> u64 {
12453 u64::try_from(objects.len())
12454 .unwrap_or(u64::MAX)
12455 .saturating_mul(u64::try_from(size_of::<ObjectId>()).unwrap_or(u64::MAX))
12456}
12457
12458#[cfg(all(feature = "local", not(target_arch = "wasm32")))]
12459fn merge_sorted_object_ids(
12460 reachable: &mut Vec<ObjectId>,
12461 incoming: &[ObjectId],
12462 incoming_bytes: u64,
12463 retained_bytes: u64,
12464 work: &mut WorkCounters,
12465 budget: WorkBudget,
12466) -> Result<(), OperationFailure<FsError>> {
12467 let maximum_items = reachable
12468 .len()
12469 .checked_add(incoming.len())
12470 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), *work))?;
12471 let maximum_bytes = u64::try_from(maximum_items)
12472 .unwrap_or(u64::MAX)
12473 .saturating_mul(u64::try_from(size_of::<ObjectId>()).unwrap_or(u64::MAX));
12474 let peak = retained_bytes
12475 .checked_add(object_vec_bytes(reachable))
12476 .and_then(|value| value.checked_add(incoming_bytes))
12477 .and_then(|value| value.checked_add(maximum_bytes))
12478 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), *work))?;
12479 let admission = work
12480 .checked_add(WorkCounters {
12481 items_examined: u64::try_from(maximum_items).unwrap_or(u64::MAX),
12482 bytes_copied: maximum_bytes,
12483 allocation_operations: u64::from(maximum_items != 0),
12484 peak_allocation_bytes: peak,
12485 ..WorkCounters::default()
12486 })
12487 .map_err(|error| OperationFailure::new(error.into(), *work))?;
12488 admission
12489 .verify(budget)
12490 .map_err(|error| OperationFailure::new(error.into(), *work))?;
12491 let mut merged = Vec::new();
12492 merged
12493 .try_reserve_exact(maximum_items)
12494 .map_err(|_| OperationFailure::new(FsError::GarbageCollectionAllocationFailed, *work))?;
12495 let mut left_iter = reachable.iter().copied().peekable();
12496 let mut right_iter = incoming.iter().copied().peekable();
12497 loop {
12498 match (left_iter.peek(), right_iter.peek()) {
12499 (Some(&left), Some(&right)) => match left.cmp(&right) {
12500 std::cmp::Ordering::Less => {
12501 merged.push(left);
12502 left_iter.next();
12503 }
12504 std::cmp::Ordering::Greater => {
12505 merged.push(right);
12506 right_iter.next();
12507 }
12508 std::cmp::Ordering::Equal => {
12509 merged.push(left);
12510 left_iter.next();
12511 right_iter.next();
12512 }
12513 },
12514 (Some(&left), None) => {
12515 merged.push(left);
12516 left_iter.next();
12517 }
12518 (None, Some(&right)) => {
12519 merged.push(right);
12520 right_iter.next();
12521 }
12522 (None, None) => break,
12523 }
12524 }
12525 let copied_bytes = u64::try_from(merged.len())
12526 .unwrap_or(u64::MAX)
12527 .saturating_mul(u64::try_from(size_of::<ObjectId>()).unwrap_or(u64::MAX));
12528 *work = work
12529 .checked_add(WorkCounters {
12530 items_examined: u64::try_from(maximum_items).unwrap_or(u64::MAX),
12531 bytes_copied: copied_bytes,
12532 allocation_operations: u64::from(maximum_items != 0),
12533 peak_allocation_bytes: peak,
12534 ..WorkCounters::default()
12535 })
12536 .map_err(|error| OperationFailure::new(error.into(), *work))?;
12537 *reachable = merged;
12538 Ok(())
12539}
12540
12541fn remaining(
12542 work: WorkCounters,
12543 budget: WorkBudget,
12544) -> Result<WorkBudget, OperationFailure<FsError>> {
12545 work.remaining(budget)
12546 .map_err(|error| OperationFailure::new(error.into(), work))
12547}
12548
12549fn merge_simultaneous_work(
12550 prior: WorkCounters,
12551 mut nested: WorkCounters,
12552 live_bytes: u64,
12553 budget: WorkBudget,
12554) -> Result<WorkCounters, OperationFailure<FsError>> {
12555 let simultaneous_peak = live_bytes
12556 .checked_add(nested.peak_allocation_bytes)
12557 .ok_or_else(|| OperationFailure::new(FsError::Work(WorkError::Overflow), prior))?;
12558 nested.peak_allocation_bytes = 0;
12559 let mut merged = prior
12560 .checked_add(nested)
12561 .map_err(|error| OperationFailure::new(error.into(), prior))?;
12562 merged.peak_allocation_bytes = merged.peak_allocation_bytes.max(simultaneous_peak);
12563 merged
12564 .verify(budget)
12565 .map_err(|error| OperationFailure::new(error.into(), merged))?;
12566 Ok(merged)
12567}
12568
12569fn merge_simultaneous_failure(
12570 prior: WorkCounters,
12571 mut nested: WorkCounters,
12572 live_bytes: u64,
12573 error: FsError,
12574) -> OperationFailure<FsError> {
12575 let Some(simultaneous_peak) = live_bytes.checked_add(nested.peak_allocation_bytes) else {
12576 return OperationFailure::new(FsError::Work(WorkError::Overflow), prior);
12577 };
12578 nested.peak_allocation_bytes = 0;
12579 let Ok(mut merged) = prior.checked_add(nested) else {
12580 return OperationFailure::new(FsError::Work(WorkError::Overflow), prior);
12581 };
12582 merged.peak_allocation_bytes = merged.peak_allocation_bytes.max(simultaneous_peak);
12583 OperationFailure::new(error, merged)
12584}
12585
12586#[cfg(all(test, feature = "memory"))]
12587#[path = "tests/facade.rs"]
12588mod tests;