Explorer: Absturz beheben und LVM im Gast unterstuetzen
Beim Einbinden stuerzte der Explorer mit IndexError ab. Ursache: prepare() meldete nur dann einen Fehler, wenn gar kein Geraet gefunden wurde - nicht aber, wenn zwar Geraete da waren, sich davon aber keins einhaengen liess. Der Aufrufer griff dann blind auf mountpoints[0] zu. * prepare() meldet jetzt einen Fehler, sobald kein Dateisystem eingehaengt werden konnte, und nennt dabei die gefundenen Geraete samt Dateisystem- Typ - sonst raet man beim Suchen nur. * Der Explorer prueft die Liste zusaetzlich selbst ab. Der wahrscheinlichste Grund fuer "gefunden, aber nicht mountbar" ist LVM im Gast: dahinter liegt zunaechst nur ein LVM2_member. Das wird jetzt unterstuetzt - inklusive der heiklen Stelle: heisst die Volume-Group im Gast genauso wie eine auf dem Host (typisch 'pve'), waere nicht mehr eindeutig, welche gemeint ist. Erkannt wird das an gleichem Namen bei unterschiedlicher UUID; dann wird bewusst nichts aktiviert und der Grund angezeigt. Beim Aufraeumen wird die Gruppe vor dem Aushaengen des Geraets wieder deaktiviert. Ausserdem "udevadm settle" nach dem Einbinden - die Partitionsgeraete tauchen sonst manchmal erst nach dem Scan auf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f37a942fea
commit
da08a81aab
@@ -858,6 +858,11 @@ def _main(stdscr, args):
|
||||
try:
|
||||
_show_notes(stdscr, session.notes)
|
||||
mountpoints = sorted(set(session.mounts.values()))
|
||||
if not mountpoints:
|
||||
_show_notes(stdscr,
|
||||
["Es wurde kein Dateisystem eingehaengt."] + session.notes,
|
||||
"Nichts zu zeigen")
|
||||
return 3
|
||||
start = mountpoints[0]
|
||||
if len(mountpoints) > 1:
|
||||
entries = [(m, m) for m in mountpoints]
|
||||
|
||||
+90
-4
@@ -242,6 +242,8 @@ def cleanup_leftovers(verbose=False):
|
||||
run(["qemu-nbd", "--disconnect", value], check=False)
|
||||
elif kind == "lvm":
|
||||
run(["lvchange", "-an", value], check=False)
|
||||
elif kind == "vgchange":
|
||||
run(["vgchange", "-an", value], check=False)
|
||||
elif kind == "zfsclone":
|
||||
run(["zfs", "destroy", value], check=False)
|
||||
elif kind == "dir":
|
||||
@@ -303,9 +305,19 @@ class Session:
|
||||
except SnapfsError as exc:
|
||||
self.notes.append("%s: %s" % (volume.key, exc))
|
||||
log.warning("%s (%s): %s", volume.key, volume.volid, exc)
|
||||
if not self.mounts and not self.devices:
|
||||
raise SnapfsError("Kein Datentraeger des Snapshots konnte "
|
||||
"eingebunden werden.\n- " + "\n- ".join(self.notes))
|
||||
|
||||
if not self.mounts:
|
||||
# Ohne Mountpunkt gibt es nichts zu durchsuchen. Was gefunden wurde,
|
||||
# gehoert trotzdem in die Meldung - sonst raet man nur.
|
||||
details = list(self.notes)
|
||||
if self.devices:
|
||||
details.append("Gefunden wurden: %s"
|
||||
% ", ".join("%s (%s, %s)"
|
||||
% (d.path, d.fstype or "kein Dateisystem",
|
||||
d.human_size)
|
||||
for d in self.devices))
|
||||
raise SnapfsError("Kein Dateisystem des Snapshots liess sich einhaengen."
|
||||
+ ("\n- " + "\n- ".join(details) if details else ""))
|
||||
return self.mounts
|
||||
|
||||
def _prepare_volume(self, volume):
|
||||
@@ -322,7 +334,17 @@ class Session:
|
||||
return
|
||||
|
||||
device_path = self._map(info, volume)
|
||||
for device in self._scan(device_path, volume):
|
||||
run(["udevadm", "settle", "--timeout=10"], check=False)
|
||||
|
||||
devices = self._scan(device_path, volume)
|
||||
|
||||
# Viele Gaeste legen ihre Dateisysteme in LVM ab - dann liegt hinter der
|
||||
# Partition erst einmal nur ein "LVM2_member".
|
||||
for device in list(devices):
|
||||
if device.fstype == "LVM2_member":
|
||||
devices.extend(self._activate_guest_lvm(device, volume))
|
||||
|
||||
for device in devices:
|
||||
self.devices.append(device)
|
||||
if device.mountable:
|
||||
try:
|
||||
@@ -519,6 +541,68 @@ class Session:
|
||||
walk(data.get("blockdevices") or [])
|
||||
return found
|
||||
|
||||
# -- LVM im Gast ------------------------------------------------------
|
||||
|
||||
def _activate_guest_lvm(self, device, volume):
|
||||
"""Volume-Group des Gastes aktivieren und ihre Volumes zurueckgeben.
|
||||
|
||||
Heikel ist der Namenskonflikt: heisst die Gruppe im Gast genauso wie
|
||||
eine auf dem Host (typisch 'pve'), waere nicht mehr eindeutig, welche
|
||||
gemeint ist. In dem Fall wird bewusst nichts aktiviert.
|
||||
"""
|
||||
if not have("pvs") or not have("vgchange"):
|
||||
self.notes.append("%s enthaelt LVM, aber die LVM-Werkzeuge fehlen"
|
||||
% device.path)
|
||||
return []
|
||||
|
||||
groups = self._physical_volumes()
|
||||
mine = groups.get(os.path.realpath(device.path)) or groups.get(device.path)
|
||||
if not mine:
|
||||
self.notes.append("%s: keine Volume-Group gefunden" % device.path)
|
||||
return []
|
||||
name, uuid = mine
|
||||
|
||||
# Gleicher Name, andere UUID = Konflikt mit einer Gruppe des Hosts.
|
||||
for other_path, (other_name, other_uuid) in groups.items():
|
||||
if other_name == name and other_uuid != uuid:
|
||||
self.notes.append(
|
||||
"LVM-Gruppe '%s' aus dem Gast heisst genauso wie eine auf dem "
|
||||
"Host (%s) - sie wird nicht aktiviert, weil sonst nicht "
|
||||
"eindeutig waere, welche gemeint ist." % (name, other_path))
|
||||
return []
|
||||
|
||||
try:
|
||||
run(["vgchange", "-ay", "--readonly", name], timeout=60)
|
||||
except SnapfsError as exc:
|
||||
self.notes.append("Volume-Group '%s' nicht aktivierbar: %s" % (name, exc))
|
||||
return []
|
||||
|
||||
self._remember("vgchange", name)
|
||||
run(["udevadm", "settle", "--timeout=10"], check=False)
|
||||
log.info("LVM-Gruppe '%s' des Gastes aktiviert", name)
|
||||
|
||||
# Nach dem Aktivieren haengen die Logical Volumes unter dem Geraet.
|
||||
found = [d for d in self._lsblk(device.path, volume)
|
||||
if d.path != device.path and d.kind == "lvm"]
|
||||
if not found:
|
||||
self.notes.append("Volume-Group '%s' enthaelt keine lesbaren Volumes" % name)
|
||||
return found
|
||||
|
||||
@staticmethod
|
||||
def _physical_volumes():
|
||||
"""{PV-Pfad: (VG-Name, VG-UUID)} - auch fuer noch nicht aktive Gruppen."""
|
||||
try:
|
||||
output = run(["pvs", "--noheadings", "--nosuffix", "-o",
|
||||
"pv_name,vg_name,vg_uuid", "--separator", "|"], timeout=60)
|
||||
except SnapfsError:
|
||||
return {}
|
||||
result = {}
|
||||
for line in output.splitlines():
|
||||
parts = [p.strip() for p in line.strip().split("|")]
|
||||
if len(parts) >= 3 and parts[0] and parts[1]:
|
||||
result[parts[0]] = (parts[1], parts[2])
|
||||
return result
|
||||
|
||||
def mount(self, device):
|
||||
if device.path in self.mounts:
|
||||
return self.mounts[device.path]
|
||||
@@ -568,6 +652,8 @@ class Session:
|
||||
run(["qemu-nbd", "--disconnect", value], check=False)
|
||||
elif kind == "lvm":
|
||||
run(["lvchange", "-an", value], check=False)
|
||||
elif kind == "vgchange":
|
||||
run(["vgchange", "-an", value], check=False)
|
||||
elif kind == "zfsclone":
|
||||
run(["zfs", "destroy", value], check=False)
|
||||
elif kind == "dir" and os.path.isdir(value):
|
||||
|
||||
Reference in New Issue
Block a user