@@ -13,14 +13,15 @@ use once_cell::sync::OnceCell;
1313use rand:: { distr:: Alphanumeric , Rng } ;
1414use rustix:: {
1515 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 ,
1818 } ,
1919 io:: { Errno , Result as ErrnoResult } ,
2020} ;
2121use sha2:: { Digest , Sha256 } ;
2222
2323use crate :: {
24+ blob:: { BlobReader , BlobWriter } ,
2425 fsverity:: {
2526 compute_verity, enable_verity, ensure_verity_equal, measure_verity, FsVerityHashValue ,
2627 } ,
@@ -362,6 +363,59 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
362363 Ok ( ( ) )
363364 }
364365
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+
365419 /// this function is not safe for untrusted users
366420 pub fn write_image ( & self , name : Option < & str > , data : & [ u8 ] ) -> Result < ObjectID > {
367421 let object_id = self . ensure_object ( data) ?;
@@ -593,6 +647,17 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
593647 } ) ?;
594648 }
595649
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+
596661 for first_byte in 0x0 ..=0xff {
597662 let dirfd = match self . openat (
598663 & format ! ( "objects/{first_byte:02x}" ) ,
0 commit comments