fix(installer): resolve nested loop ancestry to a fixed point
This commit is contained in:
+34
-10
@@ -74,6 +74,7 @@ class Topology:
|
||||
def __init__(self):
|
||||
self.nodes = {}
|
||||
self.edges = {}
|
||||
self.parents = {}
|
||||
self.protected = set()
|
||||
|
||||
@classmethod
|
||||
@@ -112,6 +113,7 @@ class Topology:
|
||||
raise InstallError('Inconsistent duplicate lsblk node')
|
||||
result.nodes[path] = node
|
||||
result.edges.setdefault(path, set())
|
||||
result.parents.setdefault(path, set())
|
||||
if any(m for m in mounts):
|
||||
result.protected.add(path)
|
||||
pkname = node.get('pkname')
|
||||
@@ -168,6 +170,17 @@ class Topology:
|
||||
def link(self, child, parent):
|
||||
if child == parent or child not in self.nodes or parent not in self.nodes:
|
||||
raise InstallError('Unresolved or cyclic block-device parent')
|
||||
# Backing links discovered after lsblk must obey the same directed
|
||||
# ancestry rule. An undirected shared disk is not itself a cycle.
|
||||
seen, pending = set(), [parent]
|
||||
while pending:
|
||||
current = pending.pop()
|
||||
if current == child:
|
||||
raise InstallError('Cyclic block-device backing ancestry')
|
||||
if current not in seen:
|
||||
seen.add(current)
|
||||
pending.extend(self.parents[current] - seen)
|
||||
self.parents[child].add(parent)
|
||||
self.edges[child].add(parent)
|
||||
self.edges[parent].add(child)
|
||||
|
||||
@@ -240,16 +253,6 @@ class SystemProbe:
|
||||
source = self._block_source(mount, topo)
|
||||
if source:
|
||||
topo.protect(source)
|
||||
# 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:
|
||||
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}')
|
||||
source = self._source_for_path(self.resolve(payload), topo)
|
||||
@@ -257,6 +260,27 @@ class SystemProbe:
|
||||
# tmpfs/overlay payload location cannot establish live-medium ancestry.
|
||||
raise InstallError('Cannot identify the payload backing block device; use --payload on the mounted live medium')
|
||||
topo.protect(source)
|
||||
# Loops can hide further loops, including ancestors of a mounted
|
||||
# partition or device mapper. Resolve protected components to a fixed
|
||||
# point, independent of lsblk ordering and payload discovery order.
|
||||
inspected = set()
|
||||
while True:
|
||||
protected_devices = set()
|
||||
for path in topo.protected:
|
||||
protected_devices.update(topo.component(path))
|
||||
pending = [path for path in topo.nodes if path in protected_devices
|
||||
and topo.nodes[path]['type'] == 'loop' and path not in inspected]
|
||||
if not pending:
|
||||
break
|
||||
for path in pending:
|
||||
identity = self._loop_backing_identity(path, topo.nodes[path]['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.link(path, source)
|
||||
topo.protect(source)
|
||||
inspected.add(path)
|
||||
return topo
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user