Blame SOURCES/0001-Add-an-ostree-boot-complete.service-to-propagate-sta.patch

b89242
From a6d45dc165e48e2a463880ebb90f34c2b9d3c4ce Mon Sep 17 00:00:00 2001
b89242
From: Colin Walters <walters@verbum.org>
b89242
Date: Fri, 22 Apr 2022 18:46:28 -0400
b89242
Subject: [PATCH 1/6] Add an `ostree-boot-complete.service` to propagate
b89242
 staging failures
b89242
b89242
Quite a while ago we added staged deployments, which solved
b89242
a bunch of issues around the `/etc` merge.  However...a persistent
b89242
problem since then is that any failures in that process that
b89242
happened in the *previous* boot are not very visible.
b89242
b89242
We ship custom code in `rpm-ostree status` to query the previous
b89242
journal.  But that has a few problems - one is that on systems
b89242
that have been up a while, that failure message may even get
b89242
rotated out.  And second, some systems may not even have a persistent
b89242
journal at all.
b89242
b89242
A general thing we do in e.g. Fedora CoreOS testing is to check
b89242
for systemd unit failures.  We do that both in our automated tests,
b89242
and we even ship code that displays them on ssh logins.  And beyond
b89242
that obviously a lot of other projects do the same; it's easy via
b89242
`systemctl --failed`.
b89242
b89242
So to make failures more visible, change our `ostree-finalize-staged.service`
b89242
to have an internal wrapper around the process that "catches" any
b89242
errors, and copies the error message into a file in `/boot/ostree`.
b89242
b89242
Then, a new `ostree-boot-complete.service` looks for this file on
b89242
startup and re-emits the error message, and fails.
b89242
b89242
It also deletes the file.  The rationale is to avoid *continually*
b89242
warning.  For example we need to handle the case when an upgrade
b89242
process creates a new staged deployment.  Now, we could change the
b89242
ostree core code to delete the warning file when that happens instead,
b89242
but this is trying to be a conservative change.
b89242
b89242
This should make failures here much more visible as is.
b89242
---
b89242
 Makefile-boot.am                             |  2 +
b89242
 Makefile-ostree.am                           |  1 +
b89242
 src/boot/ostree-boot-complete.service        | 33 +++++++++++
b89242
 src/libostree/ostree-cmdprivate.c            |  1 +
b89242
 src/libostree/ostree-cmdprivate.h            |  1 +
b89242
 src/libostree/ostree-impl-system-generator.c |  2 +
b89242
 src/libostree/ostree-sysroot-deploy.c        | 62 ++++++++++++++++++--
b89242
 src/libostree/ostree-sysroot-private.h       |  7 +++
b89242
 src/libostree/ostree-sysroot.c               |  2 +
b89242
 src/ostree/ot-admin-builtin-boot-complete.c  | 58 ++++++++++++++++++
b89242
 src/ostree/ot-admin-builtins.h               |  1 +
b89242
 src/ostree/ot-builtin-admin.c                |  3 +
b89242
 tests/kolainst/destructive/staged-deploy.sh  | 12 ++++
b89242
 13 files changed, 181 insertions(+), 4 deletions(-)
b89242
 create mode 100644 src/boot/ostree-boot-complete.service
b89242
 create mode 100644 src/ostree/ot-admin-builtin-boot-complete.c
b89242
b89242
diff --git a/Makefile-boot.am b/Makefile-boot.am
b89242
index ec10a0d6..e42e5180 100644
b89242
--- a/Makefile-boot.am
b89242
+++ b/Makefile-boot.am
b89242
@@ -38,6 +38,7 @@ endif
b89242
 if BUILDOPT_SYSTEMD
b89242
 systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \
b89242
 	src/boot/ostree-remount.service \
b89242
+	src/boot/ostree-boot-complete.service \
b89242
 	src/boot/ostree-finalize-staged.service \
b89242
 	src/boot/ostree-finalize-staged.path \
b89242
 	$(NULL)
b89242
@@ -64,6 +65,7 @@ endif
b89242
 EXTRA_DIST += src/boot/dracut/module-setup.sh \
