Skip to content

Commit 2de9db1

Browse files
repository: use OnceCell for objects_dir
Rename our `Repository::object_dir()` accessor to `.objects_dir()` (the directory is called `"objects"`) and change its implementation to use a OnceCell. ostree uses this "initialize on first use" pattern for some of its directories as well, and I like it. We use the external `once_cell::` crate (which we already had as a -devel dependency) because the .try() version of the API is not yet stable in the standard library. Clean up our ensure_object() implementation to use `.objects_dir()` and generally make things a bit more robust: - we avoid unnecessary mkdir() calls for directories which almost certainly already exist - instead of checking merely for the existence of an object file with the correct name, we actually measure it now - we play around less with joining pathnames (and we can drop a now-unused trait helper method on FsVerityHashValue) Signed-off-by: Allison Karlitskaya <[email protected]>
1 parent 6f51f10 commit 2de9db1

File tree

4 files changed

+82
-43
lines changed

4 files changed

+82
-43
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ hex = "0.4.0"
2626
indicatif = { version = "0.17.0", features = ["tokio"] }
2727
log = "0.4.8"
2828
oci-spec = "0.7.0"
29+
once_cell = { version = "1.21.3", default-features = false }
2930
regex-automata = { version = "0.4.4", default-features = false }
3031
rustix = { version = "1.0.0", features = ["fs", "mount", "process"] }
3132
serde = "1.0.145"
@@ -41,7 +42,6 @@ zstd = "0.13.0"
4142

4243
[dev-dependencies]
4344
insta = "1.42.2"
44-
once_cell = "1.21.3"
4545
similar-asserts = "1.7.0"
4646
test-with = { version = "0.14", default-features = false, features = ["executable", "runtime"] }
4747
tokio-test = "0.4.4"

src/bin/composefs-setup-root.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> {
169169
fn mount_composefs_image(sysroot: &OwnedFd, name: &str) -> Result<OwnedFd> {
170170
let repo = Repository::<Sha256HashValue>::open_path(sysroot, "composefs")?;
171171
let image = repo.open_image(name)?;
172-
composefs_fsmount(image, name, repo.object_dir()?).context("Failed to mount composefs image")
172+
composefs_fsmount(image, name, repo.objects_dir()?).context("Failed to mount composefs image")
173173
}
174174

175175
fn mount_subdir(

src/fsverity/hashvalue.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,17 @@ where
6060
}
6161

6262
fn to_object_pathname(&self) -> String {
63-
format!("{:02x}/{}", self.as_bytes()[0], self.to_object_basename())
63+
format!(
64+
"{:02x}/{}",
65+
self.as_bytes()[0],
66+
hex::encode(&self.as_bytes()[1..])
67+
)
6468
}
6569

6670
fn to_object_dir(&self) -> String {
6771
format!("{:02x}", self.as_bytes()[0])
6872
}
6973

70-
fn to_object_basename(&self) -> String {
71-
hex::encode(&self.as_bytes()[1..])
72-
}
73-
7474
fn to_hex(&self) -> String {
7575
hex::encode(self.as_bytes())
7676
}

src/repository.rs

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1010
use anyhow::{bail, ensure, Context, Result};
11+
use once_cell::sync::OnceCell;
1112
use 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)]
3056
pub 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

4168
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))
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 dirfd = 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+
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+
}
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(dirfd, &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+
dirfd,
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

Comments
 (0)