diff --git a/rust/src/daemon.rs b/rust/src/daemon.rs index 2e2a70982d..41deee01ee 100644 --- a/rust/src/daemon.rs +++ b/rust/src/daemon.rs @@ -93,6 +93,11 @@ fn deployment_populate_variant_origin( "requested-local-packages", tf.derive.packages_local.as_ref(), ); + vdict_insert_optmap( + dict, + "requested-local-fileoverride-packages", + tf.derive.packages_local_fileoverride.as_ref(), + ); vdict_insert_optvec( dict, "requested-base-removals", diff --git a/rust/src/lib.rs b/rust/src/lib.rs index bbf041ae49..1fd0faf3c6 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -353,6 +353,7 @@ pub mod ffi { fn get_repos(&self) -> Vec; fn get_packages(&self) -> Vec; fn get_packages_local(&self) -> Vec; + fn get_packages_local_fileoverride(&self) -> Vec; fn get_packages_override_replace_local(&self) -> Vec; fn get_packages_override_remove(&self) -> Vec; fn get_modules_enable(&self) -> Vec; diff --git a/rust/src/origin.rs b/rust/src/origin.rs index e7f6708d01..80073f0702 100644 --- a/rust/src/origin.rs +++ b/rust/src/origin.rs @@ -26,6 +26,7 @@ const OVERRIDES: &str = "overrides"; /// The set of keys that we parse as BTreeMap and need to ignore ordering changes. static UNORDERED_LIST_KEYS: phf::Set<&'static str> = phf::phf_set! { "packages/local", + "packages/local-fileoverride", "overrides/replace-local" }; @@ -45,6 +46,8 @@ pub(crate) fn origin_to_treefile_inner(kf: &KeyFile) -> Result> { cfg.derive.base_refspec = Some(refspec_str); cfg.packages = parse_stringlist(kf, PACKAGES, "requested")?; cfg.derive.packages_local = parse_localpkglist(kf, PACKAGES, "requested-local")?; + cfg.derive.packages_local_fileoverride = + parse_localpkglist(kf, PACKAGES, "requested-local-fileoverride")?; let modules_enable = parse_stringlist(kf, MODULES, "enable")?; let modules_install = parse_stringlist(kf, MODULES, "install")?; if modules_enable.is_some() || modules_install.is_some() { @@ -132,7 +135,9 @@ fn treefile_to_origin_inner(tf: &Treefile) -> Result { let kf = glib::KeyFile::new(); // refspec (note special handling right now for layering) - let deriving = tf.packages.is_some() || tf.derive.packages_local.is_some(); + let deriving = tf.packages.is_some() + || tf.derive.packages_local.is_some() + || tf.derive.packages_local_fileoverride.is_some(); if let Some(r) = tf.derive.base_refspec.as_deref() { let k = if deriving { "baserefspec" } else { "refspec" }; kf.set_string(ORIGIN, k, r) @@ -146,6 +151,9 @@ fn treefile_to_origin_inner(tf: &Treefile) -> Result { if let Some(pkgs) = tf.derive.packages_local.as_ref() { set_sha256_nevra_pkgs(&kf, PACKAGES, "requested-local", pkgs) } + if let Some(pkgs) = tf.derive.packages_local_fileoverride.as_ref() { + set_sha256_nevra_pkgs(&kf, PACKAGES, "requested-local-fileoverride", pkgs) + } if let Some(pkgs) = tf.derive.override_remove.as_deref() { let pkgs = pkgs.iter().map(|s| s.as_str()); kf_set_string_list(&kf, OVERRIDES, "remove", pkgs) diff --git a/rust/src/treefile.rs b/rust/src/treefile.rs index 9f6c6581f6..c996bdac73 100644 --- a/rust/src/treefile.rs +++ b/rust/src/treefile.rs @@ -618,6 +618,16 @@ impl Treefile { .collect() } + pub(crate) fn get_packages_local_fileoverride(&self) -> Vec { + self.parsed + .derive + .packages_local_fileoverride + .iter() + .flatten() + .map(|(k, v)| format!("{}:{}", v, k)) + .collect() + } + pub(crate) fn get_modules_enable(&self) -> Vec { self.parsed .modules @@ -1339,6 +1349,8 @@ pub(crate) struct DeriveConfigFields { #[serde(skip_serializing_if = "Option::is_none")] pub(crate) packages_local: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) packages_local_fileoverride: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub(crate) override_remove: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) override_replace_local: Option>, @@ -1369,6 +1381,7 @@ impl DeriveConfigFields { } check!(base_refspec); check!(packages_local); + check!(packages_local_fileoverride); check!(override_remove); check!(override_replace_local); check!(initramfs); diff --git a/src/app/rpmostree-builtin-deploy.cxx b/src/app/rpmostree-builtin-deploy.cxx index 4ed5c8e59c..91616550cb 100644 --- a/src/app/rpmostree-builtin-deploy.cxx +++ b/src/app/rpmostree-builtin-deploy.cxx @@ -170,6 +170,7 @@ rpmostree_builtin_deploy (int argc, NULL, /* refspec */ revision, install_pkgs, + NULL, /* install_fileoverride_pkgs */ uninstall_pkgs, NULL, /* override replace */ NULL, /* override remove */ diff --git a/src/app/rpmostree-builtin-rebase.cxx b/src/app/rpmostree-builtin-rebase.cxx index 976bf2001d..2b434b4380 100644 --- a/src/app/rpmostree-builtin-rebase.cxx +++ b/src/app/rpmostree-builtin-rebase.cxx @@ -201,6 +201,7 @@ rpmostree_builtin_rebase (int argc, new_provided_refspec, revision, install_pkgs, + NULL, /* install_fileoverride_pkgs */ uninstall_pkgs, NULL, /* override replace */ NULL, /* override remove */ diff --git a/src/app/rpmostree-builtin-reset.cxx b/src/app/rpmostree-builtin-reset.cxx index eeb7af8b59..a8a1eedf3c 100644 --- a/src/app/rpmostree-builtin-reset.cxx +++ b/src/app/rpmostree-builtin-reset.cxx @@ -103,7 +103,7 @@ rpmostree_builtin_reset (int argc, g_variant_dict_insert (&dict, "lock-finalization", "b", opt_lock_finalization); g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict)); - if (!rpmostree_update_deployment (os_proxy, NULL, NULL, install_pkgs, uninstall_pkgs, + if (!rpmostree_update_deployment (os_proxy, NULL, NULL, install_pkgs, NULL, uninstall_pkgs, NULL, NULL, NULL, NULL, options, &transaction_address, cancellable, error)) return FALSE; diff --git a/src/app/rpmostree-builtin-status.cxx b/src/app/rpmostree-builtin-status.cxx index 252bbb83f8..26d16a8a10 100644 --- a/src/app/rpmostree-builtin-status.cxx +++ b/src/app/rpmostree-builtin-status.cxx @@ -566,6 +566,7 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, g_autofree const gchar **origin_requested_packages = NULL; g_autofree const gchar **origin_requested_modules = NULL; g_autofree const gchar **origin_requested_local_packages = NULL; + g_autofree const gchar **origin_requested_local_fileoverride_packages = NULL; g_autoptr(GVariant) origin_base_removals = NULL; g_autofree const gchar **origin_requested_base_removals = NULL; g_autoptr(GVariant) origin_base_local_replacements = NULL; @@ -585,6 +586,8 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, lookup_array_and_canonicalize (dict, "requested-modules"); origin_requested_local_packages = lookup_array_and_canonicalize (dict, "requested-local-packages"); + origin_requested_local_fileoverride_packages = + lookup_array_and_canonicalize (dict, "requested-local-fileoverride-packages"); origin_base_removals = g_variant_dict_lookup_value (dict, "base-removals", G_VARIANT_TYPE ("av")); origin_requested_base_removals = @@ -963,6 +966,9 @@ print_one_deployment (RPMOSTreeSysroot *sysroot_proxy, if (origin_requested_local_packages) print_packages ("LocalPackages", max_key_len, origin_requested_local_packages, NULL); + if (origin_requested_local_fileoverride_packages) + print_packages ("LocalForcedPackages", max_key_len, + origin_requested_local_fileoverride_packages, NULL); if (regenerate_initramfs) { diff --git a/src/app/rpmostree-builtin-upgrade.cxx b/src/app/rpmostree-builtin-upgrade.cxx index 65785d6449..c84a566992 100644 --- a/src/app/rpmostree-builtin-upgrade.cxx +++ b/src/app/rpmostree-builtin-upgrade.cxx @@ -183,6 +183,7 @@ rpmostree_builtin_upgrade (int argc, NULL, /* refspec */ NULL, /* revision */ install_pkgs, + NULL, /* install_fileoverride_pkgs */ uninstall_pkgs, NULL, /* override replace */ NULL, /* override remove */ diff --git a/src/app/rpmostree-clientlib.cxx b/src/app/rpmostree-clientlib.cxx index 30d4bae6b4..e35689f4d0 100644 --- a/src/app/rpmostree-clientlib.cxx +++ b/src/app/rpmostree-clientlib.cxx @@ -965,13 +965,13 @@ vardict_sort_and_insert_pkgs (GVariantDict *dict, if (!rpmostree_sort_pkgs_strv (pkgs, fd_list, &repo_pkgs, &fd_idxs, error)) return FALSE; - /* for grep: here we insert install-packages/override-replace-packages */ + /* for grep: here we insert install-packages/install-fileoverride-packages/override-replace-packages */ if (repo_pkgs != NULL && repo_pkgs->len > 0) g_variant_dict_insert_value (dict, glnx_strjoina (key_prefix, "-packages"), g_variant_new_strv ((const char *const*)repo_pkgs->pdata, repo_pkgs->len)); - /* for grep: here we insert install-local-packages/override-replace-local-packages */ + /* for grep: here we insert install-local-packages/install-fileoverride-local-packages/override-replace-local-packages */ if (fd_idxs != NULL) g_variant_dict_insert_value (dict, glnx_strjoina (key_prefix, "-local-packages"), fd_idxs); @@ -982,6 +982,7 @@ static gboolean get_modifiers_variant (const char *set_refspec, const char *set_revision, const char *const* install_pkgs, + const char *const* install_fileoverride_pkgs, const char *const* uninstall_pkgs, const char *const* override_replace_pkgs, const char *const* override_remove_pkgs, @@ -1002,6 +1003,13 @@ get_modifiers_variant (const char *set_refspec, return FALSE; } + if (install_fileoverride_pkgs) + { + if (!vardict_sort_and_insert_pkgs (&dict, "install-fileoverride", fd_list, + install_fileoverride_pkgs, error)) + return FALSE; + } + if (override_replace_pkgs) { if (!vardict_sort_and_insert_pkgs (&dict, "override-replace", fd_list, @@ -1041,6 +1049,7 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, const char *set_refspec, const char *set_revision, const char *const* install_pkgs, + const char *const* install_fileoverride_pkgs, const char *const* uninstall_pkgs, const char *const* override_replace_pkgs, const char *const* override_remove_pkgs, @@ -1054,7 +1063,9 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, g_autoptr(GVariant) modifiers = NULL; glnx_unref_object GUnixFDList *fd_list = NULL; if (!get_modifiers_variant (set_refspec, set_revision, - install_pkgs, uninstall_pkgs, + install_pkgs, + install_fileoverride_pkgs, + uninstall_pkgs, override_replace_pkgs, override_remove_pkgs, override_reset_pkgs, diff --git a/src/app/rpmostree-clientlib.h b/src/app/rpmostree-clientlib.h index aacf283ec3..106b17dbcc 100644 --- a/src/app/rpmostree-clientlib.h +++ b/src/app/rpmostree-clientlib.h @@ -125,6 +125,7 @@ rpmostree_update_deployment (RPMOSTreeOS *os_proxy, const char *set_refspec, const char *set_revision, const char *const* install_pkgs, + const char *const* install_fileoverride_pkgs, const char *const* uninstall_pkgs, const char *const* override_replace_pkgs, const char *const* override_remove_pkgs, diff --git a/src/app/rpmostree-override-builtins.cxx b/src/app/rpmostree-override-builtins.cxx index 8526947f6e..a0dad1b8c1 100644 --- a/src/app/rpmostree-override-builtins.cxx +++ b/src/app/rpmostree-override-builtins.cxx @@ -158,6 +158,7 @@ handle_override (RPMOSTreeSysroot *sysroot_proxy, NULL, /* set-refspec */ NULL, /* set-revision */ install_pkgs, + NULL, /* install_fileoverride_pkgs */ uninstall_pkgs, override_replace, override_remove, diff --git a/src/app/rpmostree-pkg-builtins.cxx b/src/app/rpmostree-pkg-builtins.cxx index 95f685b073..8a9d312426 100644 --- a/src/app/rpmostree-pkg-builtins.cxx +++ b/src/app/rpmostree-pkg-builtins.cxx @@ -43,6 +43,7 @@ static gboolean opt_allow_inactive; static gboolean opt_uninstall_all; static gboolean opt_unchanged_exit_77; static gboolean opt_lock_finalization; +static gboolean opt_force_replacefiles; static GOptionEntry option_entries[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" }, @@ -66,6 +67,7 @@ static GOptionEntry install_option_entry[] = { { "cache-only", 'C', 0, G_OPTION_ARG_NONE, &opt_cache_only, "Do not download latest ostree and RPM data", NULL }, { "download-only", 0, 0, G_OPTION_ARG_NONE, &opt_download_only, "Just download latest ostree and RPM data, don't deploy", NULL }, { "apply-live", 'A', 0, G_OPTION_ARG_NONE, &opt_apply_live, "Apply changes to both pending deployment and running filesystem tree", NULL }, + { "force-replacefiles", 0, 0, G_OPTION_ARG_NONE, &opt_force_replacefiles, "Allow package to replace files from other packages", NULL }, { NULL } }; @@ -78,11 +80,16 @@ pkg_change (RpmOstreeCommandInvocation *invocation, GError **error) { const char *const strv_empty[] = { NULL }; + const char *const* install_pkgs = strv_empty; + const char *const* install_fileoverride_pkgs = strv_empty; + const char *const* uninstall_pkgs = strv_empty; - if (!packages_to_add) - packages_to_add = strv_empty; - if (!packages_to_remove) - packages_to_remove = strv_empty; + if (packages_to_add && opt_force_replacefiles) + install_fileoverride_pkgs = packages_to_add; + else if (packages_to_add && !opt_force_replacefiles) + install_pkgs = packages_to_add; + if (packages_to_remove) + uninstall_pkgs = packages_to_remove; glnx_unref_object RPMOSTreeOS *os_proxy = NULL; if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname, @@ -109,17 +116,18 @@ pkg_change (RpmOstreeCommandInvocation *invocation, gboolean met_local_pkg = FALSE; for (const char *const* it = packages_to_add; it && *it; it++) - met_local_pkg = met_local_pkg || g_str_has_suffix (*it, ".rpm"); + met_local_pkg = met_local_pkg || g_str_has_suffix (*it, ".rpm") || g_str_has_prefix (*it, "file://"); /* Use newer D-Bus API only if we have to. */ g_autofree char *transaction_address = NULL; - if (met_local_pkg || opt_apply_live) + if (met_local_pkg || opt_apply_live || (install_fileoverride_pkgs && *install_fileoverride_pkgs)) { if (!rpmostree_update_deployment (os_proxy, NULL, /* refspec */ NULL, /* revision */ - packages_to_add, - packages_to_remove, + install_pkgs, + install_fileoverride_pkgs, + uninstall_pkgs, NULL, /* override replace */ NULL, /* override remove */ NULL, /* override reset */ @@ -134,8 +142,8 @@ pkg_change (RpmOstreeCommandInvocation *invocation, { if (!rpmostree_os_call_pkg_change_sync (os_proxy, options, - packages_to_add, - packages_to_remove, + install_pkgs, + uninstall_pkgs, NULL, &transaction_address, NULL, diff --git a/src/daemon/org.projectatomic.rpmostree1.xml b/src/daemon/org.projectatomic.rpmostree1.xml index 979f1a6f2d..563e445082 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -298,6 +298,7 @@ "install-packages" (type 'as') "uninstall-packages" (type 'as') "install-local-packages" (type 'ah') + "install-local-fileoverride-packages" (type 'ah') "install-modules" (type 'as') "uninstall-modules" (type 'as') "override-remove-packages" (type 'as') diff --git a/src/daemon/rpmostree-sysroot-upgrader.cxx b/src/daemon/rpmostree-sysroot-upgrader.cxx index ac435f9297..30a539537a 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.cxx +++ b/src/daemon/rpmostree-sysroot-upgrader.cxx @@ -779,6 +779,47 @@ finalize_overrides (RpmOstreeSysrootUpgrader *self, && finalize_replacement_overrides (self, cancellable, error); } +static gboolean +add_local_pkgset_to_sack (RpmOstreeSysrootUpgrader *self, + GHashTable *pkgset, + GCancellable *cancellable, + GError **error) +{ + if (g_hash_table_size (pkgset) == 0) + return TRUE; /* nothing to do! */ + + if (!initialize_metatmpdir (self, error)) + return FALSE; + + GLNX_HASH_TABLE_FOREACH_KV (pkgset, const char*, nevra, const char*, sha256) + { + g_autoptr(GVariant) header = NULL; + g_autofree char *path = + g_strdup_printf ("%s/%s.rpm", self->metatmpdir.path, nevra); + + if (!rpmostree_pkgcache_find_pkg_header (self->repo, nevra, sha256, + &header, cancellable, error)) + return FALSE; + + if (!glnx_file_replace_contents_at (AT_FDCWD, path, + static_cast(g_variant_get_data (header)), + g_variant_get_size (header), + GLNX_FILE_REPLACE_NODATASYNC, + cancellable, error)) + return FALSE; + + /* Also check if that exact NEVRA is already in the root (if the pkg + * exists, but is a different EVR, depsolve will catch that). In the + * future, we'll allow packages to replace base pkgs. */ + if (rpmostree_sack_has_subject (self->rsack->sack, nevra)) + return glnx_throw (error, "Package '%s' is already in the base", nevra); + + dnf_sack_add_cmdline_package (self->rsack->sack, path); + } + + return TRUE; +} + /* Go through rpmdb and jot down the missing pkgs from the given set. Really, we * don't *have* to do this: we could just give everything to libdnf and let it * figure out what is already installed. The advantage of doing it ourselves is @@ -804,37 +845,12 @@ finalize_overlays (RpmOstreeSysrootUpgrader *self, * you can have foo-1.0-1.x86_64 layered, and foo or /usr/bin/foo as dormant. * */ GHashTable *local_pkgs = rpmostree_origin_get_local_packages (self->computed_origin); - if (g_hash_table_size (local_pkgs) > 0) - { - if (!initialize_metatmpdir (self, error)) - return FALSE; - - GLNX_HASH_TABLE_FOREACH_KV (local_pkgs, const char*, nevra, const char*, sha256) - { - g_autoptr(GVariant) header = NULL; - g_autofree char *path = - g_strdup_printf ("%s/%s.rpm", self->metatmpdir.path, nevra); - - if (!rpmostree_pkgcache_find_pkg_header (self->repo, nevra, sha256, - &header, cancellable, error)) - return FALSE; - - if (!glnx_file_replace_contents_at (AT_FDCWD, path, - static_cast(g_variant_get_data (header)), - g_variant_get_size (header), - GLNX_FILE_REPLACE_NODATASYNC, - cancellable, error)) - return FALSE; - - /* Also check if that exact NEVRA is already in the root (if the pkg - * exists, but is a different EVR, depsolve will catch that). In the - * future, we'll allow packages to replace base pkgs. */ - if (rpmostree_sack_has_subject (self->rsack->sack, nevra)) - return glnx_throw (error, "Package '%s' is already in the base", nevra); + if (!add_local_pkgset_to_sack (self, local_pkgs, cancellable, error)) + return FALSE; - dnf_sack_add_cmdline_package (self->rsack->sack, path); - } - } + GHashTable *local_fileoverride_pkgs = rpmostree_origin_get_local_fileoverride_packages (self->computed_origin); + if (!add_local_pkgset_to_sack (self, local_fileoverride_pkgs, cancellable, error)) + return FALSE; GHashTable *removals = rpmostree_origin_get_overrides_remove (self->computed_origin); diff --git a/src/daemon/rpmostreed-os.cxx b/src/daemon/rpmostreed-os.cxx index 24ad534cdc..e660bb0cff 100644 --- a/src/daemon/rpmostreed-os.cxx +++ b/src/daemon/rpmostreed-os.cxx @@ -190,6 +190,9 @@ os_authorize_method (GDBusInterfaceSkeleton *interface, g_autoptr(GVariant) install_local_pkgs = g_variant_dict_lookup_value (&modifiers_dict, "install-local-packages", G_VARIANT_TYPE("ah")); + g_autoptr(GVariant) install_local_fileoverride_pkgs = + g_variant_dict_lookup_value (&modifiers_dict, "install-local-fileoverride-packages", + G_VARIANT_TYPE("ah")); g_autoptr(GVariant) override_replace_local_pkgs = g_variant_dict_lookup_value (&modifiers_dict, "override-replace-local-packages", G_VARIANT_TYPE("ah")); @@ -216,7 +219,8 @@ os_authorize_method (GDBusInterfaceSkeleton *interface, no_layering) g_ptr_array_add (actions, (void*)"org.projectatomic.rpmostree1.install-uninstall-packages"); - if (install_local_pkgs != NULL && g_variant_n_children (install_local_pkgs) > 0) + if ((install_local_pkgs != NULL && g_variant_n_children (install_local_pkgs) > 0) + || (install_local_fileoverride_pkgs != NULL && g_variant_n_children (install_local_fileoverride_pkgs) > 0)) g_ptr_array_add (actions, (void*)"org.projectatomic.rpmostree1.install-local-packages"); if (override_replace_pkgs != NULL || override_remove_pkgs != NULL || override_reset_pkgs != NULL || diff --git a/src/daemon/rpmostreed-transaction-types.cxx b/src/daemon/rpmostreed-transaction-types.cxx index b9926d4af0..a1cdf5ffaf 100644 --- a/src/daemon/rpmostreed-transaction-types.cxx +++ b/src/daemon/rpmostreed-transaction-types.cxx @@ -904,6 +904,9 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, g_autoptr(GVariant) install_local_pkgs_idxs = g_variant_dict_lookup_value (self->modifiers, "install-local-packages", G_VARIANT_TYPE("ah")); + g_autoptr(GVariant) install_fileoverride_local_pkgs_idxs = + g_variant_dict_lookup_value (self->modifiers, "install-fileoverride-local-packages", + G_VARIANT_TYPE("ah")); g_autoptr(GVariant) override_replace_local_pkgs_idxs = g_variant_dict_lookup_value (self->modifiers, "override-replace-local-packages", G_VARIANT_TYPE("ah")); @@ -923,6 +926,8 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, guint expected_fdn = 0; if (install_local_pkgs_idxs) expected_fdn += g_variant_n_children (install_local_pkgs_idxs); + if (install_fileoverride_local_pkgs_idxs) + expected_fdn += g_variant_n_children (install_fileoverride_local_pkgs_idxs); if (override_replace_local_pkgs_idxs) expected_fdn += g_variant_n_children (override_replace_local_pkgs_idxs); if (local_repo_remote_idx != -1) @@ -936,6 +941,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, return glnx_throw (error, "Expected %u fds but received %u", expected_fdn, actual_fdn); g_autoptr(GUnixFDList) install_local_pkgs = NULL; + g_autoptr(GUnixFDList) install_fileoverride_local_pkgs = NULL; g_autoptr(GUnixFDList) override_replace_local_pkgs = NULL; /* split into two fd lists to make it easier for deploy_transaction_execute */ if (self->fd_list) @@ -950,6 +956,13 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, install_local_pkgs = g_unix_fd_list_new_from_array (new_fds, -1); } + if (install_fileoverride_local_pkgs_idxs) + { + g_autofree gint *new_fds = + get_fd_array_from_sparse (fds, nfds, install_fileoverride_local_pkgs_idxs); + install_fileoverride_local_pkgs = g_unix_fd_list_new_from_array (new_fds, -1); + } + if (override_replace_local_pkgs_idxs) { g_autofree gint *new_fds = @@ -1010,6 +1023,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, g_autofree const char *update_driver = deploy_has_string_option (self, "register-driver"); g_autofree char **install_pkgs = vardict_lookup_strv_canonical (self->modifiers, "install-packages"); + g_autofree char **install_fileoverride_pkgs = vardict_lookup_strv_canonical (self->modifiers, "install-fileoverride-packages"); g_autofree char **uninstall_pkgs = vardict_lookup_strv_canonical (self->modifiers, "uninstall-packages"); g_autofree char **enable_modules = vardict_lookup_strv_canonical (self->modifiers, "enable-modules"); g_autofree char **disable_modules = vardict_lookup_strv_canonical (self->modifiers, "disable-modules"); @@ -1022,6 +1036,8 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, if (deploy_has_bool_option (self, "apply-live") && deploy_has_bool_option (self, "reboot")) return glnx_throw (error, "Cannot specify `apply-live` and `reboot`"); + if (install_fileoverride_pkgs) + return glnx_throw (error, "Non-local fileoverrides not implemented"); /* In practice today */ if (no_pull_base) @@ -1035,7 +1051,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, no_overrides); if (!is_override) { - if (install_pkgs || install_local_pkgs || install_modules) + if (install_pkgs || install_local_pkgs || install_fileoverride_local_pkgs || install_modules) is_install = TRUE; else is_uninstall = TRUE; @@ -1098,6 +1114,9 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, if (install_local_pkgs) g_string_append_printf (txn_title, "; localinstall: %u", g_unix_fd_list_get_length (install_local_pkgs)); + if (install_fileoverride_local_pkgs) + g_string_append_printf (txn_title, "; fileoverride localinstall: %u", + g_unix_fd_list_get_length (install_fileoverride_local_pkgs)); if (enable_modules) g_string_append_printf (txn_title, "; module enable: %u", g_strv_length (enable_modules)); @@ -1316,7 +1335,7 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, } } - if (!rpmostree_origin_add_packages (origin, install_pkgs, FALSE, + if (!rpmostree_origin_add_packages (origin, install_pkgs, FALSE, FALSE, idempotent_layering, &changed, error)) return FALSE; } @@ -1337,7 +1356,24 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, { g_ptr_array_add (pkgs, NULL); - if (!rpmostree_origin_add_packages (origin, (char**)pkgs->pdata, TRUE, + if (!rpmostree_origin_add_packages (origin, (char**)pkgs->pdata, TRUE, FALSE, + idempotent_layering, &changed, error)) + return FALSE; + } + } + + if (install_fileoverride_local_pkgs != NULL) + { + g_autoptr(GPtrArray) pkgs = NULL; + if (!import_many_local_rpms (repo, install_fileoverride_local_pkgs, &pkgs, + cancellable, error)) + return FALSE; + + if (pkgs->len > 0) + { + g_ptr_array_add (pkgs, NULL); + + if (!rpmostree_origin_add_packages (origin, (char**)pkgs->pdata, TRUE, TRUE, idempotent_layering, &changed, error)) return FALSE; } diff --git a/src/libpriv/rpmostree-core-private.h b/src/libpriv/rpmostree-core-private.h index 2726fb88fe..905e3d4aa1 100644 --- a/src/libpriv/rpmostree-core-private.h +++ b/src/libpriv/rpmostree-core-private.h @@ -68,6 +68,8 @@ struct _RpmOstreeContext { GHashTable *pkgs_to_remove; /* pkgname --> gv_nevra */ GHashTable *pkgs_to_replace; /* new gv_nevra --> old gv_nevra */ + GHashTable *fileoverride_pkgs; /* set of nevras */ + std::optional> lockfile; gboolean lockfile_strict; diff --git a/src/libpriv/rpmostree-core.cxx b/src/libpriv/rpmostree-core.cxx index 785db9c2eb..1d3f5692c7 100644 --- a/src/libpriv/rpmostree-core.cxx +++ b/src/libpriv/rpmostree-core.cxx @@ -112,6 +112,8 @@ rpmostree_context_finalize (GObject *object) g_clear_pointer (&rctx->pkgs_to_remove, g_hash_table_unref); g_clear_pointer (&rctx->pkgs_to_replace, g_hash_table_unref); + g_clear_pointer (&rctx->fileoverride_pkgs, g_hash_table_unref); + (void)glnx_tmpdir_delete (&rctx->tmpdir, NULL, NULL); (void)glnx_tmpdir_delete (&rctx->repo_tmpdir, NULL, NULL); @@ -1714,6 +1716,7 @@ rpmostree_context_prepare (RpmOstreeContext *self, auto packages = self->treefile_rs->get_packages(); auto packages_local = self->treefile_rs->get_packages_local(); + auto packages_local_fileoverride = self->treefile_rs->get_packages_local_fileoverride(); auto packages_override_replace_local = self->treefile_rs->get_packages_override_replace_local(); auto packages_override_remove = self->treefile_rs->get_packages_override_remove(); auto exclude_packages = self->treefile_rs->get_exclude_packages (); @@ -1724,6 +1727,7 @@ rpmostree_context_prepare (RpmOstreeContext *self, if (self->lockfile) { g_assert_cmpint (packages_local.size(), ==, 0); + g_assert_cmpint (packages_local_fileoverride.size(), ==, 0); g_assert_cmpint (packages_override_replace_local.size(), ==, 0); g_assert_cmpint (packages_override_remove.size(), ==, 0); } @@ -1788,6 +1792,23 @@ rpmostree_context_prepare (RpmOstreeContext *self, g_hash_table_insert (local_pkgs_to_install, (gpointer)nevra, g_steal_pointer (&pkg)); } + /* Local fileoverride packages. XXX: dedupe */ + self->fileoverride_pkgs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + for (auto &nevra_v: packages_local_fileoverride) + { + const char *nevra = nevra_v.c_str(); + g_autofree char *sha256 = NULL; + if (!rpmostree_decompose_sha256_nevra (&nevra, &sha256, error)) + return FALSE; + + g_autoptr(DnfPackage) pkg = NULL; + if (!add_pkg_from_cache (self, nevra, sha256, &pkg, cancellable, error)) + return FALSE; + + g_hash_table_insert (local_pkgs_to_install, (gpointer)nevra, g_steal_pointer (&pkg)); + g_hash_table_add (self->fileoverride_pkgs, g_strdup (nevra)); + } + /* If we're in cache-only mode, add all the remaining pkgs now. We do this *after* the * replace & local pkgs since they already handle a subset of the cached pkgs and have * SHA256 checks. But we do it *before* dnf_context_install() since those subjects are not @@ -3737,6 +3758,7 @@ write_rpmdb (RpmOstreeContext *self, GPtrArray *overlays, GPtrArray *overrides_replace, GPtrArray *overrides_remove, + gboolean have_fileoverride, GCancellable *cancellable, GError **error) { @@ -3817,15 +3839,26 @@ write_rpmdb (RpmOstreeContext *self, rpmtsOrder (rpmdb_ts); - /* NB: Because we're using the real root here (see above for reason why), rpm + rpmprobFilterFlags flags = 0; + + /* Because we're using the real root here (see above for reason why), rpm * will see the read-only /usr mount and think that there isn't any disk space * available for install. For now, we just tell rpm to ignore space * calculations, but then we lose that nice check. What we could do is set a * root dir at least if we have CAP_SYS_CHROOT, or maybe do the space req * check ourselves if rpm makes that information easily accessible (doesn't * look like it from a quick glance). */ - /* Also enable OLDPACKAGE to allow replacement overrides to older version. */ - int r = rpmtsRun (rpmdb_ts, NULL, RPMPROB_FILTER_DISKSPACE | RPMPROB_FILTER_OLDPACKAGE); + flags |= RPMPROB_FILTER_DISKSPACE; + + /* Enable OLDPACKAGE to allow replacement overrides to older version. */ + flags |= RPMPROB_FILTER_OLDPACKAGE; + + /* If there are fileoverrides, then allow replacing files. We don't unconditionally set + * this because it's nice as an extra check on top of what ostree already does. */ + if (have_fileoverride) + flags |= RPMPROB_FILTER_REPLACEOLDFILES; + + int r = rpmtsRun (rpmdb_ts, NULL, flags); if (r < 0) return glnx_throw (error, "Failed to update rpmdb (rpmtsRun code %d)", r); if (r > 0) @@ -4080,6 +4113,9 @@ rpmostree_context_assemble (RpmOstreeContext *self, if (rpmte_is_kernel (te)) self->kernel_changed = TRUE; + else if (g_hash_table_contains (self->fileoverride_pkgs, dnf_package_get_nevra (pkg))) + /* we checkout those last */ + continue; /* The "setup" package currently contains /etc/passwd; in the treecompose * case we need to inject that beforehand, so use "add files" just for @@ -4098,6 +4134,32 @@ rpmostree_context_assemble (RpmOstreeContext *self, progress->nitems_update(n_rpmts_done); } + /* And last, any fileoverride RPMs. These *must* be done last. */ + gboolean have_fileoverrides = FALSE; + for (guint i = 0; i < n_rpmts_elements; i++) + { + rpmte te = rpmtsElement (ordering_ts, i); + rpmElementType type = rpmteType (te); + DnfPackage *pkg = (DnfPackage*)rpmteKey (te); + + if (type == TR_REMOVED) + continue; + g_assert (type == TR_ADDED); + if (!g_hash_table_contains (self->fileoverride_pkgs, dnf_package_get_nevra (pkg))) + continue; + + progress->set_sub_message(dnf_package_get_name (pkg)); + if (!checkout_package_into_root (self, pkg, tmprootfs_dfd, ".", self->devino_cache, + static_cast(g_hash_table_lookup (pkg_to_ostree_commit, pkg)), + files_skip_add, OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES, + cancellable, error)) + return FALSE; + n_rpmts_done++; + progress->nitems_update(n_rpmts_done); + + have_fileoverrides = TRUE; + } + progress->end(""); /* Some packages expect to be able to make temporary files here @@ -4295,7 +4357,8 @@ rpmostree_context_assemble (RpmOstreeContext *self, g_clear_pointer (&ordering_ts, rpmtsFree); - if (!write_rpmdb (self, tmprootfs_dfd, overlays, overrides_replace, overrides_remove, cancellable, error)) + if (!write_rpmdb (self, tmprootfs_dfd, overlays, overrides_replace, overrides_remove, + have_fileoverrides, cancellable, error)) return glnx_prefix_error (error, "Writing rpmdb"); return TRUE; diff --git a/src/libpriv/rpmostree-origin.cxx b/src/libpriv/rpmostree-origin.cxx index a3fe862b42..0e727bb5eb 100644 --- a/src/libpriv/rpmostree-origin.cxx +++ b/src/libpriv/rpmostree-origin.cxx @@ -47,14 +47,15 @@ struct RpmOstreeOrigin { char *cached_unconfigured_state; char **cached_initramfs_args; - GHashTable *cached_initramfs_etc_files; /* set of paths */ - GHashTable *cached_packages; /* set of reldeps */ - GHashTable *cached_modules_enable; /* set of module specs to enable */ - GHashTable *cached_modules_install; /* set of module specs to install */ - GHashTable *cached_local_packages; /* NEVRA --> header sha256 */ + GHashTable *cached_initramfs_etc_files; /* set of paths */ + GHashTable *cached_packages; /* set of reldeps */ + GHashTable *cached_modules_enable; /* set of module specs to enable */ + GHashTable *cached_modules_install; /* set of module specs to install */ + GHashTable *cached_local_packages; /* NEVRA --> header sha256 */ + GHashTable *cached_local_fileoverride_packages; /* NEVRA --> header sha256 */ /* GHashTable *cached_overrides_replace; XXX: NOT IMPLEMENTED YET */ - GHashTable *cached_overrides_local_replace; /* NEVRA --> header sha256 */ - GHashTable *cached_overrides_remove; /* set of pkgnames (no EVRA) */ + GHashTable *cached_overrides_local_replace; /* NEVRA --> header sha256 */ + GHashTable *cached_overrides_remove; /* set of pkgnames (no EVRA) */ }; static GKeyFile * @@ -113,6 +114,8 @@ rpmostree_origin_parse_keyfile (GKeyFile *origin, ret->cached_modules_install = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ret->cached_local_packages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + ret->cached_local_fileoverride_packages = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ret->cached_overrides_local_replace = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); ret->cached_overrides_remove = @@ -174,6 +177,10 @@ rpmostree_origin_parse_keyfile (GKeyFile *origin, ret->cached_local_packages, error)) return FALSE; + if (!parse_packages_strv (ret->kf, "packages", "requested-local-fileoverride", TRUE, + ret->cached_local_fileoverride_packages, error)) + return FALSE; + if (!parse_packages_strv (ret->kf, "modules", "enable", FALSE, ret->cached_modules_enable, error)) return FALSE; @@ -301,6 +308,12 @@ rpmostree_origin_get_local_packages (RpmOstreeOrigin *origin) return origin->cached_local_packages; } +GHashTable * +rpmostree_origin_get_local_fileoverride_packages (RpmOstreeOrigin *origin) +{ + return origin->cached_local_fileoverride_packages; +} + GHashTable * rpmostree_origin_get_overrides_remove (RpmOstreeOrigin *origin) { @@ -371,6 +384,7 @@ rpmostree_origin_has_packages (RpmOstreeOrigin *origin) return (g_hash_table_size (origin->cached_packages) > 0) || (g_hash_table_size (origin->cached_local_packages) > 0) || + (g_hash_table_size (origin->cached_local_fileoverride_packages) > 0) || (g_hash_table_size (origin->cached_overrides_local_replace) > 0) || (g_hash_table_size (origin->cached_overrides_remove) > 0) || (g_hash_table_size (origin->cached_modules_install) > 0); @@ -414,6 +428,7 @@ rpmostree_origin_unref (RpmOstreeOrigin *origin) g_clear_pointer (&origin->cached_modules_enable, g_hash_table_unref); g_clear_pointer (&origin->cached_modules_install, g_hash_table_unref); g_clear_pointer (&origin->cached_local_packages, g_hash_table_unref); + g_clear_pointer (&origin->cached_local_fileoverride_packages, g_hash_table_unref); g_clear_pointer (&origin->cached_overrides_local_replace, g_hash_table_unref); g_clear_pointer (&origin->cached_overrides_remove, g_hash_table_unref); g_clear_pointer (&origin->cached_initramfs_etc_files, g_hash_table_unref); @@ -721,6 +736,7 @@ gboolean rpmostree_origin_add_packages (RpmOstreeOrigin *origin, char **packages, gboolean local, + gboolean fileoverride, gboolean allow_existing, gboolean *out_changed, GError **error) @@ -731,7 +747,7 @@ rpmostree_origin_add_packages (RpmOstreeOrigin *origin, for (char **it = packages; it && *it; it++) { - gboolean requested, requested_local; + gboolean requested, requested_local, requested_local_fileoverride; const char *pkg = *it; g_autofree char *sha256 = NULL; @@ -743,6 +759,7 @@ rpmostree_origin_add_packages (RpmOstreeOrigin *origin, requested = g_hash_table_contains (origin->cached_packages, pkg); requested_local = g_hash_table_contains (origin->cached_local_packages, pkg); + requested_local_fileoverride = g_hash_table_contains (origin->cached_local_fileoverride_packages, pkg); /* The list of packages is really a list of provides, so string equality * is a bit weird here. Multiple provides can resolve to the same package @@ -756,7 +773,7 @@ rpmostree_origin_add_packages (RpmOstreeOrigin *origin, * strings are unique allow `rpm-ostree uninstall` to know exactly what * the user means. */ - if (requested || requested_local) + if (requested || requested_local || requested_local_fileoverride) { if (allow_existing) continue; @@ -768,7 +785,8 @@ rpmostree_origin_add_packages (RpmOstreeOrigin *origin, } if (local) - g_hash_table_insert (origin->cached_local_packages, + g_hash_table_insert (fileoverride ? origin->cached_local_fileoverride_packages + : origin->cached_local_packages, g_strdup (pkg), util::move_nullify (sha256)); else g_hash_table_add (origin->cached_packages, g_strdup (pkg)); @@ -776,10 +794,21 @@ rpmostree_origin_add_packages (RpmOstreeOrigin *origin, } if (changed) - update_keyfile_pkgs_from_cache (origin, "packages", - local ? "requested-local" : "requested", - local ? origin->cached_local_packages - : origin->cached_packages, local); + { + const char *key = "requested"; + GHashTable *ht = origin->cached_packages; + if (local && !fileoverride) + { + key = "requested-local"; + ht = origin->cached_local_packages; + } + else if (local && fileoverride) + { + key = "requested-local-fileoverride"; + ht = origin->cached_local_fileoverride_packages; + } + update_keyfile_pkgs_from_cache (origin, "packages", key, ht, local); + } set_changed (out_changed, changed); return TRUE; @@ -815,9 +844,11 @@ rpmostree_origin_remove_packages (RpmOstreeOrigin *origin, return TRUE; gboolean changed = FALSE; gboolean local_changed = FALSE; + gboolean local_fileoverride_changed = FALSE; /* lazily calculated */ g_autoptr(GHashTable) name_to_nevra = NULL; + g_autoptr(GHashTable) name_to_nevra_fileoverride = NULL; for (char **it = packages; it && *it; it++) { @@ -825,6 +856,8 @@ rpmostree_origin_remove_packages (RpmOstreeOrigin *origin, const char *package = *it; if (g_hash_table_remove (origin->cached_local_packages, package)) local_changed = TRUE; + else if (g_hash_table_remove (origin->cached_local_fileoverride_packages, package)) + local_fileoverride_changed = TRUE; else if (g_hash_table_remove (origin->cached_packages, package)) changed = TRUE; else @@ -834,6 +867,9 @@ rpmostree_origin_remove_packages (RpmOstreeOrigin *origin, if (!build_name_to_nevra_map (origin->cached_local_packages, &name_to_nevra, error)) return FALSE; + if (!build_name_to_nevra_map (origin->cached_local_fileoverride_packages, + &name_to_nevra_fileoverride, error)) + return FALSE; } if (g_hash_table_contains (name_to_nevra, package)) @@ -842,6 +878,12 @@ rpmostree_origin_remove_packages (RpmOstreeOrigin *origin, g_hash_table_lookup (name_to_nevra, package))); local_changed = TRUE; } + else if (g_hash_table_contains (name_to_nevra_fileoverride, package)) + { + g_assert (g_hash_table_remove (origin->cached_local_fileoverride_packages, + g_hash_table_lookup (name_to_nevra_fileoverride, package))); + local_fileoverride_changed = TRUE; + } else if (!allow_noent) return glnx_throw (error, "Package/capability '%s' is not currently requested", package); @@ -854,8 +896,11 @@ rpmostree_origin_remove_packages (RpmOstreeOrigin *origin, if (local_changed) update_keyfile_pkgs_from_cache (origin, "packages", "requested-local", origin->cached_local_packages, TRUE); + if (local_fileoverride_changed) + update_keyfile_pkgs_from_cache (origin, "packages", "requested-local-fileoverride", + origin->cached_local_fileoverride_packages, TRUE); - set_changed (out_changed, changed || local_changed); + set_changed (out_changed, changed || local_changed || local_fileoverride_changed); return TRUE; } @@ -912,6 +957,7 @@ rpmostree_origin_remove_all_packages (RpmOstreeOrigin *origin, { gboolean changed = FALSE; gboolean local_changed = FALSE; + gboolean local_fileoverride_changed = FALSE; gboolean modules_enable_changed = FALSE; gboolean modules_install_changed = FALSE; @@ -927,6 +973,12 @@ rpmostree_origin_remove_all_packages (RpmOstreeOrigin *origin, local_changed = TRUE; } + if (g_hash_table_size (origin->cached_local_fileoverride_packages) > 0) + { + g_hash_table_remove_all (origin->cached_local_fileoverride_packages); + local_fileoverride_changed = TRUE; + } + if (g_hash_table_size (origin->cached_modules_enable) > 0) { g_hash_table_remove_all (origin->cached_modules_enable); @@ -945,6 +997,9 @@ rpmostree_origin_remove_all_packages (RpmOstreeOrigin *origin, if (local_changed) update_keyfile_pkgs_from_cache (origin, "packages", "requested-local", origin->cached_local_packages, TRUE); + if (local_fileoverride_changed) + update_keyfile_pkgs_from_cache (origin, "packages", "requested-local-fileoverride", + origin->cached_local_fileoverride_packages, TRUE); if (modules_enable_changed) update_keyfile_pkgs_from_cache (origin, "modules", "enable", origin->cached_modules_enable, FALSE); @@ -952,7 +1007,7 @@ rpmostree_origin_remove_all_packages (RpmOstreeOrigin *origin, update_keyfile_pkgs_from_cache (origin, "modules", "install", origin->cached_modules_install, FALSE); - changed = changed || local_changed || modules_enable_changed || modules_install_changed; + changed = changed || local_changed || local_fileoverride_changed || modules_enable_changed || modules_install_changed; set_changed (out_changed, changed); return TRUE; } diff --git a/src/libpriv/rpmostree-origin.h b/src/libpriv/rpmostree-origin.h index b7325bb261..42575400da 100644 --- a/src/libpriv/rpmostree-origin.h +++ b/src/libpriv/rpmostree-origin.h @@ -89,6 +89,9 @@ rpmostree_origin_get_modules_install (RpmOstreeOrigin *origin); GHashTable * rpmostree_origin_get_local_packages (RpmOstreeOrigin *origin); +GHashTable * +rpmostree_origin_get_local_fileoverride_packages (RpmOstreeOrigin *origin); + GHashTable * rpmostree_origin_get_overrides_remove (RpmOstreeOrigin *origin); @@ -172,6 +175,7 @@ gboolean rpmostree_origin_add_packages (RpmOstreeOrigin *origin, char **packages, gboolean local, + gboolean fileoverride, gboolean allow_existing, gboolean *out_changed, GError **error); diff --git a/tests/vmcheck/test-layering-basic-1.sh b/tests/vmcheck/test-layering-basic-1.sh index 8de2feff22..29a9cf19bb 100755 --- a/tests/vmcheck/test-layering-basic-1.sh +++ b/tests/vmcheck/test-layering-basic-1.sh @@ -230,6 +230,25 @@ assert_file_has_content err.txt "foo-1.0-1.x86_64" assert_file_has_content err.txt "foo-2.0-1.x86_64" echo "ok can't layer pkg that would upgrade base pkg" +vm_build_rpm baz install "echo baz > %{buildroot}/usr/bin/foo" files "/usr/bin/foo" +if vm_rpmostree install baz &> err.txt; then + assert_no_reached "successfully layered pkg with conflicting file?" +fi +assert_file_has_content err.txt "File exists" +echo "ok can't layer pkg that would replace base files" + +vm_rpmostree install --force-replacefiles \ + /var/tmp/vmcheck/yumrepo/packages/x86_64/baz-1.0-1.x86_64.rpm +echo "ok can layer pkg that would replace base files with --force-replacefiles" + +vm_rpmostree uninstall baz +echo "ok can uninstall forced pkg by name" + +vm_rpmostree install --force-replacefiles \ + /var/tmp/vmcheck/yumrepo/packages/x86_64/baz-1.0-1.x86_64.rpm +vm_rpmostree uninstall baz-1.0-1.x86_64 +echo "ok can uninstall forced pkg by nevra" + # check that we can select a repo split pkg which matches the base version vm_build_rpm foo version 1.0 vm_build_rpm foo-ext version 1.0 requires "foo = 1.0-1"