b89242
 	src/boot/dracut/ostree.conf \
b89242
 	src/boot/mkinitcpio \
b89242
+	src/boot/ostree-boot-complete.service \
b89242
 	src/boot/ostree-prepare-root.service \
b89242
 	src/boot/ostree-finalize-staged.path \
b89242
 	src/boot/ostree-remount.service \
b89242
diff --git a/Makefile-ostree.am b/Makefile-ostree.am
b89242
index 82af1681..0fe2c5f8 100644
b89242
--- a/Makefile-ostree.am
b89242
+++ b/Makefile-ostree.am
b89242
@@ -70,6 +70,7 @@ ostree_SOURCES += \
b89242
 	src/ostree/ot-admin-builtin-diff.c \
b89242
 	src/ostree/ot-admin-builtin-deploy.c \
b89242
 	src/ostree/ot-admin-builtin-finalize-staged.c \
b89242
+	src/ostree/ot-admin-builtin-boot-complete.c \
b89242
 	src/ostree/ot-admin-builtin-undeploy.c \
b89242
 	src/ostree/ot-admin-builtin-instutil.c \
b89242
 	src/ostree/ot-admin-builtin-cleanup.c \
b89242
diff --git a/src/boot/ostree-boot-complete.service b/src/boot/ostree-boot-complete.service
b89242
new file mode 100644
b89242
index 00000000..5c09fdc9
b89242
--- /dev/null
b89242
+++ b/src/boot/ostree-boot-complete.service
b89242
@@ -0,0 +1,33 @@
b89242
+# Copyright (C) 2022 Red Hat, Inc.
b89242
+#
b89242
+# This library is free software; you can redistribute it and/or
b89242
+# modify it under the terms of the GNU Lesser General Public
b89242
+# License as published by the Free Software Foundation; either
b89242
+# version 2 of the License, or (at your option) any later version.
b89242
+#
b89242
+# This library is distributed in the hope that it will be useful,
b89242
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
b89242
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
b89242
+# Lesser General Public License for more details.
b89242
+#
b89242
+# You should have received a copy of the GNU Lesser General Public
b89242
+# License along with this library. If not, see <https://www.gnu.org/licenses/>.
b89242
+
b89242
+[Unit]
b89242
+Description=OSTree Complete Boot
b89242
+Documentation=man:ostree(1)
b89242
+# For now, this is the only condition on which we start, but it's
b89242
+# marked as a triggering condition in case in the future we want
b89242
+# to do something else.
b89242
+ConditionPathExists=|/boot/ostree/finalize-failure.stamp
b89242
+RequiresMountsFor=/boot
b89242
+# Ensure that we propagate the failure into the current boot before
b89242
+# any further finalization attempts.
b89242
+Before=ostree-finalize-staged.service
b89242
+
b89242
+[Service]
b89242
+Type=oneshot
b89242
+# To write to /boot while keeping it read-only
b89242
+MountFlags=slave
b89242
+RemainAfterExit=yes
b89242
+ExecStart=/usr/bin/ostree admin boot-complete
b89242
diff --git a/src/libostree/ostree-cmdprivate.c b/src/libostree/ostree-cmdprivate.c
b89242
index c9a6e2e1..f6c114f4 100644
b89242
--- a/src/libostree/ostree-cmdprivate.c
b89242
+++ b/src/libostree/ostree-cmdprivate.c
b89242
@@ -51,6 +51,7 @@ ostree_cmd__private__ (void)
b89242
     _ostree_repo_static_delta_delete,
b89242
     _ostree_repo_verify_bindings,
b89242
     _ostree_sysroot_finalize_staged,
b89242
+    _ostree_sysroot_boot_complete,
b89242
   };
b89242
 
b89242
   return &table;
