[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:
|
||||
print(t("Partition rewrite failed; reverting."))
|
||||
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.
|
||||
self._qemu_nbd_disconnect(dev)
|
||||
dev = None
|
||||
|
|
@ -1853,7 +1855,9 @@ class TODO:
|
|||
dev = self._qemu_nbd_connect(disk)
|
||||
if dev:
|
||||
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]
|
||||
if p2:
|
||||
subprocess.run(
|
||||
|
|
@ -1880,7 +1884,9 @@ class TODO:
|
|||
|
||||
@staticmethod
|
||||
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):
|
||||
dev = f"/dev/nbd{i}"
|
||||
# /sys/block/nbdN/pid absent => device libre.
|
||||
|
|
@ -1890,11 +1896,20 @@ class TODO:
|
|||
["sudo", "qemu-nbd", "-c", dev, disk],
|
||||
capture_output=True, text=True,
|
||||
).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)
|
||||
subprocess.run(["sudo", "partprobe", dev], check=False)
|
||||
time.sleep(1)
|
||||
return dev
|
||||
if any(
|
||||
os.path.exists(f"/sys/class/block/{base}p{n}")
|
||||
for n in range(1, 32)
|
||||
):
|
||||
break
|
||||
return dev
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1905,25 +1920,29 @@ class TODO:
|
|||
@staticmethod
|
||||
def _qemu_root_part(dev):
|
||||
"""(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:
|
||||
res = subprocess.run(
|
||||
["lsblk", "-bnro", "NAME,SIZE,FSTYPE,TYPE", dev],
|
||||
["lsblk", "-Pbno", "NAME,SIZE,TYPE,FSTYPE", dev],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None, 0, ""
|
||||
best, best_sz, best_fs = None, -1, ""
|
||||
for line in res.stdout.splitlines():
|
||||
cols = line.split()
|
||||
if len(cols) < 4 or cols[-1] != "part":
|
||||
d = dict(re.findall(r'(\w+)="([^"]*)"', line))
|
||||
if d.get("TYPE") != "part":
|
||||
continue
|
||||
name, size = cols[0], int(cols[1])
|
||||
fstype = cols[2] if len(cols) >= 4 and cols[2] != "part" else ""
|
||||
if len(cols) == 3: # pas de FSTYPE -> décalage
|
||||
fstype = ""
|
||||
try:
|
||||
size = int(d.get("SIZE") or 0)
|
||||
except ValueError:
|
||||
size = 0
|
||||
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:
|
||||
return None, 0, ""
|
||||
part = f"/dev/{best}"
|
||||
|
|
@ -1934,6 +1953,7 @@ class TODO:
|
|||
except OSError:
|
||||
start = 0
|
||||
if not best_fs:
|
||||
# FSTYPE pas encore en cache : sonder directement avec blkid.
|
||||
best_fs = subprocess.run(
|
||||
["sudo", "blkid", "-o", "value", "-s", "TYPE", part],
|
||||
capture_output=True, text=True,
|
||||
|
|
|
|||
Loading…
Reference in a new issue