@@ -2,16 +2,17 @@ use std::{
2
2
collections:: HashSet ,
3
3
ffi:: CStr ,
4
4
fs:: File ,
5
- io:: { ErrorKind , Read , Write } ,
5
+ io:: { Read , Write } ,
6
6
os:: fd:: { AsFd , OwnedFd } ,
7
7
path:: { Path , PathBuf } ,
8
8
} ;
9
9
10
10
use anyhow:: { bail, ensure, Context , Result } ;
11
+ use once_cell:: sync:: OnceCell ;
11
12
use rustix:: {
12
13
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 ,
15
16
} ,
16
17
io:: { Errno , Result as ErrnoResult } ,
17
18
} ;
@@ -26,9 +27,35 @@ use crate::{
26
27
util:: { proc_self_fd, Sha256Digest } ,
27
28
} ;
28
29
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
+
29
55
#[ derive( Debug ) ]
30
56
pub struct Repository < ObjectID : FsVerityHashValue > {
31
57
repository : OwnedFd ,
58
+ objects : OnceCell < OwnedFd > ,
32
59
_data : std:: marker:: PhantomData < ObjectID > ,
33
60
}
34
61
@@ -39,11 +66,9 @@ impl<ObjectID: FsVerityHashValue> Drop for Repository<ObjectID> {
39
66
}
40
67
41
68
impl < 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 ) )
47
72
}
48
73
49
74
pub fn open_path ( dirfd : impl AsFd , path : impl AsRef < Path > ) -> Result < Self > {
@@ -58,6 +83,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
58
83
59
84
Ok ( Self {
60
85
repository,
86
+ objects : OnceCell :: new ( ) ,
61
87
_data : std:: marker:: PhantomData ,
62
88
} )
63
89
}
@@ -80,48 +106,61 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
80
106
}
81
107
82
108
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 dirfd = self . objects_dir ( ) ?;
110
+ let id: ObjectID = compute_verity ( data) ;
86
111
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 ( ) ;
91
113
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
+ dirfd,
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
+ }
94
134
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 ( dirfd, & id. to_object_dir ( ) , OFlags :: RDWR | OFlags :: TMPFILE ) ?;
136
+ let mut file = File :: from ( fd) ;
137
+ file. write_all ( data) ?;
138
+ fdatasync ( & file) ?;
103
139
104
140
// 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 ) ;
107
143
108
144
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" ) ?;
110
146
111
- if let Err ( err ) = linkat (
147
+ match linkat (
112
148
CWD ,
113
149
proc_self_fd ( & ro_fd) ,
114
- & self . repository ,
115
- file ,
150
+ dirfd ,
151
+ path ,
116
152
AtFlags :: SYMLINK_FOLLOW ,
117
153
) {
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" ) ;
120
160
}
121
161
}
122
162
123
- drop ( ro_fd) ;
124
- Ok ( digest)
163
+ Ok ( id)
125
164
}
126
165
127
166
fn open_with_verity ( & self , filename : & str , expected_verity : & ObjectID ) -> Result < OwnedFd > {
@@ -350,7 +389,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> {
350
389
Ok ( mount_composefs_at (
351
390
image,
352
391
name,
353
- & self . object_dir ( ) ?,
392
+ self . objects_dir ( ) ?,
354
393
mountpoint,
355
394
) ?)
356
395
}
0 commit comments