b89242
diff --git a/src/libostree/ostree-cmdprivate.h b/src/libostree/ostree-cmdprivate.h
b89242
index 46452ebd..17f943c8 100644
b89242
--- a/src/libostree/ostree-cmdprivate.h
b89242
+++ b/src/libostree/ostree-cmdprivate.h
b89242
@@ -33,6 +33,7 @@ typedef struct {
b89242
   gboolean (* ostree_static_delta_delete) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
b89242
   gboolean (* ostree_repo_verify_bindings) (const char *collection_id, const char *ref_name, GVariant *commit, GError **error);
b89242
   gboolean (* ostree_finalize_staged) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
b89242
+  gboolean (* ostree_boot_complete) (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error);
b89242
 } OstreeCmdPrivateVTable;
b89242
 
b89242
 /* Note this not really "public", we just export the symbol, but not the header */
b89242
diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c
b89242
index 769f0cbd..92d71605 100644
b89242
--- a/src/libostree/ostree-impl-system-generator.c
b89242
+++ b/src/libostree/ostree-impl-system-generator.c
b89242
@@ -134,6 +134,8 @@ require_internal_units (const char *normal_dir,
b89242
     return FALSE;
b89242
   if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-finalize-staged.path", normal_dir_dfd, "multi-user.target.wants/ostree-finalize-staged.path") < 0)
b89242
     return glnx_throw_errno_prefix (error, "symlinkat");
b89242
+  if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-boot-complete.service", normal_dir_dfd, "multi-user.target.wants/ostree-boot-complete.service") < 0)
b89242
+    return glnx_throw_errno_prefix (error, "symlinkat");
b89242
 
b89242
   return TRUE;
b89242
 #else
b89242
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
b89242
index b7cc232f..fc5916d8 100644
b89242
--- a/src/libostree/ostree-sysroot-deploy.c
b89242
+++ b/src/libostree/ostree-sysroot-deploy.c
b89242
@@ -3255,10 +3255,10 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot     *self,
b89242
 }
b89242
 
b89242
 /* Invoked at shutdown time by ostree-finalize-staged.service */
