Add a libvirt/KVM test environment and fix the gcc-12 warning it found
The VM harness (tests/vm/) builds and exercises the tools on real Debian and Alpine guests, driving the serial console (no sshd/cloud-init dependency). Results on Debian 12 (glibc 2.36, gcc 12.2): 12/12 checks pass, 0 warnings with -Wall -Wextra, including a real 128M -> 256M resize and the rollback save/restore/reject paths. Results on Alpine 3.20 (musl 1.2.5): the build cannot start. configure stops at "reiserfsprogs requires a method to add printf modifiers" because reiserfscore/prints.c depends on glibc's register_printf_specifier(3) and <printf.h>, which musl does not provide. This is pre-existing and out of scope here; measured at 341 use sites across 20+ files, all routed through reiserfs_warning()/reiserfs_info(). Documented in tests/vm/RESULTS.md. Fix the one real defect the harness surfaced: fsck/semantic_check.c: check_check_regular_file() is called with NULL for new_ih (line 466), so the block that would fill *new_ih is unreachable and check_semantic_pass() never returns RELOCATED through that path. The "case RELOCATED:" arm still reads relocated_ih.ih_key, which gcc 12.2 reports as "may be used uninitialized" (-Wmaybe-uninitialized); gcc 16 does not, because it proves the path dead. Zero-initialise the struct and say why. Verified: 0 warnings on gcc 12.2 after the change, and the Debian battery stays at 12/12.
This commit is contained in:
1 parent
293e6a7280
commit
5a7382185b
8 files changed
+628
-1
No files matched your search
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Roda um comando no guest atraves do console serial do libvirt.
|
||||
|
||||
O console serial e o unico canal garantido nas imagens cloud minimas: nao
|
||||
depende de sshd, de cloud-init nem de rede. Este script sincroniza com o
|
||||
prompt, autentica (root/rfstest) e executa o comando, lendo a saida do pty
|
||||
em blocos pequenos para evitar a sobreposicao de eco que ocorre em comandos
|
||||
longos.
|
||||
|
||||
Uso:
|
||||
guestcmd.py <dominio> '<comando>'
|
||||
guestcmd.py <dominio> -f <arquivo> # executa um script local
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
USER = "root"
|
||||
PASSWORD = "rfstest"
|
||||
|
||||
|
||||
def console_pty(domain):
|
||||
xml = subprocess.run(["virsh", "dumpxml", domain],
|
||||
capture_output=True, text=True).stdout
|
||||
m = (re.search(r"<console[^>]*>\s*<source path='([^']+)'", xml)
|
||||
or re.search(r"<serial[^>]*>\s*<source path='([^']+)'", xml))
|
||||
if not m:
|
||||
sys.exit(f"console serial nao encontrada para {domain}")
|
||||
return m.group(1)
|
||||
|
||||
|
||||
class Console:
|
||||
def __init__(self, path):
|
||||
self.fd = os.open(path, os.O_RDWR | os.O_NOCTTY)
|
||||
|
||||
def send(self, s):
|
||||
os.write(self.fd, s.encode())
|
||||
|
||||
def drain(self, seconds):
|
||||
buf = b""
|
||||
end = time.time() + seconds
|
||||
while time.time() < end:
|
||||
r, _, _ = select.select([self.fd], [], [], 0.3)
|
||||
if not r:
|
||||
continue
|
||||
try:
|
||||
chunk = os.read(self.fd, 65536)
|
||||
except OSError:
|
||||
break
|
||||
if not chunk:
|
||||
break
|
||||
buf += chunk
|
||||
return buf
|
||||
|
||||
def login(self):
|
||||
self.send("\n")
|
||||
time.sleep(1.0)
|
||||
snap = self.drain(2.5).decode(errors="replace").lower()
|
||||
if "login:" not in snap:
|
||||
return # ja autenticado
|
||||
self.send(USER + "\n")
|
||||
self.drain(3.0)
|
||||
self.send(PASSWORD + "\n")
|
||||
time.sleep(2.0)
|
||||
self.drain(2.0)
|
||||
|
||||
def run(self, command, timeout=120.0):
|
||||
"""Executa e devolve a saida, sincronizando por marcador no pty."""
|
||||
out_path = "/tmp/.guestcmd.out"
|
||||
self.send(f"rm -f {out_path}; ( {command} ) > {out_path} 2>&1; "
|
||||
f"echo __GC_DONE__\n")
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if b"__GC_DONE__" in self.drain(2.0):
|
||||
break
|
||||
else:
|
||||
return "(timeout)\n"
|
||||
|
||||
# tamanho em bytes
|
||||
self.send(f"wc -c < {out_path}\n")
|
||||
time.sleep(1.2)
|
||||
raw = self.drain(2.0).decode(errors="replace")
|
||||
nums = re.findall(r"\d+", raw)
|
||||
size = int(nums[-1]) if nums else 0
|
||||
|
||||
# ler em blocos base64 (evita eco/sobreposicao)
|
||||
data = b""
|
||||
while len(data) < size:
|
||||
self.send(f"dd if={out_path} bs=1 skip={len(data)} count=150 "
|
||||
f"2>/dev/null | base64 -w0; echo\n")
|
||||
time.sleep(0.8)
|
||||
enc = self.drain(2.0).decode(errors="replace")
|
||||
token = "".join(re.findall(r"[A-Za-z0-9+/=]{4,}", enc))
|
||||
if not token:
|
||||
break
|
||||
try:
|
||||
data += base64.b64decode(token + "=" * (-len(token) % 4))
|
||||
except Exception:
|
||||
break
|
||||
return data.decode(errors="replace")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
sys.exit(__doc__)
|
||||
domain = sys.argv[1]
|
||||
console = Console(console_pty(domain))
|
||||
console.login()
|
||||
|
||||
if sys.argv[2] == "-f":
|
||||
with open(sys.argv[3]) as fh:
|
||||
script = fh.read().replace("'", "'\\''")
|
||||
command = f"sh -c '{script}'"
|
||||
else:
|
||||
command = sys.argv[2]
|
||||
|
||||
sys.stdout.write(console.run(command))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user