From 6b8dc82ef4aee01e27c20116d5c579b3a26df478 Mon Sep 17 00:00:00 2001 From: Robert Fairley Date: Wed, 10 Apr 2019 17:15:58 -0400 Subject: [PATCH] Add support for default kargs files Instead of merging kargs from the previous deployment, by default regenerate kargs from the following config files: /etc/ostree/kargs (host config) /usr/lib/ostree-boot/kargs (base config) The base config is sourced from the repo at the commit (revision) being deployed, so that newly committed kargs will take effect immediately after upgrading. The host config is sourced from the merged /etc configuration of the new deployment, so that host edits as well as edits to /usr/etc/ostree/kargs are reflected. Kernel arguments present in the base config are appended to kernel arguments in the host config. Using the commands that modify kargs (of the form `ostree admin deploy --karg*`) will cause kargs in later deployments to be copied from the merge deployment, rather than regenerate from the config files. Resolves: #479 --- Makefile-tests.am | 1 + src/libostree/ostree-sysroot-deploy.c | 140 +++++++++++++++++++++--- src/libostree/ostree-sysroot-private.h | 2 + src/libotutil/ot-gio-utils.c | 36 ++++++ src/libotutil/ot-gio-utils.h | 6 + tests/libtest.sh | 49 +++++++++ tests/test-admin-deploy-karg-default.sh | 93 ++++++++++++++++ 7 files changed, 311 insertions(+), 16 deletions(-) create mode 100755 tests/test-admin-deploy-karg-default.sh diff --git a/Makefile-tests.am b/Makefile-tests.am index 2c0916f620..b93f084776 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -98,6 +98,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-admin-deploy-syslinux.sh \ tests/test-admin-deploy-2.sh \ tests/test-admin-deploy-karg.sh \ + tests/test-admin-deploy-karg-default.sh \ tests/test-admin-deploy-switch.sh \ tests/test-admin-deploy-etcmerge-cornercases.sh \ tests/test-admin-deploy-uboot.sh \ diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index a3eb4311fc..7450ce224a 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -2568,9 +2568,101 @@ get_var_dfd (OstreeSysroot *self, return glnx_opendirat (base_dfd, base_path, TRUE, ret_fd, error); } +static gboolean +read_file_allow_noent (OstreeSysroot *sysroot, + int dfd, + char *path, + char **contents_out, + GCancellable *cancellable, + GError **error) +{ + struct stat stbuf; + if (!glnx_fstatat_allow_noent (dfd, path, &stbuf, 0, error)) + return FALSE; + const gboolean file_exists = (errno == 0); + + g_autofree char *ret_contents = NULL; + if (file_exists) + { + ret_contents = glnx_file_get_contents_utf8_at (dfd, + path, + NULL, + cancellable, + error); + if (!ret_contents) + return FALSE; + } + + *contents_out = g_steal_pointer (&ret_contents); + + return TRUE; +} + +/* Get a GFile* referring to the commit object at ref for the file at path. */ +static GFile* +get_file_from_repo (OstreeRepo *repo, + const char *ref, + const char *path, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) root = NULL; + if (!ostree_repo_read_commit (repo, ref, &root, + NULL, cancellable, error)) + return NULL; + return g_file_resolve_relative_path (root, path); +} + +static gboolean +sysroot_regenerate_kargs (OstreeSysroot *self, + const char *revision, + int deployment_dfd, + char **kargs_out, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GPtrArray) kargs_configs = g_ptr_array_new_with_free_func (g_free); + + /* Load host kargs configuration from the merge deployment, so that any + * configuration done on the host is carried to the next deployment. */ + g_autofree char *host_karg_contents = NULL; + if (!read_file_allow_noent (self, deployment_dfd, + _OSTREE_SYSROOT_KARGS_HOST, + &host_karg_contents, + cancellable, error)) + return FALSE; + if (host_karg_contents) + g_ptr_array_add (kargs_configs, g_strstrip (g_steal_pointer (&host_karg_contents))); + + /* Load base kargs configuration from the current commit, so that kargs will + * be written to the bootconfig as soon as the current deployment is + * finished (in install_deployment_kernel()). */ + g_autofree char *base_karg_contents = NULL; + g_autoptr(GFile) base_karg_file = get_file_from_repo (ostree_sysroot_repo (self), + revision, + _OSTREE_SYSROOT_KARGS_BASE, + cancellable, error); + if (!ot_file_load_contents_allow_not_found (base_karg_file, &base_karg_contents, + cancellable, error)) + return FALSE; + if (base_karg_contents) + g_ptr_array_add (kargs_configs, g_strstrip (g_steal_pointer (&base_karg_contents))); + + g_autoptr(OstreeKernelArgs) kargs = _ostree_kernel_args_new (); + for (guint i = 0; i < kargs_configs->len; i++) + _ostree_kernel_args_parse_append (kargs, kargs_configs->pdata[i]); + + g_autofree char *kargs_contents = _ostree_kernel_args_to_string (kargs); + + *kargs_out = g_steal_pointer (&kargs_contents); + + return TRUE; +} + static gboolean sysroot_finalize_deployment (OstreeSysroot *self, OstreeDeployment *deployment, + const char *revision, char **override_kernel_argv, OstreeDeployment *merge_deployment, GCancellable *cancellable, @@ -2581,36 +2673,51 @@ sysroot_finalize_deployment (OstreeSysroot *self, if (!glnx_opendirat (self->sysroot_fd, deployment_path, TRUE, &deployment_dfd, error)) return FALSE; - /* If we didn't get an override in this deployment, copy kargs directly from - * the merge deployment. */ + /* If we didn't get an override in this deployment, check if an override + * has been done previously. */ + gboolean kargs_previously_overridden = FALSE; if (!override_kernel_argv) { - OstreeBootconfigParser *merge_bootconfig = NULL; - gboolean kargs_overridden = FALSE; if (merge_deployment) { - merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment); + OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment); if (merge_bootconfig) { - /* Copy kargs from the merge deployment. */ - const char *opts = ostree_bootconfig_parser_get (merge_bootconfig, "options"); - ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts); - kargs_overridden = g_strcmp0 (ostree_bootconfig_parser_get (merge_bootconfig, "ostree-kargs-override"), - "true") == 0; + kargs_previously_overridden = g_strcmp0 (ostree_bootconfig_parser_get (merge_bootconfig, "ostree-kargs-override"), + "true") == 0; + if (kargs_previously_overridden) + { + /* Copy kargs from the merge deployment. */ + const char *opts = ostree_bootconfig_parser_get (merge_bootconfig, "options"); + ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts); + ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "ostree-kargs-override", "true"); + } } - if (kargs_overridden) - ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "ostree-kargs-override", "true"); } } if (merge_deployment) { - /* And do the /etc merge */ + /* Do the /etc merge. */ if (!merge_configuration_from (self, merge_deployment, deployment, deployment_dfd, cancellable, error)) return FALSE; } + /* If we didn't get an override in this deployment, and an override has not + * been done before, then regenerate from the config files of this + * deployment. */ + if (!override_kernel_argv && !kargs_previously_overridden) + { + /* Note this occurs after merge_configuration_from() so that + * /etc/ostree/kargs as a result of the /etc merge is used. */ + g_autofree char *opts = NULL; + if (!sysroot_regenerate_kargs (self, revision, deployment_dfd, &opts, + cancellable, error)) + return FALSE; + ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts); + } + const char *osdeploypath = glnx_strjoina ("ostree/deploy/", ostree_deployment_get_osname (deployment)); glnx_autofd int os_deploy_dfd = -1; if (!glnx_opendirat (self->sysroot_fd, osdeploypath, TRUE, &os_deploy_dfd, error)) @@ -2689,7 +2796,8 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, &deployment, cancellable, error)) return FALSE; - if (!sysroot_finalize_deployment (self, deployment, override_kernel_argv, + if (!sysroot_finalize_deployment (self, deployment, revision, + override_kernel_argv, provided_merge_deployment, cancellable, error)) return FALSE; @@ -2948,8 +3056,8 @@ _ostree_sysroot_finalize_staged (OstreeSysroot *self, if (!glnx_unlinkat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, 0, error)) return FALSE; - if (!sysroot_finalize_deployment (self, self->staged_deployment, kargs, merge_deployment, - cancellable, error)) + if (!sysroot_finalize_deployment (self, self->staged_deployment, self->staged_deployment->csum, + kargs, merge_deployment, cancellable, error)) return FALSE; /* Now, take ownership of the staged state, as normally the API below strips diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 9da6d4c9e1..0c84ed8700 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -78,6 +78,8 @@ struct OstreeSysroot { #define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development" +#define _OSTREE_SYSROOT_KARGS_HOST "etc/ostree/kargs" +#define _OSTREE_SYSROOT_KARGS_BASE "usr/lib/ostree-boot/kargs" void _ostree_sysroot_emit_journal_msg (OstreeSysroot *self, diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c index f09ee8af4f..747e9e8394 100644 --- a/src/libotutil/ot-gio-utils.c +++ b/src/libotutil/ot-gio-utils.c @@ -177,3 +177,39 @@ ot_file_get_path_cached (GFile *file) return path; } + +/** + * ot_file_load_contents_allow_not_found: + * + * Load the contents of file, allowing G_IO_ERROR_NOT_FOUND. + * If file exists, return the contents in out_contents (otherwise out_contents + * holds NULL). + * + * Return FALSE for any other error. + */ +gboolean +ot_file_load_contents_allow_not_found (GFile *file, + char **out_contents, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (file != NULL, FALSE); + + GError *local_error = NULL; + g_autofree char *ret_contents = NULL; + if (!g_file_load_contents (file, cancellable, &ret_contents, NULL, NULL, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&local_error); + } + else + { + g_propagate_error (error, local_error); + return FALSE; + } + } + + ot_transfer_out_value (out_contents, &ret_contents); + return TRUE; +} diff --git a/src/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h index d317ac296b..13a1d4a1a0 100644 --- a/src/libotutil/ot-gio-utils.h +++ b/src/libotutil/ot-gio-utils.h @@ -79,4 +79,10 @@ gs_file_get_path_cached (GFile *file) return ot_file_get_path_cached (file); } +gboolean +ot_file_load_contents_allow_not_found (GFile *file, + char **out_contents, + GCancellable *cancellable, + GError **error); + G_END_DECLS diff --git a/tests/libtest.sh b/tests/libtest.sh index e0022512e0..4ded2524f3 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -512,6 +512,55 @@ os_repository_new_commit () cd ${test_tmpdir} } +os_repository_commit () +{ + repo=${1:-testos-repo} + boot_checksum_iteration=${2:-0} + content_iteration=${3:-0} + branch=${4:-testos/buildmaster/x86_64-runtime} + export version=${5:-$(date "+%Y%m%d.${content_iteration}")} + echo "BOOT ITERATION: $boot_checksum_iteration" + cd ${test_tmpdir}/osdata + kver=3.6.0 + if test -f usr/lib/modules/${kver}/vmlinuz; then + bootdir=usr/lib/modules/${kver} + else + if test -d usr/lib/ostree-boot; then + bootdir=usr/lib/ostree-boot + else + bootdir=boot + fi + fi + rm ${bootdir}/* + kernel_path=${bootdir}/vmlinuz + initramfs_path=${bootdir}/initramfs.img + if [[ $bootdir != usr/lib/modules/* ]]; then + kernel_path=${kernel_path}-${kver} + initramfs_path=${bootdir}/initramfs-${kver}.img + fi + echo "new: a kernel ${boot_checksum_iteration}" > ${kernel_path} + echo "new: an initramfs ${boot_checksum_iteration}" > ${initramfs_path} + bootcsum=$(cat ${kernel_path} ${initramfs_path} | sha256sum | cut -f 1 -d ' ') + export bootcsum + if [[ $bootdir != usr/lib/modules/* ]]; then + mv ${kernel_path}{,-${bootcsum}} + mv ${initramfs_path}{,-${bootcsum}} + fi + + commithash=$(${CMD_PREFIX} ostree --repo=${test_tmpdir}/${repo} commit --add-metadata-string "version=${version}" -b $branch -s "Build") + export commithash + cd ${test_tmpdir} +} + +os_tree_write_file () +{ + path=${1} + contents="${2}" + cd ${test_tmpdir}/osdata + echo "${contents}" > ${path} + cd ${test_tmpdir} +} + _have_user_xattrs='' have_user_xattrs() { assert_has_setfattr diff --git a/tests/test-admin-deploy-karg-default.sh b/tests/test-admin-deploy-karg-default.sh new file mode 100755 index 0000000000..06d2614e34 --- /dev/null +++ b/tests/test-admin-deploy-karg-default.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# +# Copyright (C) 2019 Robert Fairley +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# This library 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 License, 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +# Exports OSTREE_SYSROOT so --sysroot not needed. +setup_os_repository "archive" "syslinux" + +echo "1..3" + +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime + +${CMD_PREFIX} ostree admin deploy --os=testos testos:testos/buildmaster/x86_64-runtime +assert_has_dir sysroot/boot/ostree/testos-${bootcsum} + +initial_rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +echo "initial_rev=${initial_rev}" + +# Configure kargs stored in the ostree commit. +mkdir -p osdata/usr/lib/ostree-boot +os_tree_write_file "usr/lib/ostree-boot/kargs" "FOO=USR_1" +os_repository_commit "testos-repo" + +# Upgrade to tree with newly-committed kargs file. +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos file://$(pwd)/testos-repo testos/buildmaster/x86_64-runtime +${CMD_PREFIX} ostree admin upgrade --os=testos +# Sanity check a new boot directory was created after upgrading. +assert_has_dir sysroot/boot/ostree/testos-${bootcsum} + +assert_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'FOO=USR_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'ostree-kargs-override' + +# Configure kargs through the host config file. +rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +echo "rev=${rev}" +etc=sysroot/ostree/deploy/testos/deploy/${rev}.0/etc +assert_has_dir ${etc} +mkdir -p ${etc}/ostree +echo "HELLO=ETC_1" > ${etc}/ostree/kargs + +# Re-deploy with host-configured kernel args. +${CMD_PREFIX} ostree admin deploy --os=testos testos:testos/buildmaster/x86_64-runtime + +assert_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'HELLO=ETC_1' +assert_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'FOO=USR_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'ostree-kargs-override' + +echo "ok default kargs" + +${CMD_PREFIX} ostree admin upgrade --os=testos --allow-downgrade --override-commit=${initial_rev} + +assert_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'HELLO=ETC_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'FOO=USR_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'ostree-kargs-override' + +echo "ok default kargs downgrade" + +rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +echo "rev=${rev}" +etc=sysroot/ostree/deploy/testos/deploy/${rev}.0/etc +echo "" > ${etc}/ostree/kargs + +${CMD_PREFIX} ostree admin deploy --os=testos testos:testos/buildmaster/x86_64-runtime + +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'HELLO=ETC_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'FOO=USR_1' +assert_not_file_has_content sysroot/boot/loader/entries/ostree-2-testos.conf 'ostree-kargs-override' + +echo "ok default kargs clear host config" + +# Tests needed: +# - staging a deployment +# - test cases when etc and usr configs contain the same karg