b89242
-gboolean
b89242
-_ostree_sysroot_finalize_staged (OstreeSysroot *self,
b89242
-                                 GCancellable  *cancellable,
b89242
-                                 GError       **error)
b89242
+static gboolean
b89242
+_ostree_sysroot_finalize_staged_inner (OstreeSysroot *self,
b89242
+                                       GCancellable  *cancellable,
b89242
+                                       GError       **error)
b89242
 {
b89242
   /* It's totally fine if there's no staged deployment; perhaps down the line
b89242
    * though we could teach the ostree cmdline to tell systemd to activate the
b89242
@@ -3355,9 +3355,63 @@ _ostree_sysroot_finalize_staged (OstreeSysroot *self,
b89242
   if (!ostree_sysroot_prepare_cleanup (self, cancellable, error))
b89242
     return FALSE;
b89242
 
b89242
+  // Cleanup will have closed some FDs, re-ensure writability
b89242
+  if (!_ostree_sysroot_ensure_writable (self, error))
b89242
+    return FALSE;
b89242
+
b89242
   return TRUE;
b89242
 }
b89242
 
b89242
+/* Invoked at shutdown time by ostree-finalize-staged.service */
b89242
+gboolean
b89242
+_ostree_sysroot_finalize_staged (OstreeSysroot *self,
b89242
+                                 GCancellable  *cancellable,
b89242
+                                 GError       **error)
b89242
+{
b89242
+  g_autoptr(GError) finalization_error = NULL;
b89242
+  if (!_ostree_sysroot_ensure_boot_fd (self, error))
b89242
+    return FALSE;
b89242
+  if (!_ostree_sysroot_finalize_staged_inner (self, cancellable, &finalization_error))
b89242
+    {
b89242
+      g_autoptr(GError) writing_error = NULL;
b89242
+      g_assert_cmpint (self->boot_fd, !=, -1);
b89242
+      if (!glnx_file_replace_contents_at (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, 
b89242
+                                           (guint8*)finalization_error->message, -1,
b89242
+                                           0, cancellable, &writing_error))
b89242
+        {
b89242
+          // We somehow failed to write the failure message...that's not great.  Maybe ENOSPC on /boot.
b89242
+          g_printerr ("Failed to write %s: %s\n", _OSTREE_FINALIZE_STAGED_FAILURE_PATH, writing_error->message);
b89242
+        }
b89242
+      g_propagate_error (error, g_steal_pointer (&finalization_error));
b89242
+      return FALSE;
b89242
+    }
b89242
+  return TRUE;
b89242
+}
b89242
+
b89242
+/* Invoked at bootup time by ostree-boot-complete.service */
b89242
+gboolean
b89242
+_ostree_sysroot_boot_complete (OstreeSysroot *self,
b89242
+                               GCancellable  *cancellable,
b89242
+                               GError       **error)
b89242
+{
b89242
+  if (!_ostree_sysroot_ensure_boot_fd (self, error))
b89242
+    return FALSE;
b89242
+
b89242
+  glnx_autofd int failure_fd = -1;
b89242
+  if (!ot_openat_ignore_enoent (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, &failure_fd, error))
b89242
+    return FALSE;
b89242
+  // If we didn't find a failure log, then there's nothing to do right now.
b89242
+  // (Actually this unit shouldn't even be invoked, but we may do more in the future)
b89242
+  if (failure_fd == -1)
b89242
+    return TRUE;
b89242
+  g_autofree char *failure_data = glnx_fd_readall_utf8 (failure_fd, NULL, cancellable, error);
b89242
+  if (failure_data == NULL)
b89242
+    return glnx_prefix_error (error, "Reading from %s", _OSTREE_FINALIZE_STAGED_FAILURE_PATH);
b89242
+  // Remove the file; we don't want to continually error out.
b89242
+  (void) unlinkat (self->boot_fd, _OSTREE_FINALIZE_STAGED_FAILURE_PATH, 0);
b89242
+  return glnx_throw (error, "ostree-finalize-staged.service failed on previous boot: %s", failure_data);
b89242
+}
b89242
+
b89242
 /**
b89242
  * ostree_sysroot_deployment_set_kargs:
b89242
  * @self: Sysroot
b89242
diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h
b89242
index cb34eeb3..a49a406c 100644
b89242
--- a/src/libostree/ostree-sysroot-private.h
b89242
+++ b/src/libostree/ostree-sysroot-private.h
b89242
@@ -96,6 +96,9 @@ struct OstreeSysroot {
b89242
 #define _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "ostree/initramfs-overlays"
b89242
 #define _OSTREE_SYSROOT_INITRAMFS_OVERLAYS "boot/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS
b89242
 
b89242
+// Relative to /boot, consumed by ostree-boot-complete.service
b89242
+#define _OSTREE_FINALIZE_STAGED_FAILURE_PATH "ostree/finalize-failure.stamp"
b89242
+
b89242
 gboolean
b89242
 _ostree_sysroot_ensure_writable (OstreeSysroot      *self,
b89242
                                  GError            **error);
b89242
@@ -142,6 +145,10 @@ gboolean
b89242
 _ostree_sysroot_finalize_staged (OstreeSysroot *self,
b89242
                                  GCancellable  *cancellable,
b89242
                                  GError       **error);
b89242
+gboolean
b89242
+_ostree_sysroot_boot_complete (OstreeSysroot *self,
b89242
+                               GCancellable  *cancellable,
b89242
+                               GError       **error);
b89242
 
b89242
 OstreeDeployment *
b89242
 _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v,
b89242
diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c
b89242
index 266a2975..f083f950 100644
b89242
--- a/src/libostree/ostree-sysroot.c
b89242
+++ b/src/libostree/ostree-sysroot.c
b89242
@@ -356,6 +356,8 @@ _ostree_sysroot_ensure_writable (OstreeSysroot      *self,
b89242
   ostree_sysroot_unload (self);
b89242
   if (!ensure_sysroot_fd (self, error))
b89242
     return FALSE;
b89242
+  if (!_ostree_sysroot_ensure_boot_fd (self, error))
b89242
+    return FALSE;
b89242
 
b89242
   return TRUE;
b89242
 }
b89242
diff --git a/src/ostree/ot-admin-builtin-boot-complete.c b/src/ostree/ot-admin-builtin-boot-complete.c
b89242
new file mode 100644
b89242
index 00000000..6e1052f5
b89242
--- /dev/null
b89242
+++ b/src/ostree/ot-admin-builtin-boot-complete.c
b89242
@@ -0,0 +1,58 @@
b89242
+/*
b89242
+ * Copyright (C) 2022 Red Hat, Inc.
b89242
+ *
b89242
+ * SPDX-License-Identifier: LGPL-2.0+
b89242
+ *
b89242
+ * This library is free software; you can redistribute it and/or
b89242
+ * modify it under the terms of the GNU Lesser General Public
b89242
+ * License as published by the Free Software Foundation; either
b89242
+ * version 2 of the License, or (at your option) any later version.
b89242
+ *
b89242
+ * This library is distributed in the hope that it will be useful,
b89242
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
b89242
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
b89242
+ * Lesser General Public License for more details.
b89242
+ *
b89242
+ * You should have received a copy of the GNU Lesser General Public
b89242
+ * License along with this library. If not, see <https://www.gnu.org/licenses/>.
b89242
+ */
b89242
+
b89242
+#include "config.h"
b89242
+
b89242
+#include <stdlib.h>
b89242
+
b89242
+#include "ot-main.h"
b89242
+#include "ot-admin-builtins.h"
b89242
+#include "ot-admin-functions.h"
b89242
+#include "ostree.h"
b89242
+#include "otutil.h"
b89242
+
b89242
+#include "ostree-cmdprivate.h"
b89242
+
b89242
+static GOptionEntry options[] = {
b89242
+  { NULL }
b89242
+};
b89242
+
b89242
+gboolean
b89242
+ot_admin_builtin_boot_complete (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
b89242
+{
b89242
+  /* Just a sanity check; we shouldn't be called outside of the service though.
b89242
+   */
b89242
+  struct stat stbuf;
b89242
+  if (fstatat (AT_FDCWD, OSTREE_PATH_BOOTED, &stbuf, 0) < 0)
b89242
+    return TRUE;
b89242
+  // We must have been invoked via systemd which should have set up a mount namespace.
b89242
+  g_assert (getenv ("INVOCATION_ID"));
b89242
+
b89242
+  g_autoptr(GOptionContext) context = g_option_context_new ("");
b89242
+  g_autoptr(OstreeSysroot) sysroot = NULL;
b89242
+  if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
b89242
+                                          OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
b89242
+                                          invocation, &sysroot, cancellable, error))
b89242
+    return FALSE;
b89242
+
b89242
+  if (!ostree_cmd__private__()->ostree_boot_complete (sysroot, cancellable, error))
b89242
+    return FALSE;
b89242
+
b89242
+  return TRUE;
b89242
+}
b89242
diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h
b89242
index d32b617e..8d9451be 100644
b89242
--- a/src/ostree/ot-admin-builtins.h
b89242
+++ b/src/ostree/ot-admin-builtins.h
b89242
@@ -39,6 +39,7 @@ BUILTINPROTO(deploy);
b89242
 BUILTINPROTO(cleanup);
b89242
 BUILTINPROTO(pin);
b89242
 BUILTINPROTO(finalize_staged);
b89242
+BUILTINPROTO(boot_complete);
b89242
 BUILTINPROTO(unlock);
b89242
 BUILTINPROTO(status);
b89242
 BUILTINPROTO(set_origin);
b89242
diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c
b89242
index e0d2a60c..af09a614 100644
b89242
--- a/src/ostree/ot-builtin-admin.c
b89242
+++ b/src/ostree/ot-builtin-admin.c
b89242
@@ -43,6 +43,9 @@ static OstreeCommand admin_subcommands[] = {
b89242
   { "finalize-staged", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
b89242
     ot_admin_builtin_finalize_staged,
b89242
     "Internal command to run at shutdown time" },
b89242
+  { "boot-complete", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
b89242
+    ot_admin_builtin_boot_complete,
b89242
+    "Internal command to run at boot after an update was applied" },
b89242
   { "init-fs", OSTREE_BUILTIN_FLAG_NO_REPO,
b89242
      ot_admin_builtin_init_fs,
b89242
     "Initialize a root filesystem" },