From 4b7619ec91ccb2fb78f290b24ed270b921d79ee6 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 14 Jun 2019 14:35:52 +0200 Subject: [PATCH 1/3] lib/deploy: move some file system helpers to libotutil The copy_dir_recurse(), dirfd_copy_attributes_and_xattrs() and is_ro_mount() functions could be used in other places, so move them as libotutil helpers. --- src/libostree/ostree-sysroot-deploy.c | 171 +------------------------ src/libostree/ostree-sysroot-private.h | 13 -- src/libotutil/ot-fs-utils.c | 152 ++++++++++++++++++++++ src/libotutil/ot-fs-utils.h | 42 ++++++ 4 files changed, 200 insertions(+), 178 deletions(-) diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 1096b0b071..e807f4cd12 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -25,16 +25,12 @@ #include #include #include -#include #include #include #include #include #include -#ifdef HAVE_LIBMOUNT -#include -#endif #ifdef HAVE_LIBSYSTEMD #include #endif @@ -86,15 +82,6 @@ symlink_at_replace (const char *oldpath, return TRUE; } -static GLnxFileCopyFlags -sysroot_flags_to_copy_flags (GLnxFileCopyFlags defaults, - OstreeSysrootDebugFlags sysrootflags) -{ - if (sysrootflags & OSTREE_SYSROOT_DEBUG_NO_XATTRS) - defaults |= GLNX_FILE_COPY_NOXATTRS; - return defaults; -} - /* Try a hardlink if we can, otherwise fall back to copying. Used * right now for kernels/initramfs/device trees in /boot, where we can just * hardlink if we're on the same partition. @@ -139,101 +126,6 @@ install_into_boot (OstreeSePolicy *sepolicy, return TRUE; } -/* Copy ownership, mode, and xattrs from source directory to destination */ -static gboolean -dirfd_copy_attributes_and_xattrs (int src_parent_dfd, - const char *src_name, - int src_dfd, - int dest_dfd, - OstreeSysrootDebugFlags flags, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GVariant) xattrs = NULL; - - /* Clone all xattrs first, so we get the SELinux security context - * right. This will allow other users access if they have ACLs, but - * oh well. - */ - if (!(flags & OSTREE_SYSROOT_DEBUG_NO_XATTRS)) - { - if (!glnx_dfd_name_get_all_xattrs (src_parent_dfd, src_name, - &xattrs, cancellable, error)) - return FALSE; - if (!glnx_fd_set_all_xattrs (dest_dfd, xattrs, - cancellable, error)) - return FALSE; - } - - struct stat src_stbuf; - if (!glnx_fstat (src_dfd, &src_stbuf, error)) - return FALSE; - if (fchown (dest_dfd, src_stbuf.st_uid, src_stbuf.st_gid) != 0) - return glnx_throw_errno_prefix (error, "fchown"); - if (fchmod (dest_dfd, src_stbuf.st_mode) != 0) - return glnx_throw_errno_prefix (error, "fchmod"); - - return TRUE; -} - -static gboolean -copy_dir_recurse (int src_parent_dfd, - int dest_parent_dfd, - const char *name, - OstreeSysrootDebugFlags flags, - GCancellable *cancellable, - GError **error) -{ - g_auto(GLnxDirFdIterator) src_dfd_iter = { 0, }; - glnx_autofd int dest_dfd = -1; - struct dirent *dent; - - if (!glnx_dirfd_iterator_init_at (src_parent_dfd, name, TRUE, &src_dfd_iter, error)) - return FALSE; - - /* Create with mode 0700, we'll fchmod/fchown later */ - if (!glnx_ensure_dir (dest_parent_dfd, name, 0700, error)) - return FALSE; - - if (!glnx_opendirat (dest_parent_dfd, name, TRUE, &dest_dfd, error)) - return FALSE; - - if (!dirfd_copy_attributes_and_xattrs (src_parent_dfd, name, src_dfd_iter.fd, dest_dfd, - flags, cancellable, error)) - return FALSE; - - while (TRUE) - { - struct stat child_stbuf; - - if (!glnx_dirfd_iterator_next_dent (&src_dfd_iter, &dent, cancellable, error)) - return FALSE; - if (dent == NULL) - break; - - if (!glnx_fstatat (src_dfd_iter.fd, dent->d_name, &child_stbuf, - AT_SYMLINK_NOFOLLOW, error)) - return FALSE; - - if (S_ISDIR (child_stbuf.st_mode)) - { - if (!copy_dir_recurse (src_dfd_iter.fd, dest_dfd, dent->d_name, - flags, cancellable, error)) - return FALSE; - } - else - { - if (!glnx_file_copy_at (src_dfd_iter.fd, dent->d_name, &child_stbuf, - dest_dfd, dent->d_name, - sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), - cancellable, error)) - return FALSE; - } - } - - return TRUE; -} - /* If a chain of directories is added, this function will ensure * they're created. */ @@ -290,8 +182,8 @@ ensure_directory_from_template (int orig_etc_fd, if (!glnx_opendirat (new_etc_fd, path, TRUE, &target_dfd, error)) return FALSE; - if (!dirfd_copy_attributes_and_xattrs (modified_etc_fd, path, src_dfd, target_dfd, - flags, cancellable, error)) + if (!ot_dirfd_copy_attributes_and_xattrs (modified_etc_fd, path, src_dfd, target_dfd, + flags, cancellable, error)) return FALSE; if (out_dfd) @@ -365,15 +257,15 @@ copy_modified_config_file (int orig_etc_fd, if (S_ISDIR (modified_stbuf.st_mode)) { - if (!copy_dir_recurse (modified_etc_fd, new_etc_fd, path, flags, - cancellable, error)) + if (!ot_copy_dir_recurse (modified_etc_fd, new_etc_fd, path, flags, + cancellable, error)) return FALSE; } else if (S_ISLNK (modified_stbuf.st_mode) || S_ISREG (modified_stbuf.st_mode)) { if (!glnx_file_copy_at (modified_etc_fd, path, &modified_stbuf, new_etc_fd, path, - sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), + ot_sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), cancellable, error)) return FALSE; } @@ -1979,57 +1871,6 @@ cleanup_legacy_current_symlinks (OstreeSysroot *self, return TRUE; } -/* Detect whether or not @path refers to a read-only mountpoint. This is - * currently just used to handle a potentially read-only /boot by transiently - * remounting it read-write. In the future we might also do this for e.g. - * /sysroot. - */ -static gboolean -is_ro_mount (const char *path) -{ -#ifdef HAVE_LIBMOUNT - /* Dragging in all of this crud is apparently necessary just to determine - * whether something is a mount point. - * - * Systemd has a totally different implementation in - * src/basic/mount-util.c. - */ - struct libmnt_table *tb = mnt_new_table_from_file ("/proc/self/mountinfo"); - struct libmnt_fs *fs; - struct libmnt_cache *cache; - gboolean is_mount = FALSE; - struct statvfs stvfsbuf; - - if (!tb) - return FALSE; - - /* to canonicalize all necessary paths */ - cache = mnt_new_cache (); - mnt_table_set_cache (tb, cache); - - fs = mnt_table_find_target(tb, path, MNT_ITER_BACKWARD); - is_mount = fs && mnt_fs_get_target (fs); -#ifdef HAVE_MNT_UNREF_CACHE - mnt_unref_table (tb); - mnt_unref_cache (cache); -#else - mnt_free_table (tb); - mnt_free_cache (cache); -#endif - - if (!is_mount) - return FALSE; - - /* We *could* parse the options, but it seems more reliable to - * introspect the actual mount at runtime. - */ - if (statvfs (path, &stvfsbuf) == 0) - return (stvfsbuf.f_flag & ST_RDONLY) != 0; - -#endif - return FALSE; -} - /** * ostree_sysroot_write_deployments: * @self: Sysroot @@ -2331,7 +2172,7 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, { gboolean boot_was_ro_mount = FALSE; if (self->booted_deployment) - boot_was_ro_mount = is_ro_mount ("/boot"); + boot_was_ro_mount = ot_is_ro_mount ("/boot"); g_debug ("boot is ro: %s", boot_was_ro_mount ? "yes" : "no"); diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index e4b2039e2f..eef50160ac 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -28,19 +28,6 @@ G_BEGIN_DECLS -typedef enum { - - /* Don't flag deployments as immutable. */ - OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS = 1 << 0, - /* See https://github.com/ostreedev/ostree/pull/759 */ - OSTREE_SYSROOT_DEBUG_NO_XATTRS = 1 << 1, - /* https://github.com/ostreedev/ostree/pull/1049 */ - OSTREE_SYSROOT_DEBUG_TEST_FIFREEZE = 1 << 2, - /* This is a temporary flag until we fully drop the explicit `systemctl start - * ostree-finalize-staged.service` so that tests can exercise the new path unit. */ - OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3, -} OstreeSysrootDebugFlags; - /** * OstreeSysroot: * Internal struct diff --git a/src/libotutil/ot-fs-utils.c b/src/libotutil/ot-fs-utils.c index c4fcd56f11..c09eb96e3c 100644 --- a/src/libotutil/ot-fs-utils.c +++ b/src/libotutil/ot-fs-utils.c @@ -23,11 +23,17 @@ #include "ot-fs-utils.h" #include "libglnx.h" +#include +#include #include #include #include #include +#ifdef HAVE_LIBMOUNT +#include +#endif + /* Convert a fd-relative path to a GFile* - use * for legacy code. */ @@ -247,3 +253,149 @@ ot_parse_file_by_line (const char *path, return TRUE; } + +/* Copy ownership, mode, and xattrs from source directory to destination */ +gboolean +ot_dirfd_copy_attributes_and_xattrs (int src_parent_dfd, + const char *src_name, + int src_dfd, + int dest_dfd, + OstreeSysrootDebugFlags flags, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) xattrs = NULL; + + /* Clone all xattrs first, so we get the SELinux security context + * right. This will allow other users access if they have ACLs, but + * oh well. + */ + if (!(flags & OSTREE_SYSROOT_DEBUG_NO_XATTRS)) + { + if (!glnx_dfd_name_get_all_xattrs (src_parent_dfd, src_name, + &xattrs, cancellable, error)) + return FALSE; + if (!glnx_fd_set_all_xattrs (dest_dfd, xattrs, + cancellable, error)) + return FALSE; + } + + struct stat src_stbuf; + if (!glnx_fstat (src_dfd, &src_stbuf, error)) + return FALSE; + if (fchown (dest_dfd, src_stbuf.st_uid, src_stbuf.st_gid) != 0) + return glnx_throw_errno_prefix (error, "fchown"); + if (fchmod (dest_dfd, src_stbuf.st_mode) != 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + return TRUE; +} + +gboolean +ot_copy_dir_recurse (int src_parent_dfd, + int dest_parent_dfd, + const char *name, + OstreeSysrootDebugFlags flags, + GCancellable *cancellable, + GError **error) +{ + g_auto(GLnxDirFdIterator) src_dfd_iter = { 0, }; + glnx_autofd int dest_dfd = -1; + struct dirent *dent; + + if (!glnx_dirfd_iterator_init_at (src_parent_dfd, name, TRUE, &src_dfd_iter, error)) + return FALSE; + + /* Create with mode 0700, we'll fchmod/fchown later */ + if (!glnx_ensure_dir (dest_parent_dfd, name, 0700, error)) + return FALSE; + + if (!glnx_opendirat (dest_parent_dfd, name, TRUE, &dest_dfd, error)) + return FALSE; + + if (!ot_dirfd_copy_attributes_and_xattrs (src_parent_dfd, name, src_dfd_iter.fd, dest_dfd, + flags, cancellable, error)) + return FALSE; + + while (TRUE) + { + struct stat child_stbuf; + + if (!glnx_dirfd_iterator_next_dent (&src_dfd_iter, &dent, cancellable, error)) + return FALSE; + if (dent == NULL) + break; + + if (!glnx_fstatat (src_dfd_iter.fd, dent->d_name, &child_stbuf, + AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + + if (S_ISDIR (child_stbuf.st_mode)) + { + if (!ot_copy_dir_recurse (src_dfd_iter.fd, dest_dfd, dent->d_name, + flags, cancellable, error)) + return FALSE; + } + else + { + if (!glnx_file_copy_at (src_dfd_iter.fd, dent->d_name, &child_stbuf, + dest_dfd, dent->d_name, + ot_sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), + cancellable, error)) + return FALSE; + } + } + + return TRUE; +} + +/* Detect whether or not @path refers to a read-only mountpoint. This is + * currently just used to handle a potentially read-only /boot by transiently + * remounting it read-write. In the future we might also do this for e.g. + * /sysroot. + */ +gboolean +ot_is_ro_mount (const char *path) +{ +#ifdef HAVE_LIBMOUNT + /* Dragging in all of this crud is apparently necessary just to determine + * whether something is a mount point. + * + * Systemd has a totally different implementation in + * src/basic/mount-util.c. + */ + struct libmnt_table *tb = mnt_new_table_from_file ("/proc/self/mountinfo"); + struct libmnt_fs *fs; + struct libmnt_cache *cache; + gboolean is_mount = FALSE; + struct statvfs stvfsbuf; + + if (!tb) + return FALSE; + + /* to canonicalize all necessary paths */ + cache = mnt_new_cache (); + mnt_table_set_cache (tb, cache); + + fs = mnt_table_find_target(tb, path, MNT_ITER_BACKWARD); + is_mount = fs && mnt_fs_get_target (fs); +#ifdef HAVE_MNT_UNREF_CACHE + mnt_unref_table (tb); + mnt_unref_cache (cache); +#else + mnt_free_table (tb); + mnt_free_cache (cache); +#endif + + if (!is_mount) + return FALSE; + + /* We *could* parse the options, but it seems more reliable to + * introspect the actual mount at runtime. + */ + if (statvfs (path, &stvfsbuf) == 0) + return (stvfsbuf.f_flag & ST_RDONLY) != 0; + +#endif + return FALSE; +} diff --git a/src/libotutil/ot-fs-utils.h b/src/libotutil/ot-fs-utils.h index 74a0fed6d8..d01ae0f868 100644 --- a/src/libotutil/ot-fs-utils.h +++ b/src/libotutil/ot-fs-utils.h @@ -26,6 +26,19 @@ G_BEGIN_DECLS +typedef enum { + + /* Don't flag deployments as immutable. */ + OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS = 1 << 0, + /* See https://github.com/ostreedev/ostree/pull/759 */ + OSTREE_SYSROOT_DEBUG_NO_XATTRS = 1 << 1, + /* https://github.com/ostreedev/ostree/pull/1049 */ + OSTREE_SYSROOT_DEBUG_TEST_FIFREEZE = 1 << 2, + /* This is a temporary flag until we fully drop the explicit `systemctl start + * ostree-finalize-staged.service` so that tests can exercise the new path unit. */ + OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3, +} OstreeSysrootDebugFlags; + /* A little helper to call unlinkat() as a cleanup * function. Mostly only necessary to handle * deletion of temporary symlinks. @@ -52,6 +65,15 @@ ot_cleanup_unlinkat (OtCleanupUnlinkat *cleanup) } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(OtCleanupUnlinkat, ot_cleanup_unlinkat) +static inline GLnxFileCopyFlags +ot_sysroot_flags_to_copy_flags (GLnxFileCopyFlags defaults, + OstreeSysrootDebugFlags sysrootflags) +{ + if (sysrootflags & OSTREE_SYSROOT_DEBUG_NO_XATTRS) + defaults |= GLNX_FILE_COPY_NOXATTRS; + return defaults; +} + GFile * ot_fdrel_to_gfile (int dfd, const char *path); gboolean ot_readlinkat_gfile_info (int dfd, @@ -97,4 +119,24 @@ ot_parse_file_by_line (const char *path, GCancellable *cancellable, GError **error); +gboolean +ot_dirfd_copy_attributes_and_xattrs (int src_parent_dfd, + const char *src_name, + int src_dfd, + int dest_dfd, + OstreeSysrootDebugFlags flags, + GCancellable *cancellable, + GError **error); + +gboolean +ot_copy_dir_recurse (int src_parent_dfd, + int dest_parent_dfd, + const char *name, + OstreeSysrootDebugFlags flags, + GCancellable *cancellable, + GError **error); + +gboolean +ot_is_ro_mount (const char *path); + G_END_DECLS From 8d0ab6d38c5453d296714ef11fb9a5df08bda305 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 14 Jun 2019 14:36:01 +0200 Subject: [PATCH 2/3] libotutil: add a ot_is_rw_mount() helper function There's already a function to check if a path is a read-only mountpoint, so add a function to also check for mountpoints that are not read-only. --- src/libotutil/ot-fs-utils.c | 37 ++++++++++++++++++++++++++++++------- src/libotutil/ot-fs-utils.h | 3 +++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/libotutil/ot-fs-utils.c b/src/libotutil/ot-fs-utils.c index c09eb96e3c..a9948a3eea 100644 --- a/src/libotutil/ot-fs-utils.c +++ b/src/libotutil/ot-fs-utils.c @@ -349,13 +349,11 @@ ot_copy_dir_recurse (int src_parent_dfd, return TRUE; } -/* Detect whether or not @path refers to a read-only mountpoint. This is - * currently just used to handle a potentially read-only /boot by transiently - * remounting it read-write. In the future we might also do this for e.g. - * /sysroot. +/* Detect whether or not @path refers to a mountpoint. If is a mountpoint + * the struct statvfs .f_flag is returned in @flag to get the mount flags. */ -gboolean -ot_is_ro_mount (const char *path) +static gboolean +is_mount(const char *path, unsigned long *flag) { #ifdef HAVE_LIBMOUNT /* Dragging in all of this crud is apparently necessary just to determine @@ -394,8 +392,33 @@ ot_is_ro_mount (const char *path) * introspect the actual mount at runtime. */ if (statvfs (path, &stvfsbuf) == 0) - return (stvfsbuf.f_flag & ST_RDONLY) != 0; + { + *flag = stvfsbuf.f_flag; + return TRUE; + } #endif return FALSE; } + +/* Detect whether or not @path refers to a read-only mountpoint. This is + * currently just used to handle a potentially read-only /boot by transiently + * remounting it read-write. In the future we might also do this for e.g. + * /sysroot. + */ +gboolean +ot_is_ro_mount (const char *path) +{ + unsigned long flag; + return is_mount (path, &flag) && (flag & ST_RDONLY) != 0; +} + +/* Detect whether or not @path refers to a mountpoint that is not read-only. + * This is currently used to check if /boot/efi is a read-write mountpoint. + */ +gboolean +ot_is_rw_mount (const char *path) +{ + unsigned long flag; + return is_mount (path, &flag) && (flag & ST_RDONLY) == 0; +} diff --git a/src/libotutil/ot-fs-utils.h b/src/libotutil/ot-fs-utils.h index d01ae0f868..ade48788da 100644 --- a/src/libotutil/ot-fs-utils.h +++ b/src/libotutil/ot-fs-utils.h @@ -139,4 +139,7 @@ ot_copy_dir_recurse (int src_parent_dfd, gboolean ot_is_ro_mount (const char *path); +gboolean +ot_is_rw_mount (const char *path); + G_END_DECLS From b9f2b65d2e8f335e8fff7dfd376d3a473b7bfc56 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Fri, 14 Jun 2019 14:36:07 +0200 Subject: [PATCH 3/3] admin: Add an esp-upgrade subcommand The files in the EFI System Partition (ESP) are never updated by OSTree, users will always have the same ESP that was created during installation. This happens because the ESP uses a vfat filesystem which doesn't support symbolic links so the upgrade can't be atomic and part of the transaction. But since the ESP contains the bootloader components (shim, grub, etc) and other important EFI binaries like the ones used for firmware update, there should be a way to upgrade the ESP with the latest version of these files. These binaries are stored in the /usr/lib/ostree-boot/efi directory of a deployment, because rpm-ostree places there everything that is in /boot. Add a ostree-admin-esp-upgrade subcommand that just copies the content of /usr/lib/ostree-boot/efi to /boot/efi, so users can update the ESP and get the latest version of the files that are present in the current deployment. This sub-command only works on a system that was booted with EFI and has the ESP mounted in /boot/efi. Closes: #1649 --- Makefile-man.am | 2 +- Makefile-ostree.am | 1 + Makefile-tests.am | 1 + man/ostree-admin-esp-upgrade.xml | 70 +++++++++++++++ src/ostree/ot-admin-builtin-esp-upgrade.c | 104 ++++++++++++++++++++++ src/ostree/ot-admin-builtins.h | 1 + src/ostree/ot-builtin-admin.c | 3 + tests/test-admin-esp-upgrade.sh | 66 ++++++++++++++ 8 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 man/ostree-admin-esp-upgrade.xml create mode 100644 src/ostree/ot-admin-builtin-esp-upgrade.c create mode 100755 tests/test-admin-esp-upgrade.sh diff --git a/Makefile-man.am b/Makefile-man.am index 8ccbba8c6e..fee5b89721 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -26,7 +26,7 @@ ostree-admin-config-diff.1 ostree-admin-deploy.1 \ ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 \ ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 \ ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1 \ -ostree-admin-pin.1 \ +ostree-admin-pin.1 ostree-admin-esp-upgrade.1 \ ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 \ ostree-commit.1 ostree-create-usb.1 ostree-export.1 ostree-gpg-sign.1 \ ostree-config.1 ostree-diff.1 ostree-find-remotes.1 ostree-fsck.1 \ diff --git a/Makefile-ostree.am b/Makefile-ostree.am index 8d352e38fd..7ae761eb3b 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -79,6 +79,7 @@ ostree_SOURCES += \ src/ostree/ot-admin-builtin-status.c \ src/ostree/ot-admin-builtin-switch.c \ src/ostree/ot-admin-builtin-pin.c \ + src/ostree/ot-admin-builtin-esp-upgrade.c \ src/ostree/ot-admin-builtin-upgrade.c \ src/ostree/ot-admin-builtin-unlock.c \ src/ostree/ot-admin-builtins.h \ diff --git a/Makefile-tests.am b/Makefile-tests.am index 2c0916f620..9a2bdc8f46 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -100,6 +100,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-admin-deploy-karg.sh \ tests/test-admin-deploy-switch.sh \ tests/test-admin-deploy-etcmerge-cornercases.sh \ + tests/test-admin-esp-upgrade.sh \ tests/test-admin-deploy-uboot.sh \ tests/test-admin-deploy-grub2.sh \ tests/test-admin-deploy-none.sh \ diff --git a/man/ostree-admin-esp-upgrade.xml b/man/ostree-admin-esp-upgrade.xml new file mode 100644 index 0000000000..48158debb6 --- /dev/null +++ b/man/ostree-admin-esp-upgrade.xml @@ -0,0 +1,70 @@ + + + + + + + + + ostree admin esp-upgrade + OSTree + + + + Developer + Javier + Martinez Canillas + javierm@redhat.com + + + + + + ostree admin esp-upgrade + 1 + + + + ostree-admin-esp-upgrade + Upgrade the EFI System Partition (ESP) with files from the current deployment + + + + + ostree admin esp-upgrade + + + + + Description + + + Upgrade the EFI System Partition (ESP) with the files in the /usr/lib/ostree-boot/efi directory of the current deployment. + + + + + Example + $ ostree admin esp-upgrade + + diff --git a/src/ostree/ot-admin-builtin-esp-upgrade.c b/src/ostree/ot-admin-builtin-esp-upgrade.c new file mode 100644 index 0000000000..681e16df45 --- /dev/null +++ b/src/ostree/ot-admin-builtin-esp-upgrade.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019 Red Hat, Inc. + * + * 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. + * + * Author: Javier Martinez Canillas + */ + +#include "config.h" + +#include "ostree-sysroot-private.h" +#include "ot-main.h" +#include "ot-admin-builtins.h" +#include "ot-admin-functions.h" +#include "otutil.h" + +static GOptionEntry options[] = { + { NULL } +}; + +gboolean +ot_admin_builtin_esp_upgrade (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new (""); + + g_autoptr(OstreeSysroot) sysroot = NULL; + if (!ostree_admin_option_context_parse (context, options, &argc, &argv, + OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED, + invocation, &sysroot, cancellable, error)) + return FALSE; + + g_autoptr(OstreeRepo) repo = NULL; + if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error)) + return FALSE; + + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); + + if (deployments->len == 0) + { + g_print ("No deployments.\n"); + return TRUE; + } + + OstreeDeployment *deployment = ostree_sysroot_get_booted_deployment (sysroot); + + if (!deployment) + deployment = ot_admin_get_indexed_deployment (sysroot, 0, error); + + struct stat stbuf; + + if (!glnx_fstatat_allow_noent (sysroot->sysroot_fd, "sys/firmware/efi", &stbuf, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + + if (errno == ENOENT) + { + g_print ("Not an EFI system.\n"); + return TRUE; + } + + if (!ot_is_rw_mount ("/boot/efi")) + { + if (ot_is_ro_mount ("/boot/efi")) + g_print ("The ESP can't be updated because /boot/efi is a read-only mountpoint.\n"); + else + g_print ("Only ESP mounted in /boot/efi is supported.\n"); + return TRUE; + } + + g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (sysroot, deployment); + + g_autofree char *new_esp_path = g_strdup_printf ("%s/usr/lib/ostree-boot", deployment_path); + + GLNX_AUTO_PREFIX_ERROR ("During copy files to the ESP", error); + glnx_autofd int old_esp_fd = -1; + if (!glnx_opendirat (sysroot->sysroot_fd, "boot", TRUE, &old_esp_fd, error)) + return FALSE; + + glnx_autofd int new_esp_fd = -1; + if (!glnx_opendirat (sysroot->sysroot_fd, new_esp_path, TRUE, &new_esp_fd, error)) + return FALSE; + + /* The ESP filesystem is vfat so don't attempt to copy ownership, mode, and xattrs */ + const OstreeSysrootDebugFlags flags = sysroot->debug_flags | OSTREE_SYSROOT_DEBUG_NO_XATTRS; + + if (!ot_copy_dir_recurse (new_esp_fd, old_esp_fd, "efi", flags , cancellable, error)) + return FALSE; + + return TRUE; +} diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h index d88fc0b907..5efb7101b0 100644 --- a/src/ostree/ot-admin-builtins.h +++ b/src/ostree/ot-admin-builtins.h @@ -40,6 +40,7 @@ BUILTINPROTO(undeploy); BUILTINPROTO(deploy); BUILTINPROTO(cleanup); BUILTINPROTO(pin); +BUILTINPROTO(esp_upgrade); BUILTINPROTO(finalize_staged); BUILTINPROTO(unlock); BUILTINPROTO(status); diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index 9f1a61562a..531a40da47 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -57,6 +57,9 @@ static OstreeCommand admin_subcommands[] = { { "pin", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_pin, "Change the \"pinning\" state of a deployment" }, + { "esp-upgrade", OSTREE_BUILTIN_FLAG_NO_REPO, + ot_admin_builtin_esp_upgrade, + "Upgrade the ESP with files from the current deployment" }, { "set-origin", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_set_origin, "Set Origin and create a new origin file" }, diff --git a/tests/test-admin-esp-upgrade.sh b/tests/test-admin-esp-upgrade.sh new file mode 100755 index 0000000000..ba5bcf60cb --- /dev/null +++ b/tests/test-admin-esp-upgrade.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Copyright (C) 2019 Red Hat, Inc +# +# 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 + +id=$(id -u) + +if test ${id} != 0; then + skip "this test needs to set up mount namespaces, rerun as root" +fi + +# Exports OSTREE_SYSROOT so --sysroot not needed. +setup_os_repository "archive" "sysroot.bootloader none" + +echo "1..1" + +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime +rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +export rev +echo "rev=${rev}" +${CMD_PREFIX} ostree admin deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime +assert_has_dir sysroot/boot/ostree/testos-${bootcsum} + +usr=sysroot/ostree/deploy/testos/deploy/${rev}.0/usr + +# Create /usr/lib/ostree-boot/efi dir and some test files +mkdir -p ${usr}/lib/ostree-boot/efi/EFI +touch ${usr}/lib/ostree-boot/efi/file-a +touch ${usr}/lib/ostree-boot/efi/EFI/file-b + +cd ${test_tmpdir} + +# ostree-admin-esp-upgrade checks if /sys/firmware/efi exists +# and /boot/efi is a mountpoint. +mkdir -p sysroot/sys/firmware/efi +mkdir -p sysroot/boot/efi +mount --bind sysroot/boot/efi /boot/efi + +${CMD_PREFIX} ostree admin esp-upgrade + +assert_has_file sysroot/boot/efi/file-a +assert_has_file sysroot/boot/efi/EFI/file-b + +umount /boot/efi + +echo "ok"