Skip to content

Commit f0c39e2

Browse files
src: clean up filesystem/image operations
Instead of having a large and growing list of functions which perform any number of possibly desired image flows: - from: - OCI container - filesystem - with or without root directory stat - selinux relabel (yes/no) - output: - create an image - compute an image checksum - print a dumpfile Add a function for each source (oci, fs) for creating a FileSystem and define some new high-level transformations and operations on the FileSystem object itself. These operations sort of depend on everything whereas the rest of the code in tree.rs depends on almost nothing, so it feels a bit weird to include them in that file. I tried some other approaches here: - define a bunch of functions that take a FileSystem as their first argument and operate on it. This had bad ergonomics because they essentially class methods, and it wasn't able to invoke them as such. - move FileSystem into a separate file and define the methods there. This was strange because lower-level parts of the code still had to create a FileSystem object, so they ended up depending on the higher-level file again. - define a "FileSystemOps" helper trait which is implemented only for FileSystem: this was probably the cleanest approach and had the advantage of requiring the trait to be in scope in order to perform the high-level operations. The only reason I dismissed this one was because it was pointlessly complicated and required writing the method signatures twice. In the end we just make use of the fact that it's possible to have multiple `impl` blocks for a given `struct` as long as they're in the same crate. The "trait" approach might be a bit cleaner, but this works as well. Adjust various API users to make use of the new operations. Overhaul the `cfsctl` commandline to be more consistent about the offered operations and the flags that get passed to them. Update the examples as appropriate. Signed-off-by: Allison Karlitskaya <[email protected]>
1 parent ce3b777 commit f0c39e2

File tree

12 files changed

+191
-112
lines changed

12 files changed

+191
-112
lines changed

examples/bls/build

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ podman build \
5050

5151
BASE_ID="$(sed s/sha256:// tmp/base.iid)"
5252
${CFSCTL} oci pull containers-storage:${BASE_ID}
53-
BASE_IMAGE_FSVERITY="$(${CFSCTL} oci create-image "${BASE_ID}")"
53+
BASE_IMAGE_FSVERITY="$(${CFSCTL} oci compute-id --bootable "${BASE_ID}")"
5454

5555
mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/etc/work"
5656
mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/etc/upper"
5757
mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/var"
5858

59-
${CFSCTL} oci prepare-boot "${BASE_ID}" tmp/efi
59+
${CFSCTL} oci prepare-boot "${BASE_ID}" --bootdir tmp/efi
6060

6161
OPTIONS="console=ttyS0,115200 composefs=${BASE_IMAGE_FSVERITY} rw"
6262
BLE="$(echo tmp/efi/loader/entries/*.conf)"

examples/uki/build

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ ${PODMAN_BUILD} \
4242

4343
BASE_ID="$(sed s/sha256:// tmp/base.iid)"
4444
${CFSCTL} oci pull containers-storage:"${BASE_ID}"
45-
BASE_IMAGE_FSVERITY="$(${CFSCTL} oci create-image "${BASE_ID}")"
45+
BASE_IMAGE_FSVERITY="$(${CFSCTL} oci compute-id --bootable "${BASE_ID}")"
4646

4747
${PODMAN_BUILD} \
4848
--iidfile=tmp/final.iid \
@@ -54,7 +54,7 @@ ${PODMAN_BUILD} \
5454

5555
FINAL_ID="$(sed s/sha256:// tmp/final.iid)"
5656
${CFSCTL} oci pull containers-storage:"${FINAL_ID}"
57-
FINAL_IMAGE_FSVERITY="$(${CFSCTL} oci create-image "${FINAL_ID}")"
57+
FINAL_IMAGE_FSVERITY="$(${CFSCTL} oci compute-id --bootable "${FINAL_ID}")"
5858

5959
## IMPORTANT: the filesystems of the base and final images are identical
6060
test "${BASE_IMAGE_FSVERITY}" = "${FINAL_IMAGE_FSVERITY}"
@@ -63,7 +63,7 @@ mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/etc/work"
6363
mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/etc/upper"
6464
mkdir -p "tmp/sysroot/state/${BASE_IMAGE_FSVERITY}/var"
6565

66-
${CFSCTL} oci prepare-boot "${FINAL_ID}" tmp/efi
66+
${CFSCTL} oci prepare-boot "${FINAL_ID}" --bootdir tmp/efi
6767

6868
../common/install-systemd-boot
6969
../common/make-image "${os}-uki-efi.qcow2"

