-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support RPMs installing in
/opt
and /usr/local
This solves the `/opt` problem by using the new state overlay concept in OSTree: an overlay filesystem is mounted on top of `/usr/lib/opt` and the upper dir is automatically "rebased" whenever new content comes in. Concretely, this means that app state is carried forward, all while allowing the (OSTree-managed) package contents to be updated. We also solve the `/usr/local` problem the same way. The app state issue isn't really present there, but `/usr/local` has traditionally been system state. We want to keep supporting dropping files there all while also supporting shipping OSTree-owned content. See also: ostreedev/ostree#3113 Fixes: #233
- Loading branch information
Showing
4 changed files
with
130 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,20 +145,32 @@ fn compose_init_rootfs_transient(rootfs_dfd: &cap_std::fs::Dir) -> Result<()> { | |
/// This is hardcoded; in the future we may make more things configurable, | ||
/// but the goal is for all state to be in `/etc` and `/var`. | ||
#[context("Initializing rootfs")] | ||
fn compose_init_rootfs_strict(rootfs_dfd: &cap_std::fs::Dir, tmp_is_dir: bool) -> Result<()> { | ||
fn compose_init_rootfs_strict( | ||
rootfs_dfd: &cap_std::fs::Dir, | ||
tmp_is_dir: bool, | ||
opt_state_overlay: bool, | ||
) -> Result<()> { | ||
println!("Initializing rootfs"); | ||
|
||
compose_init_rootfs_base(rootfs_dfd, tmp_is_dir)?; | ||
|
||
const OPT_SYMLINK_LEGACY: &str = "var/opt"; | ||
const OPT_SYMLINK_STATEOVERLAY: &str = "usr/lib/opt"; | ||
let opt_symlink = if opt_state_overlay { | ||
OPT_SYMLINK_STATEOVERLAY | ||
} else { | ||
OPT_SYMLINK_LEGACY | ||
}; | ||
|
||
// This is used in the case where we don't have a transient rootfs; redirect | ||
// these toplevel directories underneath /var. | ||
const OSTREE_STRICT_MODE_SYMLINKS: &[(&str, &str)] = &[ | ||
("var/opt", "opt"), | ||
let ostree_strict_mode_symlinks: &[(&str, &str)] = &[ | ||
(opt_symlink, "opt"), | ||
("var/srv", "srv"), | ||
("var/mnt", "mnt"), | ||
("run/media", "media"), | ||
]; | ||
OSTREE_STRICT_MODE_SYMLINKS | ||
ostree_strict_mode_symlinks | ||
.par_iter() | ||
.try_for_each(|&(dest, src)| { | ||
rootfs_dfd | ||
|
@@ -212,7 +224,15 @@ pub fn compose_prepare_rootfs( | |
return Ok(()); | ||
} | ||
|
||
compose_init_rootfs_strict(target_rootfs_dfd, tmp_is_dir)?; | ||
compose_init_rootfs_strict( | ||
target_rootfs_dfd, | ||
tmp_is_dir, | ||
treefile | ||
.parsed | ||
.base | ||
.opt_usrlocal_overlays | ||
.unwrap_or_default(), | ||
)?; | ||
|
||
println!("Moving /usr to target"); | ||
src_rootfs_dfd.rename("usr", target_rootfs_dfd, "usr")?; | ||
|
@@ -606,6 +626,32 @@ fn compose_postprocess_rpmdb(rootfs_dfd: &Dir) -> Result<()> { | |
Ok(()) | ||
} | ||
|
||
/// Enables [email protected] for /usr/lib/opt and /usr/local. These | ||
/// symlinks are also used later in the compose process (and client-side composes) | ||
/// as a way to check that state overlays are turned on. | ||
fn compose_postprocess_state_overlays(rootfs_dfd: &Dir) -> Result<()> { | ||
let mut db = cap_std::fs::DirBuilder::new(); | ||
db.recursive(true); | ||
db.mode(0o755); | ||
let localfs_requires = Path::new("usr/lib/systemd/system/local-fs.target.requires"); | ||
rootfs_dfd.ensure_dir_with(localfs_requires, &db)?; | ||
|
||
const UNITS: &[&str] = &[ | ||
"[email protected]", | ||
"[email protected]", | ||
]; | ||
|
||
UNITS.par_iter().try_for_each(|&unit| { | ||
let target = Path::new("..").join(unit); | ||
let linkpath = localfs_requires.join(unit); | ||
rootfs_dfd | ||
.symlink(target, linkpath) | ||
.with_context(|| format!("Enabling {unit}")) | ||
})?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Rust portion of rpmostree_treefile_postprocessing() | ||
pub fn compose_postprocess( | ||
rootfs_dfd: i32, | ||
|
@@ -627,6 +673,15 @@ pub fn compose_postprocess( | |
compose_postprocess_default_target(rootfs, t)?; | ||
} | ||
|
||
if treefile | ||
.parsed | ||
.base | ||
.opt_usrlocal_overlays | ||
.unwrap_or_default() | ||
{ | ||
compose_postprocess_state_overlays(rootfs)?; | ||
} | ||
|
||
treefile.write_compose_json(rootfs)?; | ||
|
||
let etc_guard = crate::core::prepare_tempetc_guard(rootfs_dfd.as_raw_fd())?; | ||
|
@@ -955,6 +1010,17 @@ fn convert_path_to_tmpfiles_d_recurse( | |
Ok(()) | ||
} | ||
|
||
fn state_overlay_enabled(rootfs_dfd: &cap_std::fs::Dir, state_overlay: &str) -> Result<bool> { | ||
let linkname = format!( | ||
"usr/lib/systemd/system/local-fs.target.requires/ostree-state-overlay@{state_overlay}.service" | ||
); | ||
match rootfs_dfd.symlink_metadata_optional(&linkname)? { | ||
Some(meta) if meta.is_symlink() => Ok(true), | ||
Some(_) => Err(anyhow!("{linkname} is not a symlink")), | ||
None => Ok(false), | ||
} | ||
} | ||
|
||
/// Walk over the root filesystem and perform some core conversions | ||
/// from RPM conventions to OSTree conventions. | ||
/// | ||
|
@@ -969,7 +1035,11 @@ pub fn rootfs_prepare_links(rootfs_dfd: i32, skip_usrlocal: bool) -> CxxResult<( | |
db.recursive(true); | ||
|
||
if !skip_usrlocal { | ||
if !crate::ostree_prepareroot::transient_root_enabled(rootfs)? { | ||
if state_overlay_enabled(rootfs, "usr-local")? { | ||
// because of the filesystem lua issue (see | ||
// compose_init_rootfs_base()) we need to create this manually | ||
rootfs.ensure_dir_with("usr/local", &db)?; | ||
} else if !crate::ostree_prepareroot::transient_root_enabled(rootfs)? { | ||
// Unconditionally drop /usr/local and replace it with a symlink. | ||
rootfs | ||
.remove_all_optional("usr/local") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#!/bin/bash | ||
set -xeuo pipefail | ||
|
||
dn=$(cd "$(dirname "$0")" && pwd) | ||
# shellcheck source=libcomposetest.sh | ||
. "${dn}/libcomposetest.sh" | ||
|
||
# Add a local rpm-md repo so we can mutate local test packages | ||
treefile_append "repos" '["test-repo"]' | ||
|
||
# An RPM that installs in /opt | ||
build_rpm test-opt \ | ||
install "mkdir -p %{buildroot}/opt/megacorp/bin | ||
install %{name} %{buildroot}/opt/megacorp/bin" \ | ||
files "/opt/megacorp" | ||
|
||
# An RPM that installs in /usr/local | ||
build_rpm test-usr-local \ | ||
install "mkdir -p %{buildroot}/usr/local/bin | ||
install %{name} %{buildroot}/usr/local/bin" \ | ||
files "/usr/local/bin/%{name}" | ||
|
||
echo gpgcheck=0 >> yumrepo.repo | ||
ln "$PWD/yumrepo.repo" config/yumrepo.repo | ||
|
||
# the top-level manifest doesn't have any packages, so just set it | ||
treefile_append "packages" '["test-opt", "test-usr-local"]' | ||
|
||
# enable state overlays | ||
treefile_set "opt-usrlocal-overlays" 'True' | ||
|
||
runcompose | ||
|
||
# shellcheck disable=SC2154 | ||
ostree --repo="${repo}" ls -R "${treeref}" /usr/lib/opt > opt.txt | ||
assert_file_has_content opt.txt "/usr/lib/opt/megacorp/bin/test-opt" | ||
|
||
ostree --repo="${repo}" ls -R "${treeref}" /usr/local > usr-local.txt | ||
assert_file_has_content usr-local.txt "/usr/local/bin/test-usr-local" | ||
|
||
ostree --repo="${repo}" ls -R "${treeref}" /usr/lib/systemd/system/local-fs.target.requires > local-fs.txt | ||
assert_file_has_content local-fs.txt "[email protected]" | ||
assert_file_has_content local-fs.txt "[email protected]" | ||
|
||
echo "ok /opt and /usr/local RPMs" |