Skip to content

Commit 8dca470

Browse files
authored
Merge pull request bootc-dev#602 from cgwalters/support-ostree-var
store: If ostree >= 2024.3, retain content in `/var`
2 parents 0ded4e8 + 4bcc315 commit 8dca470

File tree

5 files changed

+92
-21
lines changed

5 files changed

+92
-21
lines changed

lib/src/cli.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,8 @@ async fn testing(opts: &TestingOpts) -> Result<()> {
849849
TestingOpts::Run => crate::integrationtest::run_tests(),
850850
TestingOpts::RunIMA => crate::integrationtest::test_ima(),
851851
TestingOpts::FilterTar => {
852-
crate::tar::filter_tar(std::io::stdin(), std::io::stdout(), false).map(|_| {})
852+
crate::tar::filter_tar(std::io::stdin(), std::io::stdout(), &Default::default())
853+
.map(|_| {})
853854
}
854855
}
855856
}

lib/src/container/store.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ pub struct ImageImporter {
166166
disable_gc: bool, // If true, don't prune unused image layers
167167
/// If true, require the image has the bootable flag
168168
require_bootable: bool,
169+
/// If true, we have ostree v2024.3 or newer.
170+
ostree_v2024_3: bool,
169171
pub(crate) proxy_img: OpenedImage,
170172

171173
layer_progress: Option<Sender<ImportProgress>>,
@@ -471,6 +473,7 @@ impl ImageImporter {
471473
proxy_img,
472474
target_imgref: None,
473475
no_imgref: false,
476+
ostree_v2024_3: ostree::check_version(2024, 3),
474477
disable_gc: false,
475478
require_bootable: false,
476479
imgref: imgref.clone(),
@@ -496,6 +499,11 @@ impl ImageImporter {
496499
self.require_bootable = true;
497500
}
498501

502+
/// Override the ostree version being targeted
503+
pub fn set_ostree_version(&mut self, year: u32, v: u32) {
504+
self.ostree_v2024_3 = (year > 2024) || (year == 2024 && v >= 3)
505+
}
506+
499507
/// Do not prune image layers.
500508
pub fn disable_gc(&mut self) {
501509
self.disable_gc = true;
@@ -864,6 +872,7 @@ impl ImageImporter {
864872
base: Some(base_commit.clone()),
865873
selinux: true,
866874
allow_nonusr: root_is_transient,
875+
retain_var: self.ostree_v2024_3,
867876
};
868877
let r =
869878
crate::tar::write_tar(&self.repo, blob, layer.ostree_ref.as_str(), Some(opts));

lib/src/fixture.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,10 @@ impl Fixture {
439439
pub fn clear_destrepo(&self) -> Result<()> {
440440
self.destrepo()
441441
.set_ref_immediate(None, self.testref(), None, gio::Cancellable::NONE)?;
442+
for (r, _) in self.destrepo().list_refs(None, gio::Cancellable::NONE)? {
443+
self.destrepo()
444+
.set_ref_immediate(None, &r, None, gio::Cancellable::NONE)?;
445+
}
442446
self.destrepo()
443447
.prune(ostree::RepoPruneFlags::REFS_ONLY, 0, gio::Cancellable::NONE)?;
444448
Ok(())

lib/src/tar/write.rs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pub struct WriteTarOptions {
6868
pub selinux: bool,
6969
/// Allow content not in /usr; this should be paired with ostree rootfs.transient = true
7070
pub allow_nonusr: bool,
71+
/// If true, do not move content in /var to /usr/share/factory/var. This should be used
72+
/// with ostree v2024.3 or newer.
73+
pub retain_var: bool,
7174
}
7275

7376
/// The result of writing a tar stream.
@@ -109,10 +112,16 @@ enum NormalizedPathResult<'a> {
109112
Normal(Utf8PathBuf),
110113
}
111114

112-
fn normalize_validate_path(
113-
path: &Utf8Path,
115+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
116+
pub(crate) struct TarImportConfig {
114117
allow_nonusr: bool,
115-
) -> Result<NormalizedPathResult<'_>> {
118+
remap_factory_var: bool,
119+
}
120+
121+
fn normalize_validate_path<'a>(
122+
path: &'a Utf8Path,
123+
config: &'_ TarImportConfig,
124+
) -> Result<NormalizedPathResult<'a>> {
116125
// This converts e.g. `foo//bar/./baz` into `foo/bar/baz`.
117126
let mut components = path
118127
.components()
@@ -145,14 +154,18 @@ fn normalize_validate_path(
145154
"etc" => {
146155
ret.push("usr/etc");
147156
}
148-
// Content in /var will get copied by a systemd tmpfiles.d unit
149157
"var" => {
150-
ret.push("usr/share/factory/var");
158+
// Content in /var will get copied by a systemd tmpfiles.d unit
159+
if config.remap_factory_var {
160+
ret.push("usr/share/factory/var");
161+
} else {
162+
ret.push(part)
163+
}
151164
}
152165
o if EXCLUDED_TOPLEVEL_PATHS.contains(&o) => {
153166
return Ok(NormalizedPathResult::Filtered(part));
154167
}
155-
_ if allow_nonusr => ret.push(part),
168+
_ if config.allow_nonusr => ret.push(part),
156169
_ => {
157170
return Ok(NormalizedPathResult::Filtered(part));
158171
}
@@ -180,7 +193,7 @@ fn normalize_validate_path(
180193
pub(crate) fn filter_tar(
181194
src: impl std::io::Read,
182195
dest: impl std::io::Write,
183-
allow_nonusr: bool,
196+
config: &TarImportConfig,
184197
) -> Result<BTreeMap<String, u32>> {
185198
let src = std::io::BufReader::new(src);
186199
let mut src = tar::Archive::new(src);
@@ -190,7 +203,7 @@ pub(crate) fn filter_tar(
190203

191204
let ents = src.entries()?;
192205

193-
tracing::debug!("Filtering tar; allow_nonusr={allow_nonusr}");
206+
tracing::debug!("Filtering tar; config={config:?}");
194207

195208
// Lookaside data for dealing with hardlinked files into /sysroot; see below.
196209
let mut changed_sysroot_objects = HashMap::new();
@@ -259,7 +272,7 @@ pub(crate) fn filter_tar(
259272
}
260273
}
261274

262-
let normalized = match normalize_validate_path(path, allow_nonusr)? {
275+
let normalized = match normalize_validate_path(path, config)? {
263276
NormalizedPathResult::Filtered(path) => {
264277
tracing::trace!("Filtered: {path}");
265278
if let Some(v) = filtered.get_mut(path) {
@@ -282,15 +295,16 @@ pub(crate) fn filter_tar(
282295
async fn filter_tar_async(
283296
src: impl AsyncRead + Send + 'static,
284297
mut dest: impl AsyncWrite + Send + Unpin,
285-
allow_nonusr: bool,
298+
config: &TarImportConfig,
286299
) -> Result<BTreeMap<String, u32>> {
287300
let (tx_buf, mut rx_buf) = tokio::io::duplex(8192);
288301
// The source must be moved to the heap so we know it is stable for passing to the worker thread
289302
let src = Box::pin(src);
303+
let config = config.clone();
290304
let tar_transformer = tokio::task::spawn_blocking(move || {
291305
let mut src = tokio_util::io::SyncIoBridge::new(src);
292306
let dest = tokio_util::io::SyncIoBridge::new(tx_buf);
293-
let r = filter_tar(&mut src, dest, allow_nonusr);
307+
let r = filter_tar(&mut src, dest, &config);
294308
// Pass ownership of the input stream back to the caller - see below.
295309
(r, src)
296310
});
@@ -365,7 +379,10 @@ pub async fn write_tar(
365379
let mut child_stdout = r.stdout.take().unwrap();
366380
let mut child_stderr = r.stderr.take().unwrap();
367381
// Copy the filtered tar stream to child stdin
368-
let filtered_result = filter_tar_async(src, child_stdin, options.allow_nonusr);
382+
let mut import_config = TarImportConfig::default();
383+
import_config.allow_nonusr = options.allow_nonusr;
384+
import_config.remap_factory_var = !options.retain_var;
385+
let filtered_result = filter_tar_async(src, child_stdin, &import_config);
369386
let output_copier = async move {
370387
// Gather stdout/stderr to buffers
371388
let mut child_stdout_buf = String::new();
@@ -421,6 +438,18 @@ mod tests {
421438

422439
#[test]
423440
fn test_normalize_path() {
441+
let imp_default = &TarImportConfig {
442+
allow_nonusr: false,
443+
remap_factory_var: true,
444+
};
445+
let allow_nonusr = &TarImportConfig {
446+
allow_nonusr: true,
447+
remap_factory_var: true,
448+
};
449+
let composefs_and_new_ostree = &TarImportConfig {
450+
allow_nonusr: true,
451+
remap_factory_var: false,
452+
};
424453
let valid_all = &[
425454
("/usr/bin/blah", "./usr/bin/blah"),
426455
("usr/bin/blah", "./usr/bin/blah"),
@@ -431,29 +460,29 @@ mod tests {
431460
];
432461
let valid_nonusr = &[("boot", "./boot"), ("opt/puppet/blah", "./opt/puppet/blah")];
433462
for &(k, v) in valid_all {
434-
let r = normalize_validate_path(k.into(), false).unwrap();
435-
let r2 = normalize_validate_path(k.into(), true).unwrap();
463+
let r = normalize_validate_path(k.into(), imp_default).unwrap();
464+
let r2 = normalize_validate_path(k.into(), allow_nonusr).unwrap();
436465
assert_eq!(r, r2);
437466
match r {
438467
NormalizedPathResult::Normal(r) => assert_eq!(r, v),
439468
NormalizedPathResult::Filtered(o) => panic!("Should not have filtered {o}"),
440469
}
441470
}
442471
for &(k, v) in valid_nonusr {
443-
let strict = normalize_validate_path(k.into(), false).unwrap();
472+
let strict = normalize_validate_path(k.into(), imp_default).unwrap();
444473
assert!(
445474
matches!(strict, NormalizedPathResult::Filtered(_)),
446475
"Incorrect filter for {k}"
447476
);
448-
let nonusr = normalize_validate_path(k.into(), true).unwrap();
477+
let nonusr = normalize_validate_path(k.into(), allow_nonusr).unwrap();
449478
match nonusr {
450479
NormalizedPathResult::Normal(r) => assert_eq!(r, v),
451480
NormalizedPathResult::Filtered(o) => panic!("Should not have filtered {o}"),
452481
}
453482
}
454483
let filtered = &["/run/blah", "/sys/foo", "/dev/somedev"];
455484
for &k in filtered {
456-
match normalize_validate_path(k.into(), true).unwrap() {
485+
match normalize_validate_path(k.into(), imp_default).unwrap() {
457486
NormalizedPathResult::Filtered(_) => {}
458487
NormalizedPathResult::Normal(_) => {
459488
panic!("{} should be filtered", k)
@@ -462,9 +491,13 @@ mod tests {
462491
}
463492
let errs = &["usr/foo/../../bar"];
464493
for &k in errs {
465-
assert!(normalize_validate_path(k.into(), true).is_err());
466-
assert!(normalize_validate_path(k.into(), false).is_err());
494+
assert!(normalize_validate_path(k.into(), allow_nonusr).is_err());
495+
assert!(normalize_validate_path(k.into(), imp_default).is_err());
467496
}
497+
assert!(matches!(
498+
normalize_validate_path("var/lib/foo".into(), composefs_and_new_ostree).unwrap(),
499+
NormalizedPathResult::Normal(_)
500+
));
468501
}
469502

470503
#[tokio::test]
@@ -481,7 +514,7 @@ mod tests {
481514
let _ = rootfs_tar.into_inner()?;
482515
let mut dest = Vec::new();
483516
let src = tokio::io::BufReader::new(tokio::fs::File::open(rootfs_tar_path).await?);
484-
filter_tar_async(src, &mut dest, false).await?;
517+
filter_tar_async(src, &mut dest, &Default::default()).await?;
485518
let dest = dest.as_slice();
486519
let mut final_tar = tar::Archive::new(Cursor::new(dest));
487520
let destdir = &tempd.path().join("destdir");

lib/tests/it/main.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,7 @@ async fn test_container_var_content() -> Result<()> {
935935
};
936936
let mut imp =
937937
store::ImageImporter::new(fixture.destrepo(), &derived_imgref, Default::default()).await?;
938+
imp.set_ostree_version(2023, 11);
938939
let prep = match imp.prepare().await.unwrap() {
939940
store::PrepareResult::AlreadyPresent(_) => panic!("should not be already imported"),
940941
store::PrepareResult::Ready(r) => r,
@@ -963,6 +964,29 @@ async fn test_container_var_content() -> Result<()> {
963964
.is_none()
964965
);
965966

967+
// Reset things
968+
fixture.clear_destrepo()?;
969+
970+
let mut imp =
971+
store::ImageImporter::new(fixture.destrepo(), &derived_imgref, Default::default()).await?;
972+
imp.set_ostree_version(2024, 3);
973+
let prep = match imp.prepare().await.unwrap() {
974+
store::PrepareResult::AlreadyPresent(_) => panic!("should not be already imported"),
975+
store::PrepareResult::Ready(r) => r,
976+
};
977+
let import = imp.import(prep).await.unwrap();
978+
let ostree_root = fixture
979+
.destrepo()
980+
.read_commit(&import.merge_commit, gio::Cancellable::NONE)?
981+
.0;
982+
let varfile = ostree_root
983+
.child("usr/share/factory/var/lib/foo")
984+
.downcast::<ostree::RepoFile>()
985+
.unwrap();
986+
assert!(!varfile.query_exists(gio::Cancellable::NONE));
987+
assert!(ostree_root
988+
.child("var/lib/foo")
989+
.query_exists(gio::Cancellable::NONE));
966990
Ok(())
967991
}
968992

0 commit comments

Comments
 (0)