examples/unified-secureboot/Containerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RUN --mount=type=bind,from=base,target=/mnt/base <<EOF
3030
set -eux
3131

3232
mkdir -p /tmp/sysroot/composefs
33-
COMPOSEFS_FSVERITY="$(cfsctl --repo /tmp/sysroot create-image /mnt/base)"
33+
COMPOSEFS_FSVERITY="$(cfsctl --repo /tmp/sysroot compute-id --bootable /mnt/base)"
3434

3535
mkdir -p /etc/kernel /etc/dracut.conf.d
3636
echo "console=ttyS0,115200 composefs=${COMPOSEFS_FSVERITY} rw" > /etc/kernel/cmdline

examples/unified-secureboot/build

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ podman build \
4848

4949
IMAGE_ID="$(sed s/sha256:// tmp/iid)"
5050
${CFSCTL} oci pull containers-storage:"${IMAGE_ID}"
51-
IMAGE_FSVERITY="$(${CFSCTL} oci create-image "${IMAGE_ID}")"
51+
IMAGE_FSVERITY="$(${CFSCTL} oci compute-id --bootable "${IMAGE_ID}")"
5252

5353
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/etc/work"
5454
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/etc/upper"
5555
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/var"
5656

57-
${CFSCTL} oci prepare-boot "${IMAGE_ID}" tmp/efi
57+
${CFSCTL} oci prepare-boot "${IMAGE_ID}" --bootdir tmp/efi
5858

5959
# install a signed copy of systemd-boot
6060
mkdir -p tmp/efi/loader

examples/unified/Containerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ RUN --mount=type=bind,from=base,target=/mnt/base <<EOF
2929
set -eux
3030

3131
mkdir -p /tmp/sysroot/composefs
32-
COMPOSEFS_FSVERITY="$(cfsctl --repo /tmp/sysroot create-image /mnt/base)"
32+
COMPOSEFS_FSVERITY="$(cfsctl --repo /tmp/sysroot compute-id --bootable /mnt/base)"
3333

3434
mkdir -p /etc/kernel /etc/dracut.conf.d
3535
echo "console=ttyS0,115200 composefs=${COMPOSEFS_FSVERITY} rw" > /etc/kernel/cmdline

examples/unified/build

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ podman build \
3030

3131
IMAGE_ID="$(sed s/sha256:// tmp/iid)"
3232
${CFSCTL} oci pull containers-storage:"${IMAGE_ID}"
33-
IMAGE_FSVERITY="$(${CFSCTL} oci create-image "${IMAGE_ID}")"
33+
IMAGE_FSVERITY="$(${CFSCTL} oci compute-id --bootable "${IMAGE_ID}")"
3434

3535
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/etc/work"
3636
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/etc/upper"
3737
mkdir -p "tmp/sysroot/state/${IMAGE_FSVERITY}/var"
3838

39-
${CFSCTL} oci prepare-boot "${IMAGE_ID}" tmp/efi
39+
${CFSCTL} oci prepare-boot "${IMAGE_ID}" --bootdir tmp/efi
4040

4141
../common/install-systemd-boot
4242
../common/make-image fedora-unified-efi.qcow2

src/bin/cfsctl.rs

Lines changed: 129 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,31 @@ enum OciCommand {
3939
/// the name of the stream
4040
name: String,
4141
},
42-
CreateDumpfile {
43-
layers: Vec<String>,
42+
Dump {
43+
config_name: String,
44+
config_verity: Option<String>,
4445
},
4546
Pull {
4647
image: String,
4748
name: Option<String>,
4849
},
50+
ComputeId {
51+
config_name: String,
52+
config_verity: Option<String>,
53+
#[clap(long)]
54+
bootable: bool,
55+
},
4956
CreateImage {
50-
config: String,
51-
name: Option<String>,
57+
config_name: String,
58+
config_verity: Option<String>,
59+
#[clap(long)]
60+
bootable: bool,
61+
#[clap(long)]
62+
image_name: Option<String>,
5263
},
5364
Seal {
54-
name: String,
55-
verity: Option<String>,
65+
config_name: String,
66+
config_verity: Option<String>,
5667
},
5768
Mount {
5869
name: String,
@@ -62,8 +73,10 @@ enum OciCommand {
6273
name: String,
6374
},
6475
PrepareBoot {
65-
name: String,
66-
bootdir: Option<PathBuf>,
76+
config_name: String,
77+
config_verity: Option<String>,
78+
#[clap(long, default_value = "/boot")]
79+
bootdir: PathBuf,
6780
},
6881
}
6982

@@ -97,22 +110,45 @@ enum Command {
97110
},
98111
CreateImage {
99112
path: PathBuf,
113+
#[clap(long)]
114+
bootable: bool,
115+
#[clap(long)]
116+
stat_root: bool,
117+
image_name: Option<String>,
118+
},
119+
ComputeId {
120+
path: PathBuf,
121+
#[clap(long)]
122+
bootable: bool,
123+
#[clap(long)]
124+
stat_root: bool,
100125
},
101126
CreateDumpfile {
102127
path: PathBuf,
128+
#[clap(long)]
129+
bootable: bool,
130+
#[clap(long)]
131+
stat_root: bool,
103132
},
104133
ImageObjects {
105134
name: String,
106135
},
107136
}
108137

