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