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

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