[FIX] script todo: réduction — détection de partition robuste (FSTYPE vide)
« Partition à réduire introuvable » sur une vraie VM : _qemu_root_part parsait lsblk en positionnel et EXIGEAIT 4 colonnes. Juste après le connect nbd, le FSTYPE n'est pas encore en cache -> colonne VIDE -> 3 tokens -> toutes les partitions étaient ignorées. (Le test initial passait car le FS avait eu le temps d'être détecté.) - lsblk -P (paires clé="valeur") : robuste aux colonnes vides ; on repère la partition par TYPE="part" et on sonde le FSTYPE via blkid si absent. - _qemu_nbd_connect attend l'APPARITION des sous-périphériques nbdNpM (jusqu'à ~15 s) avant de rendre la main. - partprobe silencieux (capture) -> plus de spam « Invalid argument during seek » pendant la réparation GPT. Validé sur image jetable au layout cloud (p1 root + p14 bios + p15 ESP), détection immédiate après connect : 25G -> 15G, GPT « No problems found », partitions préservées, fsck propre. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
545193d293
commit
c9ec5b98b9
1 changed files with 36 additions and 16 deletions
|
|
@ -1838,7 +1838,9 @@ class TODO:
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
print(t("Partition rewrite failed; reverting."))
|
print(t("Partition rewrite failed; reverting."))
|
||||||
return self._qemu_shrink_revert(bak, disk, changed=True)
|
return self._qemu_shrink_revert(bak, disk, changed=True)
|
||||||
subprocess.run(["sudo", "partprobe", dev], check=False)
|
subprocess.run(
|
||||||
|
["sudo", "partprobe", dev], check=False, capture_output=True
|
||||||
|
)
|
||||||
# Détache puis tronque le conteneur qcow2.
|
# Détache puis tronque le conteneur qcow2.
|
||||||
self._qemu_nbd_disconnect(dev)
|
self._qemu_nbd_disconnect(dev)
|
||||||
dev = None
|
dev = None
|
||||||
|
|
@ -1853,7 +1855,9 @@ class TODO:
|
||||||
dev = self._qemu_nbd_connect(disk)
|
dev = self._qemu_nbd_connect(disk)
|
||||||
if dev:
|
if dev:
|
||||||
subprocess.run(["sudo", "sgdisk", "-e", dev], check=False)
|
subprocess.run(["sudo", "sgdisk", "-e", dev], check=False)
|
||||||
subprocess.run(["sudo", "partprobe", dev], check=False)
|
subprocess.run(
|
||||||
|
["sudo", "partprobe", dev], check=False, capture_output=True
|
||||||
|
)
|
||||||
p2 = self._qemu_root_part(dev)[0]
|
p2 = self._qemu_root_part(dev)[0]
|
||||||
if p2:
|
if p2:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
|
|
@ -1880,7 +1884,9 @@ class TODO:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _qemu_nbd_connect(disk):
|
def _qemu_nbd_connect(disk):
|
||||||
"""Attache `disk` à un /dev/nbdN libre et renvoie le chemin, ou None."""
|
"""Attache `disk` à un /dev/nbdN libre et renvoie le chemin, ou None.
|
||||||
|
Attend que les sous-périphériques de partition (nbdNpM) APPARAISSENT
|
||||||
|
(sinon lsblk/resize2fs ne voient rien juste après le connect)."""
|
||||||
for i in range(16):
|
for i in range(16):
|
||||||
dev = f"/dev/nbd{i}"
|
dev = f"/dev/nbd{i}"
|
||||||
# /sys/block/nbdN/pid absent => device libre.
|
# /sys/block/nbdN/pid absent => device libre.
|
||||||
|
|
@ -1890,11 +1896,20 @@ class TODO:
|
||||||
["sudo", "qemu-nbd", "-c", dev, disk],
|
["sudo", "qemu-nbd", "-c", dev, disk],
|
||||||
capture_output=True, text=True,
|
capture_output=True, text=True,
|
||||||
).returncode
|
).returncode
|
||||||
if rc == 0:
|
if rc != 0:
|
||||||
|
continue
|
||||||
|
base = f"nbd{i}"
|
||||||
|
for _ in range(15):
|
||||||
|
subprocess.run(
|
||||||
|
["sudo", "partprobe", dev], check=False, capture_output=True
|
||||||
|
)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
subprocess.run(["sudo", "partprobe", dev], check=False)
|
if any(
|
||||||
time.sleep(1)
|
os.path.exists(f"/sys/class/block/{base}p{n}")
|
||||||
return dev
|
for n in range(1, 32)
|
||||||
|
):
|
||||||
|
break
|
||||||
|
return dev
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1905,25 +1920,29 @@ class TODO:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _qemu_root_part(dev):
|
def _qemu_root_part(dev):
|
||||||
"""(partition la plus grosse, secteur de début, type FS) du disque nbd.
|
"""(partition la plus grosse, secteur de début, type FS) du disque nbd.
|
||||||
(None, 0, '') si introuvable."""
|
(None, 0, '') si introuvable. Format lsblk -P (paires) : robuste aux
|
||||||
|
colonnes VIDES — juste après le connect, FSTYPE peut être vide, et un
|
||||||
|
parsing positionnel décalait/ignorait alors toutes les partitions."""
|
||||||
|
import re
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = subprocess.run(
|
res = subprocess.run(
|
||||||
["lsblk", "-bnro", "NAME,SIZE,FSTYPE,TYPE", dev],
|
["lsblk", "-Pbno", "NAME,SIZE,TYPE,FSTYPE", dev],
|
||||||
capture_output=True, text=True, timeout=30,
|
capture_output=True, text=True, timeout=30,
|
||||||
)
|
)
|
||||||
except (OSError, subprocess.SubprocessError):
|
except (OSError, subprocess.SubprocessError):
|
||||||
return None, 0, ""
|
return None, 0, ""
|
||||||
best, best_sz, best_fs = None, -1, ""
|
best, best_sz, best_fs = None, -1, ""
|
||||||
for line in res.stdout.splitlines():
|
for line in res.stdout.splitlines():
|
||||||
cols = line.split()
|
d = dict(re.findall(r'(\w+)="([^"]*)"', line))
|
||||||
if len(cols) < 4 or cols[-1] != "part":
|
if d.get("TYPE") != "part":
|
||||||
continue
|
continue
|
||||||
name, size = cols[0], int(cols[1])
|
try:
|
||||||
fstype = cols[2] if len(cols) >= 4 and cols[2] != "part" else ""
|
size = int(d.get("SIZE") or 0)
|
||||||
if len(cols) == 3: # pas de FSTYPE -> décalage
|
except ValueError:
|
||||||
fstype = ""
|
size = 0
|
||||||
if size > best_sz:
|
if size > best_sz:
|
||||||
best, best_sz, best_fs = name, size, fstype
|
best, best_sz, best_fs = d.get("NAME"), size, d.get("FSTYPE", "")
|
||||||
if not best:
|
if not best:
|
||||||
return None, 0, ""
|
return None, 0, ""
|
||||||
part = f"/dev/{best}"
|
part = f"/dev/{best}"
|
||||||
|
|
@ -1934,6 +1953,7 @@ class TODO:
|
||||||
except OSError:
|
except OSError:
|
||||||
start = 0
|
start = 0
|
||||||
if not best_fs:
|
if not best_fs:
|
||||||
|
# FSTYPE pas encore en cache : sonder directement avec blkid.
|
||||||
best_fs = subprocess.run(
|
best_fs = subprocess.run(
|
||||||
["sudo", "blkid", "-o", "value", "-s", "TYPE", part],
|
["sudo", "blkid", "-o", "value", "-s", "TYPE", part],
|
||||||
capture_output=True, text=True,
|
capture_output=True, text=True,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue