fix(installer): identify live loop backing after dracut switch-root

This commit is contained in:
Codex
2026-10-13 05:34:39 +03:00
parent 2f48de0aa2
commit 43dc742e08
4 changed files with 361 additions and 17 deletions
+31 -11
View File
@@ -10,6 +10,7 @@ from __future__ import annotations
import argparse
from dataclasses import dataclass
import getpass
import fcntl
import json
import os
from pathlib import Path, PurePosixPath
@@ -17,6 +18,7 @@ import re
import shlex
import shutil
import stat
import struct
import subprocess
import sys
import tempfile
@@ -241,17 +243,12 @@ class SystemProbe:
# Mounted loops may hide the physical filesystem that stores their file.
for path, node in topo.nodes.items():
if node['type'] == 'loop' and path in topo.protected:
backing_file = Path('/sys/dev/block') / node['maj:min'] / 'loop/backing_file'
try:
backing = backing_file.read_text().strip()
except OSError as exc:
raise InstallError(f'Cannot inspect mounted loop backing file: {path}') from exc
backing = re.sub(r'\\([0-7]{3})', lambda m: chr(int(m[1], 8)), backing)
if not backing.startswith('/'):
backing = '/' + backing
source = self._source_for_path(backing, topo)
if not source:
raise InstallError(f'Unknown physical backing for mounted loop: {path}')
identity = self._loop_backing_identity(path, node['maj:min'])
sources = [p for p, n in topo.nodes.items() if n['maj:min'] == identity]
if len(sources) != 1:
raise InstallError(f'Unknown physical backing for mounted loop: {path} ({identity})')
source = sources[0]
topo.protect(source)
topo.link(path, source)
if not Path(payload).is_file():
raise InstallError(f'Prepared squashfs payload is not a regular file: {payload}')
@@ -262,6 +259,29 @@ class SystemProbe:
topo.protect(source)
return topo
@staticmethod
def _loop_backing_identity(path, expected_identity):
# LOOP_GET_STATUS64 reports the backing inode's filesystem device,
# independent of names which dracut switch_root may have relocated.
# lo_rdevice is nonzero when the backing inode is itself a block device.
try:
descriptor = os.open(path, os.O_RDONLY | os.O_CLOEXEC)
try:
info = os.fstat(descriptor)
identity = f'{os.major(info.st_rdev)}:{os.minor(info.st_rdev)}'
if not stat.S_ISBLK(info.st_mode) or identity != expected_identity:
raise InstallError(f'Mounted loop device identity changed: {path}')
status = fcntl.ioctl(descriptor, 0x4C05, bytes(232))
if not isinstance(status, bytes) or len(status) != 232:
raise InstallError(f'Malformed loop status: {path}')
filesystem_device, _, block_device = struct.unpack_from('=QQQ', status)
finally:
os.close(descriptor)
except (OSError, struct.error) as exc:
raise InstallError(f'Cannot inspect mounted loop backing identity: {path}') from exc
device = block_device or filesystem_device
return f'{os.major(device)}:{os.minor(device)}'
@staticmethod
def _mounts(raw):
try: