]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: change initrd_register() so that it replaces any previously registered LINUX_IN...
authorLennart Poettering <lennart@amutable.com>
Fri, 20 Mar 2026 21:41:08 +0000 (22:41 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 21 Apr 2026 06:30:25 +0000 (08:30 +0200)
So far, if an initrd is already registered we'd silently not register
one again. Let's make this more reliable and systematic, and register
ours, overriding what is previously set.

(Note, in a later commit we'll incorporate any previously set initrd,
which hence makes this all incremental instead of destructive as it
might appear now)

src/boot/initrd.c

index 4babe1671d557e41e73687b47a8e12c449b0043a..b81334ea06a30db629d58bd5f0a49e07ca7a3d2b 100644 (file)
@@ -71,8 +71,6 @@ EFI_STATUS initrd_register(
                 EFI_HANDLE *ret_initrd_handle) {
 
         EFI_STATUS err;
-        EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
-        EFI_HANDLE handle;
 
         assert(ret_initrd_handle);
 
@@ -82,12 +80,43 @@ EFI_STATUS initrd_register(
         if (!iovec_is_set(initrd))
                 return EFI_SUCCESS;
 
-        /* Check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. LocateDevicePath checks for
-         * the "closest DevicePath" and returns its handle, whereas InstallMultipleProtocolInterfaces() only
-         * matches identical DevicePaths. */
-        err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
-        if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
-                return EFI_ALREADY_STARTED;
+        /* We want to override the LINUX_INITRD_MEDIA device, let's hence first unregister any existing
+         * one. We don't really expect multiple of these to be registered, but who knows? Let's kill all we
+         * can find. */
+        for (unsigned attempt = 0;; attempt++) {
+
+                if (attempt >= 16)
+                        return log_debug_status(EFI_DEVICE_ERROR, "Unable to free LINUX_INITRD_MEDIA device path after %u attempts, giving up.", attempt);
+
+                EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
+                EFI_HANDLE handle = NULL;
+                err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
+                if (err == EFI_NOT_FOUND) /* Yay! All gone */
+                        break;
+                if (err != EFI_SUCCESS)
+                        return log_debug_status(err, "Failed to locate LINUX_INITRD_MEDIA device: %m");
+
+                /* Get the *actually* installed pointer for the device path */
+                err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void**) &dp);
+                if (err != EFI_SUCCESS)
+                        return log_debug_status(err, "Failed to acquire DevicePath protocol on LINUX_INITRD_MEDIA device: %m");
+
+                /* Take away the device path protocol */
+                err = BS->UninstallMultipleProtocolInterfaces(
+                                handle,
+                                MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), dp,
+                                /* sentinel= */ NULL);
+                if (err != EFI_SUCCESS)
+                        return log_debug_status(err, "Unable to release DevicePath protocol from old handle: %m");
+
+                /* NB: we leave the handle around (and thus leave the LoadFile2 protocol installed), because
+                 * the owner might be unhappy if we destroy it for them. It will no longer have the device
+                 * path we want to take possession of on it though. The assumption here is that whoever
+                 * registered the device path is OK with the device path being taken away, even if it might
+                 * not be OK with the handle being invalidated as a whole. */
+
+                log_debug("Successfully unregistered previous LINUX_INITRD_MEDIA device.");
+        }
 
         _cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1);
         *loader = (struct initrd_loader) {
@@ -122,12 +151,20 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) {
         if (err != EFI_SUCCESS)
                 return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m");
 
-        /* uninstall all protocols thus destroying the handle */
+        /* We uninstall the DevicePath and the LoadFile2 protocol in separate steps. That's because we want
+         * to gracefully handle the former (because it's OK if something else takes over the device path),
+         * but be strict on the latter, because that's genuinely ours */
+
+        (void) BS->UninstallMultipleProtocolInterfaces(
+                        initrd_handle,
+                        MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path,
+                        /* sentinel= */ NULL);
+
+        /* This second call will also invalidate the handle, because it should be the last protocol on the handle */
         err = BS->UninstallMultipleProtocolInterfaces(
-                        initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
-                        &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
-                        loader,
-                        NULL);
+                        initrd_handle,
+                        MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader,
+                        /* sentinel= */ NULL);
         if (err != EFI_SUCCESS)
                 return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m");