@@ -2,16 +2,17 @@ use std::{
22 collections:: HashSet ,
33 ffi:: CStr ,
44 fs:: File ,
5- io:: { ErrorKind , Read , Write } ,
5+ io:: { Read , Write } ,
66 os:: fd:: { AsFd , OwnedFd } ,
77 path:: { Path , PathBuf } ,
88} ;
99
1010use anyhow:: { bail, ensure, Context , Result } ;
11+ use once_cell:: sync:: OnceCell ;
1112use rustix:: {
1213 fs:: {
13- accessat , fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, symlinkat, Access ,
14- AtFlags , Dir , FileType , FlockOperation , Mode , OFlags , CWD ,
14+ fdatasync, flock, linkat, mkdirat, open, openat, readlinkat, symlinkat, AtFlags , Dir ,
15+ FileType , FlockOperation , Mode , OFlags , CWD ,
1516 } ,
1617 io:: { Errno , Result as ErrnoResult } ,
1718} ;
@@ -26,9 +27,35 @@ use crate::{
2627 util:: { proc_self_fd, Sha256Digest } ,
2728} ;
2829
30+ /// Call openat() on the named subdirectory of "dirfd", possibly creating it first.
31+ ///
32+ /// We assume that the directory will probably exist (ie: we try the open first), and on ENOENT, we
33+ /// mkdirat() and retry.
34+ fn ensure_dir_and_openat ( dirfd : impl AsFd , filename : & str , flags : OFlags ) -> ErrnoResult < OwnedFd > {
35+ match openat (
36+ & dirfd,
37+ filename,
38+ flags | OFlags :: CLOEXEC | OFlags :: DIRECTORY ,
39+ 0o666 . into ( ) ,
40+ ) {
41+ Ok ( file) => Ok ( file) ,
42+ Err ( Errno :: NOENT ) => match mkdirat ( & dirfd, filename, 0o777 . into ( ) ) {
43+ Ok ( ( ) ) | Err ( Errno :: EXIST ) => openat (
44+ dirfd,
45+ filename,
46+ flags | OFlags :: CLOEXEC | OFlags :: DIRECTORY ,
47+ 0o666 . into ( ) ,
48+ ) ,
49+ Err ( other) => Err ( other) ,
50+ } ,
51+ Err ( other) => Err ( other) ,
52+ }
53+ }
54+
2955#[ derive( Debug ) ]
3056pub struct Repository < ObjectID : FsVerityHashValue > {
3157 repository : OwnedFd ,
58+ objects : OnceCell < OwnedFd > ,
3259 _data : std:: marker:: PhantomData < ObjectID > ,
3360}
3461
@@ -39,11 +66,9 @@ impl<ObjectID: FsVerityHashValue> Drop for Repository<ObjectID> {
3966}
4067
4168impl < ObjectID : FsVerityHashValue > Repository < ObjectID > {
42- pub fn object_dir ( & self ) -> ErrnoResult < OwnedFd > {
43- self . openat (
44- "objects" ,
45- OFlags :: PATH | OFlags :: DIRECTORY | OFlags :: CLOEXEC ,
46- )
69+ pub fn objects_dir ( & self ) -> ErrnoResult < & OwnedFd > {
70+ self . objects
71+ . get_or_try_init ( || ensure_dir_and_openat ( & self . repository , "objects" , OFlags :: PATH ) )
4772 }
4873
4974 pub fn open_path ( dirfd : impl AsFd , path : impl AsRef < Path > ) -> Result < Self > {
@@ -58,6 +83,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
5883
5984 Ok ( Self {
6085 repository,
86+ objects : OnceCell :: new ( ) ,
6187 _data : std:: marker:: PhantomData ,
6288 } )
6389 }
@@ -80,48 +106,61 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
80106 }
81107
82108 pub fn ensure_object ( & self , data : & [ u8 ] ) -> Result < ObjectID > {
83- let digest: ObjectID = compute_verity ( data) ;
84- let dir = PathBuf :: from ( format ! ( "objects/{}" , digest. to_object_dir( ) ) ) ;
85- let file = dir. join ( digest. to_object_basename ( ) ) ;
109+ let objects = self . objects_dir ( ) ?;
110+ let id: ObjectID = compute_verity ( data) ;
86111
87- // fairly common...
88- if accessat ( & self . repository , & file, Access :: READ_OK , AtFlags :: empty ( ) ) == Ok ( ( ) ) {
89- return Ok ( digest) ;
90- }
112+ let path = id. to_object_pathname ( ) ;
91113
92- self . ensure_dir ( "objects" ) ?;
93- self . ensure_dir ( & dir) ?;
114+ // the usual case is that the file will already exist
115+ match openat (
116+ objects,
117+ & path,
118+ OFlags :: RDONLY | OFlags :: CLOEXEC ,
119+ Mode :: empty ( ) ,
120+ ) {
121+ Ok ( fd) => {
122+ // measure the existing file to ensure that it's correct
123+ // TODO: try to replace file if it's broken?
124+ ensure_verity_equal ( fd, & id) ?;
125+ return Ok ( id) ;
126+ }
127+ Err ( Errno :: NOENT ) => {
128+ // in this case we'll create the file
129+ }
130+ Err ( other) => {
131+ return Err ( other) . context ( "Checking for existing object in repository" ) ?;
132+ }
133+ }
94134
95- let fd = openat (
96- & self . repository ,
97- & dir,
98- OFlags :: RDWR | OFlags :: CLOEXEC | OFlags :: TMPFILE ,
99- 0o666 . into ( ) ,
100- ) ?;
101- rustix:: io:: write ( & fd, data) ?; // TODO: no write_all() here...
102- fdatasync ( & fd) ?;
135+ let fd = ensure_dir_and_openat ( objects, & id. to_object_dir ( ) , OFlags :: RDWR | OFlags :: TMPFILE ) ?;
136+ let mut file = File :: from ( fd) ;
137+ file. write_all ( data) ?;
138+ fdatasync ( & file) ?;
103139
104140 // We can't enable verity with an open writable fd, so re-open and close the old one.
105- let ro_fd = open ( proc_self_fd ( & fd ) , OFlags :: RDONLY , Mode :: empty ( ) ) ?;
106- drop ( fd ) ;
141+ let ro_fd = open ( proc_self_fd ( & file ) , OFlags :: RDONLY , Mode :: empty ( ) ) ?;
142+ drop ( file ) ;
107143
108144 enable_verity :: < ObjectID > ( & ro_fd) . context ( "Enabling verity digest" ) ?;
109- ensure_verity_equal ( & ro_fd, & digest ) . context ( "Double-checking verity digest" ) ?;
145+ ensure_verity_equal ( & ro_fd, & id ) . context ( "Double-checking verity digest" ) ?;
110146
111- if let Err ( err ) = linkat (
147+ match linkat (
112148 CWD ,
113149 proc_self_fd ( & ro_fd) ,
114- & self . repository ,
115- file ,
150+ objects ,
151+ path ,
116152 AtFlags :: SYMLINK_FOLLOW ,
117153 ) {
118- if err. kind ( ) != ErrorKind :: AlreadyExists {
119- return Err ( err. into ( ) ) ;
154+ Ok ( ( ) ) => { }
155+ Err ( Errno :: EXIST ) => {
156+ // TODO: strictly, we should measure the newly-appeared file
157+ }
158+ Err ( other) => {
159+ return Err ( other) . context ( "Linking created object file" ) ;
120160 }
121161 }
122162
123- drop ( ro_fd) ;
124- Ok ( digest)
163+ Ok ( id)
125164 }
126165
127166 fn open_with_verity ( & self , filename : & str , expected_verity : & ObjectID ) -> Result < OwnedFd > {
@@ -350,7 +389,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
350389 Ok ( mount_composefs_at (
351390 image,
352391 name,
353- & self . object_dir ( ) ?,
392+ self . objects_dir ( ) ?,
354393 mountpoint,
355394 ) ?)
356395 }
0 commit comments