@@ -13,14 +13,15 @@ use once_cell::sync::OnceCell;
13
13
use rand:: { distr:: Alphanumeric , Rng } ;
14
14
use rustix:: {
15
15
fs:: {
16
- fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, symlinkat , AtFlags , Dir ,
17
- FileType , FlockOperation , Mode , OFlags , CWD ,
16
+ fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, renameat , symlinkat , AtFlags ,
17
+ Dir , FileType , FlockOperation , Mode , OFlags , CWD ,
18
18
} ,
19
19
io:: { Errno , Result as ErrnoResult } ,
20
20
} ;
21
21
use sha2:: { Digest , Sha256 } ;
22
22
23
23
use crate :: {
24
+ blob:: { BlobReader , BlobWriter } ,
24
25
fsverity:: {
25
26
compute_verity, enable_verity, ensure_verity_equal, measure_verity, FsVerityHashValue ,
26
27
} ,
@@ -362,6 +363,59 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
362
363
Ok ( ( ) )
363
364
}
364
365
366
+ pub fn has_named_blob ( & self , name : & str ) -> bool {
367
+ let blob_path = format ! ( "blobs/refs/{}" , name) ;
368
+
369
+ match readlinkat ( & self . repository , & blob_path, [ ] ) {
370
+ Ok ( _) => true ,
371
+ Err ( _) => false ,
372
+ }
373
+ }
374
+
375
+ /// Creates a Blobriter for writing a blob.
376
+ /// You should write the data to the returned object and then pass it to .store_blob() to
377
+ /// store the result.
378
+ pub fn create_blob ( self : & Arc < Self > ) -> BlobWriter < ObjectID > {
379
+ BlobWriter :: new ( self )
380
+ }
381
+
382
+ pub fn write_blob ( & self , writer : BlobWriter < ObjectID > , name : Option < & str > ) -> Result < ObjectID > {
383
+ let object_id = writer. done ( ) ?;
384
+
385
+ let object_path = Self :: format_object_path ( & object_id) ;
386
+ let blob_path = format ! ( "blobs/{}" , object_id. to_hex( ) ) ;
387
+
388
+ self . ensure_symlink ( & blob_path, & object_path) ?;
389
+
390
+ if let Some ( reference) = name {
391
+ let ref_path = format ! ( "blobs/refs/{reference}" ) ;
392
+ self . symlink ( & ref_path, & blob_path) ?;
393
+ }
394
+
395
+ Ok ( object_id)
396
+ }
397
+
398
+ pub fn name_blob ( & self , object_id : & ObjectID , name : & str ) -> Result < ( ) > {
399
+ let blob_path = format ! ( "blobs/{}" , object_id. to_hex( ) ) ;
400
+ let reference_path = format ! ( "blobs/refs/{name}" ) ;
401
+ self . symlink ( & reference_path, & blob_path) ?;
402
+ Ok ( ( ) )
403
+ }
404
+
405
+ pub fn open_blob ( & self , name : & str ) -> Result < BlobReader < File , ObjectID > > {
406
+ let fd = self
407
+ . openat ( & format ! ( "blobs/{name}" ) , OFlags :: RDONLY )
408
+ . with_context ( || format ! ( "Opening ref 'blobs/{name}'" ) ) ?;
409
+
410
+ if !name. contains ( "/" ) {
411
+ // A name with no slashes in it is taken to be a sha256 fs-verity digest
412
+ ensure_verity_equal ( & fd, & ObjectID :: from_hex ( name) ?) ?;
413
+ }
414
+
415
+ let file = File :: from ( fd) ;
416
+ BlobReader :: new ( file)
417
+ }
418
+
365
419
/// this function is not safe for untrusted users
366
420
pub fn write_image ( & self , name : Option < & str > , data : & [ u8 ] ) -> Result < ObjectID > {
367
421
let object_id = self . ensure_object ( data) ?;
@@ -593,6 +647,17 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
593
647
} ) ?;
594
648
}
595
649
650
+ for object in self . gc_category ( "blobs" ) ? {
651
+ println ! ( "{object:?} lives as a blob" ) ;
652
+ objects. insert ( object. clone ( ) ) ;
653
+
654
+ let blob = self . open_blob ( & object. to_hex ( ) ) ?;
655
+ for reference in blob. refs {
656
+ println ! ( " with {reference:?}" ) ;
657
+ objects. insert ( reference. clone ( ) ) ;
658
+ }
659
+ }
660
+
596
661
for first_byte in 0x0 ..=0xff {
597
662
let dirfd = match self . openat (
598
663
& format ! ( "objects/{first_byte:02x}" ) ,
0 commit comments