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/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..a9948a3eea 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,172 @@ 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 mountpoint. If is a mountpoint
+ * the struct statvfs .f_flag is returned in @flag to get the mount flags.
+ */
+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
+ * 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)
+ {
+ *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 74a0fed6d8..ade48788da 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,27 @@ 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);
+
+gboolean
+ot_is_rw_mount (const char *path);
+
G_END_DECLS
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"