138+
fn verity_opt(opt: &Option<String>) -> Result<Option<Sha256HashValue>> {
139+
Ok(match opt {
140+
Some(value) => Some(FsVerityHashValue::from_hex(value)?),
141+
None => None,
142+
})
143+
}
144+
109145
#[tokio::main]
110146
async fn main() -> Result<()> {
111147
env_logger::init();
112148

113149
let args = App::parse();
114150

115-
let repo: Repository<Sha256HashValue> = (if let Some(path) = args.repo {
151+
let repo: Repository<Sha256HashValue> = (if let Some(path) = &args.repo {
116152
Repository::open_path(CWD, path)
117153
} else if args.system {
118154
Repository::open_system()
@@ -136,7 +172,7 @@ async fn main() -> Result<()> {
136172
}
137173
Command::ImportImage { reference } => {
138174
let image_id = repo.import_image(&reference, &mut std::io::stdin())?;
139-
println!("{}", image_id.to_hex());
175+
println!("{}", image_id.to_id());
140176
}
141177
Command::Oci { cmd: oci_cmd } => match oci_cmd {
142178
OciCommand::ImportLayer { name, sha256 } => {
@@ -146,29 +182,57 @@ async fn main() -> Result<()> {
146182
name.as_deref(),
147183
&mut std::io::stdin(),
148184
)?;
149-
println!("{}", object_id.to_hex());
185+
println!("{}", object_id.to_id());
150186
}
151187
OciCommand::LsLayer { name } => {
152188
oci::ls_layer(&repo, &name)?;
153189
}
154-
OciCommand::CreateDumpfile { layers } => {
155-
oci::image::create_dumpfile(&repo, &layers)?;
190+
OciCommand::Dump {
191+
ref config_name,
192+
ref config_verity,
193+
} => {
194+
let verity = verity_opt(config_verity)?;
195+
let mut fs = oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
196+
fs.print_dumpfile()?;
156197
}
157-
OciCommand::CreateImage { config, name } => {
158-
let image_id = oci::image::create_image(&repo, &config, name.as_deref(), None)?;
159-
println!("{}", image_id.to_hex());
198+
OciCommand::ComputeId {
199+
ref config_name,
200+
ref config_verity,
201+
bootable,
202+
} => {
203+
let verity = verity_opt(config_verity)?;
204+
let mut fs = oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
205+
if bootable {
206+
fs.transform_for_boot(&repo)?;
207+
}
208+
let id = fs.compute_image_id();
209+
println!("{}", id.to_hex());
210+
}
211+
OciCommand::CreateImage {
212+
ref config_name,
213+
ref config_verity,
214+
bootable,
215+
ref image_name,
216+
} => {
217+
let verity = verity_opt(config_verity)?;
218+
let mut fs = oci::image::create_filesystem(&repo, config_name, verity.as_ref())?;
219+
if bootable {
220+
fs.transform_for_boot(&repo)?;
221+
}
222+
let image_id = fs.commit_image(&repo, image_name.as_deref())?;
223+
println!("{}", image_id.to_id());
160224
}
161225
OciCommand::Pull { ref image, name } => {
162226
oci::pull(&Arc::new(repo), image, name.as_deref()).await?
163227
}
164-
OciCommand::Seal { verity, ref name } => {
165-
let (sha256, verity) = oci::seal(
166-
&Arc::new(repo),
167-
name,
168-
verity.map(Sha256HashValue::from_hex).transpose()?.as_ref(),
169-
)?;
228+
OciCommand::Seal {
229+
ref config_name,
230+
ref config_verity,
231+
} => {
232+
let verity = verity_opt(config_verity)?;
233+
let (sha256, verity) = oci::seal(&Arc::new(repo), config_name, verity.as_ref())?;
170234
println!("sha256 {}", hex::encode(sha256));
171-
println!("verity {}", verity.to_hex());
235+
println!("verity {}", verity.to_id());
172236
}
173237
OciCommand::Mount {
174238
ref name,
@@ -179,25 +243,58 @@ async fn main() -> Result<()> {
179243
OciCommand::MetaLayer { ref name } => {
180244
oci::meta_layer(&repo, name, None)?;
181245
}
182-
OciCommand::PrepareBoot { ref name, bootdir } => {
183-
let output = bootdir.unwrap_or(PathBuf::from("/boot"));
184-
oci::prepare_boot(&repo, name, None, &output)?;
246+
OciCommand::PrepareBoot {
247+
ref config_name,
248+
ref config_verity,
249+
ref bootdir,
250+
} => {
251+
let verity = verity_opt(config_verity)?;
252+
oci::prepare_boot(&repo, config_name, verity.as_ref(), bootdir)?;
185253
}
186254
},
187-
Command::CreateImage { ref path } => {
188-
let image_id = composefs::fs::create_image(path, Some(&repo))?;
189-
println!("{}", image_id.to_hex());
255+
Command::ComputeId {
256+
ref path,
257+
bootable,
258+
stat_root,
259+
} => {
260+
let mut fs = composefs::fs::read_from_path(path, Some(&repo), stat_root)?;
261+
if bootable {
262+
fs.transform_for_boot(&repo)?;
263+
}
264+
let id = fs.compute_image_id();
265+
println!("{}", id.to_hex());
190266
}
191-
Command::CreateDumpfile { ref path } => {
192-
composefs::fs::create_dumpfile::<Sha256HashValue>(path)?;
267+
Command::CreateImage {
268+
ref path,
269+
bootable,
270+
stat_root,
271+
ref image_name,
272+
} => {
273+
let mut fs = composefs::fs::read_from_path(path, Some(&repo), stat_root)?;
274+
if bootable {
275+
fs.transform_for_boot(&repo)?;
276+
}
277+
let id = fs.commit_image(&repo, image_name.as_deref())?;
278+
println!("{}", id.to_id());
279+
}
280+
Command::CreateDumpfile {
281+
ref path,
282+
bootable,
283+
stat_root,
284+
} => {
285+
let mut fs = composefs::fs::read_from_path(path, Some(&repo), stat_root)?;
286+
if bootable {
287+
fs.transform_for_boot(&repo)?;
288+
}
289+
fs.print_dumpfile()?;
193290
}
194291
Command::Mount { name, mountpoint } => {
195292
repo.mount(&name, &mountpoint)?;
196293
}
197294
Command::ImageObjects { name } => {
198295
let objects = repo.objects_for_image(&name)?;
199296
for object in objects {
200-
println!("{}", object.to_hex());
297+
println!("{}", object.to_id());
201298
}
202299
}
203300
Command::GC => {

src/filesystem_ops.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use anyhow::Result;
2+
3+
use crate::{
4+
dumpfile::write_dumpfile,
5+
erofs::writer::mkfs_erofs,
6+
fsverity::{compute_verity, FsVerityHashValue},
7+
repository::Repository,
8+
selabel::selabel,
9+
tree::FileSystem,
10+
};
11+
12+
impl<ObjectID: FsVerityHashValue> FileSystem<ObjectID> {
13+
pub fn transform_for_boot(&mut self, repo: &Repository<ObjectID>) -> Result<()> {
14+
selabel(self, repo)?;
15+
Ok(())
16+
}
17+
18+
pub fn commit_image(
19+
&mut self,
20+
repository: &Repository<ObjectID>,
21+
image_name: Option<&str>,
22+
) -> Result<ObjectID> {
23+
self.ensure_root_stat();
24+
repository.write_image(image_name, &mkfs_erofs(self))
25+
}
26+
27+
pub fn compute_image_id(&mut self) -> ObjectID {
28+
self.ensure_root_stat();
29+
compute_verity(&mkfs_erofs(self))
30+
}
31+
32+
pub fn print_dumpfile(&mut self) -> Result<()> {
33+
self.ensure_root_stat();
34+
write_dumpfile(&mut std::io::stdout(), self)
35+
}
36+
}

0 commit comments

Comments
 (0)