-
Notifications
You must be signed in to change notification settings - Fork 13
Add composefs-ostree and some basic CLI tools #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
e0e827f
to
9c5b086
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this! Thanks for working on it!
I made some comments on the first round of commits. Feel free to adjust those and PR them separately: we can merge those now without further discussion.
The blobs thing is going to need a call.
I didn't review the crate addition in any detail at all. That's probably also going to need a call :)
@@ -432,7 +433,30 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |||
relative.push(target_component); | |||
} | |||
|
|||
symlinkat(relative, &self.repository, name) | |||
// Atomically replace existing symlink |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this. flatpak-rs
currently has a workaround for exactly this reason, which I'd love to rip out: https://github.com/allisonkarlitskaya/flatpak-rs/blob/8e1741d06b18a450536297a334fc347b010639ba/src/install.rs#L19
Can't we get some sort of kernel love here? I've generally tried to avoid this sort of nonsense (by way of O_TMPFILE
)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Atomic rename over symlinks is the canonical way to do this atm. Don't think there is anything better.
crates/composefs/src/repository.rs
Outdated
let mut count = 0; | ||
let tmp_name = loop { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels a bit like this could be better handled with a for loop. I like the let tmp_name = loop { }
thing, of course (which you can't do with for
) but after you break the loop, the only thing you do is a renameat()... so that could probably be brought into the loop body.
You could also make a utility function that was responsible for creating the symlink and used return to escape the for loop, returning the value. Having this inline already feels like "a bit too much" here...
crates/composefs/src/repository.rs
Outdated
} | ||
}; | ||
|
||
renameat(&self.repository, &tmp_name, &self.repository, name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No cleanup of the temporary here in case the rename fails...
@@ -332,9 +332,11 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |||
let filename = format!("streams/{name}"); | |||
|
|||
let file = File::from(if let Some(verity_hash) = verity { | |||
self.open_with_verity(&filename, verity_hash)? | |||
self.open_with_verity(&filename, verity_hash) | |||
.with_context(|| format!("Opening ref 'streams/{name}'"))? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll never escape anyhow 🤣
Agreed that having this is better than not, though.
crates/composefs/src/repository.rs
Outdated
@@ -513,15 +513,30 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |||
fn gc_category(&self, category: &str) -> Result<HashSet<ObjectID>> { | |||
let mut objects = HashSet::new(); | |||
|
|||
let category_fd = self.openat(category, OFlags::RDONLY | OFlags::DIRECTORY)?; | |||
let category_fd = match self.openat(category, OFlags::RDONLY | OFlags::DIRECTORY) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe consider lifting this from flatpak-rs
into our own utils
module?
I'm sure we could find a couple more uses around the code for this...
crates/composefs/src/lib.rs
Outdated
@@ -1,3 +1,4 @@ | |||
pub mod blob; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not keen on this addition. We store OCI config json in splitstreams as well (as a single inline block) specifically because I wanted to avoid creating a separate file format. If there is a missing feature there, I'd rather extend splitstreams a bit rather than adding yet another object type to the repository. We'll need to talk about this...
@@ -11,9 +11,10 @@ rust-version.workspace = true | |||
version.workspace = true | |||
|
|||
[features] | |||
default = ['pre-6.15', 'oci'] | |||
default = ['pre-6.15', 'oci','ostree'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing space. surprised some sort of linter didn't pick on that...
crates/composefs/src/repository.rs
Outdated
@@ -366,10 +366,7 @@ impl<ObjectID: FsVerityHashValue> Repository<ObjectID> { | |||
pub fn has_named_blob(&self, name: &str) -> bool { | |||
let blob_path = format!("blobs/refs/{}", name); | |||
|
|||
match readlinkat(&self.repository, &blob_path, []) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes in a previous commit, obviously...
9c5b086
to
cd067c5
Compare
Hmmm, thinking more about this. We probably want a "content type" magic thing in the splitstream header as well, so we can error out if the wrapped thing is of the wrong type. |
cd067c5
to
2ed83a2
Compare
For example, if there has been no streams created, we don't want to fail with some random ENOENT. Signed-off-by: Alexander Larsson <[email protected]>
Naming a new version of an image the same as the old version is pretty standard, and this is currently giving EEXIST errors. We fix this by doing an atomic symlink + rename operation to replace the symlink if it doesn't already have the expected value. Drop our existing Repository::ensure_symlink() function and use the new function for that as well. Co-authored-by: Alexander Larsson <[email protected]> Signed-off-by: Alexander Larsson <[email protected]> Signed-off-by: Allison Karlitskaya <[email protected]>
This adds error context when opening images and streams. Without this it is kinda hard to figure out what ENOENT really means. Signed-off-by: Alexander Larsson <[email protected]>
This changes the splitstream format a bit. The primary differences are: * The header is not compressed * All referenced fs-verity objects are stored in the header, including external chunks, mapped splitstreams and (a new feature) references that are not used in chunks. * The mapping table is separate from the reference table (and generally smaller), and indexes into it. * There is a magic value to detect the file format. * We store a tag for what ObjectID format is used * The total size of the stream is stored in the header. The ability to reference file objects in the repo even if they are not part of the splitstream "content" will be useful for the ostree support to reference file content objects. This change also allows More efficient GC enumeration, because we don't have to parse the entire splitstream to find the referenced objects.
This is an extra check to avoid confusion.
Based on ideas from containers#141 This is an initial version of ostree support. This allows pulling from local and remote ostree repos, which will create a set of regular file content objects, as well as a blob containing all the remaining ostree objects. From the blob we can create an image. When pulling a commit, a base blob (i.e. "the previous version" can be specified. Any objects in that base blob will not be downloaded. If a name is given for the pulled commit, then pre-existing blobs with the same name will automatically be used as a base blob. This is an initial version and there are several things missing: * Pull operations are completely serial * There is no support for ostree summary files * There is no support for ostree delta files * There is no caching of local file availability (other than base blob) * Local ostree repos only support archive mode * There is no GPG validation on ostree pull Signed-off-by: Alexander Larsson <[email protected]>
2ed83a2
to
c041afe
Compare
Based on ideas from #141
This is an initial version of ostree support. This allows pulling
from local and remote ostree repos, which will create a set of
regular file content objects, as well as a blob containing all the
remaining ostree objects. From the blob we can create an image.
When pulling a commit, a base blob (i.e. "the previous version" can be
specified. Any objects in that base blob will not be downloaded. If a
name is given for the pulled commit, then pre-existing blobs with the
same name will automatically be used as a base blob.
This is an initial version and there are several things missing: