diff --git a/Makefile-boot.am b/Makefile-boot.am index ec10a0d64..79de63e24 100644 --- a/Makefile-boot.am +++ b/Makefile-boot.am @@ -40,6 +40,7 @@ systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \ src/boot/ostree-remount.service \ src/boot/ostree-finalize-staged.service \ src/boot/ostree-finalize-staged.path \ + src/boot/ostree-finalize-staged-hold.service \ $(NULL) systemdtmpfilesdir = $(prefix)/lib/tmpfiles.d dist_systemdtmpfiles_DATA = src/boot/ostree-tmpfiles.conf @@ -68,6 +69,7 @@ EXTRA_DIST += src/boot/dracut/module-setup.sh \ src/boot/ostree-finalize-staged.path \ src/boot/ostree-remount.service \ src/boot/ostree-finalize-staged.service \ + src/boot/ostree-finalize-staged-hold.service \ src/boot/grub2/grub2-15_ostree \ src/boot/grub2/ostree-grub-generator \ $(NULL) diff --git a/libglnx b/libglnx index ef502aabf..07e3e49d3 160000 --- a/libglnx +++ b/libglnx @@ -1 +1 @@ -Subproject commit ef502aabf7d3a0d37f9c4d228f870ac93404447b +Subproject commit 07e3e49d3e47dfd4265ffb5495111439131715ca diff --git a/man/ostree-summary.xml b/man/ostree-summary.xml index 4c9652ccb..e853e8cdf 100644 --- a/man/ostree-summary.xml +++ b/man/ostree-summary.xml @@ -183,6 +183,7 @@ License along with this library. If not, see . Latest Commit (4.2 MB): 9828ab80f357459b4ab50f0629beab2ae3b67318fc3d161d10a89fae353afa90 Timestamp (ostree.commit.timestamp): 2017-11-21T01:41:10-08 + Version (ostree.commit.version): 1.2.3 Last-Modified (ostree.summary.last-modified): 2018-01-12T22:06:38-08 diff --git a/src/boot/ostree-finalize-staged-hold.service b/src/boot/ostree-finalize-staged-hold.service new file mode 100644 index 000000000..85b5d543b --- /dev/null +++ b/src/boot/ostree-finalize-staged-hold.service @@ -0,0 +1,35 @@ +# Copyright (C) 2018 Red Hat, Inc. +# Copyright (C) 2022 Endless OS Foundation LLC +# +# 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, see . + +# See https://github.com/ostreedev/ostree/pull/2543 for background. +[Unit] +Description=Hold /boot Open for OSTree Finalize Staged Deployment +Documentation=man:ostree(1) +ConditionPathExists=/run/ostree-booted +DefaultDependencies=no + +RequiresMountsFor=/sysroot /boot +After=local-fs.target +Before=basic.target final.target + +[Service] +Type=exec + +# This is explicitly run in the root namespace to ensure an automounted +# /boot doesn't time out since autofs doesn't handle mount namespaces. +# +# https://bugzilla.redhat.com/show_bug.cgi?id=2056090 +ExecStart=+/usr/bin/ostree admin finalize-staged --hold diff --git a/src/boot/ostree-finalize-staged.service b/src/boot/ostree-finalize-staged.service index 2f28bbb70..63621ce19 100644 --- a/src/boot/ostree-finalize-staged.service +++ b/src/boot/ostree-finalize-staged.service @@ -29,6 +29,11 @@ Before=basic.target final.target After=systemd-journal-flush.service Conflicts=final.target +# Start the hold unit and ensure it stays active throughout this +# service. +Wants=ostree-finalize-staged-hold.service +After=ostree-finalize-staged-hold.service + [Service] Type=oneshot RemainAfterExit=yes diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 36e612909..48a75f92a 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -164,6 +164,8 @@ typedef enum { * The currently defined keys for the `a{sv}` of additional metadata for each commit are: * - key: `ostree.commit.timestamp`, value: `t`, timestamp (seconds since the * Unix epoch in UTC, big-endian) when the commit was committed + * - key: `ostree.commit.version`, value: `s`, the `version` value from the + * commit's metadata if it was defined. Since: 2022.2 */ #define OSTREE_SUMMARY_GVARIANT_STRING "(a(s(taya{sv}))a{sv})" #define OSTREE_SUMMARY_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_SUMMARY_GVARIANT_STRING) diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 6d8f0193b..988c2179a 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -63,6 +63,7 @@ G_BEGIN_DECLS /* Well-known keys for the additional metadata field in a commit in a ref entry * in a summary file. */ #define OSTREE_COMMIT_TIMESTAMP "ostree.commit.timestamp" +#define OSTREE_COMMIT_VERSION "ostree.commit.version" typedef enum { OSTREE_REPO_TEST_ERROR_PRE_COMMIT = (1 << 0), diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 65b567891..6ee8426d4 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -2244,6 +2244,9 @@ process_one_static_delta (OtPullData *pull_data, ref, cancellable, error)) return FALSE; + if (!ostree_repo_mark_commit_partial (pull_data->repo, to_revision, TRUE, error)) + return FALSE; + if (detached_data && !ostree_repo_write_commit_detached_metadata (pull_data->repo, to_revision, detached_data, @@ -3706,13 +3709,13 @@ all_requested_refs_have_commit (GHashTable *requested_refs /* (element-type Ostr * * `disable-verify-bindings` (`b`): Disable verification of commit bindings. * Since: 2020.9 */ -gboolean -ostree_repo_pull_with_options (OstreeRepo *self, - const char *remote_name_or_baseurl, - GVariant *options, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) +static gboolean +ostree_repo_pull_with_options_internal (OstreeRepo *self, + const char *remote_name_or_baseurl, + GVariant *options, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; g_autoptr(GBytes) bytes_summary = NULL; @@ -5193,6 +5196,50 @@ ostree_repo_pull_with_options (OstreeRepo *self, return ret; } +gboolean +ostree_repo_pull_with_options (OstreeRepo *self, + const char *remote_name_or_baseurl, + GVariant *options, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GError) local_error = NULL; + + if (!ostree_repo_pull_with_options_internal (self, remote_name_or_baseurl, + options, progress, cancellable, + &local_error)) + { + gboolean disable_static_deltas = FALSE, require_static_deltas = FALSE; + + if (options != NULL) + { + g_variant_lookup (options, "disable-static-deltas", "b", &disable_static_deltas); + g_variant_lookup (options, "require-static-deltas", "b", &require_static_deltas); + } + + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && + !disable_static_deltas && !require_static_deltas) + { + g_autoptr(GVariant) modified_options = NULL; + g_auto(GVariantDict) modified_options_dict; + g_variant_dict_init (&modified_options_dict, options); + g_variant_dict_insert (&modified_options_dict, "disable-static-deltas", + "b", TRUE); + modified_options = g_variant_dict_end (&modified_options_dict); + + return ostree_repo_pull_with_options_internal (self, remote_name_or_baseurl, + modified_options, progress, + cancellable, error); + } + + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + return TRUE; +} + /* Structure used in ostree_repo_find_remotes_async() which stores metadata * about a given OSTree commit. This includes the metadata from the commit * #GVariant, plus some working state which is used to work out which remotes diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 86948ee26..891e113df 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -6064,10 +6064,11 @@ summary_add_ref_entry (OstreeRepo *self, g_autoptr(GVariant) commit_obj = NULL; if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit_obj, error)) return FALSE; + g_autoptr(GVariant) orig_metadata = g_variant_get_child_value (commit_obj, 0); g_variant_dict_init (&commit_metadata_builder, NULL); - /* Forward the commit’s timestamp if it’s valid. */ + /* Forward the commit’s timestamp and version if they're valid. */ guint64 commit_timestamp = ostree_commit_get_timestamp (commit_obj); g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc (commit_timestamp); @@ -6075,6 +6076,10 @@ summary_add_ref_entry (OstreeRepo *self, g_variant_dict_insert_value (&commit_metadata_builder, OSTREE_COMMIT_TIMESTAMP, g_variant_new_uint64 (GUINT64_TO_BE (commit_timestamp))); + const char *version = NULL; + if (g_variant_lookup (orig_metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &version)) + g_variant_dict_insert (&commit_metadata_builder, OSTREE_COMMIT_VERSION, "s", version); + g_variant_builder_add_value (refs_builder, g_variant_new ("(s(t@ay@a{sv}))", ref, (guint64) g_variant_get_size (commit_obj), diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index c4ae86d54..e7f23cfdb 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #ifdef HAVE_LIBMOUNT #include @@ -54,6 +56,35 @@ #define OSTREE_DEPLOYMENT_FINALIZING_ID SD_ID128_MAKE(e8,64,6c,d6,3d,ff,46,25,b7,79,09,a8,e7,a4,09,94) #endif +static gboolean +fs_is_fat (int parent_dfd) +{ + struct statfs buf; + + if (TEMP_FAILURE_RETRY (fstatfs (parent_dfd, &buf)) < 0) + return FALSE; + + if (buf.f_type == MSDOS_SUPER_MAGIC) + return TRUE; + + return FALSE; +} + +static gboolean +symlink_fat (const char *target, + int parent_dfd, + const char *linkpath, + GCancellable *cancellable, + GError **error) +{ + if (!glnx_file_replace_contents_at(parent_dfd, linkpath, (guint8*)target, + -1, GLNX_FILE_REPLACE_DATASYNC_NEW, + cancellable, error)) + return FALSE; + + return TRUE; +} + /* * Like symlinkat() but overwrites (atomically) an existing * symlink. @@ -65,6 +96,9 @@ symlink_at_replace (const char *oldpath, GCancellable *cancellable, GError **error) { + g_autofree char *dest_dir = g_path_get_dirname(newpath); + glnx_autofd int dest_dir_dfd = -1; + gboolean fat; /* Possibly in the future generate a temporary random name here, * would need to move "generate a temporary name" code into * libglnx or glib? @@ -74,12 +108,36 @@ symlink_at_replace (const char *oldpath, /* Clean up any stale temporary links */ (void) unlinkat (parent_dfd, temppath, 0); + /* Clean up any stale FAT "symlinks" */ + g_autofree char *temp_fat_path = g_strconcat (temppath, ".sln", NULL); + (void) unlinkat (parent_dfd, temp_fat_path, 0); + + if (!glnx_opendirat (parent_dfd, dest_dir, TRUE, &dest_dir_dfd, error)) + return FALSE; + + fat = fs_is_fat (dest_dir_dfd); + /* Create the temp link */ - if (TEMP_FAILURE_RETRY (symlinkat (oldpath, parent_dfd, temppath)) < 0) - return glnx_throw_errno_prefix (error, "symlinkat"); + if (!fat) + { + if (TEMP_FAILURE_RETRY (symlinkat (oldpath, parent_dfd, temppath)) < 0) + return glnx_throw_errno_prefix (error, "symlinkat"); + } + else + { + if (!symlink_fat (oldpath, parent_dfd, temp_fat_path, cancellable, error)) + return glnx_throw_errno_prefix (error, "symlinkat"); + } /* Rename it into place */ - if (!glnx_renameat (parent_dfd, temppath, parent_dfd, newpath, error)) + if (fat) + { + g_autofree char *new_fat_path = g_strconcat (newpath, ".sln", NULL); + if (!glnx_renameat (parent_dfd, temp_fat_path, parent_dfd, new_fat_path, error)) + return FALSE; + return TRUE; + } + else if (!glnx_renameat (parent_dfd, temppath, parent_dfd, newpath, error)) return FALSE; return TRUE; @@ -991,6 +1049,8 @@ typedef struct { char *initramfs_namever; char *devicetree_srcpath; char *devicetree_namever; + char *efi_blob_srcpath; + char *efi_blob_namever; char *bootcsum; } OstreeKernelLayout; static void @@ -1005,6 +1065,8 @@ _ostree_kernel_layout_free (OstreeKernelLayout *layout) g_free (layout->initramfs_namever); g_free (layout->devicetree_srcpath); g_free (layout->devicetree_namever); + g_free (layout->efi_blob_srcpath); + g_free (layout->efi_blob_namever); g_free (layout->bootcsum); g_free (layout); } @@ -1168,6 +1230,15 @@ get_kernel_from_tree_usrlib_modules (OstreeSysroot *sysroot, g_clear_object (&in); glnx_close_fd (&fd); + if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "payg-image.efi", &fd, error)) + return FALSE; + if (fd != -1) + { + ret_layout->efi_blob_srcpath = g_strdup ("payg-image.efi"); + ret_layout->efi_blob_namever = g_strdup_printf ("payg-image-%s.efi", kver); + } + glnx_close_fd (&fd); + /* And finally, look for any HMAC file. This is needed for FIPS mode on some distros. */ if (!glnx_fstatat_allow_noent (ret_layout->boot_dfd, ".vmlinuz.hmac", NULL, 0, error)) return FALSE; @@ -1731,6 +1802,26 @@ parse_os_release (const char *contents, return ret; } +static gboolean +is_payg_deployment(GCancellable *cancellable) +{ + g_autoptr(GFile) cmdline_file = g_file_new_for_path ("/proc/cmdline"); + g_autofree char *cmdline = NULL; + gsize cmdline_len; + + if (g_getenv ("OSTREE_DEPLOY_PAYG")) + return TRUE; + + if (!g_file_load_contents (cmdline_file, cancellable, + &cmdline, &cmdline_len, NULL, NULL)) + return FALSE; + + if (g_strstr_len (cmdline, cmdline_len, "eospayg")) + return TRUE; + + return FALSE; +} + /* Given @deployment, prepare it to be booted; basically copying its * kernel/initramfs into /boot/ostree (if needed) and writing out an entry in * /boot/loader/entries. @@ -1746,6 +1837,8 @@ install_deployment_kernel (OstreeSysroot *sysroot, GError **error) { + gboolean payg; + GLNX_AUTO_PREFIX_ERROR ("Installing kernel", error); OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); g_autofree char *deployment_dirpath = ostree_sysroot_get_deployment_dirpath (sysroot, deployment); @@ -1785,24 +1878,46 @@ install_deployment_kernel (OstreeSysroot *sysroot, if (!glnx_shutil_mkdir_p_at (sysroot->boot_fd, bootconfdir, 0775, cancellable, error)) return FALSE; + struct stat stbuf; + /* If this is a payg deployment, we want the efi blob and nothing else */ + payg = kernel_layout->efi_blob_srcpath && is_payg_deployment(cancellable); + /* If we're updating an old loader entry that doesn't use efi blobs + * keep it the way it was. */ + payg = payg && !ostree_bootconfig_parser_get (bootconfig, "linux"); + if (payg) + { + g_assert (kernel_layout->efi_blob_namever); + if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->efi_blob_namever, &stbuf, 0, error)) + return FALSE; + if (errno == ENOENT) + { + if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->efi_blob_srcpath, + bootcsum_dfd, kernel_layout->efi_blob_namever, + cancellable, error)) + return FALSE; + } + } + /* Install (hardlink/copy) the kernel into /boot/ostree/osname-${bootcsum} if * it doesn't exist already. */ - struct stat stbuf; - if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->kernel_namever, &stbuf, 0, error)) - return FALSE; - if (errno == ENOENT) + if (!payg) { - if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath, - bootcsum_dfd, kernel_layout->kernel_namever, - cancellable, error)) + if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->kernel_namever, &stbuf, 0, error)) return FALSE; + if (errno == ENOENT) + { + if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath, + bootcsum_dfd, kernel_layout->kernel_namever, + cancellable, error)) + return FALSE; + } } /* If we have an initramfs, then install it into * /boot/ostree/osname-${bootcsum} if it doesn't exist already. */ - if (kernel_layout->initramfs_srcpath) + if (kernel_layout->initramfs_srcpath && !payg) { g_assert (kernel_layout->initramfs_namever); if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->initramfs_namever, &stbuf, 0, error)) @@ -1816,7 +1931,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, } } - if (kernel_layout->devicetree_srcpath) + if (kernel_layout->devicetree_srcpath && !payg) { /* If devicetree_namever is set a single device tree is deployed */ if (kernel_layout->devicetree_namever) @@ -1839,7 +1954,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, } } - if (kernel_layout->kernel_hmac_srcpath) + if (kernel_layout->kernel_hmac_srcpath && !payg) { if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->kernel_hmac_namever, &stbuf, 0, error)) return FALSE; @@ -1853,7 +1968,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, } g_autoptr(GPtrArray) overlay_initrds = NULL; - for (char **it = _ostree_deployment_get_overlay_initrds (deployment); it && *it; it++) + for (char **it = _ostree_deployment_get_overlay_initrds (deployment); !payg && it && *it; it++) { char *checksum = *it; @@ -1961,39 +2076,45 @@ install_deployment_kernel (OstreeSysroot *sysroot, g_autofree char *version_key = g_strdup_printf ("%d", n_deployments - ostree_deployment_get_index (deployment)); ostree_bootconfig_parser_set (bootconfig, OSTREE_COMMIT_META_KEY_VERSION, version_key); - g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->kernel_namever, NULL); - ostree_bootconfig_parser_set (bootconfig, "linux", boot_relpath); + + if (!payg) + { + g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->kernel_namever, NULL); + ostree_bootconfig_parser_set (bootconfig, "linux", boot_relpath); + } val = ostree_bootconfig_parser_get (bootconfig, "options"); g_autoptr(OstreeKernelArgs) kargs = ostree_kernel_args_from_string (val); - if (kernel_layout->initramfs_namever) + if (!payg) { - g_autofree char * initrd_boot_relpath = - g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL); - ostree_bootconfig_parser_set (bootconfig, "initrd", initrd_boot_relpath); + if (kernel_layout->initramfs_namever) + { + g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL); + ostree_bootconfig_parser_set (bootconfig, "initrd", boot_relpath); - if (overlay_initrds) + if (overlay_initrds) + { + g_ptr_array_add (overlay_initrds, NULL); + ostree_bootconfig_parser_set_overlay_initrds (bootconfig, (char**)overlay_initrds->pdata); + } + } + else { - g_ptr_array_add (overlay_initrds, NULL); - ostree_bootconfig_parser_set_overlay_initrds (bootconfig, (char**)overlay_initrds->pdata); + g_autofree char *prepare_root_arg = NULL; + prepare_root_arg = g_strdup_printf ("init=/ostree/boot.%d/%s/%s/%d/usr/lib/ostree/ostree-prepare-root", + new_bootversion, osname, bootcsum, + ostree_deployment_get_bootserial (deployment)); + ostree_kernel_args_replace_take (kargs, g_steal_pointer (&prepare_root_arg)); } } - else - { - g_autofree char *prepare_root_arg = NULL; - prepare_root_arg = g_strdup_printf ("init=/ostree/boot.%d/%s/%s/%d/usr/lib/ostree/ostree-prepare-root", - new_bootversion, osname, bootcsum, - ostree_deployment_get_bootserial (deployment)); - ostree_kernel_args_replace_take (kargs, g_steal_pointer (&prepare_root_arg)); - } - if (kernel_layout->devicetree_namever) + if (kernel_layout->devicetree_namever && !payg) { g_autofree char * dt_boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL); ostree_bootconfig_parser_set (bootconfig, "devicetree", dt_boot_relpath); } - else if (kernel_layout->devicetree_srcpath) + else if (kernel_layout->devicetree_srcpath && !payg) { /* If devicetree_srcpath is set but devicetree_namever is NULL, then we * want to point to a whole directory of device trees. @@ -2003,6 +2124,12 @@ install_deployment_kernel (OstreeSysroot *sysroot, ostree_bootconfig_parser_set (bootconfig, "fdtdir", dt_boot_relpath); } + if (payg) + { + g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->efi_blob_namever, NULL); + ostree_bootconfig_parser_set (bootconfig, "efi", boot_relpath); + } + /* Note this is parsed in ostree-impl-system-generator.c */ g_autofree char *ostree_kernel_arg = g_strdup_printf ("ostree=/ostree/boot.%d/%s/%s/%d", new_bootversion, osname, bootcsum, @@ -2039,11 +2166,18 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, g_assert ((current_bootversion == 0 && new_bootversion == 1) || (current_bootversion == 1 && new_bootversion == 0)); + /* Check if we're on a FAT FS, and unable to symlink */ + gboolean fat = FALSE; + glnx_autofd int dest_dir_dfd = -1; + if (glnx_opendirat (sysroot->sysroot_fd, "boot", TRUE, &dest_dir_dfd, error)) + fat = fs_is_fat (dest_dir_dfd); + /* This allows us to support both /boot on a seperate filesystem to / as well * as on the same filesystem. */ - if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0) - if (errno != EEXIST) - return glnx_throw_errno_prefix (error, "symlinkat"); + if (!fat) + if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0) + if (errno != EEXIST) + return glnx_throw_errno_prefix (error, "symlinkat"); g_autofree char *new_target = g_strdup_printf ("loader.%d", new_bootversion); @@ -2073,12 +2207,22 @@ swap_bootloader (OstreeSysroot *sysroot, if (!_ostree_sysroot_ensure_boot_fd (sysroot, error)) return FALSE; + gboolean fat = fs_is_fat (sysroot->boot_fd); + /* The symlink was already written, and we used syncfs() to ensure * its data is in place. Renaming now should give us atomic semantics; * see https://bugzilla.gnome.org/show_bug.cgi?id=755595 */ - if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error)) - return FALSE; + if (!fat) + { + if (!glnx_renameat (sysroot->boot_fd, "loader.tmp", sysroot->boot_fd, "loader", error)) + return FALSE; + } + else + { + if (!glnx_renameat (sysroot->boot_fd, "loader.tmp.sln", sysroot->boot_fd, "loader.sln", error)) + return FALSE; + } /* Now we explicitly fsync this directory, even though it * isn't required for atomicity, for two reasons: diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index eccf9375a..34b9e1680 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -621,33 +621,38 @@ read_current_bootversion (OstreeSysroot *self, GCancellable *cancellable, GError **error) { - int ret_bootversion; + int ret_bootversion = 0; struct stat stbuf; + g_autofree char *target = NULL; if (!glnx_fstatat_allow_noent (self->sysroot_fd, "boot/loader", &stbuf, AT_SYMLINK_NOFOLLOW, error)) return FALSE; if (errno == ENOENT) { - g_debug ("Didn't find $sysroot/boot/loader symlink; assuming bootversion 0"); - ret_bootversion = 0; + /* Don't share the error because we want error handling to be the same as before we + * added fake symlinks */ + g_debug ("Didn't find $sysroot/boot/loader symlink; trying fake symlink"); + target = glnx_file_get_contents_utf8_at(self->sysroot_fd, "boot/loader.sln", NULL, cancellable, NULL); + if (!target) goto not_found; } else { if (!S_ISLNK (stbuf.st_mode)) return glnx_throw (error, "Not a symbolic link: boot/loader"); - - g_autofree char *target = + target = glnx_readlinkat_malloc (self->sysroot_fd, "boot/loader", cancellable, error); if (!target) return FALSE; - if (g_strcmp0 (target, "loader.0") == 0) - ret_bootversion = 0; - else if (g_strcmp0 (target, "loader.1") == 0) - ret_bootversion = 1; - else - return glnx_throw (error, "Invalid target '%s' in boot/loader", target); } + if (g_strcmp0 (target, "loader.0") == 0) + ret_bootversion = 0; + else if (g_strcmp0 (target, "loader.1") == 0) + ret_bootversion = 1; + else + return glnx_throw (error, "Invalid target '%s' in boot/loader", target); + +not_found: *out_bootversion = ret_bootversion; return TRUE; } diff --git a/src/libotutil/ot-variant-builder.c b/src/libotutil/ot-variant-builder.c index 92ac8edea..b7a7ec759 100644 --- a/src/libotutil/ot-variant-builder.c +++ b/src/libotutil/ot-variant-builder.c @@ -830,7 +830,7 @@ static void ot_variant_builder_info_free (OtVariantBuilderInfo *info) { if (info->parent) - ot_variant_builder_info_free (info); + ot_variant_builder_info_free (info->parent); g_variant_type_free (info->type); g_array_unref (info->child_ends); diff --git a/src/ostree/ot-admin-builtin-finalize-staged.c b/src/ostree/ot-admin-builtin-finalize-staged.c index 80d8a9b79..ac08c21be 100644 --- a/src/ostree/ot-admin-builtin-finalize-staged.c +++ b/src/ostree/ot-admin-builtin-finalize-staged.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2018 Red Hat, Inc. + * Copyright (C) 2022 Endless OS Foundation LLC * * SPDX-License-Identifier: LGPL-2.0+ * @@ -19,8 +20,9 @@ #include "config.h" -#include "config.h" - +#include +#include +#include #include #include "ot-main.h" @@ -32,7 +34,10 @@ #include "ostree-cmdprivate.h" #include "ostree.h" +static gboolean opt_hold; + static GOptionEntry options[] = { + { "hold", 0, 0, G_OPTION_ARG_NONE, &opt_hold, "Hold /boot open during finalization", NULL }, { NULL } }; @@ -50,13 +55,52 @@ ot_admin_builtin_finalize_staged (int argc, char **argv, OstreeCommandInvocation g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeSysroot) sysroot = NULL; + + /* First parse the args without loading the sysroot to see what options are + * set. */ if (!ostree_admin_option_context_parse (context, options, &argc, &argv, - OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, + OSTREE_ADMIN_BUILTIN_FLAG_NO_LOAD, invocation, &sysroot, cancellable, error)) return FALSE; - if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error)) - return FALSE; + if (opt_hold) + { + /* Load the sysroot unlocked so that a separate namespace isn't + * created. */ + if (!ostree_admin_sysroot_load (sysroot, + OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER | OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED, + cancellable, error)) + return FALSE; + + /* In case it's an automount, open /boot so that the automount doesn't + * expire until before this process exits. If it did expire and got + * unmounted, the service would be stopped and the deployment would be + * finalized earlier than expected. + */ + int sysroot_fd = ostree_sysroot_get_fd (sysroot); + glnx_autofd int boot_fd = -1; + g_debug ("Opening /boot directory"); + if (!glnx_opendirat (sysroot_fd, "boot", TRUE, &boot_fd, error)) + return FALSE; + + /* We want to keep /boot open until the deployment is finalized during + * system shutdown, so block until we get SIGTERM which systemd will send + * when the unit is stopped. + */ + pause (); + } + else + { + /* Load the sysroot with the normal flags and actually finalize the + * deployment. */ + if (!ostree_admin_sysroot_load (sysroot, + OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, + cancellable, error)) + return FALSE; + + if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error)) + return FALSE; + } return TRUE; } diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index b874db0fe..509eb792d 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -249,6 +249,11 @@ dump_summary_ref (const char *collection_id, pretty_key = "Timestamp"; value_str = uint64_secs_to_iso8601 (GUINT64_FROM_BE (g_variant_get_uint64 (value))); } + else if (g_strcmp0 (key, OSTREE_COMMIT_VERSION) == 0) + { + pretty_key = "Version"; + value_str = g_strdup (g_variant_get_string (value, NULL)); + } else { value_str = g_variant_print (value, FALSE); diff --git a/src/ostree/ot-main.c b/src/ostree/ot-main.c index 8ee730382..9ad357666 100644 --- a/src/ostree/ot-main.c +++ b/src/ostree/ot-main.c @@ -564,41 +564,11 @@ on_sysroot_journal_msg (OstreeSysroot *sysroot, } gboolean -ostree_admin_option_context_parse (GOptionContext *context, - const GOptionEntry *main_entries, - int *argc, - char ***argv, - OstreeAdminBuiltinFlags flags, - OstreeCommandInvocation *invocation, - OstreeSysroot **out_sysroot, - GCancellable *cancellable, - GError **error) +ostree_admin_sysroot_load (OstreeSysroot *sysroot, + OstreeAdminBuiltinFlags flags, + GCancellable *cancellable, + GError **error) { - /* Entries are listed in --help output in the order added. We add the - * main entries ourselves so that we can add the --sysroot entry first. */ - - g_option_context_add_main_entries (context, global_admin_entries, NULL); - - if (!ostree_option_context_parse (context, main_entries, argc, argv, - invocation, NULL, cancellable, error)) - return FALSE; - - if (!opt_print_current_dir && (flags & OSTREE_ADMIN_BUILTIN_FLAG_NO_SYSROOT)) - { - g_assert_null (out_sysroot); - /* Early return if no sysroot is requested */ - return TRUE; - } - - g_autoptr(GFile) sysroot_path = NULL; - if (opt_sysroot != NULL) - sysroot_path = g_file_new_for_path (opt_sysroot); - - g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_path); - if (!ostree_sysroot_initialize (sysroot, error)) - return FALSE; - g_signal_connect (sysroot, "journal-msg", G_CALLBACK (on_sysroot_journal_msg), NULL); - if ((flags & OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED) == 0) { /* If we're requested to lock the sysroot, first check if we're operating @@ -642,6 +612,51 @@ ostree_admin_option_context_parse (GOptionContext *context, } } + return TRUE; +} + +gboolean +ostree_admin_option_context_parse (GOptionContext *context, + const GOptionEntry *main_entries, + int *argc, + char ***argv, + OstreeAdminBuiltinFlags flags, + OstreeCommandInvocation *invocation, + OstreeSysroot **out_sysroot, + GCancellable *cancellable, + GError **error) +{ + /* Entries are listed in --help output in the order added. We add the + * main entries ourselves so that we can add the --sysroot entry first. */ + + g_option_context_add_main_entries (context, global_admin_entries, NULL); + + if (!ostree_option_context_parse (context, main_entries, argc, argv, + invocation, NULL, cancellable, error)) + return FALSE; + + if (!opt_print_current_dir && (flags & OSTREE_ADMIN_BUILTIN_FLAG_NO_SYSROOT)) + { + g_assert_null (out_sysroot); + /* Early return if no sysroot is requested */ + return TRUE; + } + + g_autoptr(GFile) sysroot_path = NULL; + if (opt_sysroot != NULL) + sysroot_path = g_file_new_for_path (opt_sysroot); + + g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_path); + if (!ostree_sysroot_initialize (sysroot, error)) + return FALSE; + g_signal_connect (sysroot, "journal-msg", G_CALLBACK (on_sysroot_journal_msg), NULL); + + if (opt_print_current_dir || (flags & OSTREE_ADMIN_BUILTIN_FLAG_NO_LOAD) == 0) + { + if (!ostree_admin_sysroot_load (sysroot, flags, cancellable, error)) + return FALSE; + } + if (opt_print_current_dir) { g_autoptr(GPtrArray) deployments = NULL; diff --git a/src/ostree/ot-main.h b/src/ostree/ot-main.h index b369deb87..e296501ae 100644 --- a/src/ostree/ot-main.h +++ b/src/ostree/ot-main.h @@ -36,6 +36,7 @@ typedef enum { OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER = (1 << 0), OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED = (1 << 1), OSTREE_ADMIN_BUILTIN_FLAG_NO_SYSROOT = (1 << 2), + OSTREE_ADMIN_BUILTIN_FLAG_NO_LOAD = (1 << 3), } OstreeAdminBuiltinFlags; @@ -91,6 +92,11 @@ gboolean ostree_admin_option_context_parse (GOptionContext *context, OstreeSysroot **out_sysroot, GCancellable *cancellable, GError **error); +gboolean ostree_admin_sysroot_load (OstreeSysroot *sysroot, + OstreeAdminBuiltinFlags flags, + GCancellable *cancellable, + GError **error); + gboolean ostree_ensure_repo_writable (OstreeRepo *repo, GError **error); void ostree_print_gpg_verify_result (OstreeGpgVerifyResult *result); diff --git a/tests/inst/src/destructive.rs b/tests/inst/src/destructive.rs index 2e2bd374b..41fc53326 100644 --- a/tests/inst/src/destructive.rs +++ b/tests/inst/src/destructive.rs @@ -463,6 +463,7 @@ fn impl_transaction_test>( " systemctl stop rpm-ostreed systemctl stop ostree-finalize-staged + systemctl stop ostree-finalize-staged-hold ostree reset testrepo:{testref} {booted_commit} rpm-ostree cleanup -pbrm ", @@ -504,7 +505,8 @@ fn impl_transaction_test>( InterruptStrategy::Force(ForceInterruptStrategy::Kill9) => { bash!( "systemctl kill -s KILL rpm-ostreed || true - systemctl kill -s KILL ostree-finalize-staged || true" + systemctl kill -s KILL ostree-finalize-staged || true + systemctl kill -s KILL ostree-finalize-staged-hold || true" )?; live_strategy = Some(strategy); } @@ -528,7 +530,8 @@ fn impl_transaction_test>( InterruptStrategy::Polite(PoliteInterruptStrategy::Stop) => { bash!( "systemctl stop rpm-ostreed || true - systemctl stop ostree-finalize-staged || true" + systemctl stop ostree-finalize-staged || true + systemctl stop ostree-finalize-staged-hold || true" )?; live_strategy = Some(strategy); } diff --git a/tests/kolainst/destructive/boot-automount.sh b/tests/kolainst/destructive/boot-automount.sh new file mode 100755 index 000000000..d6d1732e7 --- /dev/null +++ b/tests/kolainst/destructive/boot-automount.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# https://github.com/ostreedev/ostree/issues/2543 +set -xeuo pipefail + +. ${KOLA_EXT_DATA}/libinsttest.sh + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + # Ensure boot is mount point + mountpoint /boot + + # Create an automount unit with an extremely short timeout + cat > /etc/systemd/system/boot.automount <<"EOF" +[Automount] +Where=/boot +TimeoutIdleSec=1 + +[Install] +WantedBy=local-fs.target +EOF + systemctl daemon-reload + systemctl enable boot.automount + + # Unmount /boot, start the automount unit, and ensure the units are + # in the correct states. + umount /boot + systemctl start boot.automount + boot_state=$(systemctl show -P ActiveState boot.mount) + boot_auto_state=$(systemctl show -P ActiveState boot.automount) + assert_streq "${boot_state}" inactive + assert_streq "${boot_auto_state}" active + + # Trigger a new staged deployment and check that the relevant units + # are enabled. + ostree admin deploy --stage --karg-append=somedummykarg=1 "${host_commit}" + rpm-ostree status --json + deployment_staged=$(rpmostree_query_json '.deployments[0].staged') + assert_streq "${deployment_staged}" true + test -f /run/ostree/staged-deployment + finalize_staged_state=$(systemctl show -P ActiveState ostree-finalize-staged.service) + finalize_staged_hold_state=$(systemctl show -P ActiveState ostree-finalize-staged-hold.service) + assert_streq "${finalize_staged_state}" active + assert_streq "${finalize_staged_hold_state}" active + + # Sleep 1 second to ensure the boot automount idle timeout has + # passed and then check that /boot is still mounted. + sleep 1 + boot_state=$(systemctl show -P ActiveState boot.mount) + assert_streq "${boot_state}" active + + /tmp/autopkgtest-reboot "2" + ;; + "2") + rpm-ostree status --json + deployment_staged=$(rpmostree_query_json '.deployments[0].staged') + assert_streq "${deployment_staged}" false + test ! -f /run/ostree/staged-deployment + assert_file_has_content_literal /proc/cmdline somedummykarg=1 + + # Check that the finalize and hold services succeeded in the + # previous boot. Dump them to the test log to help debugging. + prepare_tmpdir + journalctl -b -1 -o short-monotonic \ + -u ostree-finalize-staged.service \ + -u ostree-finalize-staged-hold.service \ + -u boot.mount \ + -u boot.automount \ + > logs.txt + cat logs.txt + assert_file_has_content logs.txt 'ostree-finalize-staged.service: \(Succeeded\|Deactivated successfully\)' + assert_file_has_content logs.txt 'ostree-finalize-staged-hold.service: \(Succeeded\|Deactivated successfully\)' + + # Check that the hold service remained active and kept /boot mounted until + # the finalize service completed. + finalize_stopped=$(journalctl -b -1 -o json -g Stopped -u ostree-finalize-staged.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + hold_stopping=$(journalctl -b -1 -o json -g Stopping -u ostree-finalize-staged-hold.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + hold_stopped=$(journalctl -b -1 -o json -g Stopped -u ostree-finalize-staged-hold.service | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + boot_unmounting=$(journalctl -b -1 -o json -g Unmounting -u boot.mount | tail -n1 | jq -r .__MONOTONIC_TIMESTAMP) + test "${finalize_stopped}" -lt "${hold_stopping}" + test "${hold_stopped}" -lt "${boot_unmounting}" + ;; + *) fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}" ;; +esac diff --git a/tests/test-pull-repeated.sh b/tests/test-pull-repeated.sh index 4c321618b..3b7d10b18 100755 --- a/tests/test-pull-repeated.sh +++ b/tests/test-pull-repeated.sh @@ -21,6 +21,10 @@ set -euo pipefail . $(dirname $0)/libtest.sh +if [ "${OSTREE_TEST_ALLOW_RANDOM:-}" != 1 ]; then + skip "Non-deterministic test will fail if we are unlucky" +fi + COMMIT_SIGN="" if has_gpgme; then COMMIT_SIGN="--gpg-homedir=${TEST_GPG_KEYHOME} --gpg-sign=${TEST_GPG_KEYID_1}" diff --git a/tests/test-summary-view.sh b/tests/test-summary-view.sh index 088628d8a..9dfc74f44 100755 --- a/tests/test-summary-view.sh +++ b/tests/test-summary-view.sh @@ -56,6 +56,7 @@ assert_file_has_content_literal summary.txt "* main" assert_file_has_content_literal summary.txt "* other" assert_file_has_content_literal summary.txt "ostree.summary.last-modified" assert_file_has_content_literal summary.txt "Timestamp (ostree.commit.timestamp): " +assert_file_has_content_literal summary.txt "Version (ostree.commit.version): 3.2" echo "ok view summary" # Check the summary can be viewed raw too.