Skip to content

Commit c6fb375

Browse files
committed
fix(smbios): wait for qemu-nbd to release the qcow2 before launching qemu
InjectSMBIOS mounts the per-VM OpenCore via qemu-nbd; qemu-nbd --disconnect frees the device asynchronously and the holding process is NOT /sys/block/nbdX/pid, so the qemu launch raced in and failed with 'Failed to get shared write lock'. disconnectNBD now polls /proc until no process holds the qcow2 open. Also wait for the nbd partition scan before mounting (was an intermittent 'config.plist not found'). Verified on testbed: two --random-smbios clones launch cleanly with distinct serials.
1 parent 7516887 commit c6fb375

1 file changed

Lines changed: 42 additions & 3 deletions

File tree

qemu/inject.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ func InjectSMBIOS(ocPath string, s SMBIOS) error {
2424
if out, cerr := exec.Command("qemu-nbd", "--connect="+nbd, "-f", "qcow2", ocPath).CombinedOutput(); cerr != nil {
2525
return fmt.Errorf("qemu-nbd connect %s: %v: %s", nbd, cerr, out)
2626
}
27-
defer func() { _ = exec.Command("qemu-nbd", "--disconnect", nbd).Run() }()
28-
time.Sleep(2 * time.Second)
29-
_ = exec.Command("partprobe", nbd).Run()
27+
defer disconnectNBD(nbd, ocPath)
28+
waitForPart(nbd)
3029
mnt, err := os.MkdirTemp("", "oc-efi-")
3130
if err != nil {
3231
return err
@@ -46,6 +45,46 @@ func InjectSMBIOS(ocPath string, s SMBIOS) error {
4645
return patchPlist(filepath.Join(mnt, "EFI", "OC", "config.plist"), s)
4746
}
4847

48+
// waitForPart blocks until the kernel has scanned the qcow2's partition table (nbd partition
49+
// creation is asynchronous after --connect), so the mount below sees nbdXp1.
50+
func waitForPart(nbd string) {
51+
for range 50 {
52+
if _, err := os.Stat(nbd + "p1"); err == nil {
53+
return
54+
}
55+
_ = exec.Command("partprobe", nbd).Run()
56+
time.Sleep(100 * time.Millisecond)
57+
}
58+
}
59+
60+
// disconnectNBD tears down the qemu-nbd mapping and waits until the qcow2 is no longer held
61+
// open by any process. qemu-nbd --disconnect releases the device asynchronously and the server
62+
// pid is not /sys/block/nbdX/pid, so returning early would let the qemu launch race in and fail
63+
// with "Failed to get shared write lock".
64+
func disconnectNBD(nbd, ocPath string) {
65+
_ = exec.Command("qemu-nbd", "--disconnect", nbd).Run()
66+
for range 100 {
67+
if !fileHeld(ocPath) {
68+
return
69+
}
70+
time.Sleep(100 * time.Millisecond)
71+
}
72+
}
73+
74+
func fileHeld(path string) bool {
75+
abs, err := filepath.Abs(path)
76+
if err != nil {
77+
abs = path
78+
}
79+
fds, _ := filepath.Glob("/proc/[0-9]*/fd/*")
80+
for _, fd := range fds {
81+
if tgt, err := os.Readlink(fd); err == nil && tgt == abs {
82+
return true
83+
}
84+
}
85+
return false
86+
}
87+
4988
func freeNBD() (string, error) {
5089
for i := range 16 {
5190
if _, err := os.Stat(fmt.Sprintf("/dev/nbd%d", i)); err != nil {

0 commit comments

Comments
 (0)