diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index acc7916f1b..757ee80a05 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -85,7 +85,7 @@ librpmostreeinternals_la_CXXFLAGS = $(AM_CXXFLAGS) $(sanitizer_flags) $(rpmostre librpmostreeinternals_la_LIBADD = $(rpmostree_common_libs) privdatadir=$(pkglibdir) -privdata_DATA = src/app/rpm-ostree-0-integration.conf src/app/rpm-ostree-0-integration-opt-usrlocal.conf +privdata_DATA = src/app/rpm-ostree-0-integration.conf src/app/rpm-ostree-0-integration-opt-usrlocal.conf src/app/rpm-ostree-0-integration-opt-usrlocal-compat.conf # Propagate automake verbose mode cargo_build = $(cargo) build $(if $(subst 0,,$(V)),--verbose,) diff --git a/docs/treefile.md b/docs/treefile.md index 8a8979410b..c03819bc79 100644 --- a/docs/treefile.md +++ b/docs/treefile.md @@ -479,3 +479,9 @@ version of `rpm-ostree`. names to use when substituting variables in yum repo files. The `releasever` variable name is invalid. Use the `releasever` key instead. The `basearch` name is invalid; it is filled in automatically. + * `opt-usrlocal-overlays`: boolean, optional: Defaults to `false`. By + default, `/opt` and `/usr/local` are symlinks to subdirectories in `/ + var`. This prevents the ability to compose with packages that install in + those directories. If enabled, RPMs with `/opt` and `/usr/local` content + are allowed; client-side, both paths are writable overlay directories on. + Requires libostree v2023.9+. diff --git a/rpmostree-cxxrs.cxx b/rpmostree-cxxrs.cxx index 3f53d35d09..fb9f17cc4f 100644 --- a/rpmostree-cxxrs.cxx +++ b/rpmostree-cxxrs.cxx @@ -1774,6 +1774,7 @@ struct Treefile final : public ::rust::Opaque ::rpmostreecxx::RepoMetadataTarget get_repo_metadata_target () const noexcept; bool rpmdb_backend_is_target () const noexcept; bool should_normalize_rpmdb () const noexcept; + bool get_opt_usrlocal_overlays () const noexcept; ::rust::Vec< ::rust::String> get_files_remove_regex (::rust::Str package) const noexcept; ::rust::String get_checksum (::rpmostreecxx::OstreeRepo const &repo) const; ::rust::String get_ostree_ref () const noexcept; @@ -2643,6 +2644,9 @@ extern "C" bool rpmostreecxx$cxxbridge1$Treefile$should_normalize_rpmdb ( ::rpmostreecxx::Treefile const &self) noexcept; + bool rpmostreecxx$cxxbridge1$Treefile$get_opt_usrlocal_overlays ( + ::rpmostreecxx::Treefile const &self) noexcept; + void rpmostreecxx$cxxbridge1$Treefile$get_files_remove_regex ( ::rpmostreecxx::Treefile const &self, ::rust::Str package, ::rust::Vec< ::rust::String> *return$) noexcept; @@ -5257,6 +5261,12 @@ Treefile::should_normalize_rpmdb () const noexcept return rpmostreecxx$cxxbridge1$Treefile$should_normalize_rpmdb (*this); } +bool +Treefile::get_opt_usrlocal_overlays () const noexcept +{ + return rpmostreecxx$cxxbridge1$Treefile$get_opt_usrlocal_overlays (*this); +} + ::rust::Vec< ::rust::String> Treefile::get_files_remove_regex (::rust::Str package) const noexcept { diff --git a/rpmostree-cxxrs.h b/rpmostree-cxxrs.h index 3798438655..262fbd2dc6 100644 --- a/rpmostree-cxxrs.h +++ b/rpmostree-cxxrs.h @@ -1556,6 +1556,7 @@ struct Treefile final : public ::rust::Opaque ::rpmostreecxx::RepoMetadataTarget get_repo_metadata_target () const noexcept; bool rpmdb_backend_is_target () const noexcept; bool should_normalize_rpmdb () const noexcept; + bool get_opt_usrlocal_overlays () const noexcept; ::rust::Vec< ::rust::String> get_files_remove_regex (::rust::Str package) const noexcept; ::rust::String get_checksum (::rpmostreecxx::OstreeRepo const &repo) const; ::rust::String get_ostree_ref () const noexcept; diff --git a/rust/src/composepost.rs b/rust/src/composepost.rs index 4d3871be32..ddd4a4c088 100644 --- a/rust/src/composepost.rs +++ b/rust/src/composepost.rs @@ -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 ostree-state-overlay@.service 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] = &[ + "ostree-state-overlay@usr-lib-opt.service", + "ostree-state-overlay@usr-local.service", + ]; + + 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 { + 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") diff --git a/rust/src/importer.rs b/rust/src/importer.rs index c5ec4cb896..dea0dabc02 100644 --- a/rust/src/importer.rs +++ b/rust/src/importer.rs @@ -392,7 +392,7 @@ fn path_is_ostree_compliant(path: &str) -> bool { return true; } - if path.starts_with("/usr/") && !path.starts_with("/usr/local") { + if path.starts_with("/usr/") { return true; } @@ -491,7 +491,7 @@ mod tests { assert_eq!(path_is_ostree_compliant(entry), false, "{}", entry); } - let denied_cases = &["/var", "/etc", "/var/run", "/usr/local", "", "./", "usr/"]; + let denied_cases = &["/var", "/etc", "/var/run", "", "./", "usr/"]; for entry in denied_cases { assert_eq!(path_is_ostree_compliant(entry), false, "{}", entry); } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6ea5d4df48..558a4b6c6d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -625,6 +625,7 @@ pub mod ffi { fn get_repo_metadata_target(&self) -> RepoMetadataTarget; fn rpmdb_backend_is_target(&self) -> bool; fn should_normalize_rpmdb(&self) -> bool; + fn get_opt_usrlocal_overlays(&self) -> bool; fn get_files_remove_regex(&self, package: &str) -> Vec; fn get_checksum(&self, repo: &OstreeRepo) -> Result; fn get_ostree_ref(&self) -> String; diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index 7528b9b0d3..0a8b904c24 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -427,6 +427,7 @@ fn treefile_merge(dest: &mut TreeComposeConfig, src: &mut TreeComposeConfig) { documentation, boot_location, tmp_is_dir, + opt_usrlocal_overlays, default_target, machineid_compat, releasever, @@ -1411,6 +1412,10 @@ impl Treefile { self.parsed.base.rpmdb_normalize.unwrap_or(false) } + pub(crate) fn get_opt_usrlocal_overlays(&self) -> bool { + self.parsed.base.opt_usrlocal_overlays.unwrap_or_default() + } + pub(crate) fn get_files_remove_regex(&self, package: &str) -> Vec { let mut files_to_remove: Vec = Vec::new(); if let Some(ref packages) = self.parsed.base.remove_from_packages { @@ -2531,6 +2536,8 @@ pub(crate) struct BaseComposeConfigFields { pub(crate) boot_location: Option, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) tmp_is_dir: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) opt_usrlocal_overlays: Option, // systemd #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/app/rpm-ostree-0-integration-opt-usrlocal-compat.conf b/src/app/rpm-ostree-0-integration-opt-usrlocal-compat.conf new file mode 100644 index 0000000000..c7e05cd680 --- /dev/null +++ b/src/app/rpm-ostree-0-integration-opt-usrlocal-compat.conf @@ -0,0 +1,5 @@ +# Traditionally, /usr/local has been a link to /var/usrlocal and /opt to /var/opt. +# A new model now is to allow OSTree commit content in those directories. For +# backwards compatibility, we keep the /var paths but flip the symlinks around. +L /var/usrlocal - - - - ../usr/local +L /var/opt - - - - ../usr/lib/opt diff --git a/src/app/rpm-ostree-0-integration-opt-usrlocal.conf b/src/app/rpm-ostree-0-integration-opt-usrlocal.conf index 9051f1f2ea..3c36481b19 100644 --- a/src/app/rpm-ostree-0-integration-opt-usrlocal.conf +++ b/src/app/rpm-ostree-0-integration-opt-usrlocal.conf @@ -1,2 +1,5 @@ +# Traditionally, /usr/local has been a link to /var/usrlocal and /opt to /var/opt. +# A new model now is to allow OSTree commit content in those directories. But +# this dropin implements the old model. d /var/opt 0755 root root - d /var/usrlocal 0755 root root - diff --git a/src/libpriv/rpmostree-core.cxx b/src/libpriv/rpmostree-core.cxx index 1e6af8edaa..9cc872b27b 100644 --- a/src/libpriv/rpmostree-core.cxx +++ b/src/libpriv/rpmostree-core.cxx @@ -4062,6 +4062,55 @@ rpmostree_context_assemble (RpmOstreeContext *self, GCancellable *cancellable, G if (!build_rootfs_usrlinks (self, error)) return FALSE; + /* This is purely for making it easier for people to test out the + * state-overlay stuff until it's stabilized and part of base composes. */ + if (g_getenv ("RPMOSTREE_EXPERIMENTAL_FORCE_OPT_USRLOCAL_OVERLAY")) + { + rpmostree_output_message ( + "Enabling experimental state overlay support for /opt and /usr/local"); + + struct stat stbuf; + + if (!glnx_fstatat_allow_noent (tmprootfs_dfd, "opt", &stbuf, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + if (errno == ENOENT || (errno == 0 && S_ISLNK (stbuf.st_mode))) + { + if (errno == 0 && !glnx_unlinkat (tmprootfs_dfd, "opt", 0, error)) + return FALSE; + if (symlinkat ("usr/lib/opt", tmprootfs_dfd, "opt") < 0) + return glnx_throw_errno_prefix (error, "symlinkat(/opt)"); + } + + if (!glnx_fstatat_allow_noent (tmprootfs_dfd, "usr/local", &stbuf, AT_SYMLINK_NOFOLLOW, + error)) + return FALSE; + if (errno == ENOENT || (errno == 0 && S_ISLNK (stbuf.st_mode))) + { + if (errno == 0 && !glnx_unlinkat (tmprootfs_dfd, "usr/local", 0, error)) + return FALSE; + if (mkdirat (tmprootfs_dfd, "usr/local", 0755) < 0) + return glnx_throw_errno_prefix (error, "mkdirat(/usr/local)"); + } + + if (!glnx_shutil_mkdir_p_at (tmprootfs_dfd, "usr/lib/systemd/system/local-fs.target.wants", + 0755, cancellable, error)) + return FALSE; + + if (symlinkat ("ostree-state-overlay@usr-lib-opt.service", tmprootfs_dfd, + "usr/lib/systemd/system/local-fs.target.wants/" + "ostree-state-overlay@usr-lib-opt.service") + < 0 + && errno != EEXIST) + return glnx_throw_errno_prefix (error, "enabling ostree-state-overlay for /usr/lib/opt"); + + if (symlinkat ( + "ostree-state-overlay@usr-local.service", tmprootfs_dfd, + "usr/lib/systemd/system/local-fs.target.wants/ostree-state-overlay@usr-local.service") + < 0 + && errno != EEXIST) + return glnx_throw_errno_prefix (error, "enabling ostree-state-overlay for /usr/local"); + } + /* We need up to date labels; the set of things needing relabeling * will have been calculated in sort_packages() */ diff --git a/src/libpriv/rpmostree-postprocess.cxx b/src/libpriv/rpmostree-postprocess.cxx index af3e661a08..b6c2179cd1 100644 --- a/src/libpriv/rpmostree-postprocess.cxx +++ b/src/libpriv/rpmostree-postprocess.cxx @@ -461,12 +461,24 @@ postprocess_final (int rootfs_dfd, rpmostreecxx::Treefile &treefile, gboolean un cancellable, error)) return FALSE; - if (!glnx_file_copy_at (pkglibdir_dfd, "rpm-ostree-0-integration-opt-usrlocal.conf", NULL, - rootfs_dfd, - "usr/lib/tmpfiles.d/rpm-ostree-0-integration-opt-usrlocal.conf", - GLNX_FILE_COPY_NOXATTRS, /* Don't take selinux label */ - cancellable, error)) - return FALSE; + if (treefile.get_opt_usrlocal_overlays ()) + { + if (!glnx_file_copy_at ( + pkglibdir_dfd, "rpm-ostree-0-integration-opt-usrlocal-compat.conf", NULL, rootfs_dfd, + "usr/lib/tmpfiles.d/rpm-ostree-0-integration-opt-usrlocal-compat.conf", + GLNX_FILE_COPY_NOXATTRS, /* Don't take selinux label */ + cancellable, error)) + return FALSE; + } + else + { + if (!glnx_file_copy_at (pkglibdir_dfd, "rpm-ostree-0-integration-opt-usrlocal.conf", NULL, + rootfs_dfd, + "usr/lib/tmpfiles.d/rpm-ostree-0-integration-opt-usrlocal.conf", + GLNX_FILE_COPY_NOXATTRS, /* Don't take selinux label */ + cancellable, error)) + return FALSE; + } /* Handle kernel/initramfs if we're not doing a container */ if (!container) diff --git a/tests/compose/test-state-overlays.sh b/tests/compose/test-state-overlays.sh new file mode 100755 index 0000000000..a5a6bfa236 --- /dev/null +++ b/tests/compose/test-state-overlays.sh @@ -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 "ostree-state-overlay@usr-lib-opt.service" +assert_file_has_content local-fs.txt "ostree-state-overlay@usr-local.service" + +echo "ok /opt and /usr/local RPMs" diff --git a/tests/kolainst/destructive/state-overlays b/tests/kolainst/destructive/state-overlays new file mode 100755 index 0000000000..c2aac0d1c5 --- /dev/null +++ b/tests/kolainst/destructive/state-overlays @@ -0,0 +1,104 @@ +#!/bin/bash +## kola: +## tags: "needs-internet" + +set -euo pipefail + +. ${KOLA_EXT_DATA}/libtest.sh + +rm -rf /etc/yum.repos.d/* +cat > /etc/yum.repos.d/vmcheck.repo << EOF +[test-repo] +name=test-repo +baseurl=file:///${KOLA_EXT_DATA}/rpm-repos/0 +gpgcheck=0 +enabled=1 +EOF + + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + # switch over to local ref so upgrades are purely about package changes + booted_commit=$(rpm-ostree status --json | jq -r '.deployments[0].checksum') + ostree refs --create kolatest "${booted_commit}" + systemctl stop rpm-ostreed + unshare -m /bin/bash -c 'mount -o rw,remount /sysroot && sed -i -e "s/refspec=.*/refspec=kolatest/" /ostree/deploy/*/deploy/*.origin' + + # XXX: until ostree v2024.1 hits FCOS + ostree_ver=$(rpm -q ostree --qf '%{version}') + if [ "${ostree_ver}" != "2024.1" ] && \ + [ "$(echo -e "${ostree_ver}\n2024.1" | sort -V | tail -n1)" = "2024.1" ]; then + rpm-ostree override replace https://bodhi.fedoraproject.org/updates/FEDORA-2024-6c7480dd2f + fi + + # FCOS doesn't enable opt-usrlocal-overlays so use the hack instead + mkdir -p /etc/systemd/system/rpm-ostreed.service.d/ + cat > /etc/systemd/system/rpm-ostreed.service.d/state-overlay.conf < /etc/systemd/system/move-usr-local.service < /tmp/out.txt + assert_file_has_content /tmp/out.txt 'test-opt' + assert_file_has_content /opt/megacorp/lib/mylib 'lib1' + + # add some state files + echo 'foobar' > /opt/megacorp/state/mystate + + # change some base files + echo 'badlib' > /opt/megacorp/lib/mylib + /tmp/autopkgtest-reboot 2 + ;; + 2) + # check our state is still there + assert_file_has_content /opt/megacorp/state/mystate 'foobar' + assert_file_has_content /opt/megacorp/lib/mylib 'badlib' + + # upgrade to -2 + sed -i -e 's,rpm-repos/0,rpm-repos/1,' /etc/yum.repos.d/vmcheck.repo + rpm-ostree upgrade + /tmp/autopkgtest-reboot 3 + ;; + 3) + # check our state is still there + assert_file_has_content /opt/megacorp/state/mystate 'foobar' + + # but base content has been reset + assert_file_has_content /opt/megacorp/lib/mylib 'lib2' + ;; + *) echo "unexpected mark: ${AUTOPKGTEST_REBOOT_MARK}"; exit 1;; +esac diff --git a/tests/kolainst/kolainst-build.sh b/tests/kolainst/kolainst-build.sh index 544e3f8d49..8ed020f163 100755 --- a/tests/kolainst/kolainst-build.sh +++ b/tests/kolainst/kolainst-build.sh @@ -96,6 +96,13 @@ build_module_defaults foomodular \ build_rpm zincati version 99.99 release 2 build_rpm zincati version 99.99 release 3 +# An RPM that installs in /opt +build_rpm test-opt \ + install "mkdir -p %{buildroot}/opt/megacorp/{bin,lib,state} + install %{name} %{buildroot}/opt/megacorp/bin + echo lib1 > %{buildroot}/opt/megacorp/lib/mylib" \ + files "/opt/megacorp" + mv ${test_tmpdir}/yumrepo/* ${test_tmpdir}/rpm-repos/${repover} # To test remote override replace update @@ -111,6 +118,14 @@ build_rpm pkgsystemd \ install "mkdir -p %{buildroot}/usr/lib/systemd/system install %{name}.service %{buildroot}/usr/lib/systemd/system" \ files "/usr/lib/systemd/system/%{name}.service" + +# to test updates to RPMs that install in /opt +build_rpm test-opt release 2 \ + install "mkdir -p %{buildroot}/opt/megacorp/{bin,lib,state} + install %{name} %{buildroot}/opt/megacorp/bin + echo lib2 > %{buildroot}/opt/megacorp/lib/mylib" \ + files "/opt/megacorp" + mv ${test_tmpdir}/yumrepo/* ${test_tmpdir}/rpm-repos/${repover} # Create an empty repo when we want to test inability to find a package diff --git a/tests/vmcheck/test-layering-basic-1.sh b/tests/vmcheck/test-layering-basic-1.sh index d35f7ad289..edeae21bda 100755 --- a/tests/vmcheck/test-layering-basic-1.sh +++ b/tests/vmcheck/test-layering-basic-1.sh @@ -79,7 +79,8 @@ vm_build_rpm test-usrlocal \ if vm_rpmostree install test-usrlocal-1.0 2>err.txt; then assert_not_reached "Was able to install a package in /usr/local/" fi -assert_file_has_content err.txt "Unsupported path; see https://github.com/projectatomic/rpm-ostree/issues/233" +# this error is worse now than it used to be now that we experimentally *do* support /usr/local RPMs +assert_file_has_content err.txt "opendir(local): No such file or directory" echo "ok failed to install in /usr/local"