From ead1ecdd23cc0e596a87113679a43ed7a96183aa Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Fri, 20 May 2016 14:21:06 -0400 Subject: [PATCH] package layering: major rework - Move the package layering logic away from pkg-add and into the upgrader - Add pkg-delete - Add dry-run option Closes: #289 Approved by: cgwalters --- Makefile-daemon.am | 1 + Makefile-rpm-ostree.am | 1 + TODO | 14 + libglnx | 2 +- src/app/main.c | 1 + src/app/rpmostree-builtin-pkgadd.c | 9 +- src/app/rpmostree-builtin-pkgdelete.c | 128 +++ src/app/rpmostree-builtins.h | 1 + src/daemon/org.projectatomic.rpmostree1.xml | 6 + src/daemon/rpmostree-sysroot-upgrader.c | 941 +++++++++++++++++- src/daemon/rpmostree-sysroot-upgrader.h | 36 +- src/daemon/rpmostreed-os.c | 84 +- src/daemon/rpmostreed-transaction-pkg-add.c | 401 +------- .../rpmostreed-transaction-pkg-delete.c | 159 +++ src/daemon/rpmostreed-transaction-types.h | 11 + src/libpriv/rpmostree-postprocess.c | 2 +- src/libpriv/rpmostree-postprocess.h | 7 + src/libpriv/rpmostree-rpm-util.c | 17 +- src/libpriv/rpmostree-rpm-util.h | 1 + src/libpriv/rpmostree-util.c | 72 +- src/libpriv/rpmostree-util.h | 5 + 21 files changed, 1452 insertions(+), 447 deletions(-) create mode 100644 src/app/rpmostree-builtin-pkgdelete.c create mode 100644 src/daemon/rpmostreed-transaction-pkg-delete.c diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 2b9df943..7cac368f 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -42,6 +42,7 @@ librpmostreed_la_SOURCES = \ src/daemon/rpmostreed-transaction-types.h \ src/daemon/rpmostreed-transaction-types.c \ src/daemon/rpmostreed-transaction-pkg-add.c \ + src/daemon/rpmostreed-transaction-pkg-delete.c \ src/daemon/rpmostree-package-variants.h \ src/daemon/rpmostree-package-variants.c \ src/daemon/rpmostreed-os.h \ diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index 0a700250..7ec9684b 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -28,6 +28,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-deploy.c \ src/app/rpmostree-builtin-rebase.c \ src/app/rpmostree-builtin-pkgadd.c \ + src/app/rpmostree-builtin-pkgdelete.c \ src/app/rpmostree-builtin-status.c \ src/app/rpmostree-builtin-internals.c \ src/app/rpmostree-builtin-container.c \ diff --git a/TODO b/TODO index 34e42e43..b067996e 100644 --- a/TODO +++ b/TODO @@ -25,3 +25,17 @@ Autobuilder * Write an intelligent scheduler - Task with same name of newer version wait until old one is done - Walk infinite test matrix + +Package layering +---------------- + +* Provide a mechanism for updating packages (& pruning older versions) +* Support pkgs which bring their own pps + https://github.com/projectatomic/rpm-ostree/pull/107#issuecomment-205082381 +* Support local RPMs installation (though `ostree admin unlock` makes this + easier now, but it's not carried over) +* Add a way to mark commits as valid for multiple policies rather than creating + a new commit everytime we relabel +* Related to the above: store the header metadata in the tree itself rather than + in the commit to avoid duplication across relabeling commits +* Add a --onto option? diff --git a/libglnx b/libglnx index 08ae6639..40ef5f74 160000 --- a/libglnx +++ b/libglnx @@ -1 +1 @@ -Subproject commit 08ae6639e522e9b11765245fbecdbbe474ccde98 +Subproject commit 40ef5f7400d4f8eed6a8f834917008b33ad4fb4e diff --git a/src/app/main.c b/src/app/main.c index 03272cea..60e2c1a2 100644 --- a/src/app/main.c +++ b/src/app/main.c @@ -40,6 +40,7 @@ static RpmOstreeCommand commands[] = { { "db", rpmostree_builtin_db }, { "deploy", rpmostree_builtin_deploy }, { "pkg-add", rpmostree_builtin_pkg_add }, + { "pkg-delete", rpmostree_builtin_pkg_delete }, { "rebase", rpmostree_builtin_rebase }, { "rollback", rpmostree_builtin_rollback }, { "status", rpmostree_builtin_status }, diff --git a/src/app/rpmostree-builtin-pkgadd.c b/src/app/rpmostree-builtin-pkgadd.c index bd1e62c6..b5f556ea 100644 --- a/src/app/rpmostree-builtin-pkgadd.c +++ b/src/app/rpmostree-builtin-pkgadd.c @@ -29,10 +29,12 @@ static char *opt_osname; static gboolean opt_reboot; +static gboolean opt_dry_run; static GOptionEntry option_entries[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after upgrade is prepared", NULL }, + { "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Exit after printing the transaction", NULL }, { NULL } }; @@ -42,6 +44,7 @@ get_args_variant (void) GVariantDict dict; g_variant_dict_init (&dict, NULL); g_variant_dict_insert (&dict, "reboot", "b", opt_reboot); + g_variant_dict_insert (&dict, "dry-run", "b", opt_dry_run); return g_variant_dict_end (&dict); } @@ -99,7 +102,11 @@ rpmostree_builtin_pkg_add (int argc, error)) goto out; - if (!opt_reboot) + if (opt_dry_run) + { + g_print ("Exiting because of '--dry-run' option\n"); + } + else if (!opt_reboot) { const char *sysroot_path; diff --git a/src/app/rpmostree-builtin-pkgdelete.c b/src/app/rpmostree-builtin-pkgdelete.c new file mode 100644 index 00000000..d3d8d35a --- /dev/null +++ b/src/app/rpmostree-builtin-pkgdelete.c @@ -0,0 +1,128 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2015 Colin Walters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "rpmostree-builtins.h" +#include "rpmostree-libbuiltin.h" +#include "rpmostree-rpm-util.h" +#include "rpmostree-dbus-helpers.h" + +#include + +static char *opt_osname; +static gboolean opt_reboot; +static gboolean opt_dry_run; + +static GOptionEntry option_entries[] = { + { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" }, + { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after upgrade is prepared", NULL }, + { "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Exit after printing the transaction", NULL }, + { NULL } +}; + +static GVariant * +get_args_variant (void) +{ + GVariantDict dict; + g_variant_dict_init (&dict, NULL); + g_variant_dict_insert (&dict, "reboot", "b", opt_reboot); + g_variant_dict_insert (&dict, "dry-run", "b", opt_dry_run); + return g_variant_dict_end (&dict); +} + +int +rpmostree_builtin_pkg_delete (int argc, + char **argv, + GCancellable *cancellable, + GError **error) +{ + int exit_status = EXIT_FAILURE; + GOptionContext *context; + glnx_unref_object RPMOSTreeOS *os_proxy = NULL; + glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; + g_autoptr(GVariant) default_deployment = NULL; + g_autofree char *transaction_address = NULL; + int i; + g_autoptr(GPtrArray) argv_and_null = g_ptr_array_new (); + + context = g_option_context_new ("PACKAGE [PACKAGE...] - Remove previously layered RPM packages"); + + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + RPM_OSTREE_BUILTIN_FLAG_NONE, + cancellable, + &sysroot_proxy, + error)) + goto out; + + if (argc < 2) + { + rpmostree_usage_error (context, "At least one PACKAGE must be specified", error); + goto out; + } + + for (i = 1; i < argc; i++) + g_ptr_array_add (argv_and_null, argv[i]); + g_ptr_array_add (argv_and_null, NULL); + + if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname, + cancellable, &os_proxy, error)) + goto out; + + if (!rpmostree_os_call_pkg_delete_sync (os_proxy, + get_args_variant (), + (const char * const*)argv_and_null->pdata, + &transaction_address, + cancellable, + error)) + goto out; + + if (!rpmostree_transaction_get_response_sync (sysroot_proxy, + transaction_address, + cancellable, + error)) + goto out; + + if (opt_dry_run) + { + g_print ("Exiting because of '--dry-run' option\n"); + } + else if (!opt_reboot) + { + const char *sysroot_path = rpmostree_sysroot_get_path (sysroot_proxy); + + if (!rpmostree_print_treepkg_diff_from_sysroot_path (sysroot_path, + cancellable, + error)) + goto out; + + g_print ("Run \"systemctl reboot\" to start a reboot\n"); + } + + exit_status = EXIT_SUCCESS; + +out: + /* Does nothing if using the message bus. */ + rpmostree_cleanup_peer (); + + return exit_status; +} diff --git a/src/app/rpmostree-builtins.h b/src/app/rpmostree-builtins.h index dd3347bc..6757dcfa 100644 --- a/src/app/rpmostree-builtins.h +++ b/src/app/rpmostree-builtins.h @@ -51,6 +51,7 @@ BUILTINPROTO(db); BUILTINPROTO(internals); BUILTINPROTO(container); BUILTINPROTO(pkg_add); +BUILTINPROTO(pkg_delete); #undef BUILTINPROTO diff --git a/src/daemon/org.projectatomic.rpmostree1.xml b/src/daemon/org.projectatomic.rpmostree1.xml index 43d6d476..cba7415e 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -178,6 +178,12 @@ + + + + + + diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index 202c2714..d9ac4e76 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -25,6 +25,12 @@ #include "rpmostree-util.h" #include "rpmostree-sysroot-upgrader.h" +#include "rpmostree-core.h" +#include "rpmostree-rpm-util.h" +#include "rpmostree-postprocess.h" +#include "rpmostree-output.h" + +#include "ostree-repo.h" /** * SECTION:rpmostree-sysroot-upgrader @@ -51,10 +57,12 @@ struct RpmOstreeSysrootUpgrader { GKeyFile *origin; char *origin_refspec; char **requested_packages; + GHashTable *packages_to_add; + GHashTable *packages_to_delete; char *override_csum; char *new_revision; -}; +}; enum { PROP_0, @@ -90,9 +98,18 @@ parse_refspec (RpmOstreeSysrootUpgrader *self, } } - if (!_rpmostree_util_parse_origin (self->origin, &self->origin_refspec, &self->requested_packages, error)) + g_clear_pointer (&self->requested_packages, g_strfreev); + g_clear_pointer (&self->origin_refspec, g_free); + + if (!_rpmostree_util_parse_origin (self->origin, &self->origin_refspec, + &self->requested_packages, error)) goto out; + /* it's just easier to make it a proper empty list than to check for NULL + * everytime */ + if (self->requested_packages == NULL) + self->requested_packages = g_new0 (gchar *, 1); + csum = g_key_file_get_string (self->origin, "origin", "override-commit", NULL); if (csum != NULL && !ostree_validate_checksum_string (csum, error)) goto out; @@ -140,7 +157,18 @@ rpmostree_sysroot_upgrader_initable_init (GInitable *initable, goto out; } - self->origin = ostree_deployment_get_origin (self->merge_deployment); + if (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY) + self->new_revision = + g_strdup (ostree_deployment_get_csum (self->merge_deployment)); + + self->origin = NULL; + { + GKeyFile *original_origin = /* I just had to use that name */ + ostree_deployment_get_origin (self->merge_deployment); + if (original_origin) + self->origin = _rpmostree_util_keyfile_clone (original_origin); + } + if (!self->origin) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -151,6 +179,11 @@ rpmostree_sysroot_upgrader_initable_init (GInitable *initable, } g_key_file_ref (self->origin); + self->packages_to_add = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + self->packages_to_delete = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + if (!parse_refspec (self, cancellable, error)) goto out; @@ -178,7 +211,10 @@ rpmostree_sysroot_upgrader_finalize (GObject *object) g_key_file_unref (self->origin); g_free (self->origin_refspec); g_strfreev (self->requested_packages); + g_hash_table_unref (self->packages_to_add); + g_hash_table_unref (self->packages_to_delete); g_free (self->override_csum); + g_free (self->new_revision); G_OBJECT_CLASS (rpmostree_sysroot_upgrader_parent_class)->finalize (object); } @@ -366,16 +402,48 @@ rpmostree_sysroot_upgrader_set_origin (RpmOstreeSysrootUpgrader *self, return ret; } -gboolean -rpmostree_sysroot_upgrader_set_origin_rebase (RpmOstreeSysrootUpgrader *self, const char *new_refspec, GError **error) +/* updates an origin's refspec without migrating format */ +static gboolean +origin_set_refspec (GKeyFile *origin, + const char *new_refspec, + GError **error) { - g_free (self->origin_refspec); - self->origin_refspec = g_strdup (new_refspec); + if (g_key_file_has_key (origin, "origin", "baserefspec", error)) + { + g_key_file_set_value (origin, "origin", "baserefspec", new_refspec); + return TRUE; + } + + if (error && *error) + return FALSE; + + g_key_file_set_value (origin, "origin", "refspec", new_refspec); + return TRUE; +} + +gboolean +rpmostree_sysroot_upgrader_set_origin_rebase (RpmOstreeSysrootUpgrader *self, + const char *new_refspec, + GError **error) +{ + g_autoptr(GKeyFile) new_origin = rpmostree_sysroot_upgrader_dup_origin (self); + + if (!origin_set_refspec (new_origin, new_refspec, error)) + return FALSE; + + g_clear_pointer (&self->origin, g_key_file_unref); + self->origin = g_key_file_ref (new_origin); + + /* this will update self->origin_refspec */ + if (!parse_refspec (self, NULL, error)) + return FALSE; + return TRUE; } void -rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, const char *override_commit) +rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, + const char *override_commit) { if (override_commit != NULL) g_key_file_set_string (self->origin, "origin", "override-commit", override_commit); @@ -383,12 +451,6 @@ rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, g_key_file_remove_key (self->origin, "origin", "override_commit", NULL); } -void -rpmostree_sysroot_upgrader_set_origin_baseref_local (RpmOstreeSysrootUpgrader *self, const char *local_commit) -{ - self->new_revision = g_strdup (local_commit); -} - const char * rpmostree_sysroot_upgrader_get_refspec (RpmOstreeSysrootUpgrader *self) { @@ -401,6 +463,12 @@ rpmostree_sysroot_upgrader_get_packages (RpmOstreeSysrootUpgrader *self) return (const char * const *)self->requested_packages; } +OstreeDeployment* +rpmostree_sysroot_upgrader_get_merge_deployment (RpmOstreeSysrootUpgrader *self) +{ + return self->merge_deployment; +} + /** * rpmostree_sysroot_upgrader_get_origin_description: * @self: Upgrader @@ -413,9 +481,101 @@ rpmostree_sysroot_upgrader_get_origin_description (RpmOstreeSysrootUpgrader *sel return g_strdup (rpmostree_sysroot_upgrader_get_refspec (self)); } +static GHashTable* +hashset_from_strv (char **strv) +{ + GHashTable *ht = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + for (char **it = strv; it && *it; it++) + g_hash_table_add (ht, g_strdup (*it)); + return ht; +} + +/** + * rpmostree_sysroot_upgrader_add_packages: + * @self: Self + * @packages: Packages to add + * @cancellable: Cancellable + * @error: Error + * + * Check that the @packages are not already requested and mark them for overlay. + * */ +gboolean +rpmostree_sysroot_upgrader_add_packages (RpmOstreeSysrootUpgrader *self, + char **packages, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(GHashTable) requested_packages = + hashset_from_strv (self->requested_packages); + + for (char **it = packages; it && *it; it++) + { + if (g_hash_table_contains (requested_packages, *it)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Package '%s' is already requested", *it); + goto out; + } + g_hash_table_add (self->packages_to_add, g_strdup (*it)); + } + + ret = TRUE; +out: + return ret; +} + +/** + * rpmostree_sysroot_upgrader_delete_packages: + * @self: Self + * @packages: Packages to delete + * @cancellable: Cancellable + * @error: Error + * + * Check that the @packages were requested and remove them from overlay. + */ +gboolean +rpmostree_sysroot_upgrader_delete_packages (RpmOstreeSysrootUpgrader *self, + char **packages, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(GHashTable) requested_packages = + hashset_from_strv (self->requested_packages); + + for (char **it = packages; it && *it; it++) + { + if (!g_hash_table_contains (requested_packages, *it)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Package '%s' is not currently requested", *it); + goto out; + } + g_hash_table_add (self->packages_to_delete, g_strdup (*it)); + } + + ret = TRUE; +out: + return ret; +} + +static gboolean +commit_get_parent_csum (OstreeRepo *repo, + const char *child, + char **out_csum, + GError **error) +{ + g_autoptr(GVariant) commit = NULL; + if (!ostree_repo_load_commit (repo, child, &commit, NULL, error)) + return FALSE; + *out_csum = ostree_commit_get_parent (commit); + return TRUE; +} + /* - * Like ostree_sysroot_upgrader_pull(), but will modify to include - * layered packages. + * Like ostree_sysroot_upgrader_pull(), but modified to handle layered packages. */ gboolean rpmostree_sysroot_upgrader_pull (RpmOstreeSysrootUpgrader *self, @@ -482,47 +642,733 @@ rpmostree_sysroot_upgrader_pull (RpmOstreeSysrootUpgrader *self, } - if (g_strcmp0 (from_revision, self->new_revision) == 0) - { - *out_changed = FALSE; - } - else - { - gboolean allow_older = (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER) > 0; + { + gboolean allow_older = + (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER) > 0; + const char *compare_rev = from_revision; + g_autofree char *base_rev = NULL; + gboolean changed = FALSE; - *out_changed = TRUE; + if (from_revision) + { + /* if there are pkgs layered on the from rev, then we should compare + * the parent instead, which is the 'base' layer */ + if (g_strv_length (self->requested_packages) > 0) + { + if (!commit_get_parent_csum (repo, from_revision, &base_rev, error)) + goto out; + g_assert (base_rev); + compare_rev = base_rev; + } + } - if (from_revision && !allow_older) - { - if (!ostree_sysroot_upgrader_check_timestamps (repo, from_revision, - self->new_revision, - error)) - goto out; - } - } + if (g_strcmp0 (compare_rev, self->new_revision) != 0) + changed = TRUE; + + if (changed && compare_rev && !allow_older) + if (!ostree_sysroot_upgrader_check_timestamps (repo, compare_rev, + self->new_revision, + error)) + goto out; + + *out_changed = changed; + } ret = TRUE; out: return ret; } +/* update the origin with the new packages */ +static gboolean +update_requested_packages (RpmOstreeSysrootUpgrader *self, + GHashTable *pkgset, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + glnx_free char **pkgv = + (char**) g_hash_table_get_keys_as_array (pkgset, NULL); + + g_key_file_set_string_list (self->origin, "packages", "requested", + (const char* const*) pkgv, g_strv_length(pkgv)); + + /* migrate to baserefspec model if necessary */ + g_key_file_set_value (self->origin, "origin", "baserefspec", + self->origin_refspec); + if (!g_key_file_remove_key (self->origin, "origin", "refspec", error)) + { + if (g_error_matches (*error, G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_KEY_NOT_FOUND)) + g_clear_error (error); + else + goto out; + } + + /* reread spec file --> this will update the current requested_packages */ + if (!parse_refspec (self, cancellable, error)) + goto out; + + ret = TRUE; +out: + return ret; +} + +static gboolean +checkout_min_tree_in_tmp (OstreeRepo *repo, + const char *revision, + char **out_tmprootfs, + int *out_tmprootfs_dfd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *tmprootfs = NULL; + glnx_fd_close int tmprootfs_dfd = -1; + int repo_dfd = ostree_repo_get_dfd (repo); /* borrowed */ + + { + g_autofree char *template = NULL; + template = glnx_fdrel_abspath (repo_dfd, "tmp/rpmostree-commit-XXXXXX"); + + if (!rpmostree_checkout_only_rpmdb_tempdir (repo, revision, template, + &tmprootfs, &tmprootfs_dfd, + cancellable, error)) + goto out; + } + + /* also check out sepolicy so that prepare_install() will be able to sort the + * packages correctly */ + { + OstreeRepoCheckoutOptions opts = {0,}; + + if (!glnx_shutil_mkdir_p_at (tmprootfs_dfd, "usr/etc", 0777, + cancellable, error)) + goto out; + + opts.subpath = "usr/etc/selinux"; + opts.disable_fsync = TRUE; + + if (!ostree_repo_checkout_tree_at (repo, &opts, tmprootfs_dfd, + "usr/etc/selinux", revision, + cancellable, error)) + goto out; + } + + if (out_tmprootfs != NULL) + *out_tmprootfs = g_steal_pointer (&tmprootfs); + + if (out_tmprootfs_dfd != NULL) + *out_tmprootfs_dfd = glnx_steal_fd (&tmprootfs_dfd); + + ret = TRUE; +out: + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (AT_FDCWD, tmprootfs, cancellable, NULL); + return ret; +} + +static gboolean +checkout_tree_in_tmp (OstreeRepo *repo, + const char *revision, + OstreeRepoDevInoCache *devino_cache, + char **out_tmprootfs, + int *out_tmprootfs_dfd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + OstreeRepoCheckoutOptions checkout_options = { 0, }; + + g_autofree char *tmprootfs = g_strdup ("tmp/rpmostree-commit-XXXXXX"); + glnx_fd_close int tmprootfs_dfd = -1; + + int repo_dfd = ostree_repo_get_dfd (repo); /* borrowed */ + + if (!glnx_mkdtempat (repo_dfd, tmprootfs, 00755, error)) + goto out; + + if (!glnx_opendirat (repo_dfd, tmprootfs, FALSE, &tmprootfs_dfd, error)) + goto out; + + /* let's give the user some feedback so they don't think we're blocked */ + rpmostree_output_task_begin ("Checking out tree %.7s", revision); + + /* we actually only need this here because we use "." for path */ + checkout_options.disable_fsync = TRUE; + checkout_options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; + checkout_options.devino_to_csum_cache = devino_cache; + if (!ostree_repo_checkout_tree_at (repo, &checkout_options, tmprootfs_dfd, + ".", revision, cancellable, error)) + goto out; + + rpmostree_output_task_end ("done"); + + if (out_tmprootfs != NULL) + *out_tmprootfs = glnx_fdrel_abspath (repo_dfd, tmprootfs); + + if (out_tmprootfs_dfd != NULL) + *out_tmprootfs_dfd = glnx_steal_fd (&tmprootfs_dfd); + + ret = TRUE; +out: + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (AT_FDCWD, tmprootfs, cancellable, NULL); + return ret; +} + +/* XXX: This is ugly, but the alternative is to de-couple RpmOstreeTreespec from + * RpmOstreeContext, which also use it for hashing and store it directly in + * assembled commit metadata. Probably assemble_commit() should live somewhere + * else, maybe directly in `container-builtins.c`. */ +static RpmOstreeTreespec * +generate_treespec (GHashTable *packages) +{ + g_autoptr(RpmOstreeTreespec) ret = NULL; + g_autoptr(GError) tmp_error = NULL; + g_autoptr(GKeyFile) treespec = g_key_file_new (); + glnx_free char **pkgv = /* NB: don't use g_strv_free() -- the keys belong to the table */ + (char**) g_hash_table_get_keys_as_array (packages, NULL); + + g_key_file_set_string_list (treespec, "tree", "packages", + (const char* const*) pkgv, g_strv_length(pkgv)); + + ret = rpmostree_treespec_new_from_keyfile (treespec, &tmp_error); + g_assert_no_error (tmp_error); + + return g_steal_pointer (&ret); +} + +/* Given a rootfs containing an rpmdb and a list of packages, calls back for + * each pkg given found in the db. */ +/* XXX: move to a utility file? */ +static gboolean +find_pkgs_in_rpmdb (int rootfs_dfd, GHashTable *pkgs, + gboolean (*callback) (GHashTableIter *it, + const char *pkg, + GError **error, + gpointer opaque), + GCancellable *cancellable, + gpointer opaque, + GError **error) +{ + gboolean ret = FALSE; + GHashTableIter it; + gpointer itkey; + g_autoptr(RpmOstreeRefSack) rsack = NULL; + + rsack = rpmostree_get_refsack_for_root (rootfs_dfd, ".", cancellable, error); + if (rsack == NULL) + goto out; + + /* search for each package */ + g_hash_table_iter_init (&it, pkgs); + while (g_hash_table_iter_next (&it, &itkey, NULL)) + { + g_autoptr(GPtrArray) pkglist = NULL; + HyQuery query = hy_query_create (rsack->sack); + hy_query_filter (query, HY_PKG_REPONAME, HY_EQ, HY_SYSTEM_REPO_NAME); + hy_query_filter (query, HY_PKG_NAME, HY_EQ, itkey); + pkglist = hy_query_run (query); + + /* did we find the package? */ + if (pkglist->len != 0) + if (!callback (&it, itkey, error, opaque)) + goto out; + } + + ret = TRUE; +out: + return ret; +} + +static gboolean +pkg_find_cb (GHashTableIter *it, + const char *pkg, + GError **error, + gpointer opaque) +{ + RpmOstreeSysrootUpgrader *self = RPMOSTREE_SYSROOT_UPGRADER (opaque); + + /* did the user explicitly request this package during this session? */ + if (g_hash_table_contains (self->packages_to_add, pkg)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "package '%s' is already in the deployment", pkg); + return FALSE; + } + + g_print ("Note: package '%s' is already in the deployment; " + "it will no longer be layered.\n", pkg); + + g_hash_table_iter_remove (it); + + return TRUE; +} + +/* Do a partial checkout of the rpmdb, and given requested_packages and + * packages_to_add and packages_to_delete, update requested_packages to reflect + * the final set of packages to actually overlay. */ +static gboolean +finalize_requested_packages (RpmOstreeSysrootUpgrader *self, + OstreeRepo *repo, + const char *base_rev, + int tmprootfs_dfd, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(GHashTable) pkgset = hashset_from_strv (self->requested_packages); + + /* remove packages_to_delete from the set */ + { GHashTableIter it; + gpointer itkey; + g_hash_table_iter_init (&it, self->packages_to_delete); + while (g_hash_table_iter_next (&it, &itkey, NULL)) + g_hash_table_remove (pkgset, g_strdup (itkey)); + } + + /* add packages_to_add to the set */ + { GHashTableIter it; + gpointer itkey; + g_hash_table_iter_init (&it, self->packages_to_add); + while (g_hash_table_iter_next (&it, &itkey, NULL)) + g_hash_table_add (pkgset, g_strdup (itkey)); + } + + if (!find_pkgs_in_rpmdb (tmprootfs_dfd, pkgset, pkg_find_cb, + cancellable, self, error)) + goto out; + + if (!update_requested_packages (self, pkgset, cancellable, error)) + goto out; + + ret = TRUE; +out: + return ret; +} + +static gboolean +get_pkgcache_repo (OstreeRepo *parent, + OstreeRepo **out_pkgcache, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + glnx_unref_object OstreeRepo *pkgcache = NULL; + g_autoptr(GFile) pkgcache_path = NULL; + + /* get the GFile to it */ + { + int parent_dfd = ostree_repo_get_dfd (parent); /* borrowed */ + g_autofree char *pkgcache_path_s = + glnx_fdrel_abspath (parent_dfd, "extensions/rpmostree/pkgcache"); + pkgcache_path = g_file_new_for_path (pkgcache_path_s); + } + + pkgcache = ostree_repo_new (pkgcache_path); + + if (!g_file_query_exists (pkgcache_path, cancellable)) + { + g_autoptr(GKeyFile) config = NULL; + GFile *parent_path = ostree_repo_get_path (parent); + + if (!g_file_make_directory_with_parents (pkgcache_path, + cancellable, error)) + goto out; + + if (!ostree_repo_create (pkgcache, OSTREE_REPO_MODE_BARE, + cancellable, error)) + goto out; + + config = ostree_repo_copy_config (pkgcache); + g_key_file_set_string (config, "core", "parent", + gs_file_get_path_cached (parent_path)); + ostree_repo_write_config (pkgcache, config, error); + + /* yuck... ostree already opened the repo when we did create, but that was + * before we had the parent repo set in its config. there's no way to + * "reload" the config, so let's just tear down and recreate for now */ + g_clear_object (&pkgcache); + pkgcache = ostree_repo_new (pkgcache_path); + } + + if (!ostree_repo_open (pkgcache, cancellable, error)) + goto out; + + *out_pkgcache = g_steal_pointer (&pkgcache); + + ret = TRUE; +out: + return ret; +} + +static gboolean +final_assembly (RpmOstreeSysrootUpgrader *self, + RpmOstreeContext *ctx, + OstreeRepo *repo, + const char *base_rev, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *tmprootfs = NULL; + glnx_fd_close int tmprootfs_dfd = -1; + OstreeRepoDevInoCache *devino_cache = ostree_repo_devino_cache_new (); + + /* Now, we create a new tmprootfs containing the *full* tree. Yes, we're + * wasting an already partially checked out tmprootfs, but some of that stuff + * is checked out in user mode, plus the dir perms are all made up. */ + if (!checkout_tree_in_tmp (repo, base_rev, devino_cache, &tmprootfs, + &tmprootfs_dfd, cancellable, error)) + goto out; + + /* --- Overlay and commit --- */ + if (!rpmostree_context_assemble_commit (ctx, tmprootfs_dfd, devino_cache, + base_rev, &self->new_revision, + cancellable, error)) + goto out; + + ret = TRUE; +out: + if (devino_cache) + ostree_repo_devino_cache_unref (devino_cache); + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (AT_FDCWD, tmprootfs, cancellable, NULL); + return ret; +} + +static gboolean +overlay_final_pkgset (RpmOstreeSysrootUpgrader *self, + const char *tmprootfs, + int tmprootfs_dfd, + OstreeRepo *repo, + const char *base_rev, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(RpmOstreeContext) ctx = NULL; + g_autoptr(RpmOstreeTreespec) treespec = NULL; + g_autoptr(RpmOstreeInstall) install = {0,}; + g_autoptr(GHashTable) pkgset = hashset_from_strv (self->requested_packages); + glnx_unref_object OstreeRepo *pkgcache_repo = NULL; + + { int n = g_hash_table_size (pkgset); + GHashTableIter it; + gpointer itkey; + + g_assert (n > 0); + g_print ("Need to overlay %d package%s onto tree %.7s:\n", + n, n > 1 ? "s" : "", base_rev); + + g_hash_table_iter_init (&it, pkgset); + while (g_hash_table_iter_next (&it, &itkey, NULL)) + g_print (" %s\n", (char*)itkey); + } + + ctx = rpmostree_context_new_system (cancellable, error); + + /* point libhif to the yum.repos.d and os-release of the merge_deployment */ + { HifContext *hifctx = rpmostree_context_get_hif (ctx); + g_autofree char *sysroot_path = + g_file_get_path (ostree_sysroot_get_path (self->sysroot)); + g_autofree char *merge_deployment_dirpath = + ostree_sysroot_get_deployment_dirpath (self->sysroot, + self->merge_deployment); + g_autofree char *merge_deployment_root = + g_build_filename (sysroot_path, merge_deployment_dirpath, NULL); + g_autofree char *reposdir = g_build_filename (merge_deployment_root, + "etc/yum.repos.d", NULL); + hif_context_set_repo_dir (hifctx, reposdir); + hif_context_set_source_root (hifctx, merge_deployment_root); + } + + /* load the sepolicy to use during import */ + { + glnx_unref_object OstreeSePolicy *sepolicy = NULL; + if (!rpmostree_prepare_rootfs_get_sepolicy (tmprootfs_dfd, ".", &sepolicy, + cancellable, error)) + goto out; + + rpmostree_context_set_sepolicy (ctx, sepolicy); + } + + /* NB: We're pretty much using the defaults for the other treespec values like + * instlang and docs since it would be hard to expose the cli for them because + * they wouldn't affect just the new pkgs, but even previously added ones. */ + treespec = generate_treespec (pkgset); + if (treespec == NULL) + goto out; + + if (!rpmostree_context_setup (ctx, tmprootfs, NULL, treespec, + cancellable, error)) + goto out; + + if (!get_pkgcache_repo (repo, &pkgcache_repo, cancellable, error)) + goto out; + + rpmostree_context_set_repo (ctx, pkgcache_repo); + + /* --- Downloading metadata --- */ + if (!rpmostree_context_download_metadata (ctx, cancellable, error)) + goto out; + + /* --- Resolving dependencies --- */ + if (!rpmostree_context_prepare_install (ctx, &install, cancellable, error)) + goto out; + + /* we just printed the transaction in prepare_install() -- leave here if it's + * a dry run */ + if (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN) + { + ret = TRUE; + goto out; + } + + /* --- Download as necessary --- */ + if (!rpmostree_context_download (ctx, install, cancellable, error)) + goto out; + + /* --- Import as necessary --- */ + if (!rpmostree_context_import (ctx, install, cancellable, error)) + goto out; + + /* --- Relabel as necessary --- */ + if (!rpmostree_context_relabel (ctx, install, cancellable, error)) + goto out; + + /* --- Overlay packages on base layer --- */ + if (!final_assembly (self, ctx, repo, base_rev, cancellable, error)) + goto out; + + /* Send the final commit to the parent repo */ + { + GFile *repo_path = ostree_repo_get_path (pkgcache_repo); + g_autofree char *uri = + g_strdup_printf ("file://%s", gs_file_get_path_cached (repo_path)); + + GVariantBuilder builder; + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{s@v}", "refs", g_variant_new_variant + (g_variant_new_strv ((const char * const*)&self->new_revision, 1))); + + if (!ostree_repo_pull_with_options (repo, uri, + g_variant_builder_end (&builder), + NULL, cancellable, error)) + goto out; + } + + ret = TRUE; +out: + return ret; +} + +static gboolean +overlay_packages (RpmOstreeSysrootUpgrader *self, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + glnx_unref_object OstreeRepo *repo = NULL; + g_autofree char *tmprootfs = NULL; + glnx_fd_close int tmprootfs_dfd = -1; + g_autofree char *base_rev = NULL; + + if (!ostree_sysroot_get_repo (self->sysroot, &repo, cancellable, error)) + goto out; + + /* determine the base commit on which to layer stuff */ + + /* the only case in which new_revision isn't a "base" commit is if we're + * redeploying and there's already stuff layered down, in which case, it's the + * parent commit */ + if ((self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY) && + (g_strv_length (self->requested_packages) > 0)) + { + if (!commit_get_parent_csum (repo, self->new_revision, &base_rev, error)) + goto out; + g_assert (base_rev); + } + else + base_rev = g_strdup (self->new_revision); + + /* create a tmprootfs in which we initially only check out the bare minimum to + * make libhif happy. this allows us to provide more immediate feedback to the + * user if e.g. resolving/downloading/etc... goes wrong without making them + * wait for a full tree checkout first. */ + if (!checkout_min_tree_in_tmp (repo, base_rev, &tmprootfs, &tmprootfs_dfd, + cancellable, error)) + goto out; + + /* check if there are any items in requested_packages or pkgs_to_add that are + * already installed in base_rev */ + if (!finalize_requested_packages (self, repo, base_rev, tmprootfs_dfd, + cancellable, error)) + goto out; + + /* trivial case: no packages to overlay */ + if (g_strv_length (self->requested_packages) == 0) + { + /* XXX: check if there's a function for this */ + g_free (self->new_revision); + self->new_revision = g_steal_pointer (&base_rev); + } + else + { + if (!overlay_final_pkgset (self, tmprootfs, tmprootfs_dfd, repo, base_rev, + cancellable, error)) + goto out; + } + + ret = TRUE; +out: + if (tmprootfs_dfd != -1) + glnx_shutil_rm_rf_at (AT_FDCWD, tmprootfs, cancellable, NULL); + return ret; +} + +/* For each deployment (including the new one yet to be written), if they are + * layered deployments, then create a ref pointing to their bases. This is + * mostly to work around ostree's auto-ref cleanup. Otherwise we might get into + * a situation where after the origin ref is updated, we lose our parent, which + * means that users can no longer add/delete packages on that deployment. (They + * can always just re-pull it, but let's try to be nice). */ +static gboolean +generate_baselayer_refs (RpmOstreeSysrootUpgrader *self, + OstreeDeployment *new_deployment, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + glnx_unref_object OstreeRepo *repo = NULL; + g_autoptr(GHashTable) refs = NULL; + g_autoptr(GHashTable) bases = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (!ostree_sysroot_get_repo (self->sysroot, &repo, cancellable, error)) + goto out; + + if (!ostree_repo_list_refs_ext (repo, "rpmostree/base", &refs, + OSTREE_REPO_LIST_REFS_EXT_NONE, + cancellable, error)) + goto out; + + if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) + goto out; + + /* delete all the refs */ + { + GHashTableIter it; + gpointer key; + + g_hash_table_iter_init (&it, refs); + while (g_hash_table_iter_next (&it, &key, NULL)) + { + const char *ref = key; + ostree_repo_transaction_set_refspec (repo, ref, NULL); + } + } + + /* collect the csums */ + { + guint i = 0; + g_autoptr(GPtrArray) deployments = + ostree_sysroot_get_deployments (self->sysroot); + + /* existing deployments */ + for (; i < deployments->len; i++) + { + OstreeDeployment *deployment = deployments->pdata[i]; + GKeyFile *origin = ostree_deployment_get_origin (deployment); + g_auto(GStrv) packages = NULL; + + if (!_rpmostree_util_parse_origin (origin, NULL, &packages, error)) + goto out; + + if (packages && g_strv_length (packages) > 0) + { + const char *csum = ostree_deployment_get_csum (deployment); + g_autofree char *base_rev = NULL; + + if (!commit_get_parent_csum (repo, csum, &base_rev, error)) + goto out; + g_assert (base_rev); + + g_hash_table_add (bases, g_steal_pointer (&base_rev)); + } + } + + /* our new deployment, in case we're called before it's added */ + if (g_strv_length (self->requested_packages) > 0) + { + g_autofree char *base_rev = NULL; + + if (!commit_get_parent_csum (repo, self->new_revision, &base_rev, error)) + goto out; + g_assert (base_rev); + + g_hash_table_add (bases, g_steal_pointer (&base_rev)); + } + } + + /* create the new refs */ + { + guint i = 0; + GHashTableIter it; + gpointer key; + + g_hash_table_iter_init (&it, bases); + while (g_hash_table_iter_next (&it, &key, NULL)) + { + const char *base = key; + g_autofree char *ref = g_strdup_printf ("rpmostree/base/%u", i++); + ostree_repo_transaction_set_refspec (repo, ref, base); + } + } + + if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) + goto out; + + ret = TRUE; +out: + ostree_repo_abort_transaction (repo, cancellable, NULL); + return ret; +} + /** * rpmostree_sysroot_upgrader_deploy: * @self: Self * @cancellable: Cancellable * @error: Error * - * Write the new deployment to disk, perform a configuration merge - * with /etc, and update the bootloader configuration. + * Write the new deployment to disk, overlay any packages requested, perform a + * configuration merge with /etc, and update the bootloader configuration. */ gboolean -rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, - GCancellable *cancellable, - GError **error) +rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; glnx_unref_object OstreeDeployment *new_deployment = NULL; + /* make sure we have a known target to deploy */ + g_assert (self->new_revision); + + /* any packages requested for overlay? */ + if ((g_strv_length (self->requested_packages) > 0) || + (g_hash_table_size (self->packages_to_add) > 0) || + (g_hash_table_size (self->packages_to_delete) > 0)) + if (!overlay_packages (self, cancellable, error)) + goto out; + + if (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN) + { + ret = TRUE; + goto out; + } + if (!ostree_sysroot_deploy_tree (self->sysroot, self->osname, self->new_revision, self->origin, @@ -532,6 +1378,9 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, cancellable, error)) goto out; + if (!generate_baselayer_refs (self, new_deployment, cancellable, error)) + goto out; + if (!ostree_sysroot_simple_write_deployment (self->sysroot, self->osname, new_deployment, self->merge_deployment, @@ -539,6 +1388,16 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, cancellable, error)) goto out; + /* regenerate the baselayer refs in case we just kicked out an ancient layered + * deployment whose base layer is not needed anymore */ + if (!generate_baselayer_refs (self, new_deployment, cancellable, error)) + goto out; + + /* and shake it loose */ + if (!ostree_sysroot_cleanup (self->sysroot, cancellable, error)) + goto out; + + ret = TRUE; out: return ret; @@ -557,7 +1416,13 @@ rpmostree_sysroot_upgrader_flags_get_type (void) "ignore-unconfigured" }, { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER, "RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER", - "allow-older" } + "allow-older" }, + { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY, + "RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY", + "redeploy" }, + { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN, + "RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN", + "pkgoverlay-dry-run" } }; GType g_define_type_id = g_flags_register_static (g_intern_static_string ("RpmOstreeSysrootUpgraderFlags"), values); diff --git a/src/daemon/rpmostree-sysroot-upgrader.h b/src/daemon/rpmostree-sysroot-upgrader.h index b3bed019..7c29ec2b 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.h +++ b/src/daemon/rpmostree-sysroot-upgrader.h @@ -36,13 +36,18 @@ typedef struct RpmOstreeSysrootUpgrader RpmOstreeSysrootUpgrader; * RpmOstreeSysrootUpgraderFlags: * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_NONE: No options * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED: Do not error if the origin has an unconfigured-state key + * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER: Do not error if the new deployment was composed earlier than the current deployment + * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY: Use the same revision as the current deployment + * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN: If layering packages, only print the transaction * * Flags controlling operation of an #RpmOstreeSysrootUpgrader. */ typedef enum { - RPMOSTREE_SYSROOT_UPGRADER_FLAGS_NONE = (1 << 0), - RPMOSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED = (1 << 1), - RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER = (1 << 2) + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_NONE = (1 << 0), + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_IGNORE_UNCONFIGURED = (1 << 1), + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER = (1 << 2), + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY = (1 << 3), + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN = (1 << 4) } RpmOstreeSysrootUpgraderFlags; GType rpmostree_sysroot_upgrader_get_type (void); @@ -55,6 +60,7 @@ RpmOstreeSysrootUpgrader *rpmostree_sysroot_upgrader_new (OstreeSysroot GCancellable *cancellable, GError **error); +OstreeDeployment* rpmostree_sysroot_upgrader_get_merge_deployment (RpmOstreeSysrootUpgrader *self); const char *rpmostree_sysroot_upgrader_get_refspec (RpmOstreeSysrootUpgrader *self); const char *const*rpmostree_sysroot_upgrader_get_packages (RpmOstreeSysrootUpgrader *self); @@ -65,6 +71,25 @@ GKeyFile *rpmostree_sysroot_upgrader_dup_origin (RpmOstreeSysrootUpgrader *self) gboolean rpmostree_sysroot_upgrader_set_origin (RpmOstreeSysrootUpgrader *self, GKeyFile *origin, GCancellable *cancellable, GError **error); +gboolean rpmostree_sysroot_upgrader_set_origin_rebase (RpmOstreeSysrootUpgrader *self, + const char *new_refspec, + GError **error); + +void rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, + const char *override_commit); + +gboolean +rpmostree_sysroot_upgrader_add_packages (RpmOstreeSysrootUpgrader *self, + char **new_packages, + GCancellable *cancellable, + GError **error); + +gboolean +rpmostree_sysroot_upgrader_delete_packages (RpmOstreeSysrootUpgrader *self, + char **packages, + GCancellable *cancellable, + GError **error); + gboolean rpmostree_sysroot_upgrader_pull (RpmOstreeSysrootUpgrader *self, const char *dir_to_pull, @@ -79,9 +104,4 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, GCancellable *cancellable, GError **error); -gboolean rpmostree_sysroot_upgrader_set_origin_rebase (RpmOstreeSysrootUpgrader *self, const char *new_refspec, GError **error); -void rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, const char *override_commit); -void rpmostree_sysroot_upgrader_set_origin_baseref_local (RpmOstreeSysrootUpgrader *self, const char *local_commit); - - G_END_DECLS diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index 83102155..52c56a5b 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -711,7 +711,6 @@ os_handle_pkg_add (RPMOSTreeOS *interface, GVariant *arg_options, const char * const *arg_packages) { - /* TODO: Totally ignoring arg_packages for now */ RpmostreedOS *self = RPMOSTREED_OS (interface); glnx_unref_object RpmostreedTransaction *transaction = NULL; glnx_unref_object OstreeSysroot *ot_sysroot = NULL; @@ -719,6 +718,7 @@ os_handle_pkg_add (RPMOSTreeOS *interface, GVariantDict options_dict; const char *osname; gboolean opt_reboot = FALSE; + gboolean opt_dry_run = FALSE; GError *local_error = NULL; /* If a compatible transaction is in progress, share its bus address. */ @@ -748,6 +748,9 @@ os_handle_pkg_add (RPMOSTreeOS *interface, g_variant_dict_lookup (&options_dict, "reboot", "b", &opt_reboot); + g_variant_dict_lookup (&options_dict, + "dry-run", "b", + &opt_dry_run); g_variant_dict_clear (&options_dict); transaction = rpmostreed_transaction_new_pkg_add (invocation, @@ -755,6 +758,7 @@ os_handle_pkg_add (RPMOSTreeOS *interface, osname, arg_packages, opt_reboot, + opt_dry_run, cancellable, &local_error); @@ -778,6 +782,83 @@ out: return TRUE; } +static gboolean +os_handle_pkg_delete (RPMOSTreeOS *interface, + GDBusMethodInvocation *invocation, + GVariant *arg_options, + const char * const *arg_packages) +{ + RpmostreedOS *self = RPMOSTREED_OS (interface); + glnx_unref_object RpmostreedTransaction *transaction = NULL; + glnx_unref_object OstreeSysroot *ot_sysroot = NULL; + glnx_unref_object GCancellable *cancellable = NULL; + GVariantDict options_dict; + const char *osname; + gboolean opt_reboot = FALSE; + gboolean opt_dry_run = FALSE; + GError *local_error = NULL; + + /* If a compatible transaction is in progress, share its bus address. */ + transaction = rpmostreed_transaction_monitor_ref_active_transaction (self->transaction_monitor); + if (transaction != NULL) + { + if (rpmostreed_transaction_is_compatible (transaction, invocation)) + goto out; + + g_clear_object (&transaction); + } + + cancellable = g_cancellable_new (); + + if (!rpmostreed_sysroot_load_state (rpmostreed_sysroot_get (), + cancellable, + &ot_sysroot, + NULL, + &local_error)) + goto out; + + osname = rpmostree_os_get_name (interface); + + /* XXX Fail if option type is wrong? */ + + g_variant_dict_init (&options_dict, arg_options); + g_variant_dict_lookup (&options_dict, + "reboot", "b", + &opt_reboot); + g_variant_dict_lookup (&options_dict, + "dry-run", "b", + &opt_dry_run); + g_variant_dict_clear (&options_dict); + + transaction = rpmostreed_transaction_new_pkg_delete (invocation, + ot_sysroot, + osname, + arg_packages, + opt_reboot, + opt_dry_run, + cancellable, + &local_error); + + if (transaction == NULL) + goto out; + + rpmostreed_transaction_monitor_add (self->transaction_monitor, transaction); + +out: + if (local_error != NULL) + { + g_dbus_method_invocation_take_error (invocation, local_error); + } + else + { + const char *client_address; + client_address = rpmostreed_transaction_get_client_address (transaction); + rpmostree_os_complete_pkg_delete (interface, invocation, client_address); + } + + return TRUE; +} + static gboolean os_handle_get_cached_rebase_rpm_diff (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation, @@ -1152,6 +1233,7 @@ rpmostreed_os_iface_init (RPMOSTreeOSIface *iface) iface->handle_clear_rollback_target = os_handle_clear_rollback_target; iface->handle_rebase = os_handle_rebase; iface->handle_pkg_add = os_handle_pkg_add; + iface->handle_pkg_delete = os_handle_pkg_delete; iface->handle_get_cached_rebase_rpm_diff = os_handle_get_cached_rebase_rpm_diff; iface->handle_download_rebase_rpm_diff = os_handle_download_rebase_rpm_diff; iface->handle_get_cached_deploy_rpm_diff = os_handle_get_cached_deploy_rpm_diff; diff --git a/src/daemon/rpmostreed-transaction-pkg-add.c b/src/daemon/rpmostreed-transaction-pkg-add.c index f7b6007d..4b229568 100644 --- a/src/daemon/rpmostreed-transaction-pkg-add.c +++ b/src/daemon/rpmostreed-transaction-pkg-add.c @@ -43,6 +43,7 @@ typedef struct { char *osname; char **packages; gboolean reboot; + gboolean dry_run; } PkgAddTransaction; typedef RpmostreedTransactionClass PkgAddTransactionClass; @@ -65,407 +66,37 @@ pkg_add_transaction_finalize (GObject *object) G_OBJECT_CLASS (pkg_add_transaction_parent_class)->finalize (object); } -static gboolean -copy_dir_contents_nonrecurse_at (int src_dfd, - const char *srcpath, - int dest_dfd, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - g_auto(GLnxDirFdIterator) dfd_iter = { FALSE, }; - struct dirent *dent = NULL; - - if (!glnx_dirfd_iterator_init_at (src_dfd, srcpath, TRUE, - &dfd_iter, error)) - goto out; - - while (glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) - { - if (dent == NULL) - break; - if (!glnx_file_copy_at (dfd_iter.fd, dent->d_name, NULL, dest_dfd, dent->d_name, 0, - cancellable, error)) - { - goto out; - } - } - - ret = TRUE; - out: - return ret; -} - -/* Given a directory referred to by @dfd and @dirpath, ensure that - * physical (or reflink'd) copies of all files are done. - */ -static gboolean -break_hardlinks_at (int dfd, - const char *dirpath, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - glnx_fd_close int dest_dfd = -1; - g_autofree char *dirpath_tmp = g_strconcat (dirpath, ".tmp", NULL); - - if (TEMP_FAILURE_RETRY (renameat (dfd, dirpath, dfd, dirpath_tmp)) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - /* We're not accurately copying the mode, but in reality modes don't - * matter since it's all immutable anyways. - */ - if (TEMP_FAILURE_RETRY (mkdirat (dfd, dirpath, 0755)) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - if (!glnx_opendirat (dfd, dirpath, TRUE, &dest_dfd, error)) - goto out; - - if (!copy_dir_contents_nonrecurse_at (dfd, dirpath_tmp, dest_dfd, cancellable, error)) - goto out; - - if (!glnx_shutil_rm_rf_at (dfd, dirpath_tmp, cancellable, error)) - goto out; - - ret = TRUE; - out: - return ret; -} - -static void -on_hifstate_percentage_changed (HifState *hifstate, - guint percentage, - gpointer user_data) -{ - const char *text = user_data; - glnx_console_progress_text_percent (text, percentage); -} - -static gboolean -overlay_packages_in_deploydir (HifContext *hifctx, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - /* --- Run transaction --- */ - { g_auto(GLnxConsoleRef) console = { 0, }; - glnx_unref_object HifState *hifstate = hif_state_new (); - guint progress_sigid; - - progress_sigid = g_signal_connect (hifstate, "percentage-changed", - G_CALLBACK (on_hifstate_percentage_changed), - "Installing: "); - - glnx_console_lock (&console); - - if (!hif_context_commit (hifctx, hifstate, error)) - goto out; - - g_signal_handler_disconnect (hifstate, progress_sigid); - } - - ret = TRUE; - out: - return ret; -} - static gboolean pkg_add_transaction_execute (RpmostreedTransaction *transaction, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; - PkgAddTransaction *self; - OstreeSysroot *sysroot; - int sysroot_fd; /* Borrowed */ - OstreeRepoCheckoutOptions checkout_options = { 0, }; - + PkgAddTransaction *self = NULL; + OstreeSysroot *sysroot = NULL; glnx_unref_object RpmOstreeSysrootUpgrader *upgrader = NULL; - glnx_unref_object OstreeRepo *repo = NULL; - glnx_unref_object OstreeAsyncProgress *progress = NULL; - glnx_unref_object OstreeDeployment *merge_deployment = NULL; - glnx_unref_object OstreeDeployment *new_deployment = NULL; - g_autofree char *merge_deployment_dirpath = NULL; - g_autofree char *tmp_deploy_workdir_name = NULL; - gboolean tmp_deploy_workdir_created = FALSE; - g_autofree char *tmp_deploy_root_path = NULL; - g_autofree char *new_revision = NULL; - glnx_fd_close int merge_deployment_dirfd = -1; - glnx_fd_close int ostree_repo_tmp_dirfd = -1; - glnx_fd_close int deploy_tmp_dirfd = -1; - g_autoptr(GKeyFile) origin = NULL; - HifContext *hifctx = NULL; - g_autoptr(RpmOstreeContext) ctx = NULL; - g_autoptr(GHashTable) cur_origin_pkgrequests = g_hash_table_new (g_str_hash, g_str_equal); - g_autoptr(GHashTable) new_pkgrequests = g_hash_table_new (g_str_hash, g_str_equal); - g_autoptr(GHashTable) layer_new_packages = g_hash_table_new (g_str_hash, g_str_equal); + int flags = RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY; self = (PkgAddTransaction *) transaction; - sysroot = rpmostreed_transaction_get_sysroot (transaction); - sysroot_fd = ostree_sysroot_get_fd (sysroot); - merge_deployment = ostree_sysroot_get_merge_deployment (sysroot, self->osname); - if (merge_deployment == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "No deployments found for osname %s", self->osname); - goto out; - } + if (self->dry_run) + flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN; - merge_deployment_dirpath = ostree_sysroot_get_deployment_dirpath (sysroot, merge_deployment); - - if (!glnx_opendirat (sysroot_fd, merge_deployment_dirpath, TRUE, - &merge_deployment_dirfd, error)) - goto out; - - if (!glnx_opendirat (sysroot_fd, "ostree/repo/tmp", TRUE, - &ostree_repo_tmp_dirfd, error)) - goto out; - - upgrader = rpmostree_sysroot_upgrader_new (sysroot, self->osname, 0, - cancellable, error); + upgrader = rpmostree_sysroot_upgrader_new (sysroot, self->osname, flags, + cancellable, error); if (upgrader == NULL) - goto out; - - if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) - goto out; - - origin = rpmostree_sysroot_upgrader_dup_origin (upgrader); - if (origin == NULL) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Merge deployment has no origin"); + if (error && *error == NULL) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create sysroot upgrader"); goto out; } - { - const char *cur_origin_refspec = - rpmostree_sysroot_upgrader_get_refspec (upgrader); - const char *const *cur_origin_packages = - rpmostree_sysroot_upgrader_get_packages (upgrader); - const char *const*strviter; - - g_assert (cur_origin_refspec); - - for (strviter = cur_origin_packages; strviter && *strviter; strviter++) - { - const char *pkg = *strviter; - g_hash_table_add (cur_origin_pkgrequests, (char*)pkg); - g_hash_table_add (new_pkgrequests, (char*)pkg); - } - - (void) g_key_file_remove_key (origin, "origin", "refspec", NULL); - g_key_file_set_value (origin, "origin", "baserefspec", cur_origin_refspec); - } - - if (!rpmostree_sysroot_upgrader_set_origin (upgrader, origin, cancellable, error)) + if (!rpmostree_sysroot_upgrader_add_packages (upgrader, self->packages, + cancellable, error)) goto out; - { - char **iter = self->packages; - g_autoptr(RpmOstreeRefSack) rsack = NULL; - - rsack = rpmostree_get_refsack_for_root (sysroot_fd, - merge_deployment_dirpath, - cancellable, error); - if (!rsack) - goto out; - - for (; iter && *iter; iter++) - { - const char *desired_pkg = *iter; - HyQuery query = NULL; - g_autoptr(GPtrArray) pkglist = NULL; - - if (g_hash_table_contains (cur_origin_pkgrequests, desired_pkg)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Package '%s' is already requested", desired_pkg); - goto out; - } - - /* It's now requested */ - g_hash_table_add (new_pkgrequests, (char*)desired_pkg); - - query = hy_query_create (rsack->sack); - hy_query_filter (query, HY_PKG_NAME, HY_EQ, desired_pkg); - hy_query_filter (query, HY_PKG_REPONAME, HY_EQ, HY_SYSTEM_REPO_NAME); - pkglist = hy_query_run (query); - - /* This one tracks whether it actually needs to be installed */ - if (pkglist->len == 0) - g_hash_table_add (layer_new_packages, (char*)desired_pkg); - - if (query) - hy_query_free (query); - } - } - - ctx = rpmostree_context_new_system (cancellable, error); - if (!ctx) - goto out; - - hifctx = rpmostree_context_get_hif (ctx); - { - g_autofree char *reposdir = - g_build_filename (merge_deployment_dirpath, "etc/yum.repos.d", NULL); - hif_context_set_repo_dir (hifctx, reposdir); - } - - tmp_deploy_workdir_name = g_strdup ("rpmostree-deploy-XXXXXX"); - if (!glnx_mkdtempat (ostree_repo_tmp_dirfd, tmp_deploy_workdir_name, 0755, error)) - goto out; - tmp_deploy_workdir_created = TRUE; - - tmp_deploy_root_path = g_strconcat (tmp_deploy_workdir_name, "root", NULL); - - checkout_options.devino_to_csum_cache = ostree_repo_devino_cache_new (); - - if (!ostree_repo_checkout_tree_at (repo, &checkout_options, ostree_repo_tmp_dirfd, tmp_deploy_root_path, - ostree_deployment_get_csum (merge_deployment), - cancellable, error)) - goto out; - - if (!glnx_opendirat (ostree_repo_tmp_dirfd, tmp_deploy_root_path, TRUE, - &deploy_tmp_dirfd, error)) - goto out; - - /* Convert usr/share/rpm into physical copies, as librpm mutates it in place */ - if (!break_hardlinks_at (deploy_tmp_dirfd, "usr/share/rpm", cancellable, error)) - goto out; - - /* Temporarily rename /usr/etc to /etc so that RPMs can drop new files there */ - if (TEMP_FAILURE_RETRY (renameat (deploy_tmp_dirfd, "usr/etc", - deploy_tmp_dirfd, "etc")) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - { g_autofree char *tmp_deploy_abspath = glnx_fdrel_abspath (deploy_tmp_dirfd, "."); - hif_context_set_install_root (hifctx, tmp_deploy_abspath); - } - - /* Note this path is relative to the install root */ - hif_context_set_rpm_macro (hifctx, "_dbpath", "/usr/share/rpm"); - - if (!hif_context_setup (hifctx, cancellable, error)) - goto out; - - if (g_hash_table_size (layer_new_packages) > 0) - { - HifTransaction *hiftx; - HyGoal goal; - GHashTableIter hashiter; - gpointer hkey, hvalue; - - /* --- Downloading metadata --- */ - { g_auto(GLnxConsoleRef) console = { 0, }; - glnx_unref_object HifState *hifstate = hif_state_new (); - guint progress_sigid; - - progress_sigid = g_signal_connect (hifstate, "percentage-changed", - G_CALLBACK (on_hifstate_percentage_changed), - "Downloading metadata: "); - - glnx_console_lock (&console); - - if (!hif_context_setup_sack (hifctx, hifstate, error)) - goto out; - - g_signal_handler_disconnect (hifstate, progress_sigid); - } - - hiftx = hif_context_get_transaction (hifctx); - - g_hash_table_iter_init (&hashiter, layer_new_packages); - while (g_hash_table_iter_next (&hashiter, &hkey, &hvalue)) - { - const char *pkg = hkey; - - if (!hif_context_install (hifctx, pkg, error)) - goto out; - } - - goal = hif_context_get_goal (hifctx); - - if (!hif_transaction_depsolve (hiftx, goal, NULL, error)) - goto out; - - /* --- Downloading packages --- */ - { g_auto(GLnxConsoleRef) console = { 0, }; - glnx_unref_object HifState *hifstate = hif_state_new (); - guint progress_sigid; - - progress_sigid = g_signal_connect (hifstate, "percentage-changed", - G_CALLBACK (on_hifstate_percentage_changed), - "Downloading: "); - - glnx_console_lock (&console); - - if (!hif_transaction_download (hiftx, hifstate, error)) - goto out; - - g_signal_handler_disconnect (hifstate, progress_sigid); - } - - /* find any packages without valid GPG signatures */ -#if 0 - if (!hif_transaction_check_untrusted (hiftx, goal, error)) - goto out; -#endif - } - - /* Add previous package requests with newly requested packages */ - { gs_unref_ptrarray GPtrArray *new_requested_pkglist = g_ptr_array_new (); - GHashTableIter hashiter; - gpointer hkey, hvalue; - - g_hash_table_iter_init (&hashiter, cur_origin_pkgrequests); - while (g_hash_table_iter_next (&hashiter, &hkey, &hvalue)) - g_ptr_array_add (new_requested_pkglist, hkey); - - g_hash_table_iter_init (&hashiter, layer_new_packages); - while (g_hash_table_iter_next (&hashiter, &hkey, &hvalue)) - g_ptr_array_add (new_requested_pkglist, hkey); - - g_key_file_set_string_list (origin, "packages", "requested", - (const char*const*)new_requested_pkglist->pdata, - new_requested_pkglist->len); - } - - if (!overlay_packages_in_deploydir (hifctx, cancellable, error)) - goto out; - - /* Rename /usr/etc back to /etc */ - if (TEMP_FAILURE_RETRY (renameat (deploy_tmp_dirfd, "etc", deploy_tmp_dirfd, "usr/etc")) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - if (!rpmostree_commit (deploy_tmp_dirfd, repo, NULL, NULL, NULL, TRUE, - checkout_options.devino_to_csum_cache, - &new_revision, cancellable, error)) - goto out; - - (void) close (deploy_tmp_dirfd); - deploy_tmp_dirfd = -1; - - if (!glnx_shutil_rm_rf_at (ostree_repo_tmp_dirfd, tmp_deploy_workdir_name, cancellable, error)) - goto out; - tmp_deploy_workdir_created = FALSE; - - rpmostree_sysroot_upgrader_set_origin_baseref_local (upgrader, new_revision); - if (!rpmostree_sysroot_upgrader_deploy (upgrader, cancellable, error)) goto out; @@ -474,10 +105,6 @@ pkg_add_transaction_execute (RpmostreedTransaction *transaction, ret = TRUE; out: - if (tmp_deploy_workdir_created) - (void) glnx_shutil_rm_rf_at (ostree_repo_tmp_dirfd, tmp_deploy_workdir_name, NULL, NULL); - if (checkout_options.devino_to_csum_cache) - ostree_repo_devino_cache_unref (checkout_options.devino_to_csum_cache); return ret; } @@ -503,6 +130,7 @@ rpmostreed_transaction_new_pkg_add (GDBusMethodInvocation *invocation, const char *osname, const char * const *packages, gboolean reboot, + gboolean dry_run, GCancellable *cancellable, GError **error) { @@ -524,6 +152,7 @@ rpmostreed_transaction_new_pkg_add (GDBusMethodInvocation *invocation, self->osname = g_strdup (osname); self->packages = g_strdupv ((char**)packages); self->reboot = reboot; + self->dry_run = dry_run; } return (RpmostreedTransaction *) self; diff --git a/src/daemon/rpmostreed-transaction-pkg-delete.c b/src/daemon/rpmostreed-transaction-pkg-delete.c new file mode 100644 index 00000000..e8000c4b --- /dev/null +++ b/src/daemon/rpmostreed-transaction-pkg-delete.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "ostree.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "rpmostreed-transaction-types.h" +#include "rpmostreed-transaction-types.h" +#include "rpmostreed-transaction.h" +#include "rpmostreed-deployment-utils.h" +#include "rpmostreed-sysroot.h" +#include "rpmostree-sysroot-upgrader.h" +#include "rpmostreed-utils.h" +#include "rpmostree-postprocess.h" +#include "rpmostree-rpm-util.h" +#include "rpmostree-core.h" + +typedef struct { + RpmostreedTransaction parent; + char *osname; + char **packages; + gboolean reboot; + gboolean dry_run; +} PkgDeleteTransaction; + +typedef RpmostreedTransactionClass PkgDeleteTransactionClass; + +GType pkg_delete_transaction_get_type (void); + +G_DEFINE_TYPE (PkgDeleteTransaction, + pkg_delete_transaction, + RPMOSTREED_TYPE_TRANSACTION) + +static void +pkg_delete_transaction_finalize (GObject *object) +{ + PkgDeleteTransaction *self; + + self = (PkgDeleteTransaction *) object; + g_free (self->osname); + g_strfreev (self->packages); + + G_OBJECT_CLASS (pkg_delete_transaction_parent_class)->finalize (object); +} + +static gboolean +pkg_delete_transaction_execute (RpmostreedTransaction *transaction, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + PkgDeleteTransaction *self = NULL; + OstreeSysroot *sysroot = NULL; + glnx_unref_object RpmOstreeSysrootUpgrader *upgrader = NULL; + int flags = RPMOSTREE_SYSROOT_UPGRADER_FLAGS_REDEPLOY; + + self = (PkgDeleteTransaction *) transaction; + sysroot = rpmostreed_transaction_get_sysroot (transaction); + + if (self->dry_run) + flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGOVERLAY_DRY_RUN; + + upgrader = rpmostree_sysroot_upgrader_new (sysroot, self->osname, flags, + cancellable, error); + if (upgrader == NULL) + { + if (error && *error == NULL) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create sysroot upgrader"); + goto out; + } + + if (!rpmostree_sysroot_upgrader_delete_packages (upgrader, self->packages, + cancellable, error)) + goto out; + + if (!rpmostree_sysroot_upgrader_deploy (upgrader, cancellable, error)) + goto out; + + if (self->reboot) + rpmostreed_reboot (cancellable, error); + + ret = TRUE; +out: + return ret; +} + +static void +pkg_delete_transaction_class_init (PkgDeleteTransactionClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = pkg_delete_transaction_finalize; + + class->execute = pkg_delete_transaction_execute; +} + +static void +pkg_delete_transaction_init (PkgDeleteTransaction *self) +{ +} + +RpmostreedTransaction * +rpmostreed_transaction_new_pkg_delete (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + const char * const *packages, + gboolean reboot, + gboolean dry_run, + GCancellable *cancellable, + GError **error) +{ + PkgDeleteTransaction *self; + + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + g_return_val_if_fail (OSTREE_IS_SYSROOT (sysroot), NULL); + g_return_val_if_fail (osname != NULL, NULL); + g_return_val_if_fail (packages != NULL, NULL); + + self = g_initable_new (pkg_delete_transaction_get_type (), + cancellable, error, + "invocation", invocation, + "sysroot-path", gs_file_get_path_cached (ostree_sysroot_get_path (sysroot)), + NULL); + + if (self != NULL) + { + self->osname = g_strdup (osname); + self->packages = g_strdupv ((char**)packages); + self->reboot = reboot; + self->dry_run = dry_run; + } + + return (RpmostreedTransaction *) self; +} diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index fac52a5c..ed2602cf 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -79,5 +79,16 @@ RpmostreedTransaction * const char *osname, const char *const *packages, gboolean reboot, + gboolean dry_run, + GCancellable *cancellable, + GError **error); + +RpmostreedTransaction * + rpmostreed_transaction_new_pkg_delete (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + const char *const *packages, + gboolean reboot, + gboolean dry_run, GCancellable *cancellable, GError **error); diff --git a/src/libpriv/rpmostree-postprocess.c b/src/libpriv/rpmostree-postprocess.c index bffb151c..3f1fb6fa 100644 --- a/src/libpriv/rpmostree-postprocess.c +++ b/src/libpriv/rpmostree-postprocess.c @@ -708,7 +708,7 @@ workaround_selinux_cross_labeling_recurse (int dfd, return ret; } -static gboolean +gboolean rpmostree_prepare_rootfs_get_sepolicy (int dfd, const char *path, OstreeSePolicy **out_sepolicy, diff --git a/src/libpriv/rpmostree-postprocess.h b/src/libpriv/rpmostree-postprocess.h index 65a4802c..b8cc29e2 100644 --- a/src/libpriv/rpmostree-postprocess.h +++ b/src/libpriv/rpmostree-postprocess.h @@ -36,6 +36,13 @@ rpmostree_rootfs_postprocess_common (int rootfs_fd, GCancellable *cancellable, GError **error); +gboolean +rpmostree_prepare_rootfs_get_sepolicy (int dfd, + const char *path, + OstreeSePolicy **out_sepolicy, + GCancellable *cancellable, + GError **error); + gboolean rpmostree_prepare_rootfs_for_commit (GFile *rootfs, JsonObject *treefile, diff --git a/src/libpriv/rpmostree-rpm-util.c b/src/libpriv/rpmostree-rpm-util.c index 45a14a3e..964b02d7 100644 --- a/src/libpriv/rpmostree-rpm-util.c +++ b/src/libpriv/rpmostree-rpm-util.c @@ -703,6 +703,7 @@ rpmrev_free (struct RpmRevisionData *ptr) gboolean rpmostree_checkout_only_rpmdb_tempdir (OstreeRepo *repo, const char *ref, + const char *template, char **out_tempdir, int *out_tempdir_dfd, GCancellable *cancellable, @@ -716,7 +717,7 @@ rpmostree_checkout_only_rpmdb_tempdir (OstreeRepo *repo, g_return_val_if_fail (out_tempdir != NULL, FALSE); - if (!rpmostree_mkdtemp ("/tmp/rpmostree-dbquery-XXXXXX", &tempdir, &tempdir_dfd, error)) + if (!rpmostree_mkdtemp (template, &tempdir, &tempdir_dfd, error)) goto out; if (!ostree_repo_resolve_rev (repo, ref, FALSE, &commit, error)) @@ -772,6 +773,8 @@ get_sack_for_root (int dfd, g_return_val_if_fail (out_sack != NULL, FALSE); sack = hif_sack_new (); + hif_sack_set_rootdir (sack, fullpath); + if (!hif_sack_setup (sack, HIF_SACK_LOAD_FLAG_BUILD_CACHE, error)) goto out; @@ -813,11 +816,13 @@ rpmostree_get_refsack_for_commit (OstreeRepo *repo, g_autofree char *tempdir = NULL; glnx_fd_close int tempdir_dfd = -1; HifSack *hsack; - - if (!rpmostree_checkout_only_rpmdb_tempdir (repo, ref, &tempdir, &tempdir_dfd, + + if (!rpmostree_checkout_only_rpmdb_tempdir (repo, ref, + "/tmp/rpmostree-dbquery-XXXXXX", + &tempdir, &tempdir_dfd, cancellable, error)) goto out; - + if (!get_sack_for_root (tempdir_dfd, ".", &hsack, cancellable, error)) goto out; @@ -842,7 +847,9 @@ rpmostree_get_refts_for_commit (OstreeRepo *repo, rpmts ts; int r; - if (!rpmostree_checkout_only_rpmdb_tempdir (repo, ref, &tempdir, NULL, + if (!rpmostree_checkout_only_rpmdb_tempdir (repo, ref, + "/tmp/rpmostree-dbquery-XXXXXX", + &tempdir, NULL, cancellable, error)) goto out; diff --git a/src/libpriv/rpmostree-rpm-util.h b/src/libpriv/rpmostree-rpm-util.h index 0d0f24a0..19a4614d 100644 --- a/src/libpriv/rpmostree-rpm-util.h +++ b/src/libpriv/rpmostree-rpm-util.h @@ -85,6 +85,7 @@ GS_DEFINE_CLEANUP_FUNCTION0(struct RpmRevisionData *, _cleanup_rpmrev_free, rpmr gboolean rpmostree_checkout_only_rpmdb_tempdir (OstreeRepo *repo, const char *ref, + const char *template, char **out_tempdir, int *out_tempdir_dfd, GCancellable *cancellable, diff --git a/src/libpriv/rpmostree-util.c b/src/libpriv/rpmostree-util.c index 634d6bc7..bc25f12a 100644 --- a/src/libpriv/rpmostree-util.c +++ b/src/libpriv/rpmostree-util.c @@ -427,7 +427,6 @@ _rpmostree_util_parse_origin (GKeyFile *origin, { gboolean ret = FALSE; g_autofree char *origin_refspec = NULL; - g_auto(GStrv) origin_packages = NULL; gboolean origin_is_bare_refspec = TRUE; origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL); @@ -443,13 +442,74 @@ _rpmostree_util_parse_origin (GKeyFile *origin, goto out; } - *out_refspec = g_steal_pointer (&origin_refspec); - if (origin_is_bare_refspec) - *out_packages = NULL; - else - *out_packages = g_key_file_get_string_list (origin, "packages", "requested", NULL, NULL); + if (out_refspec) + *out_refspec = g_steal_pointer (&origin_refspec); + + if (out_packages) + { + if (origin_is_bare_refspec) + *out_packages = NULL; + else + *out_packages = g_key_file_get_string_list (origin, "packages", "requested", NULL, NULL); + } ret = TRUE; out: return ret; } + +gboolean +rpmostree_split_path_ptrarray_validate (const char *path, + GPtrArray **out_components, + GError **error) +{ + gboolean ret = FALSE; + g_autoptr(GPtrArray) ret_components = NULL; + + if (strlen (path) > PATH_MAX) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Path '%s' is too long", path); + goto out; + } + + ret_components = g_ptr_array_new_with_free_func (g_free); + + do + { + const char *p = strchr (path, '/'); + g_autofree char *component = NULL; + + if (!p) + { + component = g_strdup (path); + path = NULL; + } + else + { + component = g_strndup (path, p - path); + path = p + 1; + } + + if (!component[0]) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid empty component in path '%s'", path); + goto out; + } + if (g_str_equal (component, ".") || + g_str_equal (component, "..")) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid special element '.' or '..' in path %s", path); + goto out; + } + + g_ptr_array_add (ret_components, (char*)g_steal_pointer (&component)); + } while (path && *path); + + ret = TRUE; + *out_components = g_steal_pointer (&ret_components); + out: + return ret; +} diff --git a/src/libpriv/rpmostree-util.h b/src/libpriv/rpmostree-util.h index de7db072..b4c1dfb7 100644 --- a/src/libpriv/rpmostree-util.h +++ b/src/libpriv/rpmostree-util.h @@ -95,3 +95,8 @@ _rpmostree_util_parse_origin (GKeyFile *origin, char **out_refspec, char ***out_packages, GError **error); + +gboolean +rpmostree_split_path_ptrarray_validate (const char *path, + GPtrArray **out_components, + GError **error);