* [make-initrd] [PATCH v1 01/10] utils: Add PE header parsing to locate .initrd section
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
@ 2026-11-09 13:06 ` Alexey Gladkov
2026-11-09 13:06 ` [make-initrd] [PATCH v1 02/10] shutdown: Add exitrd shutdown feature Alexey Gladkov
` (10 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:06 UTC (permalink / raw)
To: make-initrd
Unified Kernel Images (UKI) combine the UEFI stub, Linux kernel, cmdline,
and initramfs into a single PE/COFF executable binary (.efi).
Teach initrd-parse to inspect PE headers when opening an image at stream
level 1. If the image begins with an MS-DOS header ("MZ") and PE signature
("PE\0\0"), find_pe_initrd locates the .initrd section table entry and
slices arv->addr and arv->size directly to the raw .initrd section data.
This enables initrd-ls, initrd-extract, and initrd-diff to inspect and
extract UKI images natively without external tools (such as objcopy) or
intermediate temporary files.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
| 3 +
| 62 ++++++++++++++++++
utils/initrd-ls/tests/ts0008/expect | 2 +-
utils/initrd-ls/tests/ts0017/expect | 2 +-
utils/initrd-ls/tests/ts0025/expect | 2 +-
utils/initrd-ls/tests/ts0033/expect | 2 +-
utils/initrd-ls/tests/ts0034/expect | 2 +-
utils/initrd-ls/tests/ts0035/expect | 2 +-
utils/initrd-ls/tests/ts0036/expect | 2 +-
utils/initrd-ls/tests/ts0037/expect | 2 +-
utils/initrd-ls/tests/ts0038/expect | 2 +-
utils/initrd-ls/tests/ts0039/expect | 2 +-
utils/initrd-ls/tests/ts0041/expect | 2 +-
utils/initrd-ls/tests/ts0045/expect | 7 ++
utils/initrd-ls/tests/ts0045/run | 83 ++++++++++++++++++++++++
utils/initrd-parse.c | 81 ++++++++++++++++++++---
16 files changed, 237 insertions(+), 21 deletions(-)
create mode 100644 utils/initrd-extract/tests/ts0005/expect
create mode 100755 utils/initrd-extract/tests/ts0005/run
create mode 100644 utils/initrd-ls/tests/ts0045/expect
create mode 100755 utils/initrd-ls/tests/ts0045/run
--git a/utils/initrd-extract/tests/ts0005/expect b/utils/initrd-extract/tests/ts0005/expect
new file mode 100644
index 00000000..a4776771
--- /dev/null
+++ b/utils/initrd-extract/tests/ts0005/expect
@@ -0,0 +1,3 @@
+=== extract cpio from UKI ===
+file1
+rc=0
--git a/utils/initrd-extract/tests/ts0005/run b/utils/initrd-extract/tests/ts0005/run
new file mode 100755
index 00000000..b1fd49fd
--- /dev/null
+++ b/utils/initrd-extract/tests/ts0005/run
@@ -0,0 +1,62 @@
+#!/bin/bash -efu
+
+export LANG=C
+export LC_ALL=C
+export TZ=UTC
+
+cwd="${0%/*}"
+
+python3 - "$cwd" << 'EOF'
+import sys
+import io
+import stat
+import struct
+
+def make_cpio_entry(name, mode, content=b"", uid=0, gid=0, nlink=1, mtime=0):
+ namesize = len(name.encode()) + 1
+ filesize = len(content)
+ hdr = f"070701{1:08x}{mode:08x}{uid:08x}{gid:08x}{nlink:08x}{mtime:08x}{filesize:08x}{0:08x}{0:08x}{0:08x}{0:08x}{namesize:08x}{0:08x}".encode()
+ out = io.BytesIO()
+ out.write(hdr)
+ out.write(name.encode() + b"\0")
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ if content:
+ out.write(content)
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ return out.getvalue()
+
+trailer = make_cpio_entry("TRAILER!!!", 0)
+
+entries = [
+ make_cpio_entry("file1", stat.S_IFREG | 0o644, b"test content"),
+ trailer
+]
+
+cpio_data = b"".join(entries)
+pad = (512 - (len(cpio_data) % 512)) % 512
+cpio_data += b"\0" * pad
+
+dos_header = b'MZ' + b'\0' * 58 + struct.pack('<I', 64)
+pe_sig = b'PE\0\0'
+coff_header = struct.pack('<HHIIIHH', 0x8664, 1, 0, 0, 0, 0, 2)
+headers = dos_header + pe_sig + coff_header
+
+raw_ptr = 512
+raw_size = len(cpio_data)
+vsize = len(cpio_data)
+sec_hdr = struct.pack('<8sIIIIIIHHI', b'.initrd\0', vsize, 0x1000, raw_size, raw_ptr, 0, 0, 0, 0, 0x40000040)
+headers += sec_hdr
+
+pe = headers + b'\0' * (512 - len(headers)) + cpio_data
+
+with open(sys.argv[1] + "/uki.efi", "wb") as f:
+ f.write(pe)
+EOF
+
+trap 'rm -f -- "$cwd/uki.efi" "$cwd/extracted.cpio"' EXIT
+
+echo "=== extract cpio from UKI ==="
+.build/dest/usr/sbin/initrd-extract "$cwd/uki.efi" > "$cwd/extracted.cpio"
+cpio -it --quiet < "$cwd/extracted.cpio"
diff --git a/utils/initrd-ls/tests/ts0008/expect b/utils/initrd-ls/tests/ts0008/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0008/expect
+++ b/utils/initrd-ls/tests/ts0008/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0017/expect b/utils/initrd-ls/tests/ts0017/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0017/expect
+++ b/utils/initrd-ls/tests/ts0017/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0025/expect b/utils/initrd-ls/tests/ts0025/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0025/expect
+++ b/utils/initrd-ls/tests/ts0025/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0033/expect b/utils/initrd-ls/tests/ts0033/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0033/expect
+++ b/utils/initrd-ls/tests/ts0033/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0034/expect b/utils/initrd-ls/tests/ts0034/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0034/expect
+++ b/utils/initrd-ls/tests/ts0034/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0035/expect b/utils/initrd-ls/tests/ts0035/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0035/expect
+++ b/utils/initrd-ls/tests/ts0035/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0036/expect b/utils/initrd-ls/tests/ts0036/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0036/expect
+++ b/utils/initrd-ls/tests/ts0036/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0037/expect b/utils/initrd-ls/tests/ts0037/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0037/expect
+++ b/utils/initrd-ls/tests/ts0037/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0038/expect b/utils/initrd-ls/tests/ts0038/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0038/expect
+++ b/utils/initrd-ls/tests/ts0038/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0039/expect b/utils/initrd-ls/tests/ts0039/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0039/expect
+++ b/utils/initrd-ls/tests/ts0039/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0041/expect b/utils/initrd-ls/tests/ts0041/expect
index 29f3d68d..77c740d2 100644
--- a/utils/initrd-ls/tests/ts0041/expect
+++ b/utils/initrd-ls/tests/ts0041/expect
@@ -1,3 +1,3 @@
initrd-ls: ERROR: utils/initrd-decompress-zstd.c: 63: ZSTD_decompressStream: Unknown frame descriptor
-initrd-ls: ERROR: utils/initrd-parse.c: 65: decompressor failed: Success
+initrd-ls: ERROR: utils/initrd-parse.c: 126: decompressor failed: Success
rc=1
diff --git a/utils/initrd-ls/tests/ts0045/expect b/utils/initrd-ls/tests/ts0045/expect
new file mode 100644
index 00000000..d60e1591
--- /dev/null
+++ b/utils/initrd-ls/tests/ts0045/expect
@@ -0,0 +1,7 @@
+=== list contents of PE UKI ===
+1 -rw-r--r-- 1 0 0 14 file1
+1 -rwxr-xr-x 1 0 0 17 shutdown
+=== PE image without .initrd ===
+initrd-ls: ERROR: PE image does not contain .initrd section
+rc=1
+rc=0
diff --git a/utils/initrd-ls/tests/ts0045/run b/utils/initrd-ls/tests/ts0045/run
new file mode 100755
index 00000000..24d2bb31
--- /dev/null
+++ b/utils/initrd-ls/tests/ts0045/run
@@ -0,0 +1,83 @@
+#!/bin/bash -efu
+
+export LANG=C
+export LC_ALL=C
+export TZ=UTC
+
+cwd="${0%/*}"
+
+python3 - "$cwd" << 'EOF'
+import sys
+import io
+import stat
+import struct
+
+def make_cpio_entry(name, mode, content=b"", uid=0, gid=0, nlink=1, mtime=0):
+ namesize = len(name.encode()) + 1
+ filesize = len(content)
+ hdr = f"070701{1:08x}{mode:08x}{uid:08x}{gid:08x}{nlink:08x}{mtime:08x}{filesize:08x}{0:08x}{0:08x}{0:08x}{0:08x}{namesize:08x}{0:08x}".encode()
+ out = io.BytesIO()
+ out.write(hdr)
+ out.write(name.encode() + b"\0")
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ if content:
+ out.write(content)
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ return out.getvalue()
+
+trailer = make_cpio_entry("TRAILER!!!", 0)
+
+entries = [
+ make_cpio_entry("file1", stat.S_IFREG | 0o644, b"hello from uki"),
+ make_cpio_entry("shutdown", stat.S_IFREG | 0o755, b"#!/bin/sh\nexit 0\n"),
+ trailer
+]
+
+cpio_data = b"".join(entries)
+pad = (512 - (len(cpio_data) % 512)) % 512
+cpio_data += b"\0" * pad
+
+def make_pe(sections):
+ dos_header = b'MZ' + b'\0' * 58 + struct.pack('<I', 64)
+ pe_sig = b'PE\0\0'
+ num_sec = len(sections)
+ coff_header = struct.pack('<HHIIIHH', 0x8664, num_sec, 0, 0, 0, 0, 2)
+
+ headers = dos_header + pe_sig + coff_header
+ sec_hdrs = []
+ raw_ptr = 512
+ for name, data in sections:
+ raw_size = ((len(data) + 511) // 512) * 512
+ vsize = len(data)
+ vaddr = 0x1000 * (len(sec_hdrs) + 1)
+ sec_hdrs.append((struct.pack('<8sIIIIIIHHI', name, vsize, vaddr, raw_size, raw_ptr, 0, 0, 0, 0, 0x40000040), data, raw_size))
+ raw_ptr += raw_size
+
+ for hdr, _, _ in sec_hdrs:
+ headers += hdr
+
+ pe = headers + b'\0' * (512 - len(headers))
+ for _, data, raw_size in sec_hdrs:
+ pe += data + b'\0' * (raw_size - len(data))
+ return pe
+
+uki = make_pe([(b'.initrd\0', cpio_data)])
+with open(sys.argv[1] + "/uki.efi", "wb") as f:
+ f.write(uki)
+
+no_initrd = make_pe([(b'.text\0\0\0', b'\x90' * 64)])
+with open(sys.argv[1] + "/no-initrd.efi", "wb") as f:
+ f.write(no_initrd)
+EOF
+
+trap 'rm -f -- "$cwd/uki.efi" "$cwd/no-initrd.efi"' EXIT
+
+echo "=== list contents of PE UKI ==="
+.build/dest/usr/sbin/initrd-ls --no-mtime "$cwd/uki.efi"
+
+echo "=== PE image without .initrd ==="
+rc=0
+.build/dest/usr/sbin/initrd-ls "$cwd/no-initrd.efi" 2>&1 || rc=$?
+echo "rc=$rc"
diff --git a/utils/initrd-parse.c b/utils/initrd-parse.c
index 58d6a70c..10122cb4 100644
--- a/utils/initrd-parse.c
+++ b/utils/initrd-parse.c
@@ -18,6 +18,63 @@
static int stream_level = 0;
+static void
+find_pe_initrd(struct stream *arv)
+{
+ if (arv->size < 0x40)
+ return;
+
+ if (arv->addr[0] != 'M' || arv->addr[1] != 'Z')
+ return;
+
+ uint32_t pe_offset;
+ memcpy(&pe_offset, arv->addr + 0x3c, sizeof(pe_offset));
+ pe_offset = le32toh(pe_offset);
+
+ if ((unsigned long)pe_offset + 24 > arv->size)
+ return;
+
+ if (memcmp(arv->addr + pe_offset, "PE\0\0", 4) != 0)
+ return;
+
+ uint16_t num_sections, opt_hdr_size;
+ memcpy(&num_sections, arv->addr + pe_offset + 6, sizeof(num_sections));
+ memcpy(&opt_hdr_size, arv->addr + pe_offset + 20, sizeof(opt_hdr_size));
+ num_sections = le16toh(num_sections);
+ opt_hdr_size = le16toh(opt_hdr_size);
+
+ unsigned long sec_table = (unsigned long)pe_offset + 24 + opt_hdr_size;
+
+ for (uint16_t i = 0; i < num_sections; i++) {
+ unsigned long sec_offset = sec_table + (unsigned long)i * 40;
+ if (sec_offset + 40 > arv->size)
+ break;
+
+ if (!memcmp(arv->addr + sec_offset, ".initrd\0", 8)) {
+ uint32_t vsize, raw_size, raw_ptr;
+ memcpy(&vsize, arv->addr + sec_offset + 8, sizeof(vsize));
+ memcpy(&raw_size, arv->addr + sec_offset + 16, sizeof(raw_size));
+ memcpy(&raw_ptr, arv->addr + sec_offset + 20, sizeof(raw_ptr));
+ vsize = le32toh(vsize);
+ raw_size = le32toh(raw_size);
+ raw_ptr = le32toh(raw_ptr);
+
+ if ((unsigned long)raw_ptr + raw_size > arv->size)
+ errx(EXIT_FAILURE, "ERROR: .initrd section extends beyond file size");
+
+ uint32_t sec_size = raw_size;
+ if (vsize > 0 && vsize <= raw_size)
+ sec_size = vsize;
+
+ arv->addr += raw_ptr;
+ arv->size = sec_size;
+ return;
+ }
+ }
+
+ errx(EXIT_FAILURE, "ERROR: PE image does not contain .initrd section");
+}
+
void
read_stream(const char *compress, struct stream *arv, struct result *res)
{
@@ -35,19 +92,23 @@ read_stream(const char *compress, struct stream *arv, struct result *res)
stream_level++;
if (stream_level == 1) {
- /*
- * Check bootconfig first.
- */
- data = arv->addr + arv->size - BOOTCONFIG_MAGIC_LEN;
+ find_pe_initrd(arv);
/*
- * Since Grub may align the size of initrd to 4, we must
- * check the preceding 3 bytes as well.
+ * Check bootconfig first.
*/
- for (int i = 0; i < 4; i++) {
- if (!memcmp(data, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN))
- goto bootconfig;
- data--;
+ if (arv->size >= BOOTCONFIG_MAGIC_LEN) {
+ data = arv->addr + arv->size - BOOTCONFIG_MAGIC_LEN;
+
+ /*
+ * Since Grub may align the size of initrd to 4, we must
+ * check the preceding 3 bytes as well.
+ */
+ for (int i = 0; i < 4; i++) {
+ if (!memcmp(data, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN))
+ goto bootconfig;
+ data--;
+ }
}
data = NULL;
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 02/10] shutdown: Add exitrd shutdown feature
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
2026-11-09 13:06 ` [make-initrd] [PATCH v1 01/10] utils: Add PE header parsing to locate .initrd section Alexey Gladkov
@ 2026-11-09 13:06 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service Alexey Gladkov
` (9 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:06 UTC (permalink / raw)
To: make-initrd
Complex storage stacks (such as device-mapper, lvm, luks, raid, loop
mounts, and layered filesystems) often cannot be completely torn down
while the root filesystem is active on the host os. Systemd supports
pivoting to an exitrd (/run/initramfs/shutdown) at the final shutdown
phase, allowing the system to pivot back to a ramfs, move the old root,
unmount all remaining filesystems, and dismantle underlying storage
devices cleanly before halt or reboot.
Add the shutdown feature containing the /shutdown exitrd script and
modular hook stages:
- Setup console redirection and virtual filesystems (/proc, /sys, /dev).
- Move /oldroot/{sys,proc,run,dev} to /oldsys/ and terminate any
lingering processes referencing /oldroot via fuser.
- Execute pre-umount.d hooks.
- Unmount all /oldroot mountpoints in reverse topological order, calling
umount.d hooks for each filesystem type.
- Perform iterative teardown of leaf block devices (devices with empty
holders/ in sysfs), invoking post-umount.d hooks with their major
device number until no more progress can be made.
- Execute shutdown.d hooks immediately before the final reboot/poweroff.
- Include a default post-umount hook for loop device detachment.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
features/shutdown/README.md | 67 ++++++
features/shutdown/config.mk | 5 +
features/shutdown/rules.mk | 7 +
.../initrd/shutdown/post-umount.d/.gitignore | 1 +
.../initrd/shutdown/post-umount.d/10-loop.sh | 21 ++
.../initrd/shutdown/pre-umount.d/.gitignore | 1 +
.../lib/initrd/shutdown/shutdown.d/.gitignore | 2 +
.../lib/initrd/shutdown/umount.d/.gitignore | 1 +
features/shutdown/shutdown/shutdown | 196 ++++++++++++++++++
9 files changed, 301 insertions(+)
create mode 100644 features/shutdown/README.md
create mode 100644 features/shutdown/config.mk
create mode 100644 features/shutdown/rules.mk
create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
create mode 100755 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
create mode 100755 features/shutdown/shutdown/shutdown
diff --git a/features/shutdown/README.md b/features/shutdown/README.md
new file mode 100644
index 00000000..949a40bd
--- /dev/null
+++ b/features/shutdown/README.md
@@ -0,0 +1,67 @@
+# Feature: shutdown
+
+This feature adds support for cleanly unmounting and tearing down storage stacks
+during system shutdown.
+
+When a system running systemd is shut down or rebooted, systemd performs a
+`pivot_root` into `/run/initramfs` and executes `/shutdown` (the exitrd
+mechanism). This allows the real root filesystem (`/oldroot`) to be safely
+unmounted, and underlying block devices (LUKS containers, LVM volume groups, MD
+RAID arrays, multipath) to be cleanly deactivated.
+
+## Shutdown hooks
+
+Any active feature can provide shutdown hooks by adding files to its `shutdown/`
+directory. The contents of all `features/*/shutdown/` directories from active
+features are automatically included into the initramfs.
+
+All hooks are invoked with a consistent signature:
+```
+"$hook" "$hook_type" "$action" "$target" [metadata...]
+```
+
+### Exit codes convention
+
+Hooks indicate their handling status via standard exit codes:
+- `0`: Device was handled and successfully dismantled (progress made).
+- `2`: Device was skipped / not applicable to this hook.
+- `1` (or other non-zero): Error occurred during dismantling.
+
+In `post-umount.d`, `/shutdown` repeats leaf teardown iterations as long as at
+least one hook returns `0` (making progress).
+
+### Hook stages
+
+Shutdown hooks are organized into the following stages:
+
+1. **`lib/initrd/shutdown/pre-umount.d/`**
+ - Executed before unmounting begins.
+ - Arguments: `"$hook_type"` `"$action"` `"/oldroot"`
+ - Use cases: early storage preparations (e.g. `mdmon --takeover --all`),
+ flushing caches, diagnostics.
+
+2. **`lib/initrd/shutdown/umount.d/`**
+ - Executed for each mountpoint right before calling `umount -d`.
+ - Arguments: `"$hook_type"` `"$action"` `"$mountpoint"` `"$fstype"`
+ - Use cases: filesystem-specific teardown (flushing network filesystems,
+ stopping filesystem-specific daemons).
+
+3. **`lib/initrd/shutdown/post-umount.d/`**
+ - Executed during topological block device teardown for each leaf device (no
+ entries in `holders/`).
+ - Arguments: `"$hook_type"` `"$action"` `"$sysfs_path"` `"$major"`
+ - Use cases: modular deactivation of specific block layers (`10-loop.sh`,
+ `50-dm.sh`, `60-md.sh`, etc.).
+
+4. **`lib/initrd/shutdown/shutdown.d/`**
+ - Executed once at the very end after all mountpoints and block devices have
+ been torn down, immediately before `sync` and reboot/poweroff.
+ - Arguments: `"$hook_type"` `"$action"`
+
+## Systemd integration
+
+The `system/` directory provides files for host system integration:
+- `make-initrd-shutdown-restore`: script executed upon shutdown to unpack the
+ initramfs into `/run/initramfs`.
+- `make-initrd-shutdown.service`: systemd unit triggering
+ `make-initrd-shutdown-restore` on shutdown.
diff --git a/features/shutdown/config.mk b/features/shutdown/config.mk
new file mode 100644
index 00000000..be61ef0d
--- /dev/null
+++ b/features/shutdown/config.mk
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+$(call feature-requires,runtime)
+
+SHUTDOWN_DIRS ?=
diff --git a/features/shutdown/rules.mk b/features/shutdown/rules.mk
new file mode 100644
index 00000000..e7435090
--- /dev/null
+++ b/features/shutdown/rules.mk
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+SHUTDOWN_FEATURE_DIRS := $(wildcard $(foreach f,$(ALL_ACTIVE_FEATURES),$(FEATURESDIR)/$(f)/shutdown))
+
+PUT_FEATURE_DIRS += $(SHUTDOWN_FEATURE_DIRS) $(SHUTDOWN_DIRS)
+
+PUT_FEATURE_PROGS += fuser stat sync timeout losetup
diff --git a/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore b/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
new file mode 100644
index 00000000..e2c97e51
--- /dev/null
+++ b/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
@@ -0,0 +1 @@
+# Post-umount hooks (called after unmounting completes to teardown block devices: $ACTION)
diff --git a/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh b/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
new file mode 100755
index 00000000..44329443
--- /dev/null
+++ b/features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+sysfs_path="$3"
+major="$4"
+
+[ "$major" = 7 ] || [ -d "$sysfs_path/loop" ] ||
+ exit 2
+
+command -v losetup >/dev/null 2>&1 ||
+ exit 1
+
+# If loop device has no backing file, nothing to detach
+[ -s "$sysfs_path/loop/backing_file" ] ||
+ exit 2
+
+devname="${sysfs_path##*/}"
+
+losetup -d "/dev/$devname"
diff --git a/features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore b/features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
new file mode 100644
index 00000000..225db639
--- /dev/null
+++ b/features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
@@ -0,0 +1 @@
+# Pre-umount hooks (called before unmounting starts: $ACTION)
diff --git a/features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore b/features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore b/features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
new file mode 100644
index 00000000..0b4ade1c
--- /dev/null
+++ b/features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
@@ -0,0 +1 @@
+# Per-mountpoint hooks (called for each mountpoint before umount: $FSTYPE $MOUNTPOINT $ACTION)
diff --git a/features/shutdown/shutdown/shutdown b/features/shutdown/shutdown/shutdown
new file mode 100755
index 00000000..6bdbb805
--- /dev/null
+++ b/features/shutdown/shutdown/shutdown
@@ -0,0 +1,196 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+trap : INT TSTP QUIT HUP
+
+is_leaf_blockdev() {
+ local bdev="$1"
+ local h
+
+ for h in "$bdev"/holders/*; do
+ [ ! -e "$h" ] || return 1
+ done
+ return 0
+}
+
+run_hooks() {
+ local hook_type="$1"
+ local action="$2"
+ shift 2
+
+ local target="${1-}"
+ local hook rc=1
+
+ for hook in "/lib/initrd/shutdown/${hook_type}.d"/*; do
+ [ -x "$hook" ] ||
+ continue
+
+ if "$hook" "$hook_type" "$action" "$@"; then
+ rc=0
+ if [ "$hook_type" = "post-umount" ] && [ -n "$target" ] && [ ! -e "$target" ]; then
+ break
+ fi
+ fi
+ done
+
+ return "$rc"
+}
+
+teardown_blockdevs() {
+ local action="$1"
+ local progress=1
+ local bdev
+
+ while [ "$progress" -eq 1 ]; do
+ progress=0
+
+ for bdev in /sys/class/block/*; do
+ [ -e "$bdev" ] ||
+ continue
+
+ # Skip partitions: they are cleaned up with their parent device
+ [ ! -f "$bdev/partition" ] ||
+ continue
+
+ # Check if device has holders (must be a leaf in the block layer)
+ if ! is_leaf_blockdev "$bdev"; then
+ continue
+ fi
+
+ local majmin="" major=""
+
+ read -r majmin < "$bdev/dev" 2>/dev/null ||
+ continue
+
+ major="${majmin%%:*}"
+
+ # Run post-umount hooks for this leaf device
+ if run_hooks "post-umount" "$action" "$bdev" "$major"; then
+ progress=1
+ fi
+ done
+ done
+}
+
+main() {
+ export TERM=linux
+ export PATH="/sbin:/usr/sbin:/bin:/usr/bin"
+
+ local ACTION="${1:-reboot}"
+
+ # Remount root as read-write if it is in tmpfs or ramfs
+ local root_fs
+ root_fs="$(stat -c '%T' -f / 2>/dev/null ||:)"
+
+ if [ "$root_fs" = "tmpfs" ] || [ "$root_fs" = "ramfs" ]; then
+ mount -o remount,rw / 2>/dev/null ||:
+ fi
+
+ # Ensure essential virtual filesystems are mounted
+ [ -d /proc ] || mkdir -m 755 /proc
+ [ -d /sys ] || mkdir -m 755 /sys
+ [ -d /dev ] || mkdir -m 755 /dev
+
+ mountpoint -q /proc 2>/dev/null || mount -n -t proc proc /proc 2>/dev/null ||:
+ mountpoint -q /sys 2>/dev/null || mount -n -t sysfs sysfs /sys 2>/dev/null ||:
+ mountpoint -q /dev 2>/dev/null || mount -n -t devtmpfs devtmpfs /dev 2>/dev/null ||:
+
+ # Redirect stdio to /dev/console once /dev is mounted
+ if [ -w /dev/console ] && (: < /dev/console) 2>/dev/null; then
+ exec < /dev/console >> /dev/console 2>> /dev/console
+ fi
+
+ if [ -d /oldroot ]; then
+ # Move nested virtual filesystems out of /oldroot
+ mkdir -p /oldsys
+
+ local d
+ for d in sys proc run dev; do
+ if mountpoint -q "/oldroot/$d" 2>/dev/null; then
+ mkdir -p "/oldsys/$d"
+ mount --move "/oldroot/$d" "/oldsys/$d" 2>/dev/null ||:
+ fi
+ done
+
+ # Terminate any remaining processes holding files on /oldroot
+ fuser -s -k -9 -m /oldroot 2>/dev/null ||:
+
+ # Run pre-umount hooks (e.g. mdmon takeover, storage prep)
+ run_hooks "pre-umount" "$ACTION" "/oldroot"
+
+ # Unmount everything under /oldroot
+ local cnt=0 lines fstype mp did_umount i line
+ while [ "$cnt" -le 30 ]; do
+ mapfile -t lines < <(findmnt -rno FSTYPE,TARGET -R /oldroot 2>/dev/null ||:)
+
+ [ "${#lines[@]}" -gt 0 ] ||
+ break
+
+ did_umount=0
+
+ for (( i=${#lines[@]}-1; i>=0; i-- )); do
+ line="${lines[i]}"
+ fstype="${line%% *}"
+ mp="${line#* }"
+
+ # Run per-mountpoint hook
+ run_hooks "umount" "$ACTION" "$mp" "$fstype"
+
+ if ! mountpoint -q "$mp" 2>/dev/null; then
+ did_umount=1
+ continue
+ fi
+
+ if timeout -s KILL 5 umount -d "$mp" 2>/dev/null; then
+ did_umount=1
+ fi
+ done
+
+ [ "$did_umount" -eq 1 ] ||
+ break
+
+ cnt=$(( cnt + 1 ))
+ done
+ fi
+
+ # Dismantle underlying storage stack layer by layer
+ teardown_blockdevs "$ACTION"
+
+ # Run final shutdown hooks before syncing and rebooting
+ run_hooks "shutdown" "$ACTION"
+
+ # Clean up /oldsys mounts if present
+ if [ -d /oldsys ]; then
+ local d
+ for d in dev run proc sys; do
+ if mountpoint -q "/oldsys/$d" 2>/dev/null; then
+ umount -l "/oldsys/$d" 2>/dev/null ||:
+ fi
+ done
+ fi
+
+ sync
+
+ case "$ACTION" in
+ poweroff)
+ poweroff -f 2>/dev/null || halt -f -p 2>/dev/null || reboot -f
+ ;;
+ halt)
+ halt -f 2>/dev/null || reboot -f
+ ;;
+ kexec)
+ if command -v kexec >/dev/null 2>&1; then
+ kexec -e 2>/dev/null || reboot -f
+ else
+ reboot -f
+ fi
+ ;;
+ reboot|*)
+ reboot -f
+ ;;
+ esac
+}
+
+if [ "${BASH_SOURCE[0]}" = "$0" ]; then
+ main "$@"
+fi
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
2026-11-09 13:06 ` [make-initrd] [PATCH v1 01/10] utils: Add PE header parsing to locate .initrd section Alexey Gladkov
2026-11-09 13:06 ` [make-initrd] [PATCH v1 02/10] shutdown: Add exitrd shutdown feature Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 17:10 ` [make-initrd] [PATCH v2 " Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 04/10] shutdown: Add basic unit test suite Alexey Gladkov
` (8 subsequent siblings)
11 siblings, 1 reply; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
For systemd-shutdown to pivot to an exitrd at shutdown, the unpacked
initramfs contents containing the /shutdown binary must be populated in
/run/initramfs before local filesystems are unmounted.
Add initrd-find utility and make-initrd-shutdown-restore to locate and
restore the exitrd during shutdown:
- Locate the initramfs matching the running kernel version across:
* Boot Loader Specification (BLS) Type #1 entries in
loader/entries/*.conf, supporting multi-part initrd directives.
* BLS Type #1 directory structure ($BOOT/$token/$kver/initrd*).
* BLS Type #2 Unified Kernel Images (.efi binaries).
* Kernel command line (initrd=...) from /proc/cmdline.
* /lib/modules/$kver/initrd and traditional distro image paths.
- Ensure candidate boot and ESP partitions (/boot, /efi, /boot/efi, and
locations reported by bootctl) are mounted read-only before searching.
- Add guess/common to automatically guess feature shutdown when the
detected image includes /shutdown.
- Unpack the archive into /run/initramfs using initrd-extract, and
unpack compressed-fs layers if present.
- Cleanly unmount any temporary mounts made by the script upon exit.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
Makefile.in | 6 +
features/shutdown/README.md | 22 +-
features/shutdown/guess/common | 32 ++
.../system/make-initrd-shutdown.service | 20 +
utils/initrd-find/Makefile.mk | 6 +
utils/initrd-find/initrd-find.in | 401 ++++++++++++++++++
.../tests/ts0001-initrd-find-basic/expect | 10 +
.../tests/ts0001-initrd-find-basic/run | 108 +++++
.../make-initrd-shutdown-restore/Makefile.mk | 6 +
.../make-initrd-shutdown-restore.in | 147 +++++++
10 files changed, 754 insertions(+), 4 deletions(-)
create mode 100755 features/shutdown/guess/common
create mode 100644 features/shutdown/system/make-initrd-shutdown.service
create mode 100644 utils/initrd-find/Makefile.mk
create mode 100755 utils/initrd-find/initrd-find.in
create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
diff --git a/Makefile.in b/Makefile.in
index 96058b73..1dddcdaa 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -32,6 +32,7 @@ kernelconfdir = $(prefix)/lib/kernel/install.conf.d
libcdir := $(shell $(srcdir)/tools/get-libc-dir)
completionsdir := $(shell pkg-config --variable=completionsdir bash-completion 2>/dev/null ||:)
+systemdunitdir ?= $(shell pkg-config --variable=systemdsystemunitdir systemd 2>/dev/null ||:)
runtime_srcdir = $(srcdir)/runtime/src
utils_srcdir = $(srcdir)/utils
@@ -611,6 +612,9 @@ install: build installdirs
$(INSTALL) -d -m 755 -- "$(DESTDIR)$(kernelinstalldir)" "$(DESTDIR)$(kernelconfdir)" && \
$(INSTALL_SCRIPT) -m 755 -t "$(DESTDIR)$(kernelinstalldir)" -- $(wildcard $(srcdir)/contrib/kernel-install/*.install) && \
$(INSTALL_DATA) -t "$(DESTDIR)$(kernelconfdir)" -- $(wildcard $(srcdir)/contrib/kernel-install/*.conf); }
+ $(Q)[ -z "$(systemdunitdir)" ] || { \
+ $(INSTALL) -d -m 755 -- "$(DESTDIR)$(systemdunitdir)" && \
+ $(INSTALL_DATA) -t "$(DESTDIR)$(systemdunitdir)" -- $(srcdir)/features/shutdown/system/make-initrd-shutdown.service; }
ifneq ($(SCDOC),)
$(Q)$(INSTALL_DATA) -t "$(DESTDIR)$(man1dir)" -- $(addprefix $(dest_man1dir)/,$(MANPAGE_NAMES))
endif
@@ -634,6 +638,8 @@ uninstall:
done
$(Q)[ -z "$(completionsdir)" ] || \
rm -f -- $(addprefix $(DESTDIR)$(completionsdir)/,$(notdir $(wildcard $(srcdir)/contrib/bash-completion/*)))
+ $(Q)[ -z "$(systemdunitdir)" ] || \
+ rm -f -- "$(DESTDIR)$(systemdunitdir)/make-initrd-shutdown.service"
$(Q)[ -z "$(kernelinstalldir)" ] || \
rm -f -- $(addprefix $(DESTDIR)$(kernelinstalldir)/,$(notdir $(wildcard $(srcdir)/contrib/kernel-install/*.install))) \
$(addprefix $(DESTDIR)$(kernelconfdir)/,$(notdir $(wildcard $(srcdir)/contrib/kernel-install/*.conf)))
diff --git a/features/shutdown/README.md b/features/shutdown/README.md
index 949a40bd..6764be70 100644
--- a/features/shutdown/README.md
+++ b/features/shutdown/README.md
@@ -58,10 +58,24 @@ Shutdown hooks are organized into the following stages:
been torn down, immediately before `sync` and reboot/poweroff.
- Arguments: `"$hook_type"` `"$action"`
+## Auto-detection (guess)
+
+This feature provides `guess/common`:
+When generating a new initramfs, `guess/common` queries `initrd-find` to locate
+the previous or current kernel's initramfs image. If that image contains
+`/shutdown`, `features/shutdown` is automatically guessed (`guess_feature shutdown`)
+for the new image.
+
## Systemd integration
-The `system/` directory provides files for host system integration:
-- `make-initrd-shutdown-restore`: script executed upon shutdown to unpack the
- initramfs into `/run/initramfs`.
-- `make-initrd-shutdown.service`: systemd unit triggering
+Host system components handle exitrd restoration:
+- `initrd-find` (installed into `/usr/sbin/` from `utils/`):
+ utility used to locate the active or matching initramfs image across BLS Type #1
+ configurations, BLS directory trees, BLS Type #2 UKIs, `/proc/cmdline`,
+ `/lib/modules/$kver/initrd`, and standard boot directories.
+- `make-initrd-shutdown-restore` (installed into `/usr/sbin/` from `utils/`):
+ utility executed upon shutdown to unpack the initramfs into `/run/initramfs`.
+ It locates the initramfs via `initrd-find` and only unpacks if `/shutdown`
+ is present in the archive (meaning feature `shutdown` was included).
+- `make-initrd-shutdown.service` (in `system/`): systemd unit triggering
`make-initrd-shutdown-restore` on shutdown.
diff --git a/features/shutdown/guess/common b/features/shutdown/guess/common
new file mode 100755
index 00000000..b292cef4
--- /dev/null
+++ b/features/shutdown/guess/common
@@ -0,0 +1,32 @@
+#!/bin/bash -efu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+. guess-functions
+
+get_sys_command initrd-find
+get_sys_command initrd-ls
+
+initrd=""
+if [ -n "${IMAGEFILE-}" ] && [ -f "$IMAGEFILE" ]; then
+ initrd="$IMAGEFILE"
+fi
+
+if [ -z "$initrd" ]; then
+ for k in "${KERNEL-}" "$(uname -r)"; do
+ [ -n "$k" ] || continue
+ if initrd="$(sys_initrd_find "$k" 2>/dev/null)" && [ -n "$initrd" ] && [ -f "$initrd" ]; then
+ break
+ fi
+ initrd=""
+ done
+fi
+
+[ -n "$initrd" ] || exit 0
+
+has_shutdown() {
+ sys_initrd_ls "$1" 2>/dev/null | grep -qsE '(1970 \./shutdown|[[:space:]](\./)?shutdown$)'
+}
+
+if has_shutdown "$initrd"; then
+ guess_feature shutdown
+fi
diff --git a/features/shutdown/system/make-initrd-shutdown.service b/features/shutdown/system/make-initrd-shutdown.service
new file mode 100644
index 00000000..1f3fa042
--- /dev/null
+++ b/features/shutdown/system/make-initrd-shutdown.service
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[Unit]
+Description=Restore /run/initramfs on shutdown
+Documentation=man:make-initrd(8)
+After=local-fs.target boot.mount boot.automount
+Wants=local-fs.target
+ConditionPathExists=!/run/initramfs/shutdown
+
+[Service]
+RemainAfterExit=yes
+Type=oneshot
+Environment=VERBOSE=1
+ExecStart=/bin/true
+ExecStop=/usr/sbin/make-initrd-shutdown-restore
+StandardOutput=journal+console
+StandardError=journal+console
+
+[Install]
+WantedBy=sysinit.target
diff --git a/utils/initrd-find/Makefile.mk b/utils/initrd-find/Makefile.mk
new file mode 100644
index 00000000..39383810
--- /dev/null
+++ b/utils/initrd-find/Makefile.mk
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+initrd_find_DEST = $(dest_sbindir)/initrd-find
+initrd_find_SRCS = $(utils_srcdir)/initrd-find/initrd-find.in
+
+SCRIPTS += initrd_find
diff --git a/utils/initrd-find/initrd-find.in b/utils/initrd-find/initrd-find.in
new file mode 100755
index 00000000..f427a4f6
--- /dev/null
+++ b/utils/initrd-find/initrd-find.in
@@ -0,0 +1,401 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+. shell-error
+. shell-getopt
+. shell-args
+
+PROG="${0##*/}"
+
+show_help() {
+ cat <<-EOF
+ Usage: $PROG [options] [KERNEL_VERSION]
+
+ Find the active or matching initramfs image for a kernel version.
+
+ Options:
+ -k, --kernel=VERSION kernel version (default: current running kernel);
+ -b, --boot-dir=DIR directory to search for boot entries and images
+ (can be specified multiple times);
+ -m, --mount mount candidate boot directories read-only if unmounted;
+ --no-mount do not attempt to mount filesystems (default);
+ -a, --any accept any initramfs image (do not require
+ make-initrd signature);
+ -v, --verbose print diagnostic information to stderr;
+ -q, --quiet suppress diagnostic messages;
+ -V, --version print program version and exit;
+ -h, --help show this help message and exit.
+
+ Report bugs to authors.
+
+ EOF
+ exit 0
+}
+
+print_version() {
+ cat <<-EOF
+ ${1:-$PROG} version @PACKAGE_VERSION@
+ Written by Alexey Gladkov <gladkov.alexey@gmail.com>
+
+ Copyright (C) 2026 Alexey Gladkov <gladkov.alexey@gmail.com>
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ EOF
+ exit 0
+}
+
+boot_dirs=()
+
+add_boot_dir() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ local canonical
+ canonical="$(readlink -f "$d" 2>/dev/null || echo "$d")"
+ [ -n "$canonical" ] || canonical="$d"
+
+ local existing
+ for existing in "${boot_dirs[@]}"; do
+ [ "$existing" != "$canonical" ] || return 0
+ done
+
+ boot_dirs+=("$canonical")
+}
+
+kver=""
+do_mount=0
+check_make_initrd=1
+
+TEMP=$(getopt -n "$PROG" -o "a,b:,k:,m,$getopt_common_opts" -l "any,boot-dir:,kernel:,mount,no-mount,$getopt_common_longopts" -- "$@") ||
+ show_usage
+eval set -- "$TEMP"
+
+while :; do
+ case "$1" in
+ -a|--any)
+ check_make_initrd=0
+ ;;
+ -b|--boot-dir) shift
+ add_boot_dir "$1"
+ ;;
+ -k|--kernel) shift
+ kver="$1"
+ ;;
+ -m|--mount)
+ do_mount=1
+ ;;
+ --no-mount)
+ do_mount=0
+ ;;
+ -q|--quiet|-v|--verbose|-V|--version|-h|--help)
+ parse_common_option "$1"
+ ;;
+ --) shift
+ break
+ ;;
+ *)
+ fatal "Unrecognized option: $1"
+ ;;
+ esac
+ shift
+done
+
+if [ $# -gt 0 ]; then
+ if [ -z "$kver" ]; then
+ kver="$1"
+ shift
+ fi
+fi
+
+[ $# -eq 0 ] ||
+ show_usage "Unexpected argument: $1"
+
+kver="${kver:-${SYS_KVER:-${KERNEL:-$(uname -r)}}}"
+
+is_mounted() {
+ local d="$1"
+ if command -v mountpoint >/dev/null 2>&1; then
+ mountpoint -q "$d"
+ else
+ grep -qs "[[:space:]]${d}[[:space:]]" /proc/mounts
+ fi
+}
+
+mount_candidate() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ if ! is_mounted "$d"; then
+ mount -o ro "$d" >/dev/null 2>&1 || mount "$d" >/dev/null 2>&1 || :
+ fi
+}
+
+if [ "${#boot_dirs[@]}" -eq 0 ] && [ -n "${BOOT_DIRS:-}" ]; then
+ for d in $BOOT_DIRS; do
+ add_boot_dir "$d"
+ done
+fi
+
+if [ "${#boot_dirs[@]}" -eq 0 ]; then
+ if [ "$do_mount" -eq 1 ]; then
+ for d in /boot /efi /boot/efi; do
+ mount_candidate "$d"
+ done
+ fi
+
+ if command -v bootctl >/dev/null 2>&1; then
+ bp="$(bootctl -q --print-boot-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$bp"
+ add_boot_dir "$bp"
+
+ ep="$(bootctl -q --print-esp-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$ep"
+ add_boot_dir "$ep"
+ fi
+
+ for d in /boot /efi /boot/efi; do
+ add_boot_dir "$d"
+ done
+fi
+
+is_matching_initrd() {
+ local img="$1"
+
+ [ -f "$img" ] && [ -s "$img" ] ||
+ return 1
+
+ if [ "$check_make_initrd" -eq 0 ]; then
+ return 0
+ fi
+
+ initrd-extract "$img" 2>/dev/null |
+ cpio -i --to-stdout --quiet '*initrd-release' 2>/dev/null | {
+ while read -r line || [ -n "$line" ]; do
+ line="${line%$'\r'}"
+ case "$line" in
+ ID=make-initrd|ID=\"make-initrd\"|ID=\'make-initrd\')
+ exit 0
+ ;;
+ esac
+ done
+ exit 1
+ }
+}
+
+initrd_image=""
+
+set_initrd_image() {
+ local img="$1"
+
+ if is_matching_initrd "$img"; then
+ initrd_image="$(readlink -f "$img" 2>/dev/null || echo "$img")"
+ [ -n "$initrd_image" ] || initrd_image="$img"
+ return 0
+ fi
+ return 1
+}
+
+find_initramfs() {
+ local boot_dir conf matched key val rel_path token entry_token machine_id arg p efi img
+
+ # 1. BLS Type #1: $boot_dir/loader/entries/*.conf
+ for boot_dir in "${boot_dirs[@]}"; do
+ for conf in "$boot_dir"/loader/entries/*.conf; do
+ [ -f "$conf" ] ||
+ continue
+
+ matched=0
+ if grep -Eq "^version[[:space:]]+(\")?${kver}(\")?$" "$conf" 2>/dev/null; then
+ matched=1
+ else
+ case "$(basename "$conf")" in
+ *"$kver"*) matched=1 ;;
+ esac
+ if [ "$matched" -eq 0 ] && grep -Eq "^linux[[:space:]]+.*${kver}" "$conf" 2>/dev/null; then
+ matched=1
+ fi
+ fi
+
+ [ "$matched" -eq 1 ] ||
+ continue
+
+ while read -r key val || [ -n "$key" ]; do
+ [ "$key" = "initrd" ] ||
+ continue
+
+ val="${val%$'\r'}"
+ val="${val#"${val%%[![:space:]]*}"}"
+ val="${val%"${val##*[![:space:]]}"}"
+ val="${val%\"}"
+ val="${val#\"}"
+
+ rel_path="${val#/}"
+
+ if set_initrd_image "$boot_dir/$rel_path"; then
+ return 0
+ fi
+
+ local b
+ for b in "${boot_dirs[@]}"; do
+ if set_initrd_image "$b/$rel_path"; then
+ return 0
+ fi
+ done
+ done < "$conf"
+ done
+ done
+
+ # 2. BLS Type #1 directory structure: $boot_dir/$token/$kver/initrd*
+ entry_token=""
+ if [ -s /etc/kernel/entry-token ]; then
+ read -r entry_token < /etc/kernel/entry-token || :
+ entry_token="${entry_token%$'\r'}"
+ fi
+
+ machine_id=""
+ if [ -s /etc/machine-id ]; then
+ read -r machine_id < /etc/machine-id || :
+ machine_id="${machine_id%$'\r'}"
+ [ "$machine_id" != "uninitialized" ] || machine_id=""
+ fi
+
+ for boot_dir in "${boot_dirs[@]}"; do
+ for token in "$entry_token" "$machine_id" "Default" "default"; do
+ [ -n "$token" ] ||
+ continue
+
+ for img in \
+ "$boot_dir/$token/$kver/initrd" \
+ "$boot_dir/$token/$kver/initramfs" \
+ "$boot_dir/$token/$kver/initrd.img" \
+ "$boot_dir/$token/$kver/initramfs.img"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir/$token/$kver"/initr*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # Without token: $boot_dir/$kver/initrd*
+ for img in \
+ "$boot_dir/$kver/initrd" \
+ "$boot_dir/$kver/initramfs" \
+ "$boot_dir/$kver/initrd.img" \
+ "$boot_dir/$kver/initramfs.img"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir/$kver"/initr*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # 3. Kernel cmdline: /proc/cmdline
+ if [ -f /proc/cmdline ]; then
+ local cmdline_args=()
+
+ read -r -a cmdline_args < /proc/cmdline 2>/dev/null || :
+
+ for arg in "${cmdline_args[@]}"; do
+ case "$arg" in
+ initrd=*)
+ p="${arg#initrd=}"
+ p="${p//\\//}"
+
+ if set_initrd_image "$p"; then
+ return 0
+ fi
+
+ rel_path="${p#/}"
+ for boot_dir in "${boot_dirs[@]}"; do
+ if set_initrd_image "$boot_dir/$rel_path"; then
+ return 0
+ fi
+ done
+ ;;
+ esac
+ done
+ fi
+
+ # 4. BLS Type #2: UKI ($boot_dir/EFI/Linux/*.efi)
+ for boot_dir in "${boot_dirs[@]}"; do
+ for efi in "$boot_dir/EFI/Linux"/*"$kver"*.efi "$boot_dir/EFI/Linux"/*.efi; do
+ [ -f "$efi" ] ||
+ continue
+
+ if set_initrd_image "$efi"; then
+ return 0
+ fi
+ done
+ done
+
+ # 5. /lib/modules/$kver/initrd
+ if [ -f "/lib/modules/$kver/initrd" ]; then
+ if set_initrd_image "/lib/modules/$kver/initrd"; then
+ return 0
+ fi
+ fi
+
+ # 6. Traditional paths in boot_dirs
+ for boot_dir in "${boot_dirs[@]}"; do
+ for img in \
+ "$boot_dir/initrd-$kver.img" \
+ "$boot_dir/initramfs-$kver.img" \
+ "$boot_dir/initrd.img-$kver" \
+ "$boot_dir/initrd-$kver" \
+ "$boot_dir/initramfs-$kver"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir"/initr*"$kver"*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in \
+ "$boot_dir/initramfs-linux.img" \
+ "$boot_dir/initrd.img" \
+ "$boot_dir/initrd"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # 7. Root fallback (/initrd.img)
+ if [ -f "/initrd.img" ]; then
+ if set_initrd_image "/initrd.img"; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+if find_initramfs && [ -n "$initrd_image" ] && [ -e "$initrd_image" ]; then
+ verbose "found initramfs: $initrd_image"
+ printf '%s\n' "$initrd_image"
+ exit 0
+fi
+
+[ -n "$quiet" ] || message "no initramfs found for kernel $kver"
+exit 1
diff --git a/utils/initrd-find/tests/ts0001-initrd-find-basic/expect b/utils/initrd-find/tests/ts0001-initrd-find-basic/expect
new file mode 100644
index 00000000..2535efed
--- /dev/null
+++ b/utils/initrd-find/tests/ts0001-initrd-find-basic/expect
@@ -0,0 +1,10 @@
+Usage: initrd-find.in [options] [KERNEL_VERSION]
+help flag: ok
+traditional layout: ok
+bls conf: ok
+bls dir: ok
+non-matching kernel: ok
+foreign initrd rejected: ok
+foreign initrd accepted with --any: ok
+bls type 2 uki: ok
+rc=0
diff --git a/utils/initrd-find/tests/ts0001-initrd-find-basic/run b/utils/initrd-find/tests/ts0001-initrd-find-basic/run
new file mode 100755
index 00000000..ec62f2e8
--- /dev/null
+++ b/utils/initrd-find/tests/ts0001-initrd-find-basic/run
@@ -0,0 +1,108 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+PROG="${PROG:-utils/initrd-find/initrd-find.in}"
+export PATH="$PWD/.build/dest/usr/sbin:$PWD/.build/dest/usr/bin:$PATH"
+
+tmpdir="$(mktemp -d)"
+trap 'rm -rf -- "$tmpdir"' EXIT
+
+create_mock_initrd() {
+ local outfile="$1"
+ local id="${2:-make-initrd}"
+ local mock_src="$tmpdir/mock_initrd_src"
+ rm -rf "$mock_src"
+ mkdir -p "$mock_src/etc"
+ cat <<- EOF > "$mock_src/etc/initrd-release"
+ ID=$id
+ VERSION_ID=test
+ NAME="make-initrd"
+ EOF
+ ( cd "$mock_src" && printf "etc\netc/initrd-release\n" | cpio -o -H newc --quiet > "$outfile" )
+}
+
+# 1. Test help flag
+"$PROG" --help | head -n 1
+echo "help flag: ok"
+
+# 2. Test traditional layout
+mkdir -p "$tmpdir/boot1"
+create_mock_initrd "$tmpdir/boot1/initrd-6.6.1.img"
+found="$("$PROG" -b "$tmpdir/boot1" 6.6.1)"
+[ "$found" = "$tmpdir/boot1/initrd-6.6.1.img" ] && echo "traditional layout: ok"
+
+# 3. Test BLS Type #1 conf entry
+mkdir -p "$tmpdir/boot2/loader/entries"
+cat << 'EOF' > "$tmpdir/boot2/loader/entries/test.conf"
+title Test OS
+version 6.6.1
+initrd /images/initrd-custom
+EOF
+mkdir -p "$tmpdir/boot2/images"
+create_mock_initrd "$tmpdir/boot2/images/initrd-custom"
+found="$("$PROG" -b "$tmpdir/boot2" 6.6.1)"
+[ "$found" = "$tmpdir/boot2/images/initrd-custom" ] && echo "bls conf: ok"
+
+# 4. Test BLS Type #1 directory structure
+mkdir -p "$tmpdir/boot3/Default/6.6.1"
+create_mock_initrd "$tmpdir/boot3/Default/6.6.1/initrd"
+found="$("$PROG" -b "$tmpdir/boot3" 6.6.1)"
+[ "$found" = "$tmpdir/boot3/Default/6.6.1/initrd" ] && echo "bls dir: ok"
+
+# 5. Non-matching kernel returns 1
+rc=0
+"$PROG" -b "$tmpdir/boot1" 9.9.9 >/dev/null 2>&1 || rc=$?
+[ "$rc" -eq 1 ] && echo "non-matching kernel: ok"
+
+# 6. Foreign initrd rejection without --any and acceptance with --any
+mkdir -p "$tmpdir/boot4"
+create_mock_initrd "$tmpdir/boot4/initrd-6.6.1.img" "ubuntu"
+rc=0
+"$PROG" -b "$tmpdir/boot4" 6.6.1 >/dev/null 2>&1 || rc=$?
+[ "$rc" -eq 1 ] && echo "foreign initrd rejected: ok"
+
+found="$("$PROG" --any -b "$tmpdir/boot4" 6.6.1)"
+[ "$found" = "$tmpdir/boot4/initrd-6.6.1.img" ] && echo "foreign initrd accepted with --any: ok"
+
+# 7. BLS Type #2 UKI discovery
+mkdir -p "$tmpdir/boot5/EFI/Linux"
+python3 - "$tmpdir/boot5/EFI/Linux/linux-6.6.1.efi" << 'EOF'
+import sys, io, struct
+def make_cpio_entry(name, content=b""):
+ namesize = len(name.encode()) + 1
+ filesize = len(content)
+ hdr = f"070701{1:08x}{0o100644:08x}{0:08x}{0:08x}{1:08x}{0:08x}{filesize:08x}{0:08x}{0:08x}{0:08x}{0:08x}{namesize:08x}{0:08x}".encode()
+ out = io.BytesIO()
+ out.write(hdr)
+ out.write(name.encode() + b"\0")
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ if content:
+ out.write(content)
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ return out.getvalue()
+
+trailer = make_cpio_entry("TRAILER!!!")
+entries = [make_cpio_entry("etc/initrd-release", b"ID=make-initrd\n"), trailer]
+cpio_data = b"".join(entries)
+pad = (512 - (len(cpio_data) % 512)) % 512
+cpio_data += b"\0" * pad
+
+dos_header = b'MZ' + b'\0' * 58 + struct.pack('<I', 64)
+pe_sig = b'PE\0\0'
+coff_header = struct.pack('<HHIIIHH', 0x8664, 1, 0, 0, 0, 0, 2)
+headers = dos_header + pe_sig + coff_header
+raw_ptr = 512
+raw_size = len(cpio_data)
+vsize = len(cpio_data)
+sec_hdr = struct.pack('<8sIIIIIIHHI', b'.initrd\0', vsize, 0x1000, raw_size, raw_ptr, 0, 0, 0, 0, 0x40000040)
+headers += sec_hdr
+pe = headers + b'\0' * (512 - len(headers)) + cpio_data
+
+with open(sys.argv[1], "wb") as f:
+ f.write(pe)
+EOF
+
+found="$("$PROG" -b "$tmpdir/boot5" 6.6.1)"
+[ "$found" = "$tmpdir/boot5/EFI/Linux/linux-6.6.1.efi" ] && echo "bls type 2 uki: ok"
diff --git a/utils/make-initrd-shutdown-restore/Makefile.mk b/utils/make-initrd-shutdown-restore/Makefile.mk
new file mode 100644
index 00000000..68349848
--- /dev/null
+++ b/utils/make-initrd-shutdown-restore/Makefile.mk
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+make_initrd_shutdown_restore_DEST = $(dest_sbindir)/make-initrd-shutdown-restore
+make_initrd_shutdown_restore_SRCS = $(utils_srcdir)/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
+
+SCRIPTS += make_initrd_shutdown_restore
diff --git a/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in b/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
new file mode 100755
index 00000000..c8a4c285
--- /dev/null
+++ b/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
@@ -0,0 +1,147 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+target_dir="${TARGET_DIR:-/run/initramfs}"
+
+# If shutdown executable already exists, do nothing
+[ ! -x "$target_dir/shutdown" ] || exit 0
+
+if ! command -v initrd-find >/dev/null 2>&1 || ! command -v initrd-extract >/dev/null 2>&1 || ! command -v initrd-ls >/dev/null 2>&1; then
+ echo "make-initrd-shutdown-restore: required tools (initrd-find, initrd-extract, initrd-ls) not found" >&2
+ exit 1
+fi
+
+kver="${SYS_KVER:-$(uname -r)}"
+boot_override="${BOOT_DIRS:-}"
+
+mounted_by_us=()
+
+cleanup() {
+ if [ "${#mounted_by_us[@]}" -gt 0 ]; then
+ for (( i=${#mounted_by_us[@]}-1; i>=0; i-- )); do
+ umount "${mounted_by_us[i]}" >/dev/null 2>&1 ||:
+ done
+ fi
+}
+
+trap cleanup EXIT
+
+is_mounted() {
+ local d="$1"
+ if command -v mountpoint >/dev/null 2>&1; then
+ mountpoint -q "$d"
+ else
+ grep -qs "[[:space:]]${d}[[:space:]]" /proc/mounts
+ fi
+}
+
+mount_candidate() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ if ! is_mounted "$d"; then
+ if mount -o ro "$d" >/dev/null 2>&1 || mount "$d" >/dev/null 2>&1; then
+ mounted_by_us+=("$d")
+ fi
+ fi
+}
+
+boot_dirs=()
+
+add_boot_dir() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ local existing
+
+ for existing in "${boot_dirs[@]}"; do
+ [ "$existing" != "$d" ] || return 0
+ done
+
+ boot_dirs+=("$d")
+}
+
+has_shutdown() {
+ initrd-ls "$1" 2>/dev/null | grep -qsE '(1970 \./shutdown|[[:space:]](\./)?shutdown$)'
+}
+
+unpack_initrd() {
+ initrd-extract "$1" |
+ cpio -idu --quiet --no-absolute-filenames
+}
+
+if [ -n "$boot_override" ]; then
+ for d in $boot_override; do
+ add_boot_dir "$d"
+ done
+else
+ # Ensure candidate boot directories are mounted before searching
+ for d in /boot /efi /boot/efi; do
+ mount_candidate "$d"
+ done
+
+ if command -v bootctl >/dev/null 2>&1; then
+ bp="$(bootctl -q --print-boot-path 2>/dev/null || :)"
+ mount_candidate "$bp"
+ add_boot_dir "$bp"
+
+ ep="$(bootctl -q --print-esp-path 2>/dev/null || :)"
+ mount_candidate "$ep"
+ add_boot_dir "$ep"
+ fi
+
+ for d in /boot /efi /boot/efi; do
+ add_boot_dir "$d"
+ done
+fi
+
+find_args=()
+if [ "${#boot_dirs[@]}" -gt 0 ]; then
+ for d in "${boot_dirs[@]}"; do
+ find_args+=("-b" "$d")
+ done
+fi
+
+initrd_image="$(initrd-find "${find_args[@]}" "$kver" 2>/dev/null || :)"
+
+if [ -z "$initrd_image" ] || [ ! -e "$initrd_image" ]; then
+ [ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: no initramfs found for kernel $kver" >&2
+ exit 0
+fi
+
+if ! has_shutdown "$initrd_image"; then
+ [ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: shutdown feature not included in $initrd_image" >&2
+ exit 0
+fi
+
+[ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: restoring initramfs from: $initrd_image" >&2
+
+mkdir -p "$target_dir"
+cd "$target_dir"
+
+unpack_initrd "$initrd_image"
+
+if [ -f "$target_dir/initramfs.img" ]; then
+ if command -v unsquashfs >/dev/null 2>&1; then
+ unsquashfs -f -d "$target_dir" "$target_dir/initramfs.img" >/dev/null 2>&1 ||:
+
+ elif command -v fsck.erofs >/dev/null 2>&1; then
+ fsck.erofs --extract="$target_dir" --overwrite "$target_dir/initramfs.img" >/dev/null 2>&1 ||:
+ fi
+fi
+
+if [ ! -x "$target_dir/shutdown" ]; then
+ echo "make-initrd-shutdown-restore: $target_dir/shutdown missing or not executable" >&2
+ rm -rf "${target_dir:?}"/*
+ exit 1
+fi
+
+[ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: successfully restored initramfs into $target_dir" >&2
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v2 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service
2026-11-09 13:07 ` [make-initrd] [PATCH v1 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service Alexey Gladkov
@ 2026-11-09 17:10 ` Alexey Gladkov
2026-11-09 18:45 ` Anton Midyukov
0 siblings, 1 reply; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 17:10 UTC (permalink / raw)
To: make-initrd
For systemd-shutdown to pivot to an exitrd at shutdown, the unpacked
initramfs contents containing the /shutdown binary must be populated in
/run/initramfs before local filesystems are unmounted.
Add initrd-find utility and make-initrd-shutdown-restore to locate and
restore the exitrd during shutdown:
- Locate the initramfs matching the running kernel version across:
* Boot Loader Specification (BLS) Type #1 entries in
loader/entries/*.conf, supporting multi-part initrd directives.
* BLS Type #1 directory structure ($BOOT/$token/$kver/initrd*).
* BLS Type #2 Unified Kernel Images (.efi binaries).
* Kernel command line (initrd=...) from /proc/cmdline.
* /lib/modules/$kver/initrd and traditional distro image paths.
- Ensure candidate boot and ESP partitions (/boot, /efi, /boot/efi, and
locations reported by bootctl) are mounted read-only before searching.
- Add guess/common to automatically guess feature shutdown when the
detected image includes /shutdown.
- Unpack the archive into /run/initramfs using initrd-extract, and
unpack compressed-fs layers if present.
- Cleanly unmount any temporary mounts made by the script upon exit.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
Makefile.in | 6 +
features/shutdown/README.md | 22 +-
features/shutdown/guess/common | 32 ++
.../system/make-initrd-shutdown.service | 20 +
utils/initrd-find/Makefile.mk | 6 +
utils/initrd-find/initrd-find.in | 448 ++++++++++++++++++
.../tests/ts0001-initrd-find-basic/expect | 14 +
.../tests/ts0001-initrd-find-basic/run | 124 +++++
.../make-initrd-shutdown-restore/Makefile.mk | 6 +
.../make-initrd-shutdown-restore.in | 147 ++++++
10 files changed, 821 insertions(+), 4 deletions(-)
create mode 100755 features/shutdown/guess/common
create mode 100644 features/shutdown/system/make-initrd-shutdown.service
create mode 100644 utils/initrd-find/Makefile.mk
create mode 100755 utils/initrd-find/initrd-find.in
create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
diff --git a/Makefile.in b/Makefile.in
index 96058b73..1dddcdaa 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -32,6 +32,7 @@ kernelconfdir = $(prefix)/lib/kernel/install.conf.d
libcdir := $(shell $(srcdir)/tools/get-libc-dir)
completionsdir := $(shell pkg-config --variable=completionsdir bash-completion 2>/dev/null ||:)
+systemdunitdir ?= $(shell pkg-config --variable=systemdsystemunitdir systemd 2>/dev/null ||:)
runtime_srcdir = $(srcdir)/runtime/src
utils_srcdir = $(srcdir)/utils
@@ -611,6 +612,9 @@ install: build installdirs
$(INSTALL) -d -m 755 -- "$(DESTDIR)$(kernelinstalldir)" "$(DESTDIR)$(kernelconfdir)" && \
$(INSTALL_SCRIPT) -m 755 -t "$(DESTDIR)$(kernelinstalldir)" -- $(wildcard $(srcdir)/contrib/kernel-install/*.install) && \
$(INSTALL_DATA) -t "$(DESTDIR)$(kernelconfdir)" -- $(wildcard $(srcdir)/contrib/kernel-install/*.conf); }
+ $(Q)[ -z "$(systemdunitdir)" ] || { \
+ $(INSTALL) -d -m 755 -- "$(DESTDIR)$(systemdunitdir)" && \
+ $(INSTALL_DATA) -t "$(DESTDIR)$(systemdunitdir)" -- $(srcdir)/features/shutdown/system/make-initrd-shutdown.service; }
ifneq ($(SCDOC),)
$(Q)$(INSTALL_DATA) -t "$(DESTDIR)$(man1dir)" -- $(addprefix $(dest_man1dir)/,$(MANPAGE_NAMES))
endif
@@ -634,6 +638,8 @@ uninstall:
done
$(Q)[ -z "$(completionsdir)" ] || \
rm -f -- $(addprefix $(DESTDIR)$(completionsdir)/,$(notdir $(wildcard $(srcdir)/contrib/bash-completion/*)))
+ $(Q)[ -z "$(systemdunitdir)" ] || \
+ rm -f -- "$(DESTDIR)$(systemdunitdir)/make-initrd-shutdown.service"
$(Q)[ -z "$(kernelinstalldir)" ] || \
rm -f -- $(addprefix $(DESTDIR)$(kernelinstalldir)/,$(notdir $(wildcard $(srcdir)/contrib/kernel-install/*.install))) \
$(addprefix $(DESTDIR)$(kernelconfdir)/,$(notdir $(wildcard $(srcdir)/contrib/kernel-install/*.conf)))
diff --git a/features/shutdown/README.md b/features/shutdown/README.md
index 949a40bd..6764be70 100644
--- a/features/shutdown/README.md
+++ b/features/shutdown/README.md
@@ -58,10 +58,24 @@ Shutdown hooks are organized into the following stages:
been torn down, immediately before `sync` and reboot/poweroff.
- Arguments: `"$hook_type"` `"$action"`
+## Auto-detection (guess)
+
+This feature provides `guess/common`:
+When generating a new initramfs, `guess/common` queries `initrd-find` to locate
+the previous or current kernel's initramfs image. If that image contains
+`/shutdown`, `features/shutdown` is automatically guessed (`guess_feature shutdown`)
+for the new image.
+
## Systemd integration
-The `system/` directory provides files for host system integration:
-- `make-initrd-shutdown-restore`: script executed upon shutdown to unpack the
- initramfs into `/run/initramfs`.
-- `make-initrd-shutdown.service`: systemd unit triggering
+Host system components handle exitrd restoration:
+- `initrd-find` (installed into `/usr/sbin/` from `utils/`):
+ utility used to locate the active or matching initramfs image across BLS Type #1
+ configurations, BLS directory trees, BLS Type #2 UKIs, `/proc/cmdline`,
+ `/lib/modules/$kver/initrd`, and standard boot directories.
+- `make-initrd-shutdown-restore` (installed into `/usr/sbin/` from `utils/`):
+ utility executed upon shutdown to unpack the initramfs into `/run/initramfs`.
+ It locates the initramfs via `initrd-find` and only unpacks if `/shutdown`
+ is present in the archive (meaning feature `shutdown` was included).
+- `make-initrd-shutdown.service` (in `system/`): systemd unit triggering
`make-initrd-shutdown-restore` on shutdown.
diff --git a/features/shutdown/guess/common b/features/shutdown/guess/common
new file mode 100755
index 00000000..b292cef4
--- /dev/null
+++ b/features/shutdown/guess/common
@@ -0,0 +1,32 @@
+#!/bin/bash -efu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+. guess-functions
+
+get_sys_command initrd-find
+get_sys_command initrd-ls
+
+initrd=""
+if [ -n "${IMAGEFILE-}" ] && [ -f "$IMAGEFILE" ]; then
+ initrd="$IMAGEFILE"
+fi
+
+if [ -z "$initrd" ]; then
+ for k in "${KERNEL-}" "$(uname -r)"; do
+ [ -n "$k" ] || continue
+ if initrd="$(sys_initrd_find "$k" 2>/dev/null)" && [ -n "$initrd" ] && [ -f "$initrd" ]; then
+ break
+ fi
+ initrd=""
+ done
+fi
+
+[ -n "$initrd" ] || exit 0
+
+has_shutdown() {
+ sys_initrd_ls "$1" 2>/dev/null | grep -qsE '(1970 \./shutdown|[[:space:]](\./)?shutdown$)'
+}
+
+if has_shutdown "$initrd"; then
+ guess_feature shutdown
+fi
diff --git a/features/shutdown/system/make-initrd-shutdown.service b/features/shutdown/system/make-initrd-shutdown.service
new file mode 100644
index 00000000..1f3fa042
--- /dev/null
+++ b/features/shutdown/system/make-initrd-shutdown.service
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+[Unit]
+Description=Restore /run/initramfs on shutdown
+Documentation=man:make-initrd(8)
+After=local-fs.target boot.mount boot.automount
+Wants=local-fs.target
+ConditionPathExists=!/run/initramfs/shutdown
+
+[Service]
+RemainAfterExit=yes
+Type=oneshot
+Environment=VERBOSE=1
+ExecStart=/bin/true
+ExecStop=/usr/sbin/make-initrd-shutdown-restore
+StandardOutput=journal+console
+StandardError=journal+console
+
+[Install]
+WantedBy=sysinit.target
diff --git a/utils/initrd-find/Makefile.mk b/utils/initrd-find/Makefile.mk
new file mode 100644
index 00000000..39383810
--- /dev/null
+++ b/utils/initrd-find/Makefile.mk
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+initrd_find_DEST = $(dest_sbindir)/initrd-find
+initrd_find_SRCS = $(utils_srcdir)/initrd-find/initrd-find.in
+
+SCRIPTS += initrd_find
diff --git a/utils/initrd-find/initrd-find.in b/utils/initrd-find/initrd-find.in
new file mode 100755
index 00000000..c2b1ed18
--- /dev/null
+++ b/utils/initrd-find/initrd-find.in
@@ -0,0 +1,448 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+. shell-error
+. shell-getopt
+. shell-args
+
+PROG="${0##*/}"
+
+show_help() {
+ cat <<-EOF
+ Usage: $PROG [options] [KERNEL_VERSION]
+
+ Find the active or matching initramfs image for a kernel version.
+
+ Options:
+ -k, --kernel=VERSION kernel version (default: current running kernel);
+ -b, --boot-dir=DIR directory to search for boot entries and images
+ (can be specified multiple times);
+ -r, --root-dir=DIR root directory under which to search for boot directories
+ and images (default: /);
+ -m, --mount mount candidate boot directories read-only if unmounted;
+ --no-mount do not attempt to mount filesystems (default);
+ -a, --any accept any initramfs image (do not require
+ make-initrd signature);
+ -v, --verbose print diagnostic information to stderr;
+ -q, --quiet suppress diagnostic messages;
+ -V, --version print program version and exit;
+ -h, --help show this help message and exit.
+
+ Report bugs to authors.
+
+ EOF
+ exit 0
+}
+
+print_version() {
+ cat <<-EOF
+ ${1:-$PROG} version @PACKAGE_VERSION@
+ Written by Alexey Gladkov <gladkov.alexey@gmail.com>
+
+ Copyright (C) 2026 Alexey Gladkov <gladkov.alexey@gmail.com>
+ This is free software; see the source for copying conditions. There is NO
+ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ EOF
+ exit 0
+}
+
+boot_dirs=()
+raw_boot_dirs=()
+
+kver=""
+root_dir=""
+do_mount=0
+check_make_initrd=1
+
+TEMP=$(getopt -n "$PROG" -o "a,b:,k:,m,r:,$getopt_common_opts" -l "any,boot-dir:,kernel:,mount,no-mount,root-dir:,rootdir:,$getopt_common_longopts" -- "$@") ||
+ show_usage
+eval set -- "$TEMP"
+
+while :; do
+ case "$1" in
+ -a|--any)
+ check_make_initrd=0
+ ;;
+ -b|--boot-dir) shift
+ raw_boot_dirs+=("$1")
+ ;;
+ -k|--kernel) shift
+ kver="$1"
+ ;;
+ -m|--mount)
+ do_mount=1
+ ;;
+ --no-mount)
+ do_mount=0
+ ;;
+ -r|--root-dir|--rootdir) shift
+ root_dir="$1"
+ ;;
+ -q|--quiet|-v|--verbose|-V|--version|-h|--help)
+ parse_common_option "$1"
+ ;;
+ --) shift
+ break
+ ;;
+ *)
+ fatal "Unrecognized option: $1"
+ ;;
+ esac
+ shift
+done
+
+if [ $# -gt 0 ]; then
+ if [ -z "$kver" ]; then
+ kver="$1"
+ shift
+ fi
+fi
+
+[ $# -eq 0 ] ||
+ show_usage "Unexpected argument: $1"
+
+kver="${kver:-${SYS_KVER:-${KERNEL:-$(uname -r)}}}"
+root_dir="${root_dir:-${ROOT_DIR:-}}"
+
+if [ -n "$root_dir" ]; then
+ root_dir="${root_dir%/}"
+ [ -n "$root_dir" ] || root_dir="/"
+fi
+[ "$root_dir" != "/" ] || root_dir=""
+
+add_boot_dir() {
+ local d="$1"
+
+ [ -n "$d" ] || return 0
+
+ if [ -n "$root_dir" ]; then
+ case "$d" in
+ "$root_dir"/*|"$root_dir") ;;
+ /*) d="$root_dir$d" ;;
+ *) d="$root_dir/$d" ;;
+ esac
+ fi
+
+ [ -d "$d" ] || return 0
+
+ local canonical
+ canonical="$(readlink -f "$d" 2>/dev/null || echo "$d")"
+ [ -n "$canonical" ] || canonical="$d"
+
+ local existing
+ for existing in "${boot_dirs[@]}"; do
+ [ "$existing" != "$canonical" ] || return 0
+ done
+
+ boot_dirs+=("$canonical")
+}
+
+if [ "${#raw_boot_dirs[@]}" -gt 0 ]; then
+ for d in "${raw_boot_dirs[@]}"; do
+ add_boot_dir "$d"
+ done
+elif [ -n "${BOOT_DIRS:-}" ]; then
+ for d in $BOOT_DIRS; do
+ add_boot_dir "$d"
+ done
+fi
+
+is_mounted() {
+ local d="$1"
+ if command -v mountpoint >/dev/null 2>&1; then
+ mountpoint -q "$d"
+ else
+ grep -qs "[[:space:]]${d}[[:space:]]" /proc/mounts
+ fi
+}
+
+mount_candidate() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ if ! is_mounted "$d"; then
+ mount -o ro "$d" >/dev/null 2>&1 || mount "$d" >/dev/null 2>&1 || :
+ fi
+}
+
+if [ "${#boot_dirs[@]}" -eq 0 ]; then
+ if [ "$do_mount" -eq 1 ]; then
+ for d in "$root_dir"/boot "$root_dir"/efi "$root_dir"/boot/efi; do
+ mount_candidate "$d"
+ done
+ fi
+
+ if [ -z "$root_dir" ]; then
+ if command -v bootctl >/dev/null 2>&1; then
+ bp="$(bootctl -q --print-boot-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$bp"
+ add_boot_dir "$bp"
+
+ ep="$(bootctl -q --print-esp-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$ep"
+ add_boot_dir "$ep"
+ fi
+ else
+ if command -v bootctl >/dev/null 2>&1; then
+ bp="$(bootctl --root="$root_dir" -q --print-boot-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$bp"
+ add_boot_dir "$bp"
+
+ ep="$(bootctl --root="$root_dir" -q --print-esp-path 2>/dev/null || :)"
+ [ "$do_mount" -eq 0 ] || mount_candidate "$ep"
+ add_boot_dir "$ep"
+ fi
+ fi
+
+ for d in "$root_dir"/boot "$root_dir"/efi "$root_dir"/boot/efi; do
+ add_boot_dir "$d"
+ done
+fi
+
+is_matching_initrd() {
+ local img="$1"
+
+ [ -f "$img" ] && [ -s "$img" ] ||
+ return 1
+
+ if [ "$check_make_initrd" -eq 0 ]; then
+ return 0
+ fi
+
+ initrd-extract "$img" 2>/dev/null |
+ cpio -i --to-stdout --quiet '*initrd-release' 2>/dev/null | {
+ while read -r line || [ -n "$line" ]; do
+ line="${line%$'\r'}"
+ case "$line" in
+ ID=make-initrd|ID=\"make-initrd\"|ID=\'make-initrd\')
+ exit 0
+ ;;
+ esac
+ done
+ exit 1
+ }
+}
+
+initrd_image=""
+
+set_initrd_image() {
+ local img="$1"
+
+ if is_matching_initrd "$img"; then
+ initrd_image="$(readlink -f "$img" 2>/dev/null || echo "$img")"
+ [ -n "$initrd_image" ] || initrd_image="$img"
+ return 0
+ fi
+ return 1
+}
+
+find_initramfs() {
+ local boot_dir conf matched key val rel_path token entry_token machine_id arg p efi img
+
+ # 1. BLS Type #1: $boot_dir/loader/entries/*.conf
+ for boot_dir in "${boot_dirs[@]}"; do
+ for conf in "$boot_dir"/loader/entries/*.conf; do
+ [ -f "$conf" ] ||
+ continue
+
+ matched=0
+ if grep -Eq "^version[[:space:]]+(\")?${kver}(\")?$" "$conf" 2>/dev/null; then
+ matched=1
+ else
+ case "$(basename "$conf")" in
+ *"$kver"*) matched=1 ;;
+ esac
+ if [ "$matched" -eq 0 ] && grep -Eq "^linux[[:space:]]+.*${kver}" "$conf" 2>/dev/null; then
+ matched=1
+ fi
+ fi
+
+ [ "$matched" -eq 1 ] ||
+ continue
+
+ while read -r key val || [ -n "$key" ]; do
+ [ "$key" = "initrd" ] ||
+ continue
+
+ val="${val%$'\r'}"
+ val="${val#"${val%%[![:space:]]*}"}"
+ val="${val%"${val##*[![:space:]]}"}"
+ val="${val%\"}"
+ val="${val#\"}"
+
+ rel_path="${val#/}"
+
+ if set_initrd_image "$boot_dir/$rel_path"; then
+ return 0
+ fi
+
+ local b
+ for b in "${boot_dirs[@]}"; do
+ if set_initrd_image "$b/$rel_path"; then
+ return 0
+ fi
+ done
+ done < "$conf"
+ done
+ done
+
+ # 2. BLS Type #1 directory structure: $boot_dir/$token/$kver/initrd*
+ entry_token=""
+ if [ -s "$root_dir/etc/kernel/entry-token" ]; then
+ read -r entry_token < "$root_dir/etc/kernel/entry-token" || :
+ entry_token="${entry_token%$'\r'}"
+ fi
+
+ machine_id=""
+ if [ -s "$root_dir/etc/machine-id" ]; then
+ read -r machine_id < "$root_dir/etc/machine-id" || :
+ machine_id="${machine_id%$'\r'}"
+ [ "$machine_id" != "uninitialized" ] || machine_id=""
+ fi
+
+ for boot_dir in "${boot_dirs[@]}"; do
+ for token in "$entry_token" "$machine_id" "Default" "default"; do
+ [ -n "$token" ] ||
+ continue
+
+ for img in \
+ "$boot_dir/$token/$kver/initrd" \
+ "$boot_dir/$token/$kver/initramfs" \
+ "$boot_dir/$token/$kver/initrd.img" \
+ "$boot_dir/$token/$kver/initramfs.img"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir/$token/$kver"/initr*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # Without token: $boot_dir/$kver/initrd*
+ for img in \
+ "$boot_dir/$kver/initrd" \
+ "$boot_dir/$kver/initramfs" \
+ "$boot_dir/$kver/initrd.img" \
+ "$boot_dir/$kver/initramfs.img"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir/$kver"/initr*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # 3. Kernel cmdline: /proc/cmdline
+ if [ -f /proc/cmdline ]; then
+ local cmdline_args=()
+
+ read -r -a cmdline_args < /proc/cmdline 2>/dev/null || :
+
+ for arg in "${cmdline_args[@]}"; do
+ case "$arg" in
+ initrd=*)
+ p="${arg#initrd=}"
+ p="${p//\\//}"
+
+ if [ -n "$root_dir" ]; then
+ case "$p" in
+ "$root_dir"/*|"$root_dir") ;;
+ /*) p="$root_dir$p" ;;
+ *) p="$root_dir/$p" ;;
+ esac
+ fi
+
+ if set_initrd_image "$p"; then
+ return 0
+ fi
+
+ rel_path="${p#/}"
+ for boot_dir in "${boot_dirs[@]}"; do
+ if set_initrd_image "$boot_dir/$rel_path"; then
+ return 0
+ fi
+ done
+ ;;
+ esac
+ done
+ fi
+
+ # 4. BLS Type #2: UKI ($boot_dir/EFI/Linux/*.efi)
+ for boot_dir in "${boot_dirs[@]}"; do
+ for efi in "$boot_dir/EFI/Linux"/*"$kver"*.efi "$boot_dir/EFI/Linux"/*.efi; do
+ [ -f "$efi" ] ||
+ continue
+
+ if set_initrd_image "$efi"; then
+ return 0
+ fi
+ done
+ done
+
+ # 5. $root_dir/lib/modules/$kver/initrd
+ if [ -f "$root_dir/lib/modules/$kver/initrd" ]; then
+ if set_initrd_image "$root_dir/lib/modules/$kver/initrd"; then
+ return 0
+ fi
+ fi
+
+ # 6. Traditional paths in boot_dirs
+ for boot_dir in "${boot_dirs[@]}"; do
+ for img in \
+ "$boot_dir/initrd-$kver.img" \
+ "$boot_dir/initramfs-$kver.img" \
+ "$boot_dir/initrd.img-$kver" \
+ "$boot_dir/initrd-$kver" \
+ "$boot_dir/initramfs-$kver"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in "$boot_dir"/initr*"$kver"*; do
+ [ -f "$img" ] || continue
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+
+ for img in \
+ "$boot_dir/initramfs-linux.img" \
+ "$boot_dir/initrd.img" \
+ "$boot_dir/initrd"; do
+ if set_initrd_image "$img"; then
+ return 0
+ fi
+ done
+ done
+
+ # 7. Root fallback ($root_dir/initrd.img)
+ if [ -f "$root_dir/initrd.img" ]; then
+ if set_initrd_image "$root_dir/initrd.img"; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+if find_initramfs && [ -n "$initrd_image" ] && [ -e "$initrd_image" ]; then
+ verbose "found initramfs: $initrd_image"
+ printf '%s\n' "$initrd_image"
+ exit 0
+fi
+
+[ -n "$quiet" ] || message "no initramfs found for kernel $kver"
+exit 1
diff --git a/utils/initrd-find/tests/ts0001-initrd-find-basic/expect b/utils/initrd-find/tests/ts0001-initrd-find-basic/expect
new file mode 100644
index 00000000..91f09a9c
--- /dev/null
+++ b/utils/initrd-find/tests/ts0001-initrd-find-basic/expect
@@ -0,0 +1,14 @@
+Usage: initrd-find.in [options] [KERNEL_VERSION]
+help flag: ok
+traditional layout: ok
+bls conf: ok
+bls dir: ok
+non-matching kernel: ok
+foreign initrd rejected: ok
+foreign initrd accepted with --any: ok
+bls type 2 uki: ok
+root-dir option: ok
+root-dir with -b: ok
+ROOTDIR env: ok
+ROOT_DIR env: ok
+rc=0
diff --git a/utils/initrd-find/tests/ts0001-initrd-find-basic/run b/utils/initrd-find/tests/ts0001-initrd-find-basic/run
new file mode 100755
index 00000000..b208db33
--- /dev/null
+++ b/utils/initrd-find/tests/ts0001-initrd-find-basic/run
@@ -0,0 +1,124 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+PROG="${PROG:-utils/initrd-find/initrd-find.in}"
+export PATH="$PWD/.build/dest/usr/sbin:$PWD/.build/dest/usr/bin:$PATH"
+
+tmpdir="$(mktemp -d)"
+trap 'rm -rf -- "$tmpdir"' EXIT
+
+create_mock_initrd() {
+ local outfile="$1"
+ local id="${2:-make-initrd}"
+ local mock_src="$tmpdir/mock_initrd_src"
+ rm -rf "$mock_src"
+ mkdir -p "$mock_src/etc"
+ cat <<- EOF > "$mock_src/etc/initrd-release"
+ ID=$id
+ VERSION_ID=test
+ NAME="make-initrd"
+ EOF
+ ( cd "$mock_src" && printf "etc\netc/initrd-release\n" | cpio -o -H newc --quiet > "$outfile" )
+}
+
+# 1. Test help flag
+"$PROG" --help | head -n 1
+echo "help flag: ok"
+
+# 2. Test traditional layout
+mkdir -p "$tmpdir/boot1"
+create_mock_initrd "$tmpdir/boot1/initrd-6.6.1.img"
+found="$("$PROG" -b "$tmpdir/boot1" 6.6.1)"
+[ "$found" = "$tmpdir/boot1/initrd-6.6.1.img" ] && echo "traditional layout: ok"
+
+# 3. Test BLS Type #1 conf entry
+mkdir -p "$tmpdir/boot2/loader/entries"
+cat << 'EOF' > "$tmpdir/boot2/loader/entries/test.conf"
+title Test OS
+version 6.6.1
+initrd /images/initrd-custom
+EOF
+mkdir -p "$tmpdir/boot2/images"
+create_mock_initrd "$tmpdir/boot2/images/initrd-custom"
+found="$("$PROG" -b "$tmpdir/boot2" 6.6.1)"
+[ "$found" = "$tmpdir/boot2/images/initrd-custom" ] && echo "bls conf: ok"
+
+# 4. Test BLS Type #1 directory structure
+mkdir -p "$tmpdir/boot3/Default/6.6.1"
+create_mock_initrd "$tmpdir/boot3/Default/6.6.1/initrd"
+found="$("$PROG" -b "$tmpdir/boot3" 6.6.1)"
+[ "$found" = "$tmpdir/boot3/Default/6.6.1/initrd" ] && echo "bls dir: ok"
+
+# 5. Non-matching kernel returns 1
+rc=0
+"$PROG" -b "$tmpdir/boot1" 9.9.9 >/dev/null 2>&1 || rc=$?
+[ "$rc" -eq 1 ] && echo "non-matching kernel: ok"
+
+# 6. Foreign initrd rejection without --any and acceptance with --any
+mkdir -p "$tmpdir/boot4"
+create_mock_initrd "$tmpdir/boot4/initrd-6.6.1.img" "ubuntu"
+rc=0
+"$PROG" -b "$tmpdir/boot4" 6.6.1 >/dev/null 2>&1 || rc=$?
+[ "$rc" -eq 1 ] && echo "foreign initrd rejected: ok"
+
+found="$("$PROG" --any -b "$tmpdir/boot4" 6.6.1)"
+[ "$found" = "$tmpdir/boot4/initrd-6.6.1.img" ] && echo "foreign initrd accepted with --any: ok"
+
+# 7. BLS Type #2 UKI discovery
+mkdir -p "$tmpdir/boot5/EFI/Linux"
+python3 - "$tmpdir/boot5/EFI/Linux/linux-6.6.1.efi" << 'EOF'
+import sys, io, struct
+def make_cpio_entry(name, content=b""):
+ namesize = len(name.encode()) + 1
+ filesize = len(content)
+ hdr = f"070701{1:08x}{0o100644:08x}{0:08x}{0:08x}{1:08x}{0:08x}{filesize:08x}{0:08x}{0:08x}{0:08x}{0:08x}{namesize:08x}{0:08x}".encode()
+ out = io.BytesIO()
+ out.write(hdr)
+ out.write(name.encode() + b"\0")
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ if content:
+ out.write(content)
+ pad = (4 - (out.tell() % 4)) % 4
+ out.write(b"\0" * pad)
+ return out.getvalue()
+
+trailer = make_cpio_entry("TRAILER!!!")
+entries = [make_cpio_entry("etc/initrd-release", b"ID=make-initrd\n"), trailer]
+cpio_data = b"".join(entries)
+pad = (512 - (len(cpio_data) % 512)) % 512
+cpio_data += b"\0" * pad
+
+dos_header = b'MZ' + b'\0' * 58 + struct.pack('<I', 64)
+pe_sig = b'PE\0\0'
+coff_header = struct.pack('<HHIIIHH', 0x8664, 1, 0, 0, 0, 0, 2)
+headers = dos_header + pe_sig + coff_header
+raw_ptr = 512
+raw_size = len(cpio_data)
+vsize = len(cpio_data)
+sec_hdr = struct.pack('<8sIIIIIIHHI', b'.initrd\0', vsize, 0x1000, raw_size, raw_ptr, 0, 0, 0, 0, 0x40000040)
+headers += sec_hdr
+pe = headers + b'\0' * (512 - len(headers)) + cpio_data
+
+with open(sys.argv[1], "wb") as f:
+ f.write(pe)
+EOF
+
+found="$("$PROG" -b "$tmpdir/boot5" 6.6.1)"
+[ "$found" = "$tmpdir/boot5/EFI/Linux/linux-6.6.1.efi" ] && echo "bls type 2 uki: ok"
+
+# 8. Test --root-dir and ROOTDIR/ROOT_DIR
+mkdir -p "$tmpdir/sysroot/boot"
+create_mock_initrd "$tmpdir/sysroot/boot/initramfs-6.6.1.img"
+
+found="$("$PROG" --root-dir="$tmpdir/sysroot" 6.6.1)"
+[ "$found" = "$tmpdir/sysroot/boot/initramfs-6.6.1.img" ] && echo "root-dir option: ok"
+
+found="$("$PROG" -r "$tmpdir/sysroot" -b /boot 6.6.1)"
+[ "$found" = "$tmpdir/sysroot/boot/initramfs-6.6.1.img" ] && echo "root-dir with -b: ok"
+
+found="$(ROOTDIR="$tmpdir/sysroot" "$PROG" 6.6.1)"
+[ "$found" = "$tmpdir/sysroot/boot/initramfs-6.6.1.img" ] && echo "ROOTDIR env: ok"
+
+found="$(ROOT_DIR="$tmpdir/sysroot" "$PROG" 6.6.1)"
+[ "$found" = "$tmpdir/sysroot/boot/initramfs-6.6.1.img" ] && echo "ROOT_DIR env: ok"
diff --git a/utils/make-initrd-shutdown-restore/Makefile.mk b/utils/make-initrd-shutdown-restore/Makefile.mk
new file mode 100644
index 00000000..68349848
--- /dev/null
+++ b/utils/make-initrd-shutdown-restore/Makefile.mk
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+make_initrd_shutdown_restore_DEST = $(dest_sbindir)/make-initrd-shutdown-restore
+make_initrd_shutdown_restore_SRCS = $(utils_srcdir)/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
+
+SCRIPTS += make_initrd_shutdown_restore
diff --git a/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in b/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
new file mode 100755
index 00000000..c8a4c285
--- /dev/null
+++ b/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
@@ -0,0 +1,147 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+target_dir="${TARGET_DIR:-/run/initramfs}"
+
+# If shutdown executable already exists, do nothing
+[ ! -x "$target_dir/shutdown" ] || exit 0
+
+if ! command -v initrd-find >/dev/null 2>&1 || ! command -v initrd-extract >/dev/null 2>&1 || ! command -v initrd-ls >/dev/null 2>&1; then
+ echo "make-initrd-shutdown-restore: required tools (initrd-find, initrd-extract, initrd-ls) not found" >&2
+ exit 1
+fi
+
+kver="${SYS_KVER:-$(uname -r)}"
+boot_override="${BOOT_DIRS:-}"
+
+mounted_by_us=()
+
+cleanup() {
+ if [ "${#mounted_by_us[@]}" -gt 0 ]; then
+ for (( i=${#mounted_by_us[@]}-1; i>=0; i-- )); do
+ umount "${mounted_by_us[i]}" >/dev/null 2>&1 ||:
+ done
+ fi
+}
+
+trap cleanup EXIT
+
+is_mounted() {
+ local d="$1"
+ if command -v mountpoint >/dev/null 2>&1; then
+ mountpoint -q "$d"
+ else
+ grep -qs "[[:space:]]${d}[[:space:]]" /proc/mounts
+ fi
+}
+
+mount_candidate() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ if ! is_mounted "$d"; then
+ if mount -o ro "$d" >/dev/null 2>&1 || mount "$d" >/dev/null 2>&1; then
+ mounted_by_us+=("$d")
+ fi
+ fi
+}
+
+boot_dirs=()
+
+add_boot_dir() {
+ local d="$1"
+
+ [ -n "$d" ] && [ -d "$d" ] ||
+ return 0
+
+ local existing
+
+ for existing in "${boot_dirs[@]}"; do
+ [ "$existing" != "$d" ] || return 0
+ done
+
+ boot_dirs+=("$d")
+}
+
+has_shutdown() {
+ initrd-ls "$1" 2>/dev/null | grep -qsE '(1970 \./shutdown|[[:space:]](\./)?shutdown$)'
+}
+
+unpack_initrd() {
+ initrd-extract "$1" |
+ cpio -idu --quiet --no-absolute-filenames
+}
+
+if [ -n "$boot_override" ]; then
+ for d in $boot_override; do
+ add_boot_dir "$d"
+ done
+else
+ # Ensure candidate boot directories are mounted before searching
+ for d in /boot /efi /boot/efi; do
+ mount_candidate "$d"
+ done
+
+ if command -v bootctl >/dev/null 2>&1; then
+ bp="$(bootctl -q --print-boot-path 2>/dev/null || :)"
+ mount_candidate "$bp"
+ add_boot_dir "$bp"
+
+ ep="$(bootctl -q --print-esp-path 2>/dev/null || :)"
+ mount_candidate "$ep"
+ add_boot_dir "$ep"
+ fi
+
+ for d in /boot /efi /boot/efi; do
+ add_boot_dir "$d"
+ done
+fi
+
+find_args=()
+if [ "${#boot_dirs[@]}" -gt 0 ]; then
+ for d in "${boot_dirs[@]}"; do
+ find_args+=("-b" "$d")
+ done
+fi
+
+initrd_image="$(initrd-find "${find_args[@]}" "$kver" 2>/dev/null || :)"
+
+if [ -z "$initrd_image" ] || [ ! -e "$initrd_image" ]; then
+ [ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: no initramfs found for kernel $kver" >&2
+ exit 0
+fi
+
+if ! has_shutdown "$initrd_image"; then
+ [ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: shutdown feature not included in $initrd_image" >&2
+ exit 0
+fi
+
+[ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: restoring initramfs from: $initrd_image" >&2
+
+mkdir -p "$target_dir"
+cd "$target_dir"
+
+unpack_initrd "$initrd_image"
+
+if [ -f "$target_dir/initramfs.img" ]; then
+ if command -v unsquashfs >/dev/null 2>&1; then
+ unsquashfs -f -d "$target_dir" "$target_dir/initramfs.img" >/dev/null 2>&1 ||:
+
+ elif command -v fsck.erofs >/dev/null 2>&1; then
+ fsck.erofs --extract="$target_dir" --overwrite "$target_dir/initramfs.img" >/dev/null 2>&1 ||:
+ fi
+fi
+
+if [ ! -x "$target_dir/shutdown" ]; then
+ echo "make-initrd-shutdown-restore: $target_dir/shutdown missing or not executable" >&2
+ rm -rf "${target_dir:?}"/*
+ exit 1
+fi
+
+[ -z "${VERBOSE:-}" ] ||
+ echo "make-initrd-shutdown-restore: successfully restored initramfs into $target_dir" >&2
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [make-initrd] [PATCH v2 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service
2026-11-09 17:10 ` [make-initrd] [PATCH v2 " Alexey Gladkov
@ 2026-11-09 18:45 ` Anton Midyukov
2026-11-09 18:52 ` Leonid Krivoshein
2026-11-10 9:31 ` Alexey Gladkov
0 siblings, 2 replies; 31+ messages in thread
From: Anton Midyukov @ 2026-11-09 18:45 UTC (permalink / raw)
To: make-initrd
С этим патчем получилось. Спасибо.
Добавил /etc/systemd/system/make-initrd-shutdown.service.d/override.conf:
[Service]
Environment=BOOT_DIRS=/image/boot
# systemctl daemon reload
# poweroff
Осталось сделать хук для правильного размонтирования.
--
best regards, Anton Midyukov <antohami@altlinux.org>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [make-initrd] [PATCH v2 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service
2026-11-09 18:45 ` Anton Midyukov
@ 2026-11-09 18:52 ` Leonid Krivoshein
2026-11-10 9:31 ` Alexey Gladkov
1 sibling, 0 replies; 31+ messages in thread
From: Leonid Krivoshein @ 2026-11-09 18:52 UTC (permalink / raw)
To: make-initrd
On 11/9/26 9:45 PM, Anton Midyukov wrote:
> С этим патчем получилось. Спасибо.
>
> Добавил /etc/systemd/system/make-initrd-shutdown.service.d/override.conf:
> [Service]
> Environment=BOOT_DIRS=/image/boot
>
> # systemctl daemon reload
> # poweroff
>
> Осталось сделать хук для правильного размонтирования.
>
Можно будет потом проверить на LiveCD с liverw на предмет ошибок от
overlayfs после первого запуска.
--
WBR, Leonid Krivoshein.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [make-initrd] [PATCH v2 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service
2026-11-09 18:45 ` Anton Midyukov
2026-11-09 18:52 ` Leonid Krivoshein
@ 2026-11-10 9:31 ` Alexey Gladkov
1 sibling, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-10 9:31 UTC (permalink / raw)
To: make-initrd
On Mon, Nov 09, 2026 at 09:45:33PM +0300, Anton Midyukov wrote:
> С этим патчем получилось. Спасибо.
Смерджил shutdown в master.
> Добавил /etc/systemd/system/make-initrd-shutdown.service.d/override.conf:
> [Service]
> Environment=BOOT_DIRS=/image/boot
>
> # systemctl daemon reload
> # poweroff
>
> Осталось сделать хук для правильного размонтирования.
>
> --
> best regards, Anton Midyukov <antohami@altlinux.org>
>
> _______________________________________________
> Make-initrd mailing list
> Make-initrd@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/make-initrd
--
Rgrds, legion
^ permalink raw reply [flat|nested] 31+ messages in thread
* [make-initrd] [PATCH v1 04/10] shutdown: Add basic unit test suite
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (2 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 03/10] shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd service Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 05/10] devmapper: shutdown: Teardown device-mapper targets on exit Alexey Gladkov
` (7 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
Add unit test ts0001-shutdown-basic to verify core functionality of the
shutdown feature:
- Validate syntax of shutdown, make-initrd-shutdown-restore, and default
loop teardown hook.
- Test early exit when /run/initramfs/shutdown already exists.
- Test initramfs discovery and extraction for traditional layouts,
Boot Loader Specification Type #1 entries, and BLS Type #1 directory
structures.
- Test sysfs block device leaf detection (devices without holders).
- Test hook execution order and exit code conventions (0 for progress,
2 for skip, non-zero for error).
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.../tests/ts0001-shutdown-basic/expect | 17 ++
.../shutdown/tests/ts0001-shutdown-basic/run | 145 ++++++++++++++++++
.../ts0002-guess-common-with-shutdown/expect | 3 +
.../ts0002-guess-common-with-shutdown/run | 32 ++++
.../expect | 1 +
.../ts0003-guess-common-without-shutdown/run | 27 ++++
.../ts0004-guess-common-no-initrd/expect | 1 +
.../tests/ts0004-guess-common-no-initrd/run | 16 ++
8 files changed, 242 insertions(+)
create mode 100644 features/shutdown/tests/ts0001-shutdown-basic/expect
create mode 100755 features/shutdown/tests/ts0001-shutdown-basic/run
create mode 100644 features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
create mode 100755 features/shutdown/tests/ts0002-guess-common-with-shutdown/run
create mode 100644 features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
create mode 100755 features/shutdown/tests/ts0003-guess-common-without-shutdown/run
create mode 100644 features/shutdown/tests/ts0004-guess-common-no-initrd/expect
create mode 100755 features/shutdown/tests/ts0004-guess-common-no-initrd/run
diff --git a/features/shutdown/tests/ts0001-shutdown-basic/expect b/features/shutdown/tests/ts0001-shutdown-basic/expect
new file mode 100644
index 00000000..699f5709
--- /dev/null
+++ b/features/shutdown/tests/ts0001-shutdown-basic/expect
@@ -0,0 +1,17 @@
+syntax shutdown: ok
+syntax make-initrd-shutdown-restore: ok
+syntax 10-loop.sh: ok
+syntax guess/common: ok
+already restored: ok
+traditional restore: ok
+bls type1 conf restore: ok
+bls type1 dir restore: ok
+foreign initrd ignored: ok
+shutdown not included skipped: ok
+dm-0 is leaf: ok
+sda has holders: ok
+run_hooks nonexistent dir: ok
+loop hook skip on dm: ok
+dm hook handle on dm: ok
+dm hook skip on sda: ok
+rc=0
diff --git a/features/shutdown/tests/ts0001-shutdown-basic/run b/features/shutdown/tests/ts0001-shutdown-basic/run
new file mode 100755
index 00000000..0669b4a6
--- /dev/null
+++ b/features/shutdown/tests/ts0001-shutdown-basic/run
@@ -0,0 +1,145 @@
+#!/bin/bash -eu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+bash -n features/shutdown/shutdown/shutdown
+echo "syntax shutdown: ok"
+
+bash -n utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
+echo "syntax make-initrd-shutdown-restore: ok"
+
+bash -n features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
+echo "syntax 10-loop.sh: ok"
+
+bash -n features/shutdown/guess/common
+echo "syntax guess/common: ok"
+
+PROG="${PROG:-utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in}"
+export PATH="$PWD/.build/dest/usr/sbin:$PWD/.build/dest/usr/bin:$PATH"
+tmpdir="$(mktemp -d)"
+trap 'rm -rf -- "$tmpdir"' EXIT
+
+# Helper to build mock initramfs archive
+create_mock_initrd() {
+ local outfile="$1"
+ local id="${2:-make-initrd}"
+ local with_shutdown="${3:-1}"
+ local mock_src="$tmpdir/mock_initrd_src"
+ rm -rf "$mock_src"
+ mkdir -p "$mock_src/etc"
+ cat <<- EOF > "$mock_src/etc/initrd-release"
+ ID=$id
+ VERSION_ID=test
+ NAME="make-initrd"
+ EOF
+ if [ "$with_shutdown" = "1" ]; then
+ cat <<- 'EOF' > "$mock_src/shutdown"
+ #!/bin/sh
+ exit 0
+ EOF
+ chmod +x "$mock_src/shutdown"
+ ( cd "$mock_src" && printf "etc\netc/initrd-release\nshutdown\n" | cpio -o -H newc --quiet > "$outfile" )
+ else
+ ( cd "$mock_src" && printf "etc\netc/initrd-release\n" | cpio -o -H newc --quiet > "$outfile" )
+ fi
+}
+
+# 1. Test exit 0 when target shutdown is already executable
+mkdir -p "$tmpdir/target0"
+touch "$tmpdir/target0/shutdown"
+chmod +x "$tmpdir/target0/shutdown"
+TARGET_DIR="$tmpdir/target0" "$PROG"
+echo "already restored: ok"
+
+# 2. Test traditional layout restore
+mkdir -p "$tmpdir/boot1"
+create_mock_initrd "$tmpdir/boot1/initrd-test-kver.img"
+TARGET_DIR="$tmpdir/target1" SYS_KVER="test-kver" BOOT_DIRS="$tmpdir/boot1" "$PROG"
+[ -x "$tmpdir/target1/shutdown" ] && echo "traditional restore: ok"
+
+# 3. Test BLS Type #1 config entry restore
+mkdir -p "$tmpdir/boot2/loader/entries" "$tmpdir/boot2/my-token/test-kver"
+create_mock_initrd "$tmpdir/boot2/my-token/test-kver/initrd"
+cat << 'EOF' > "$tmpdir/boot2/loader/entries/test.conf"
+title Test OS
+version test-kver
+initrd /my-token/test-kver/initrd
+EOF
+TARGET_DIR="$tmpdir/target2" SYS_KVER="test-kver" BOOT_DIRS="$tmpdir/boot2" "$PROG"
+[ -x "$tmpdir/target2/shutdown" ] && echo "bls type1 conf restore: ok"
+
+# 4. Test BLS Type #1 directory structure restore
+mkdir -p "$tmpdir/boot3/Default/test-kver"
+create_mock_initrd "$tmpdir/boot3/Default/test-kver/initrd"
+TARGET_DIR="$tmpdir/target3" SYS_KVER="test-kver" BOOT_DIRS="$tmpdir/boot3" "$PROG"
+[ -x "$tmpdir/target3/shutdown" ] && echo "bls type1 dir restore: ok"
+
+# 5. Test foreign initrd rejection (e.g. ID=ubuntu is ignored, make-initrd picked)
+mkdir -p "$tmpdir/boot4"
+create_mock_initrd "$tmpdir/boot4/initrd-test-kver.img" "ubuntu"
+create_mock_initrd "$tmpdir/boot4/initramfs-test-kver.img" "make-initrd"
+TARGET_DIR="$tmpdir/target4" SYS_KVER="test-kver" BOOT_DIRS="$tmpdir/boot4" "$PROG"
+[ -x "$tmpdir/target4/shutdown" ] && echo "foreign initrd ignored: ok"
+
+# 6. Test make-initrd without shutdown feature is cleanly skipped (exit 0, target not restored)
+mkdir -p "$tmpdir/boot5"
+create_mock_initrd "$tmpdir/boot5/initrd-test-kver.img" "make-initrd" "0"
+TARGET_DIR="$tmpdir/target5" SYS_KVER="test-kver" BOOT_DIRS="$tmpdir/boot5" "$PROG"
+[ ! -e "$tmpdir/target5/shutdown" ] && echo "shutdown not included skipped: ok"
+
+# Test leaf detection, run_hooks, and hook exit code convention
+(
+ # shellcheck source=features/shutdown/shutdown/shutdown
+ source features/shutdown/shutdown/shutdown
+
+ # Mock sysfs block device tree
+ mkdir -p "$tmpdir/sys/class/block/dm-0/holders"
+ mkdir -p "$tmpdir/sys/class/block/dm-0/dm"
+ echo "253:0" > "$tmpdir/sys/class/block/dm-0/dev"
+ echo "my-luks" > "$tmpdir/sys/class/block/dm-0/dm/name"
+
+ mkdir -p "$tmpdir/sys/class/block/sda/holders"
+ echo "8:0" > "$tmpdir/sys/class/block/sda/dev"
+ ln -s ../../dm-0 "$tmpdir/sys/class/block/sda/holders/dm-0"
+
+ is_leaf_blockdev "$tmpdir/sys/class/block/dm-0" && echo "dm-0 is leaf: ok"
+ ! is_leaf_blockdev "$tmpdir/sys/class/block/sda" && echo "sda has holders: ok"
+
+ # Test run_hooks with nonexistent directory
+ ! run_hooks "nonexistent" "reboot" && echo "run_hooks nonexistent dir: ok"
+
+ # Test hook exit code convention:
+ # exit 0 -> progress
+ # exit 2 -> skip
+ # exit 1 -> error
+ mock_hooks_dir="$tmpdir/lib/initrd/shutdown/post-umount.d"
+ mkdir -p "$mock_hooks_dir"
+
+ cat << 'EOF' > "$mock_hooks_dir/10-loop.sh"
+#!/bin/bash
+major="$4"
+[ "$major" = 7 ] || [ -d "$3/loop" ] || exit 2
+exit 0
+EOF
+ chmod +x "$mock_hooks_dir/10-loop.sh"
+
+ cat << 'EOF' > "$mock_hooks_dir/50-dm.sh"
+#!/bin/bash
+[ -d "$3/dm" ] || exit 2
+exit 0
+EOF
+ chmod +x "$mock_hooks_dir/50-dm.sh"
+
+ # Test calling loop hook on dm device (should skip with exit code 2)
+ rc=0
+ "$mock_hooks_dir/10-loop.sh" "post-umount" "reboot" "$tmpdir/sys/class/block/dm-0" "253" || rc=$?
+ [ "$rc" -eq 2 ] && echo "loop hook skip on dm: ok"
+
+ # Test calling dm hook on dm device (should succeed with exit code 0)
+ "$mock_hooks_dir/50-dm.sh" "post-umount" "reboot" "$tmpdir/sys/class/block/dm-0" "253"
+ echo "dm hook handle on dm: ok"
+
+ # Test calling dm hook on sda (no dm dir, should skip with exit code 2)
+ rc=0
+ "$mock_hooks_dir/50-dm.sh" "post-umount" "reboot" "$tmpdir/sys/class/block/sda" "8" || rc=$?
+ [ "$rc" -eq 2 ] && echo "dm hook skip on sda: ok"
+)
diff --git a/features/shutdown/tests/ts0002-guess-common-with-shutdown/expect b/features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
new file mode 100644
index 00000000..fc256dc4
--- /dev/null
+++ b/features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
@@ -0,0 +1,3 @@
+guess:features
+shutdown
+rc=0
diff --git a/features/shutdown/tests/ts0002-guess-common-with-shutdown/run b/features/shutdown/tests/ts0002-guess-common-with-shutdown/run
new file mode 100755
index 00000000..50e3d4c7
--- /dev/null
+++ b/features/shutdown/tests/ts0002-guess-common-with-shutdown/run
@@ -0,0 +1,32 @@
+#!/bin/bash -efu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+cwd="${0%/*}"
+
+. "$cwd"/../../../../testing/tests/guess-feature-sh-functions
+
+guess_feature_test_init "$0"
+
+export KERNEL="6.6.1"
+boot_dir="$GUESS_TEST_TMPDIR/boot"
+mkdir -p "$boot_dir"
+mock_img="$boot_dir/initrd-6.6.1.img"
+
+mock_src="$GUESS_TEST_TMPDIR/mock_src"
+mkdir -p "$mock_src/etc"
+cat <<- EOF > "$mock_src/etc/initrd-release"
+ID=make-initrd
+VERSION_ID=test
+NAME="make-initrd"
+EOF
+cat <<- 'EOF' > "$mock_src/shutdown"
+#!/bin/sh
+exit 0
+EOF
+chmod +x "$mock_src/shutdown"
+( cd "$mock_src" && printf "etc\netc/initrd-release\nshutdown\n" | cpio -o -H newc --quiet > "$mock_img" )
+
+export BOOT_DIRS="$boot_dir"
+
+features/shutdown/guess/common
+guess_feature_test_dump
diff --git a/features/shutdown/tests/ts0003-guess-common-without-shutdown/expect b/features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
new file mode 100644
index 00000000..ab3f8d19
--- /dev/null
+++ b/features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
@@ -0,0 +1 @@
+rc=0
diff --git a/features/shutdown/tests/ts0003-guess-common-without-shutdown/run b/features/shutdown/tests/ts0003-guess-common-without-shutdown/run
new file mode 100755
index 00000000..d91ae6b0
--- /dev/null
+++ b/features/shutdown/tests/ts0003-guess-common-without-shutdown/run
@@ -0,0 +1,27 @@
+#!/bin/bash -efu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+cwd="${0%/*}"
+
+. "$cwd"/../../../../testing/tests/guess-feature-sh-functions
+
+guess_feature_test_init "$0"
+
+export KERNEL="6.6.1"
+boot_dir="$GUESS_TEST_TMPDIR/boot"
+mkdir -p "$boot_dir"
+mock_img="$boot_dir/initrd-6.6.1.img"
+
+mock_src="$GUESS_TEST_TMPDIR/mock_src"
+mkdir -p "$mock_src/etc"
+cat <<- EOF > "$mock_src/etc/initrd-release"
+ID=make-initrd
+VERSION_ID=test
+NAME="make-initrd"
+EOF
+( cd "$mock_src" && printf "etc\netc/initrd-release\n" | cpio -o -H newc --quiet > "$mock_img" )
+
+export BOOT_DIRS="$boot_dir"
+
+features/shutdown/guess/common
+guess_feature_test_dump
diff --git a/features/shutdown/tests/ts0004-guess-common-no-initrd/expect b/features/shutdown/tests/ts0004-guess-common-no-initrd/expect
new file mode 100644
index 00000000..ab3f8d19
--- /dev/null
+++ b/features/shutdown/tests/ts0004-guess-common-no-initrd/expect
@@ -0,0 +1 @@
+rc=0
diff --git a/features/shutdown/tests/ts0004-guess-common-no-initrd/run b/features/shutdown/tests/ts0004-guess-common-no-initrd/run
new file mode 100755
index 00000000..795ab3c9
--- /dev/null
+++ b/features/shutdown/tests/ts0004-guess-common-no-initrd/run
@@ -0,0 +1,16 @@
+#!/bin/bash -efu
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+cwd="${0%/*}"
+
+. "$cwd"/../../../../testing/tests/guess-feature-sh-functions
+
+guess_feature_test_init "$0"
+
+export KERNEL="99.99.99"
+empty_boot="$GUESS_TEST_TMPDIR/empty_boot"
+mkdir -p "$empty_boot"
+export BOOT_DIRS="$empty_boot"
+
+features/shutdown/guess/common
+guess_feature_test_dump
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 05/10] devmapper: shutdown: Teardown device-mapper targets on exit
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (3 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 04/10] shutdown: Add basic unit test suite Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 06/10] mdadm: shutdown: Stop MD arrays " Alexey Gladkov
` (6 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
Device-mapper targets (including lvm logical volumes, luks encrypted
devices, multipath maps, and linear mappings) hold underlying devices or
partitions open. During exitrd shutdown, once higher-level mounts and
holders are dismantled, these DM devices become leaves in sysfs.
Add a post-umount hook for the shutdown feature to dismantle DM targets
via dmsetup remove --retry once all holders are removed.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.../initrd/shutdown/post-umount.d/50-dm.sh | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100755 features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
diff --git a/features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh b/features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
new file mode 100755
index 00000000..ceda653d
--- /dev/null
+++ b/features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+sysfs_path="$3"
+_major="$4"
+
+[ -d "$sysfs_path/dm" ] ||
+ exit 2
+
+command -v dmsetup >/dev/null 2>&1 ||
+ exit 1
+
+read -r dm_name < "$sysfs_path/dm/name" 2>/dev/null ||
+ exit 1
+
+[ -n "$dm_name" ] ||
+ exit 1
+
+dmsetup remove --retry "$dm_name"
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 06/10] mdadm: shutdown: Stop MD arrays on exit
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (4 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 05/10] devmapper: shutdown: Teardown device-mapper targets on exit Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 07/10] btrfs: shutdown: Sync filesystem before umount " Alexey Gladkov
` (5 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
Active Linux software raid arrays must be cleanly stopped and marked
clean on shutdown to avoid array resync or rebuild operations on next
boot.
Add a post-umount hook for the shutdown feature to stop raid arrays
using mdadm --stop or writing to sysfs array_state when no holders
remain.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.../initrd/shutdown/post-umount.d/60-md.sh | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100755 features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
diff --git a/features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh b/features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
new file mode 100755
index 00000000..af732ea5
--- /dev/null
+++ b/features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+sysfs_path="$3"
+major="$4"
+
+[ "$major" = 9 ] || [ -d "$sysfs_path/md" ] ||
+ exit 2
+
+# Check if array is already inactive or clear
+if [ -f "$sysfs_path/md/array_state" ]; then
+ state=""
+
+ read -r state < "$sysfs_path/md/array_state" 2>/dev/null ||
+ state=""
+
+ [ "$state" != "clear" ] && [ "$state" != "inactive" ] ||
+ exit 2
+fi
+
+devname="${sysfs_path##*/}"
+
+if command -v mdadm >/dev/null 2>&1; then
+ mdadm --stop "/dev/$devname"
+
+elif [ -f "$sysfs_path/md/array_state" ]; then
+ echo "idle" > "$sysfs_path/md/sync_action" 2>/dev/null
+ echo "inactive" > "$sysfs_path/md/array_state" 2>/dev/null
+ echo "clear" > "$sysfs_path/md/array_state" 2>/dev/null
+else
+ exit 1
+fi
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 07/10] btrfs: shutdown: Sync filesystem before umount on exit
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (5 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 06/10] mdadm: shutdown: Stop MD arrays " Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 08/10] zfs: shutdown: Unmount filesystems and export zpools " Alexey Gladkov
` (4 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
Btrfs relies on asynchronous transaction commits and deferred metadata
writeback. While unmounting forces a sync, explicitly committing
transactions and running pending delayed operations via btrfs filesystem
sync prior to umount ensures metadata integrity during exitrd teardown.
Add an umount hook for the shutdown feature to invoke btrfs filesystem
sync on btrfs mountpoints before unmounting.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.../lib/initrd/shutdown/umount.d/50-btrfs.sh | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100755 features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
diff --git a/features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh b/features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
new file mode 100755
index 00000000..7503ce68
--- /dev/null
+++ b/features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+mountpoint="$3"
+fstype="$4"
+
+[ "$fstype" = "btrfs" ] || exit 2
+
+if command -v btrfs >/dev/null 2>&1; then
+ btrfs filesystem sync "$mountpoint" 2>/dev/null ||:
+fi
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 08/10] zfs: shutdown: Unmount filesystems and export zpools on exit
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (6 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 07/10] btrfs: shutdown: Sync filesystem before umount " Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 09/10] testing: Add end-to-end test for shutdown feature Alexey Gladkov
` (3 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
zfs storage pools must be cleanly exported before system poweroff or
reboot. If a pool is not cleanly exported, uberblocks and dirty data
remain in an uncommitted state, requiring pool recovery or manual force
import on next boot.
Add hooks for the shutdown feature:
- An umount hook that unmounts zfs filesystems (using zfs unmount -f)
and exports all active pools (via zpool export -a -f) once the last
zfs mountpoint is released.
- A shutdown.d hook that performs a final safety pool export immediately
prior to reboot or poweroff.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.../lib/initrd/shutdown/shutdown.d/20-zfs.sh | 9 +++++++++
.../lib/initrd/shutdown/umount.d/50-zfs.sh | 20 +++++++++++++++++++
2 files changed, 29 insertions(+)
create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
diff --git a/features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh b/features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
new file mode 100755
index 00000000..cf0af71b
--- /dev/null
+++ b/features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+
+if command -v zpool >/dev/null 2>&1; then
+ zpool export -a -f 2>/dev/null || zpool export -a 2>/dev/null ||:
+fi
diff --git a/features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh b/features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
new file mode 100755
index 00000000..aec235a0
--- /dev/null
+++ b/features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+_hook_type="$1"
+_action="$2"
+mountpoint="$3"
+fstype="$4"
+
+[ "$fstype" = "zfs" ] || exit 2
+
+if command -v zfs >/dev/null 2>&1; then
+ zfs unmount -f "$mountpoint" 2>/dev/null ||:
+fi
+
+# If all ZFS mountpoints are unmounted, export the pools
+if command -v zpool >/dev/null 2>&1; then
+ if ! findmnt -rno TARGET -t zfs >/dev/null 2>&1; then
+ zpool export -a -f 2>/dev/null || zpool export -a 2>/dev/null ||:
+ fi
+fi
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 09/10] testing: Add end-to-end test for shutdown feature
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (7 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 08/10] zfs: shutdown: Unmount filesystems and export zpools " Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 13:07 ` [make-initrd] [PATCH v1 10/10] docs: Add manpages and bash-completion for initrd-find and make-initrd-shutdown-restore Alexey Gladkov
` (2 subsequent siblings)
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
This test works only on systems running systemd. It may also work on
other systems, but that depends on the abilities of their init system.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
.github/workflows/ci.yml | 2 +-
.github/workflows/testcases.yml | 1 +
testing/test-root-shutdown.cfg | 147 ++++++++++++++++++++++++++++++++
3 files changed, 149 insertions(+), 1 deletion(-)
create mode 100644 testing/test-root-shutdown.cfg
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 13d10591..2ff2f0ba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
uses: ./.github/workflows/testcases.yml
with:
vendor: gentoo_musl
- exclude: '[{"testcase": "zfs-partition"}, {"testcase": "initramfs-on-erofs"}, {"testcase": "initramfs-on-squashfs"}, {"testcase": "sshfsroot"}]'
+ exclude: '[{"testcase": "zfs-partition"}, {"testcase": "initramfs-on-erofs"}, {"testcase": "initramfs-on-squashfs"}, {"testcase": "sshfsroot"}, {"testcase": "shutdown"}]'
metrics:
needs:
- altlinux
diff --git a/.github/workflows/testcases.yml b/.github/workflows/testcases.yml
index dccc40d0..77e7a32b 100644
--- a/.github/workflows/testcases.yml
+++ b/.github/workflows/testcases.yml
@@ -47,6 +47,7 @@ jobs:
- raid1-degraded
- raid5
- sdcard
+ - shutdown
- sshfsroot
- xfs-partition
- zfs-partition
diff --git a/testing/test-root-shutdown.cfg b/testing/test-root-shutdown.cfg
new file mode 100644
index 00000000..71f96035
--- /dev/null
+++ b/testing/test-root-shutdown.cfg
@@ -0,0 +1,147 @@
+# [test]
+# kickstart_disks = 2
+# boot_disks = 2
+# boot_cmdline = [
+# "rdacct=1",
+# "rdlog=console",
+# "rd-debug-udev=1",
+# "quiet",
+# "panic=0",
+# "root=LABEL=ROOT",
+# ]
+
+ignoredisk --drives=LABEL=SYSIMAGE
+clearpart --all --initlabel --disklabel=mbr
+
+part /boot --size=100% --fstype=ext4 --label=BOOT
+part / --size=60% --fstype=ext4 --label=ROOT
+part /var --size=40% --fstype=ext4 --label=DATA
+
+liveimg --url=dir:///sysimage/
+
+shutdown
+
+%post --erroronfail --nochroot
+ set -x
+
+ # Locate make-initrd source/build directory
+ src_srv=""
+ for d in /mnt/sysimage/srv /sysimage/srv /srv; do
+ if [ -d "$d/features/shutdown" ]; then
+ src_srv="$d"
+ break
+ fi
+ done
+
+ if [ -z "$src_srv" ]; then
+ echo "ERROR: cannot locate make-initrd source directory (tried /mnt/sysimage/srv, /sysimage/srv, /srv)" >&2
+ exit 1
+ fi
+
+ # Configure make-initrd with shutdown feature and verification hook
+ tee /mnt/sysimage/etc/initrd.mk.addon <<-EOF
+ FEATURES += shutdown
+ PUT_FILES += /lib/initrd/shutdown/shutdown.d/99-verify-shutdown.sh
+ EOF
+
+ # Install make-initrd-shutdown-restore and make-initrd-shutdown.service
+ mkdir -p /mnt/sysimage/usr/sbin /mnt/sysimage/usr/lib/systemd/system
+ if [ -x "$src_srv/.build/dest/usr/sbin/make-initrd-shutdown-restore" ]; then
+ install -m 755 "$src_srv/.build/dest/usr/sbin/make-initrd-shutdown-restore" \
+ /mnt/sysimage/usr/sbin/make-initrd-shutdown-restore
+ else
+ install -m 755 "$src_srv/utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in" \
+ /mnt/sysimage/usr/sbin/make-initrd-shutdown-restore
+ fi
+ install -m 644 "$src_srv/features/shutdown/system/make-initrd-shutdown.service" \
+ /mnt/sysimage/usr/lib/systemd/system/make-initrd-shutdown.service
+
+ mkdir -p /mnt/sysimage/usr/lib/systemd/system/sysinit.target.wants
+ ln -sf ../make-initrd-shutdown.service \
+ /mnt/sysimage/usr/lib/systemd/system/sysinit.target.wants/make-initrd-shutdown.service
+
+ mkdir -p /mnt/sysimage/etc/systemd/system/sysinit.target.wants
+ ln -sf /usr/lib/systemd/system/make-initrd-shutdown.service \
+ /mnt/sysimage/etc/systemd/system/sysinit.target.wants/make-initrd-shutdown.service
+
+ if command -v systemctl >/dev/null 2>&1; then
+ systemctl --root=/mnt/sysimage enable make-initrd-shutdown.service 2>/dev/null || :
+ fi
+
+ # Ensure initrd-find, initrd-extract and initrd-ls are available on the target system for restore
+ if [ -x "$src_srv/.build/dest/usr/sbin/initrd-find" ]; then
+ install -m 755 "$src_srv/.build/dest/usr/sbin/initrd-find" \
+ /mnt/sysimage/usr/sbin/initrd-find
+ else
+ install -m 755 "$src_srv/utils/initrd-find/initrd-find.in" \
+ /mnt/sysimage/usr/sbin/initrd-find
+ fi
+ if [ -x "$src_srv/.build/dest/usr/sbin/initrd-extract" ]; then
+ install -m 755 "$src_srv/.build/dest/usr/sbin/initrd-extract" \
+ /mnt/sysimage/usr/sbin/initrd-extract
+ fi
+ if [ -x "$src_srv/.build/dest/usr/sbin/initrd-ls" ]; then
+ install -m 755 "$src_srv/.build/dest/usr/sbin/initrd-ls" \
+ /mnt/sysimage/usr/sbin/initrd-ls
+ fi
+
+ # Ensure libshell is available on the target system for initrd-find
+ mkdir -p /mnt/sysimage/usr/bin
+ if [ -d "$src_srv/.build/dest/usr/bin" ]; then
+ cp -a "$src_srv/.build/dest/usr/bin"/shell-* /mnt/sysimage/usr/bin/ 2>/dev/null || :
+ fi
+
+ # Ensure cpio is present on the target rootfs
+ if ! [ -x /mnt/sysimage/usr/bin/cpio ] && ! [ -x /mnt/sysimage/bin/cpio ]; then
+ cpio_path="$(command -v cpio ||:)"
+ if [ -n "$cpio_path" ]; then
+ install -m 755 "$cpio_path" /mnt/sysimage/usr/bin/cpio
+ fi
+ fi
+
+ # Verify essential files are installed
+ test -x /mnt/sysimage/usr/sbin/make-initrd-shutdown-restore
+ test -f /mnt/sysimage/usr/lib/systemd/system/make-initrd-shutdown.service
+
+ # Create verification shutdown hook to validate clean exitrd teardown
+ mkdir -p /mnt/sysimage/lib/initrd/shutdown/shutdown.d
+ tee /mnt/sysimage/lib/initrd/shutdown/shutdown.d/99-verify-shutdown.sh <<-'EOF'
+ #!/bin/sh
+ # SPDX-License-Identifier: GPL-3.0-or-later
+
+ if mountpoint -q /oldroot 2>/dev/null; then
+ echo "SHUTDOWN VERIFY FAIL: /oldroot is still mounted!" >&2
+ [ ! -w /dev/console ] || echo "SHUTDOWN VERIFY FAIL: /oldroot is still mounted!" > /dev/console
+ exit 1
+ fi
+
+ echo "SHUTDOWN VERIFY SUCCESS: /oldroot cleanly unmounted"
+ echo "IT WORKS!"
+ [ ! -w /dev/console ] || echo "IT WORKS!" > /dev/console
+ EOF
+ chmod 755 /mnt/sysimage/lib/initrd/shutdown/shutdown.d/99-verify-shutdown.sh
+
+ # Replace /init.once to initiate graceful systemd poweroff instead of immediate reboot -f
+ tee /mnt/sysimage/init.once <<-'EOF'
+ #!/bin/sh
+ set -eu
+ test -d /proc && mountpoint -q /proc || mount -t proc proc /proc
+ test -d /sys && mountpoint -q /sys || mount -t sysfs sysfs /sys
+
+ status="$(systemctl is-active make-initrd-shutdown.service 2>/dev/null || echo inactive)"
+ echo "Init-once: make-initrd-shutdown service status: $status" > /dev/console
+ systemctl status make-initrd-shutdown.service > /dev/console 2>&1 || :
+
+ if [ "$status" != "active" ]; then
+ echo "Init-once: starting make-initrd-shutdown.service..." > /dev/console
+ systemctl start make-initrd-shutdown.service > /dev/console 2>&1 || :
+ status="$(systemctl is-active make-initrd-shutdown.service 2>/dev/null || echo inactive)"
+ echo "Init-once: make-initrd-shutdown service status after start: $status" > /dev/console
+ fi
+
+ echo "Init-once: initiating graceful system poweroff to trigger make-initrd exitrd..." > /dev/console
+ systemctl poweroff --no-block
+ exit 0
+ EOF
+ chmod 755 /mnt/sysimage/init.once
+%end
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* [make-initrd] [PATCH v1 10/10] docs: Add manpages and bash-completion for initrd-find and make-initrd-shutdown-restore
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (8 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 09/10] testing: Add end-to-end test for shutdown feature Alexey Gladkov
@ 2026-11-09 13:07 ` Alexey Gladkov
2026-11-09 15:23 ` [make-initrd] [PATCH v1 00/10] New shutdown feature Антон Мидюков
2026-11-09 15:25 ` Anton Midyukov
11 siblings, 0 replies; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 13:07 UTC (permalink / raw)
To: make-initrd
Add scdoc manpages for the new utilities:
- initrd-find(1): document search algorithm across BLS Type #1/2,
kernel command line, /lib/modules, and traditional boot directories,
along with options and exit status.
- make-initrd-shutdown-restore(1): document exitrd restoration
behavior during shutdown, environment variables (TARGET_DIR,
SYS_KVER, BOOT_DIRS, VERBOSE), and systemd integration.
Add programmable bash completion for initrd-find covering all options,
candidate boot directories, and installed kernel versions from
/lib/modules.
Signed-off-by: Alexey Gladkov <gladkov.alexey@gmail.com>
---
Documentation/manpages/initrd-find.1.md | 94 +++++++++++++++++++
.../make-initrd-shutdown-restore.1.md | 72 ++++++++++++++
contrib/bash-completion/initrd-find | 38 ++++++++
3 files changed, 204 insertions(+)
create mode 100644 Documentation/manpages/initrd-find.1.md
create mode 100644 Documentation/manpages/make-initrd-shutdown-restore.1.md
create mode 100644 contrib/bash-completion/initrd-find
diff --git a/Documentation/manpages/initrd-find.1.md b/Documentation/manpages/initrd-find.1.md
new file mode 100644
index 00000000..293d8103
--- /dev/null
+++ b/Documentation/manpages/initrd-find.1.md
@@ -0,0 +1,94 @@
+initrd-find(1)
+
+# NAME
+
+initrd-find - finds matching initramfs or Unified Kernel Image (UKI) for a kernel
+
+# SYNOPSIS
+
+*initrd-find* [options] [KERNEL_VERSION]
+
+# DESCRIPTION
+
+*initrd-find* locates the initial ramdisk (initramfs) or Unified Kernel Image (UKI)
+matching a specified kernel version. If no kernel version is specified, the
+running kernel version (_uname -r_) is used.
+
+The utility searches the following locations in order:
+
+. Boot Loader Specification (BLS) Type #1 entry files (*loader/entries/\*.conf*) in candidate boot and ESP directories. Multi-part initrd directives are supported.
+. BLS Type #1 directory hierarchies (*$BOOT/$TOKEN/$KERNEL_VERSION/initrd\**).
+. BLS Type #2 Unified Kernel Images (*$BOOT/EFI/Linux/\*.efi*).
+. Kernel command line parameters (*initrd=...*) from */proc/cmdline*.
+. Distro module directory (*\/lib/modules/$KERNEL_VERSION/initrd*).
+. Traditional boot directory naming schemes (*initrd-$KERNEL_VERSION.img*, *initramfs-$KERNEL_VERSION.img*, etc.) in candidate boot directories.
+. Root directory fallback (*/initrd.img*).
+
+By default, candidate images are verified to be created by *make-initrd* by checking
+for the identification signature *ID=make-initrd* in *etc/initrd-release*. Use
+*-a, --any* to accept any initramfs image.
+
+Upon success, *initrd-find* prints the path of the found image to standard output
+and exits with status 0. If no matching image is found, it exits with status 1.
+
+# OPTIONS
+
+*-k, --kernel*=_VERSION_
+ Specifies the kernel version to find an initramfs for. Defaults to the running
+ kernel version.
+
+*-b, --boot-dir*=_DIR_
+ Adds a directory to the list of candidate boot directories to search for
+ boot loader entries and images. This option can be specified multiple times.
+
+*-m, --mount*
+ Mounts candidate boot and ESP partitions (*\/boot*, *\/efi*, *\/boot/efi*, and
+ paths reported by *bootctl*(1)) read-only if they are not already mounted.
+ Temporary mounts performed by the tool are automatically unmounted upon exit.
+
+*--no-mount*
+ Do not attempt to mount filesystems (default).
+
+*-a, --any*
+ Accept any matching initramfs image, even if it does not contain the *make-initrd*
+ signature in *etc/initrd-release*.
+
+*-v, --verbose*
+ Print diagnostic information about the search process to standard error.
+
+*-q, --quiet*
+ Suppress warning messages when no image is found.
+
+*-V, --version*
+ Print program version and exit.
+
+*-h, --help*
+ Show help message and exit.
+
+# ENVIRONMENT
+
+*SYS_KVER*
+ Kernel version to search for if *-k* is not specified.
+
+*BOOT_DIRS*
+ Whitespace-separated list of candidate boot directories to search.
+
+# EXIT STATUS
+
+*0*
+ Matching initramfs or UKI image was found.
+
+*1*
+ No matching image was found or invalid options were given.
+
+# AUTHOR
+
+Written by Alexey Gladkov <gladkov.alexey@gmail.com>.
+
+# BUGS
+
+Report bugs to the authors.
+
+# SEE ALSO
+
+*make-initrd*(1), *initrd-ls*(1), *initrd-extract*(1), *make-initrd-shutdown-restore*(1)
diff --git a/Documentation/manpages/make-initrd-shutdown-restore.1.md b/Documentation/manpages/make-initrd-shutdown-restore.1.md
new file mode 100644
index 00000000..19b44cbc
--- /dev/null
+++ b/Documentation/manpages/make-initrd-shutdown-restore.1.md
@@ -0,0 +1,72 @@
+make-initrd-shutdown-restore(1)
+
+# NAME
+
+make-initrd-shutdown-restore - restores initramfs for clean shutdown (exitrd)
+
+# SYNOPSIS
+
+*make-initrd-shutdown-restore*
+
+# DESCRIPTION
+
+*make-initrd-shutdown-restore* is a system shutdown utility that prepares the
+environment for *systemd*(1) to pivot back into an exitrd during system shutdown
+or reboot.
+
+Before local filesystems are unmounted during shutdown, the utility locates the
+active kernel's initramfs using *initrd-find*(1) and inspects its contents using
+*initrd-ls*(1). If the image contains the *\/shutdown* executable (meaning that
+the *shutdown* feature was included in the initramfs), the archive is unpacked
+into the target directory (*\/run/initramfs* by default) using *initrd-extract*(1).
+If compressed filesystem layers are present (such as SquashFS or EROFS), they are
+unpacked as well.
+
+If *\/run/initramfs/shutdown* already exists and is executable, the utility does
+nothing and exits immediately with status 0. If the found initramfs does not
+contain the *shutdown* feature, the utility exits cleanly without error, allowing
+distributions to enable *make-initrd-shutdown.service* unconditionally.
+
+During the final shutdown phase, *systemd-shutdown*(8) pivots root to
+*\/run/initramfs* and executes *\/shutdown*, which cleanly terminates remaining
+processes, unmounts filesystems, deactivates device-mapper and RAID block devices,
+and halts or reboots the machine.
+
+# ENVIRONMENT
+
+*TARGET_DIR*
+ Destination directory where the initramfs should be unpacked.
+ Defaults to *\/run/initramfs*.
+
+*SYS_KVER*
+ Kernel version to search an initramfs for. Defaults to the running
+ kernel version (_uname -r_).
+
+*BOOT_DIRS*
+ Whitespace-separated list of candidate boot directories passed to
+ *initrd-find*(1).
+
+*VERBOSE*
+ If set to a non-empty value, enables diagnostic output to standard error.
+
+# EXIT STATUS
+
+*0*
+ Initramfs was successfully unpacked, *\/shutdown* was already present, or
+ the initramfs does not contain the *shutdown* feature.
+
+*1*
+ Required utilities (*initrd-find*, *initrd-extract*, *initrd-ls*) are missing,
+ or *\/shutdown* was not found after unpacking.
+
+# AUTHOR
+
+Written by Alexey Gladkov <gladkov.alexey@gmail.com>.
+
+# BUGS
+
+Report bugs to the authors.
+
+# SEE ALSO
+
+*initrd-find*(1), *make-initrd*(1), *systemd-shutdown*(8)
diff --git a/contrib/bash-completion/initrd-find b/contrib/bash-completion/initrd-find
new file mode 100644
index 00000000..43f532d5
--- /dev/null
+++ b/contrib/bash-completion/initrd-find
@@ -0,0 +1,38 @@
+_initrd_find()
+{
+ local cur prev opts
+ _init_completion || return
+
+ opts="
+ -k --kernel=
+ -b --boot-dir=
+ -m --mount
+ --no-mount
+ -a --any
+ -v --verbose
+ -q --quiet
+ -V --version
+ -h --help
+ "
+
+ case "$prev" in
+ -k|--kernel)
+ local kernels=$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f ' 2>/dev/null)
+ COMPREPLY=( $(compgen -W "$kernels" -- "$cur") )
+ return 0
+ ;;
+ -b|--boot-dir)
+ COMPREPLY=( $(compgen -d -- "$cur") )
+ return 0
+ ;;
+ esac
+
+ if [[ "$cur" == -* ]]; then
+ COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
+ return 0
+ fi
+
+ local kernels=$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f ' 2>/dev/null)
+ COMPREPLY=( $(compgen -W "$kernels" -- "$cur") )
+}
+complete -F _initrd_find initrd-find
--
2.56.0
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (9 preceding siblings ...)
2026-11-09 13:07 ` [make-initrd] [PATCH v1 10/10] docs: Add manpages and bash-completion for initrd-find and make-initrd-shutdown-restore Alexey Gladkov
@ 2026-11-09 15:23 ` Антон Мидюков
2026-11-09 15:25 ` Anton Midyukov
11 siblings, 0 replies; 31+ messages in thread
From: Антон Мидюков @ 2026-11-09 15:23 UTC (permalink / raw)
To: make-initrd
09.11.2026 16:06, Alexey Gladkov пишет:
> Привет!
>
> Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
> всех дистрибутивах.
>
> Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
> перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
> же поведение можно реализовать и в других реализациях init.
>
> В фиче есть guess, который проверяет предыдущий initrd и если он содержит
> shutdown, то в новый образ фича будет добавлена.
>
> Как сайд эффект были добавлены несколько улучшений.
>
> 1. initrd-find пытается искать initrd (и это часто получается), который был
> использован для загрузки.
>
> 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
> Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
>
> Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
> работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
> других фичей, что-то ещё.
>
> [1] https://systemd.io/INITRD_INTERFACE/
> [2] https://github.com/osboot/make-initrd/tree/features-shutdown-v1
>
В live у меня не находит initrd:
make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
Что естественно, initrd c ядром лежит в /image/boot/
Можно ли как-то путь для поиска подсказать?
>
> Alexey Gladkov (10):
> utils: Add PE header parsing to locate .initrd section
> shutdown: Add exitrd shutdown feature
> shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd
> service
> shutdown: Add basic unit test suite
> devmapper: shutdown: Teardown device-mapper targets on exit
> mdadm: shutdown: Stop MD arrays on exit
> btrfs: shutdown: Sync filesystem before umount on exit
> zfs: shutdown: Unmount filesystems and export zpools on exit
> testing: Add end-to-end test for shutdown feature
> docs: Add manpages and bash-completion for initrd-find and
> make-initrd-shutdown-restore
>
> .github/workflows/ci.yml | 2 +-
> .github/workflows/testcases.yml | 1 +
> Documentation/manpages/initrd-find.1.md | 94 ++++
> .../make-initrd-shutdown-restore.1.md | 72 ++++
> Makefile.in | 6 +
> contrib/bash-completion/initrd-find | 38 ++
> .../lib/initrd/shutdown/umount.d/50-btrfs.sh | 13 +
> .../initrd/shutdown/post-umount.d/50-dm.sh | 21 +
> .../initrd/shutdown/post-umount.d/60-md.sh | 34 ++
> features/shutdown/README.md | 81 ++++
> features/shutdown/config.mk | 5 +
> features/shutdown/guess/common | 32 ++
> features/shutdown/rules.mk | 7 +
> .../initrd/shutdown/post-umount.d/.gitignore | 1 +
> .../initrd/shutdown/post-umount.d/10-loop.sh | 21 +
> .../initrd/shutdown/pre-umount.d/.gitignore | 1 +
> .../lib/initrd/shutdown/shutdown.d/.gitignore | 2 +
> .../lib/initrd/shutdown/umount.d/.gitignore | 1 +
> features/shutdown/shutdown/shutdown | 196 +++++++++
> .../system/make-initrd-shutdown.service | 20 +
> .../tests/ts0001-shutdown-basic/expect | 17 +
> .../shutdown/tests/ts0001-shutdown-basic/run | 145 +++++++
> .../ts0002-guess-common-with-shutdown/expect | 3 +
> .../ts0002-guess-common-with-shutdown/run | 32 ++
> .../expect | 1 +
> .../ts0003-guess-common-without-shutdown/run | 27 ++
> .../ts0004-guess-common-no-initrd/expect | 1 +
> .../tests/ts0004-guess-common-no-initrd/run | 16 +
> .../lib/initrd/shutdown/shutdown.d/20-zfs.sh | 9 +
> .../lib/initrd/shutdown/umount.d/50-zfs.sh | 20 +
> testing/test-root-shutdown.cfg | 147 +++++++
> utils/initrd-extract/tests/ts0005/expect | 3 +
> utils/initrd-extract/tests/ts0005/run | 62 +++
> utils/initrd-find/Makefile.mk | 6 +
> utils/initrd-find/initrd-find.in | 401 ++++++++++++++++++
> .../tests/ts0001-initrd-find-basic/expect | 10 +
> .../tests/ts0001-initrd-find-basic/run | 108 +++++
> utils/initrd-ls/tests/ts0008/expect | 2 +-
> utils/initrd-ls/tests/ts0017/expect | 2 +-
> utils/initrd-ls/tests/ts0025/expect | 2 +-
> utils/initrd-ls/tests/ts0033/expect | 2 +-
> utils/initrd-ls/tests/ts0034/expect | 2 +-
> utils/initrd-ls/tests/ts0035/expect | 2 +-
> utils/initrd-ls/tests/ts0036/expect | 2 +-
> utils/initrd-ls/tests/ts0037/expect | 2 +-
> utils/initrd-ls/tests/ts0038/expect | 2 +-
> utils/initrd-ls/tests/ts0039/expect | 2 +-
> utils/initrd-ls/tests/ts0041/expect | 2 +-
> utils/initrd-ls/tests/ts0045/expect | 7 +
> utils/initrd-ls/tests/ts0045/run | 83 ++++
> utils/initrd-parse.c | 81 +++-
> .../make-initrd-shutdown-restore/Makefile.mk | 6 +
> .../make-initrd-shutdown-restore.in | 147 +++++++
> 53 files changed, 1980 insertions(+), 22 deletions(-)
> create mode 100644 Documentation/manpages/initrd-find.1.md
> create mode 100644 Documentation/manpages/make-initrd-shutdown-restore.1.md
> create mode 100644 contrib/bash-completion/initrd-find
> create mode 100755 features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
> create mode 100755 features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
> create mode 100755 features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
> create mode 100644 features/shutdown/README.md
> create mode 100644 features/shutdown/config.mk
> create mode 100755 features/shutdown/guess/common
> create mode 100644 features/shutdown/rules.mk
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
> create mode 100755 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
> create mode 100755 features/shutdown/shutdown/shutdown
> create mode 100644 features/shutdown/system/make-initrd-shutdown.service
> create mode 100644 features/shutdown/tests/ts0001-shutdown-basic/expect
> create mode 100755 features/shutdown/tests/ts0001-shutdown-basic/run
> create mode 100644 features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
> create mode 100755 features/shutdown/tests/ts0002-guess-common-with-shutdown/run
> create mode 100644 features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
> create mode 100755 features/shutdown/tests/ts0003-guess-common-without-shutdown/run
> create mode 100644 features/shutdown/tests/ts0004-guess-common-no-initrd/expect
> create mode 100755 features/shutdown/tests/ts0004-guess-common-no-initrd/run
> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
> create mode 100644 testing/test-root-shutdown.cfg
> create mode 100644 utils/initrd-extract/tests/ts0005/expect
> create mode 100755 utils/initrd-extract/tests/ts0005/run
> create mode 100644 utils/initrd-find/Makefile.mk
> create mode 100755 utils/initrd-find/initrd-find.in
> create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
> create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
> create mode 100644 utils/initrd-ls/tests/ts0045/expect
> create mode 100755 utils/initrd-ls/tests/ts0045/run
> create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
> create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
>
--
С уважением,
Мидюков Антон
Начальник отдела разработки программного обеспечения ООО "Базальт СПО"
+7(495)123-47-99 доб. 718
antohami@basealt.ru
https://www.basealt.ru
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 13:06 ` [make-initrd] [PATCH v1 00/10] " Alexey Gladkov
` (10 preceding siblings ...)
2026-11-09 15:23 ` [make-initrd] [PATCH v1 00/10] New shutdown feature Антон Мидюков
@ 2026-11-09 15:25 ` Anton Midyukov
2026-11-09 15:47 ` Alexey Gladkov
11 siblings, 1 reply; 31+ messages in thread
From: Anton Midyukov @ 2026-11-09 15:25 UTC (permalink / raw)
To: make-initrd
09.11.2026 16:06, Alexey Gladkov пишет:
> Привет!
>
> Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
> всех дистрибутивах.
>
> Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
> перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
> же поведение можно реализовать и в других реализациях init.
>
> В фиче есть guess, который проверяет предыдущий initrd и если он содержит
> shutdown, то в новый образ фича будет добавлена.
>
> Как сайд эффект были добавлены несколько улучшений.
>
> 1. initrd-find пытается искать initrd (и это часто получается), который был
> использован для загрузки.
>
> 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
> Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
>
> Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
> работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
> других фичей, что-то ещё.
>
В live у меня не находит initrd:
make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
Что естественно, initrd c ядром лежит в /image/boot/
Можно ли как-то путь для поиска подсказать?
(прошу админов моё предыдущее письмо, попавшее на премодерацию, не пускать, не с того почтового ящика отправил случайно)
> [1] https://systemd.io/INITRD_INTERFACE/
> [2] https://github.com/osboot/make-initrd/tree/features-shutdown-v1
>
>
> Alexey Gladkov (10):
> utils: Add PE header parsing to locate .initrd section
> shutdown: Add exitrd shutdown feature
> shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd
> service
> shutdown: Add basic unit test suite
> devmapper: shutdown: Teardown device-mapper targets on exit
> mdadm: shutdown: Stop MD arrays on exit
> btrfs: shutdown: Sync filesystem before umount on exit
> zfs: shutdown: Unmount filesystems and export zpools on exit
> testing: Add end-to-end test for shutdown feature
> docs: Add manpages and bash-completion for initrd-find and
> make-initrd-shutdown-restore
>
> .github/workflows/ci.yml | 2 +-
> .github/workflows/testcases.yml | 1 +
> Documentation/manpages/initrd-find.1.md | 94 ++++
> .../make-initrd-shutdown-restore.1.md | 72 ++++
> Makefile.in | 6 +
> contrib/bash-completion/initrd-find | 38 ++
> .../lib/initrd/shutdown/umount.d/50-btrfs.sh | 13 +
> .../initrd/shutdown/post-umount.d/50-dm.sh | 21 +
> .../initrd/shutdown/post-umount.d/60-md.sh | 34 ++
> features/shutdown/README.md | 81 ++++
> features/shutdown/config.mk | 5 +
> features/shutdown/guess/common | 32 ++
> features/shutdown/rules.mk | 7 +
> .../initrd/shutdown/post-umount.d/.gitignore | 1 +
> .../initrd/shutdown/post-umount.d/10-loop.sh | 21 +
> .../initrd/shutdown/pre-umount.d/.gitignore | 1 +
> .../lib/initrd/shutdown/shutdown.d/.gitignore | 2 +
> .../lib/initrd/shutdown/umount.d/.gitignore | 1 +
> features/shutdown/shutdown/shutdown | 196 +++++++++
> .../system/make-initrd-shutdown.service | 20 +
> .../tests/ts0001-shutdown-basic/expect | 17 +
> .../shutdown/tests/ts0001-shutdown-basic/run | 145 +++++++
> .../ts0002-guess-common-with-shutdown/expect | 3 +
> .../ts0002-guess-common-with-shutdown/run | 32 ++
> .../expect | 1 +
> .../ts0003-guess-common-without-shutdown/run | 27 ++
> .../ts0004-guess-common-no-initrd/expect | 1 +
> .../tests/ts0004-guess-common-no-initrd/run | 16 +
> .../lib/initrd/shutdown/shutdown.d/20-zfs.sh | 9 +
> .../lib/initrd/shutdown/umount.d/50-zfs.sh | 20 +
> testing/test-root-shutdown.cfg | 147 +++++++
> utils/initrd-extract/tests/ts0005/expect | 3 +
> utils/initrd-extract/tests/ts0005/run | 62 +++
> utils/initrd-find/Makefile.mk | 6 +
> utils/initrd-find/initrd-find.in | 401 ++++++++++++++++++
> .../tests/ts0001-initrd-find-basic/expect | 10 +
> .../tests/ts0001-initrd-find-basic/run | 108 +++++
> utils/initrd-ls/tests/ts0008/expect | 2 +-
> utils/initrd-ls/tests/ts0017/expect | 2 +-
> utils/initrd-ls/tests/ts0025/expect | 2 +-
> utils/initrd-ls/tests/ts0033/expect | 2 +-
> utils/initrd-ls/tests/ts0034/expect | 2 +-
> utils/initrd-ls/tests/ts0035/expect | 2 +-
> utils/initrd-ls/tests/ts0036/expect | 2 +-
> utils/initrd-ls/tests/ts0037/expect | 2 +-
> utils/initrd-ls/tests/ts0038/expect | 2 +-
> utils/initrd-ls/tests/ts0039/expect | 2 +-
> utils/initrd-ls/tests/ts0041/expect | 2 +-
> utils/initrd-ls/tests/ts0045/expect | 7 +
> utils/initrd-ls/tests/ts0045/run | 83 ++++
> utils/initrd-parse.c | 81 +++-
> .../make-initrd-shutdown-restore/Makefile.mk | 6 +
> .../make-initrd-shutdown-restore.in | 147 +++++++
> 53 files changed, 1980 insertions(+), 22 deletions(-)
> create mode 100644 Documentation/manpages/initrd-find.1.md
> create mode 100644 Documentation/manpages/make-initrd-shutdown-restore.1.md
> create mode 100644 contrib/bash-completion/initrd-find
> create mode 100755 features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
> create mode 100755 features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
> create mode 100755 features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
> create mode 100644 features/shutdown/README.md
> create mode 100644 features/shutdown/config.mk
> create mode 100755 features/shutdown/guess/common
> create mode 100644 features/shutdown/rules.mk
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
> create mode 100755 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
> create mode 100755 features/shutdown/shutdown/shutdown
> create mode 100644 features/shutdown/system/make-initrd-shutdown.service
> create mode 100644 features/shutdown/tests/ts0001-shutdown-basic/expect
> create mode 100755 features/shutdown/tests/ts0001-shutdown-basic/run
> create mode 100644 features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
> create mode 100755 features/shutdown/tests/ts0002-guess-common-with-shutdown/run
> create mode 100644 features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
> create mode 100755 features/shutdown/tests/ts0003-guess-common-without-shutdown/run
> create mode 100644 features/shutdown/tests/ts0004-guess-common-no-initrd/expect
> create mode 100755 features/shutdown/tests/ts0004-guess-common-no-initrd/run
> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
> create mode 100644 testing/test-root-shutdown.cfg
> create mode 100644 utils/initrd-extract/tests/ts0005/expect
> create mode 100755 utils/initrd-extract/tests/ts0005/run
> create mode 100644 utils/initrd-find/Makefile.mk
> create mode 100755 utils/initrd-find/initrd-find.in
> create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
> create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
> create mode 100644 utils/initrd-ls/tests/ts0045/expect
> create mode 100755 utils/initrd-ls/tests/ts0045/run
> create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
> create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
>
--
best regards, Anton Midyukov <antohami@altlinux.org>
^ permalink raw reply [flat|nested] 31+ messages in thread* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 15:25 ` Anton Midyukov
@ 2026-11-09 15:47 ` Alexey Gladkov
2026-11-09 15:57 ` Anton Midyukov
0 siblings, 1 reply; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 15:47 UTC (permalink / raw)
To: make-initrd
On Mon, Nov 09, 2026 at 06:25:59PM +0300, Anton Midyukov wrote:
> 09.11.2026 16:06, Alexey Gladkov пишет:
> > Привет!
> >
> > Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
> > всех дистрибутивах.
> >
> > Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
> > перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
> > же поведение можно реализовать и в других реализациях init.
> >
> > В фиче есть guess, который проверяет предыдущий initrd и если он содержит
> > shutdown, то в новый образ фича будет добавлена.
> >
> > Как сайд эффект были добавлены несколько улучшений.
> >
> > 1. initrd-find пытается искать initrd (и это часто получается), который был
> > использован для загрузки.
> >
> > 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
> > Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
> >
> > Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
> > работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
> > других фичей, что-то ещё.
> >
>
> В live у меня не находит initrd:
> make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
>
> Что естественно, initrd c ядром лежит в /image/boot/
> Можно ли как-то путь для поиска подсказать?
у initrd-find есть опция --boot-dir=DIR, но эта утилита вызывается из
make-initrd-shutdown-restore, который в свою очередь из сервиса
вызывается. То есть env не вариант.
я подумаю, как это можно решить, но если у тебя появятся идеи, то
накидывай сюда.
> (прошу админов моё предыдущее письмо, попавшее на премодерацию, не пускать, не с того почтового ящика отправил случайно)
Упс. Поздно.
> > [1] https://systemd.io/INITRD_INTERFACE/
> > [2] https://github.com/osboot/make-initrd/tree/features-shutdown-v1
> >
> >
> > Alexey Gladkov (10):
> > utils: Add PE header parsing to locate .initrd section
> > shutdown: Add exitrd shutdown feature
> > shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd
> > service
> > shutdown: Add basic unit test suite
> > devmapper: shutdown: Teardown device-mapper targets on exit
> > mdadm: shutdown: Stop MD arrays on exit
> > btrfs: shutdown: Sync filesystem before umount on exit
> > zfs: shutdown: Unmount filesystems and export zpools on exit
> > testing: Add end-to-end test for shutdown feature
> > docs: Add manpages and bash-completion for initrd-find and
> > make-initrd-shutdown-restore
> >
> > .github/workflows/ci.yml | 2 +-
> > .github/workflows/testcases.yml | 1 +
> > Documentation/manpages/initrd-find.1.md | 94 ++++
> > .../make-initrd-shutdown-restore.1.md | 72 ++++
> > Makefile.in | 6 +
> > contrib/bash-completion/initrd-find | 38 ++
> > .../lib/initrd/shutdown/umount.d/50-btrfs.sh | 13 +
> > .../initrd/shutdown/post-umount.d/50-dm.sh | 21 +
> > .../initrd/shutdown/post-umount.d/60-md.sh | 34 ++
> > features/shutdown/README.md | 81 ++++
> > features/shutdown/config.mk | 5 +
> > features/shutdown/guess/common | 32 ++
> > features/shutdown/rules.mk | 7 +
> > .../initrd/shutdown/post-umount.d/.gitignore | 1 +
> > .../initrd/shutdown/post-umount.d/10-loop.sh | 21 +
> > .../initrd/shutdown/pre-umount.d/.gitignore | 1 +
> > .../lib/initrd/shutdown/shutdown.d/.gitignore | 2 +
> > .../lib/initrd/shutdown/umount.d/.gitignore | 1 +
> > features/shutdown/shutdown/shutdown | 196 +++++++++
> > .../system/make-initrd-shutdown.service | 20 +
> > .../tests/ts0001-shutdown-basic/expect | 17 +
> > .../shutdown/tests/ts0001-shutdown-basic/run | 145 +++++++
> > .../ts0002-guess-common-with-shutdown/expect | 3 +
> > .../ts0002-guess-common-with-shutdown/run | 32 ++
> > .../expect | 1 +
> > .../ts0003-guess-common-without-shutdown/run | 27 ++
> > .../ts0004-guess-common-no-initrd/expect | 1 +
> > .../tests/ts0004-guess-common-no-initrd/run | 16 +
> > .../lib/initrd/shutdown/shutdown.d/20-zfs.sh | 9 +
> > .../lib/initrd/shutdown/umount.d/50-zfs.sh | 20 +
> > testing/test-root-shutdown.cfg | 147 +++++++
> > utils/initrd-extract/tests/ts0005/expect | 3 +
> > utils/initrd-extract/tests/ts0005/run | 62 +++
> > utils/initrd-find/Makefile.mk | 6 +
> > utils/initrd-find/initrd-find.in | 401 ++++++++++++++++++
> > .../tests/ts0001-initrd-find-basic/expect | 10 +
> > .../tests/ts0001-initrd-find-basic/run | 108 +++++
> > utils/initrd-ls/tests/ts0008/expect | 2 +-
> > utils/initrd-ls/tests/ts0017/expect | 2 +-
> > utils/initrd-ls/tests/ts0025/expect | 2 +-
> > utils/initrd-ls/tests/ts0033/expect | 2 +-
> > utils/initrd-ls/tests/ts0034/expect | 2 +-
> > utils/initrd-ls/tests/ts0035/expect | 2 +-
> > utils/initrd-ls/tests/ts0036/expect | 2 +-
> > utils/initrd-ls/tests/ts0037/expect | 2 +-
> > utils/initrd-ls/tests/ts0038/expect | 2 +-
> > utils/initrd-ls/tests/ts0039/expect | 2 +-
> > utils/initrd-ls/tests/ts0041/expect | 2 +-
> > utils/initrd-ls/tests/ts0045/expect | 7 +
> > utils/initrd-ls/tests/ts0045/run | 83 ++++
> > utils/initrd-parse.c | 81 +++-
> > .../make-initrd-shutdown-restore/Makefile.mk | 6 +
> > .../make-initrd-shutdown-restore.in | 147 +++++++
> > 53 files changed, 1980 insertions(+), 22 deletions(-)
> > create mode 100644 Documentation/manpages/initrd-find.1.md
> > create mode 100644 Documentation/manpages/make-initrd-shutdown-restore.1.md
> > create mode 100644 contrib/bash-completion/initrd-find
> > create mode 100755 features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
> > create mode 100755 features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
> > create mode 100755 features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
> > create mode 100644 features/shutdown/README.md
> > create mode 100644 features/shutdown/config.mk
> > create mode 100755 features/shutdown/guess/common
> > create mode 100644 features/shutdown/rules.mk
> > create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
> > create mode 100755 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
> > create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
> > create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
> > create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
> > create mode 100755 features/shutdown/shutdown/shutdown
> > create mode 100644 features/shutdown/system/make-initrd-shutdown.service
> > create mode 100644 features/shutdown/tests/ts0001-shutdown-basic/expect
> > create mode 100755 features/shutdown/tests/ts0001-shutdown-basic/run
> > create mode 100644 features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
> > create mode 100755 features/shutdown/tests/ts0002-guess-common-with-shutdown/run
> > create mode 100644 features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
> > create mode 100755 features/shutdown/tests/ts0003-guess-common-without-shutdown/run
> > create mode 100644 features/shutdown/tests/ts0004-guess-common-no-initrd/expect
> > create mode 100755 features/shutdown/tests/ts0004-guess-common-no-initrd/run
> > create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
> > create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
> > create mode 100644 testing/test-root-shutdown.cfg
> > create mode 100644 utils/initrd-extract/tests/ts0005/expect
> > create mode 100755 utils/initrd-extract/tests/ts0005/run
> > create mode 100644 utils/initrd-find/Makefile.mk
> > create mode 100755 utils/initrd-find/initrd-find.in
> > create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
> > create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
> > create mode 100644 utils/initrd-ls/tests/ts0045/expect
> > create mode 100755 utils/initrd-ls/tests/ts0045/run
> > create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
> > create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
> >
>
> --
> best regards, Anton Midyukov <antohami@altlinux.org>
>
> _______________________________________________
> Make-initrd mailing list
> Make-initrd@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/make-initrd
--
Rgrds, legion
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 15:47 ` Alexey Gladkov
@ 2026-11-09 15:57 ` Anton Midyukov
2026-11-09 16:14 ` Alexey Gladkov
0 siblings, 1 reply; 31+ messages in thread
From: Anton Midyukov @ 2026-11-09 15:57 UTC (permalink / raw)
To: make-initrd
09.11.2026 18:47, Alexey Gladkov пишет:
> On Mon, Nov 09, 2026 at 06:25:59PM +0300, Anton Midyukov wrote:
>> 09.11.2026 16:06, Alexey Gladkov пишет:
>>> Привет!
>>>
>>> Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
>>> всех дистрибутивах.
>>>
>>> Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
>>> перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
>>> же поведение можно реализовать и в других реализациях init.
>>>
>>> В фиче есть guess, который проверяет предыдущий initrd и если он содержит
>>> shutdown, то в новый образ фича будет добавлена.
>>>
>>> Как сайд эффект были добавлены несколько улучшений.
>>>
>>> 1. initrd-find пытается искать initrd (и это часто получается), который был
>>> использован для загрузки.
>>>
>>> 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
>>> Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
>>>
>>> Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
>>> работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
>>> других фичей, что-то ещё.
>>>
>>
>> В live у меня не находит initrd:
>> make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
>>
>> Что естественно, initrd c ядром лежит в /image/boot/
>> Можно ли как-то путь для поиска подсказать?
>
> у initrd-find есть опция --boot-dir=DIR, но эта утилита вызывается из
> make-initrd-shutdown-restore, который в свою очередь из сервиса
> вызывается. То есть env не вариант.
>
Сервис можно заовверайдить. Переменную окружения можно задать в юните:
Environment="VAR1=value1"
Так что, по идее, как раз вариант.
> я подумаю, как это можно решить, но если у тебя появятся идеи, то
> накидывай сюда.
>
>> (прошу админов моё предыдущее письмо, попавшее на премодерацию, не пускать, не с того почтового ящика отправил случайно)
>
> Упс. Поздно.
>
>>> [1] https://systemd.io/INITRD_INTERFACE/
>>> [2] https://github.com/osboot/make-initrd/tree/features-shutdown-v1
>>>
>>>
>>> Alexey Gladkov (10):
>>> utils: Add PE header parsing to locate .initrd section
>>> shutdown: Add exitrd shutdown feature
>>> shutdown: Add make-initrd-shutdown-restore, initrd-find, and systemd
>>> service
>>> shutdown: Add basic unit test suite
>>> devmapper: shutdown: Teardown device-mapper targets on exit
>>> mdadm: shutdown: Stop MD arrays on exit
>>> btrfs: shutdown: Sync filesystem before umount on exit
>>> zfs: shutdown: Unmount filesystems and export zpools on exit
>>> testing: Add end-to-end test for shutdown feature
>>> docs: Add manpages and bash-completion for initrd-find and
>>> make-initrd-shutdown-restore
>>>
>>> .github/workflows/ci.yml | 2 +-
>>> .github/workflows/testcases.yml | 1 +
>>> Documentation/manpages/initrd-find.1.md | 94 ++++
>>> .../make-initrd-shutdown-restore.1.md | 72 ++++
>>> Makefile.in | 6 +
>>> contrib/bash-completion/initrd-find | 38 ++
>>> .../lib/initrd/shutdown/umount.d/50-btrfs.sh | 13 +
>>> .../initrd/shutdown/post-umount.d/50-dm.sh | 21 +
>>> .../initrd/shutdown/post-umount.d/60-md.sh | 34 ++
>>> features/shutdown/README.md | 81 ++++
>>> features/shutdown/config.mk | 5 +
>>> features/shutdown/guess/common | 32 ++
>>> features/shutdown/rules.mk | 7 +
>>> .../initrd/shutdown/post-umount.d/.gitignore | 1 +
>>> .../initrd/shutdown/post-umount.d/10-loop.sh | 21 +
>>> .../initrd/shutdown/pre-umount.d/.gitignore | 1 +
>>> .../lib/initrd/shutdown/shutdown.d/.gitignore | 2 +
>>> .../lib/initrd/shutdown/umount.d/.gitignore | 1 +
>>> features/shutdown/shutdown/shutdown | 196 +++++++++
>>> .../system/make-initrd-shutdown.service | 20 +
>>> .../tests/ts0001-shutdown-basic/expect | 17 +
>>> .../shutdown/tests/ts0001-shutdown-basic/run | 145 +++++++
>>> .../ts0002-guess-common-with-shutdown/expect | 3 +
>>> .../ts0002-guess-common-with-shutdown/run | 32 ++
>>> .../expect | 1 +
>>> .../ts0003-guess-common-without-shutdown/run | 27 ++
>>> .../ts0004-guess-common-no-initrd/expect | 1 +
>>> .../tests/ts0004-guess-common-no-initrd/run | 16 +
>>> .../lib/initrd/shutdown/shutdown.d/20-zfs.sh | 9 +
>>> .../lib/initrd/shutdown/umount.d/50-zfs.sh | 20 +
>>> testing/test-root-shutdown.cfg | 147 +++++++
>>> utils/initrd-extract/tests/ts0005/expect | 3 +
>>> utils/initrd-extract/tests/ts0005/run | 62 +++
>>> utils/initrd-find/Makefile.mk | 6 +
>>> utils/initrd-find/initrd-find.in | 401 ++++++++++++++++++
>>> .../tests/ts0001-initrd-find-basic/expect | 10 +
>>> .../tests/ts0001-initrd-find-basic/run | 108 +++++
>>> utils/initrd-ls/tests/ts0008/expect | 2 +-
>>> utils/initrd-ls/tests/ts0017/expect | 2 +-
>>> utils/initrd-ls/tests/ts0025/expect | 2 +-
>>> utils/initrd-ls/tests/ts0033/expect | 2 +-
>>> utils/initrd-ls/tests/ts0034/expect | 2 +-
>>> utils/initrd-ls/tests/ts0035/expect | 2 +-
>>> utils/initrd-ls/tests/ts0036/expect | 2 +-
>>> utils/initrd-ls/tests/ts0037/expect | 2 +-
>>> utils/initrd-ls/tests/ts0038/expect | 2 +-
>>> utils/initrd-ls/tests/ts0039/expect | 2 +-
>>> utils/initrd-ls/tests/ts0041/expect | 2 +-
>>> utils/initrd-ls/tests/ts0045/expect | 7 +
>>> utils/initrd-ls/tests/ts0045/run | 83 ++++
>>> utils/initrd-parse.c | 81 +++-
>>> .../make-initrd-shutdown-restore/Makefile.mk | 6 +
>>> .../make-initrd-shutdown-restore.in | 147 +++++++
>>> 53 files changed, 1980 insertions(+), 22 deletions(-)
>>> create mode 100644 Documentation/manpages/initrd-find.1.md
>>> create mode 100644 Documentation/manpages/make-initrd-shutdown-restore.1.md
>>> create mode 100644 contrib/bash-completion/initrd-find
>>> create mode 100755 features/btrfs/shutdown/lib/initrd/shutdown/umount.d/50-btrfs.sh
>>> create mode 100755 features/devmapper/shutdown/lib/initrd/shutdown/post-umount.d/50-dm.sh
>>> create mode 100755 features/mdadm/shutdown/lib/initrd/shutdown/post-umount.d/60-md.sh
>>> create mode 100644 features/shutdown/README.md
>>> create mode 100644 features/shutdown/config.mk
>>> create mode 100755 features/shutdown/guess/common
>>> create mode 100644 features/shutdown/rules.mk
>>> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/.gitignore
>>> create mode 100755 features/shutdown/shutdown/lib/initrd/shutdown/post-umount.d/10-loop.sh
>>> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/pre-umount.d/.gitignore
>>> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/shutdown.d/.gitignore
>>> create mode 100644 features/shutdown/shutdown/lib/initrd/shutdown/umount.d/.gitignore
>>> create mode 100755 features/shutdown/shutdown/shutdown
>>> create mode 100644 features/shutdown/system/make-initrd-shutdown.service
>>> create mode 100644 features/shutdown/tests/ts0001-shutdown-basic/expect
>>> create mode 100755 features/shutdown/tests/ts0001-shutdown-basic/run
>>> create mode 100644 features/shutdown/tests/ts0002-guess-common-with-shutdown/expect
>>> create mode 100755 features/shutdown/tests/ts0002-guess-common-with-shutdown/run
>>> create mode 100644 features/shutdown/tests/ts0003-guess-common-without-shutdown/expect
>>> create mode 100755 features/shutdown/tests/ts0003-guess-common-without-shutdown/run
>>> create mode 100644 features/shutdown/tests/ts0004-guess-common-no-initrd/expect
>>> create mode 100755 features/shutdown/tests/ts0004-guess-common-no-initrd/run
>>> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/shutdown.d/20-zfs.sh
>>> create mode 100755 features/zfs/shutdown/lib/initrd/shutdown/umount.d/50-zfs.sh
>>> create mode 100644 testing/test-root-shutdown.cfg
>>> create mode 100644 utils/initrd-extract/tests/ts0005/expect
>>> create mode 100755 utils/initrd-extract/tests/ts0005/run
>>> create mode 100644 utils/initrd-find/Makefile.mk
>>> create mode 100755 utils/initrd-find/initrd-find.in
>>> create mode 100644 utils/initrd-find/tests/ts0001-initrd-find-basic/expect
>>> create mode 100755 utils/initrd-find/tests/ts0001-initrd-find-basic/run
>>> create mode 100644 utils/initrd-ls/tests/ts0045/expect
>>> create mode 100755 utils/initrd-ls/tests/ts0045/run
>>> create mode 100644 utils/make-initrd-shutdown-restore/Makefile.mk
>>> create mode 100755 utils/make-initrd-shutdown-restore/make-initrd-shutdown-restore.in
>>>
>>
>> --
>> best regards, Anton Midyukov <antohami@altlinux.org>
>>
>> _______________________________________________
>> Make-initrd mailing list
>> Make-initrd@lists.altlinux.org
>> https://lists.altlinux.org/mailman/listinfo/make-initrd
>
--
best regards, Anton Midyukov <antohami@altlinux.org>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 15:57 ` Anton Midyukov
@ 2026-11-09 16:14 ` Alexey Gladkov
2026-11-09 16:16 ` Anton Midyukov
0 siblings, 1 reply; 31+ messages in thread
From: Alexey Gladkov @ 2026-11-09 16:14 UTC (permalink / raw)
To: make-initrd
On Mon, Nov 09, 2026 at 06:57:31PM +0300, Anton Midyukov wrote:
> 09.11.2026 18:47, Alexey Gladkov пишет:
> > On Mon, Nov 09, 2026 at 06:25:59PM +0300, Anton Midyukov wrote:
> >> 09.11.2026 16:06, Alexey Gladkov пишет:
> >>> Привет!
> >>>
> >>> Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
> >>> всех дистрибутивах.
> >>>
> >>> Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
> >>> перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
> >>> же поведение можно реализовать и в других реализациях init.
> >>>
> >>> В фиче есть guess, который проверяет предыдущий initrd и если он содержит
> >>> shutdown, то в новый образ фича будет добавлена.
> >>>
> >>> Как сайд эффект были добавлены несколько улучшений.
> >>>
> >>> 1. initrd-find пытается искать initrd (и это часто получается), который был
> >>> использован для загрузки.
> >>>
> >>> 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
> >>> Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
> >>>
> >>> Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
> >>> работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
> >>> других фичей, что-то ещё.
> >>>
> >>
> >> В live у меня не находит initrd:
> >> make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
> >>
> >> Что естественно, initrd c ядром лежит в /image/boot/
> >> Можно ли как-то путь для поиска подсказать?
> >
> > у initrd-find есть опция --boot-dir=DIR, но эта утилита вызывается из
> > make-initrd-shutdown-restore, который в свою очередь из сервиса
> > вызывается. То есть env не вариант.
> >
>
> Сервис можно заовверайдить. Переменную окружения можно задать в юните:
> Environment="VAR1=value1"
>
> Так что, по идее, как раз вариант.
Если это вариант, то можно добавить опцию и переменную окружения для
указания рута системы и тогда initrd-find будет искать каталоги не в / а
в /image.
Тебе такое подойдёт ?
--
Rgrds, legion
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [make-initrd] [PATCH v1 00/10] New shutdown feature
2026-11-09 16:14 ` Alexey Gladkov
@ 2026-11-09 16:16 ` Anton Midyukov
0 siblings, 0 replies; 31+ messages in thread
From: Anton Midyukov @ 2026-11-09 16:16 UTC (permalink / raw)
To: make-initrd
09.11.2026 19:14, Alexey Gladkov пишет:
> On Mon, Nov 09, 2026 at 06:57:31PM +0300, Anton Midyukov wrote:
>> 09.11.2026 18:47, Alexey Gladkov пишет:
>>> On Mon, Nov 09, 2026 at 06:25:59PM +0300, Anton Midyukov wrote:
>>>> 09.11.2026 16:06, Alexey Gladkov пишет:
>>>>> Привет!
>>>>>
>>>>> Вот теперь я более менее уверен, что код работает. End-to-end тест проходит на
>>>>> всех дистрибутивах.
>>>>>
>>>>> Патчсет добавляет фичу shutdown, которая позволяет размонтировать систему перед
>>>>> перезагрузкой. Реализация механизма предложена разработчиками systemd [1]. Такое
>>>>> же поведение можно реализовать и в других реализациях init.
>>>>>
>>>>> В фиче есть guess, который проверяет предыдущий initrd и если он содержит
>>>>> shutdown, то в новый образ фича будет добавлена.
>>>>>
>>>>> Как сайд эффект были добавлены несколько улучшений.
>>>>>
>>>>> 1. initrd-find пытается искать initrd (и это часто получается), который был
>>>>> использован для загрузки.
>>>>>
>>>>> 2. Во все утилиты (initrd-ls, initrd-extract) добавлена поддержка нативная UKI.
>>>>> Это позволяет просматривать и извлекать initrd даже из UKI без objcopy.
>>>>>
>>>>> Я не хочу допихивать дополнительные хуки в тот же бранч [2]. Если эта фича
>>>>> работает в целом, я её смерджу в master и уже далее буду дорабатывать хуки для
>>>>> других фичей, что-то ещё.
>>>>>
>>>>
>>>> В live у меня не находит initrd:
>>>> make-initrd-shutdown-restore[3248]: make-initrd-shutdown-restore: no initramfs found for kernel 6.18.62-6.18-alt1
>>>>
>>>> Что естественно, initrd c ядром лежит в /image/boot/
>>>> Можно ли как-то путь для поиска подсказать?
>>>
>>> у initrd-find есть опция --boot-dir=DIR, но эта утилита вызывается из
>>> make-initrd-shutdown-restore, который в свою очередь из сервиса
>>> вызывается. То есть env не вариант.
>>>
>>
>> Сервис можно заовверайдить. Переменную окружения можно задать в юните:
>> Environment="VAR1=value1"
>>
>> Так что, по идее, как раз вариант.
>
> Если это вариант, то можно добавить опцию и переменную окружения для
> указания рута системы и тогда initrd-find будет искать каталоги не в / а
> в /image.
>
> Тебе такое подойдёт ?
>
Думаю да. Я это проверю оперативно.
--
best regards, Anton Midyukov <antohami@altlinux.org>
^ permalink raw reply [flat|nested] 31+ messages in thread