Skip to content

Commit 9496293

Browse files
committed
review: whole-repo round — net rollback, stray-helper reap, NBD busy check, VNC password rules; pin cocoon 51ff88b
provisionNet rolls its netns back through a shared toggleNet; createVM reaps stray qemu/qemu-nbd helpers by cmdline before relaunch; the NBD busy check reads /proc cmdlines through a testable seam; VNC passwords reject whitespace; clone inherits the source's network mode unless --net is set; OpenStore takes the caller's context. cocoon is pinned to its merged head (ReflinkCopy takes a context, Network.Delete takes one id, the image JSON namespace comes from core.ImageJSONNamespace).
1 parent 74cbb73 commit 9496293

21 files changed

Lines changed: 208 additions & 142 deletions

cmd/image/handler.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ type Handler struct{}
2323
func NewHandler() *Handler { return &Handler{} }
2424

2525
func (h *Handler) Pull(cmd *cobra.Command, args []string) error {
26-
ctx, s, err := home.OpenStore(cmd)
26+
ctx := cliutil.CommandContext(cmd)
27+
s, err := home.OpenStore(ctx, cmd)
2728
if err != nil {
2829
return err
2930
}
@@ -68,7 +69,8 @@ func (h *Handler) Pull(cmd *cobra.Command, args []string) error {
6869
}
6970

7071
func (h *Handler) List(cmd *cobra.Command, _ []string) error {
71-
ctx, s, err := home.OpenStore(cmd)
72+
ctx := cliutil.CommandContext(cmd)
73+
s, err := home.OpenStore(ctx, cmd)
7274
if err != nil {
7375
return err
7476
}
@@ -77,17 +79,18 @@ func (h *Handler) List(cmd *cobra.Command, _ []string) error {
7779
return err
7880
}
7981
return cliutil.OutputFormatted(cmd, imgs, func(w *tabwriter.Writer) {
80-
fmt.Fprintln(w, "NAME\tTYPE\tSIZE\tDIGEST\tCREATED") //nolint:errcheck
82+
fmt.Fprintln(w, "NAME\tTYPE\tSIZE\tDIGEST\tCREATED") //nolint:errcheck // the tabwriter flush reports the write error
8183
for _, img := range imgs {
82-
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", //nolint:errcheck
84+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", //nolint:errcheck // the tabwriter flush reports the write error
8385
img.Name, img.Type, cliutil.FormatSize(img.Size),
8486
shortDigest(img.ID), img.CreatedAt.Local().Format(time.DateTime))
8587
}
8688
})
8789
}
8890

8991
func (h *Handler) Inspect(cmd *cobra.Command, args []string) error {
90-
ctx, s, err := home.OpenStore(cmd)
92+
ctx := cliutil.CommandContext(cmd)
93+
s, err := home.OpenStore(ctx, cmd)
9194
if err != nil {
9295
return err
9396
}
@@ -102,7 +105,8 @@ func (h *Handler) Inspect(cmd *cobra.Command, args []string) error {
102105
}
103106

104107
func (h *Handler) RM(cmd *cobra.Command, args []string) error {
105-
ctx, s, err := home.OpenStore(cmd)
108+
ctx := cliutil.CommandContext(cmd)
109+
s, err := home.OpenStore(ctx, cmd)
106110
if err != nil {
107111
return err
108112
}

cmd/vm/clone.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ func (h *Handler) Clone(cmd *cobra.Command, args []string) error {
4141
}
4242

4343
func (h *Handler) clone(cmd *cobra.Command, srcRec *record, name string) (retErr error) {
44-
netMode, _ := cmd.Flags().GetString("net")
44+
netMode := srcRec.NetMode
45+
if cmd.Flags().Changed("net") {
46+
netMode, _ = cmd.Flags().GetString("net")
47+
}
4548
vnc, _ := cmd.Flags().GetInt("vnc")
4649
vncPass, _ := cmd.Flags().GetString("vnc-password")
4750
if err := requireCNIVNCPassword(netMode == netCNI, vnc, vncPass); err != nil {
@@ -86,7 +89,7 @@ func (h *Handler) clone(cmd *cobra.Command, srcRec *record, name string) (retErr
8689
if err != nil {
8790
return err
8891
}
89-
copied, err := copyDataDisks(dir, srcRec.DataDisks)
92+
copied, err := copyDataDisks(ctx, dir, srcRec.DataDisks)
9093
if err != nil {
9194
return err
9295
}

cmd/vm/commands.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func Command(h *Handler) *cobra.Command {
3333
RunE: h.Start,
3434
}
3535
startCmd.Flags().Int("vnc", -1, "VNC display number for this start only (n => port 590n); omit to keep VNC off")
36-
startCmd.Flags().String("vnc-password", "", "VNC password for this start (≤8 chars, QEMU password auth)")
36+
startCmd.Flags().String("vnc-password", "", "VNC password for this start (≤8 bytes, QEMU password auth)")
3737

3838
stopCmd := &cobra.Command{
3939
Use: "stop VM [VM...]",
@@ -89,11 +89,11 @@ func Command(h *Handler) *cobra.Command {
8989
}
9090
restoreCmd.Flags().String("tag", "", "snapshot tag to restore (default: newest)")
9191
restoreCmd.Flags().Bool("force", false, "stop the VM, restore, then relaunch if it was running")
92-
restoreCmd.Flags().String("vnc-password", "", "VNC password for the relaunch of a running VM whose display is password-gated (≤8 chars)")
92+
restoreCmd.Flags().String("vnc-password", "", "VNC password for the relaunch of a running VM whose display is password-gated (≤8 bytes)")
9393

9494
cloneCmd := &cobra.Command{
9595
Use: "clone SRC",
96-
Short: "Clone a stopped VM: fresh CoW overlay on the shared base + a unique Apple identity + its own TAP",
96+
Short: "Clone a stopped VM: fresh CoW overlay on the shared base + a unique Apple identity + its own network endpoint (--net inherited from the source)",
9797
Args: cobra.ExactArgs(1),
9898
RunE: h.Clone,
9999
}
@@ -119,7 +119,7 @@ func addVMFlags(cmd *cobra.Command) {
119119
cmd.Flags().String("ovmf-code", "", "OVMF_CODE firmware (default: <state-dir>/firmware/OVMF_CODE.fd)")
120120
cmd.Flags().String("ovmf-vars", "", "OVMF_VARS template, copied per-VM (default: <state-dir>/firmware/OVMF_VARS.fd)")
121121
cmd.Flags().Bool("random-smbios", false, "inject a unique Apple SMBIOS identity per VM (serial/MLB/UUID/ROM)")
122-
cmd.Flags().String("vnc-password", "", "set a VNC password (≤8 chars) so macOS Screen Sharing can connect (QEMU password auth)")
122+
cmd.Flags().String("vnc-password", "", "set a VNC password (≤8 bytes) so macOS Screen Sharing can connect (QEMU password auth)")
123123
cmd.Flags().String("net", "user", "network mode: user (SLIRP + --ssh-port hostfwd) | tap | cni | bridge (cocoon auto-creates the TAP, Linux only)")
124124
cmd.Flags().String("tap", "", "pre-created host TAP ifname (skips auto-create; e.g. an existing bridge port / cocoon CNI tap)")
125125
cmd.Flags().String("bridge", "", "existing Linux bridge to enslave the auto-created TAP to (--net tap|bridge)")

cmd/vm/datadisk.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ func createDataDisks(ctx context.Context, dir string, specs []types.DataDiskSpec
113113
return paths, nil
114114
}
115115

116-
func copyDataDisks(dir string, src []string) ([]string, error) {
116+
func copyDataDisks(ctx context.Context, dir string, src []string) ([]string, error) {
117117
paths := make([]string, 0, len(src))
118118
for _, srcPath := range src {
119119
dst := filepath.Join(dir, filepath.Base(srcPath))
120-
if err := utils.ReflinkCopy(dst, srcPath, utils.Sync); err != nil {
120+
if err := utils.ReflinkCopy(ctx, dst, srcPath, utils.Sync); err != nil {
121121
return nil, fmt.Errorf("copy data disk %s: %w", filepath.Base(srcPath), err)
122122
}
123123
paths = append(paths, dst)

cmd/vm/datadisk_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ func TestParseDataDisks(t *testing.T) {
3636
{name: "directio rejected on macOS", raw: []string{"size=1G,directio=on"}, wantErr: true},
3737
{name: "over the four-disk cap", raw: []string{"size=1G", "size=1G", "size=1G", "size=1G", "size=1G"}, wantErr: true},
3838

39-
// clone reserves the copied disks' names: collisions error and reserved count against the cap
4039
{name: "collides with a reserved name", raw: []string{"name=data1,size=1G"}, reserved: []string{"data1"}, wantErr: true},
4140
{name: "auto name skips reserved", raw: []string{"size=1G"}, reserved: []string{"data1"}, wantNames: []string{"data2"}, wantSizes: []int64{gib}},
4241
{name: "reserved fills the cap", raw: []string{"size=1G", "size=1G"}, reserved: []string{"data1", "data2", "data3"}, wantErr: true},
@@ -69,7 +68,6 @@ func TestParseDataDisks(t *testing.T) {
6968
}
7069

7170
func TestDataDiskNameRoundTrip(t *testing.T) {
72-
// clone recovers reserved names from the copied disks' paths, so this must invert dataDiskPath
7371
for _, name := range []string{"data1", "logs", "a-b_c"} {
7472
if got := dataDiskName(dataDiskPath("/vm/dir", name)); got != name {
7573
t.Errorf("dataDiskName(dataDiskPath(%q)) = %q", name, got)

cmd/vm/handler_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ func TestStartAlreadyRunningIsIdempotent(t *testing.T) {
4141
t.Fatal(err)
4242
}
4343

44-
// A real process whose argv0+args satisfy isRunning without qemu/KVM.
4544
fakeQEMU := filepath.Join(t.TempDir(), qemuBinary)
4645
if err := os.Symlink("/bin/sh", fakeQEMU); err != nil {
4746
t.Fatal(err)
@@ -155,9 +154,7 @@ func TestImagesToSnapshot(t *testing.T) {
155154
rec *record
156155
want []string
157156
}{
158-
// raw .fd NVRAM can't hold internal snapshots, so only the disk is captured
159157
{"raw nvram captures disk only", &record{Disk: "/v/disk.qcow2", OVMFVars: "/v/OVMF_VARS.fd"}, []string{"/v/disk.qcow2"}},
160-
// a qcow2 NVRAM rolls back too
161158
{"qcow2 nvram captures both", &record{Disk: "/v/disk.qcow2", OVMFVars: "/v/OVMF_VARS.qcow2"}, []string{"/v/disk.qcow2", "/v/OVMF_VARS.qcow2"}},
162159
}
163160
for _, tt := range tests {
@@ -169,7 +166,6 @@ func TestImagesToSnapshot(t *testing.T) {
169166
}
170167
}
171168

172-
// auto-create/CNI/bridge need Linux + CAP_NET_ADMIN; they are smoke-tested on the testbed.
173169
func TestPrepareNetNoProvision(t *testing.T) {
174170
tests := []struct {
175171
name string

cmd/vm/lifecycle.go

Lines changed: 37 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,11 @@ import (
1919
)
2020

2121
func (h *Handler) Create(cmd *cobra.Command, args []string) error {
22-
name := requestedVMName(cmd, "macos-"+time.Now().Format("20060102-150405"))
23-
dir, err := home.VMDir(cmd, name)
24-
if err != nil {
25-
return err
26-
}
27-
return withVMLock(cliutil.CommandContext(cmd), dir, func() error {
28-
r, err := h.create(cmd, args[0], name)
29-
if err != nil {
30-
return err
31-
}
32-
fmt.Println(r.Name)
33-
return nil
34-
})
22+
return h.createVM(cmd, args[0], false)
3523
}
3624

3725
func (h *Handler) Run(cmd *cobra.Command, args []string) error {
38-
name := requestedVMName(cmd, "macos-"+time.Now().Format("20060102-150405"))
39-
dir, err := home.VMDir(cmd, name)
40-
if err != nil {
41-
return err
42-
}
43-
return withVMLock(cliutil.CommandContext(cmd), dir, func() error {
44-
r, err := h.create(cmd, args[0], name)
45-
if err != nil {
46-
return err
47-
}
48-
if err := h.launch(cmd, dir, r); err != nil {
49-
return errors.Join(err, cleanupFailedVM(cmd, dir, r))
50-
}
51-
fmt.Printf("%s (pid %d)\n", r.Name, r.PID)
52-
return nil
53-
})
26+
return h.createVM(cmd, args[0], true)
5427
}
5528

5629
func (h *Handler) Start(cmd *cobra.Command, args []string) error {
@@ -89,7 +62,7 @@ func (h *Handler) Start(cmd *cobra.Command, args []string) error {
8962
if err := h.launch(cmd, dir, r); err != nil {
9063
return err
9164
}
92-
unquiesceNet(cmd, r)
65+
toggleNet(cmd, r, true)
9366
fmt.Printf("%s (pid %d)\n", n, r.PID)
9467
return nil
9568
}); err != nil {
@@ -116,7 +89,7 @@ func (h *Handler) Stop(cmd *cobra.Command, args []string) error {
11689
return err
11790
}
11891
terminate(ctx, r, grace)
119-
quiesceNet(cmd, r)
92+
toggleNet(cmd, r, false)
12093
stopVNCProxy(ctx, dir)
12194
r.PID, r.VNCDisp, r.VNCPass, r.VNCPassSet = 0, -1, "", false // VNC is launch-scoped: gone with the qemu it belonged to
12295
return saveRec(dir, r)
@@ -155,10 +128,7 @@ func (h *Handler) RM(cmd *cobra.Command, args []string) error {
155128
} else {
156129
cleanupCtx, cancel := context.WithTimeout(context.Background(), vmCleanupTimeout)
157130
defer cancel()
158-
if cleanupErr := procutil.TerminateByCmdline(cleanupCtx, qemuBinary, dir, 0); cleanupErr != nil {
159-
return cleanupErr
160-
}
161-
if cleanupErr := procutil.TerminateByCmdline(cleanupCtx, "qemu-nbd", dir, time.Second); cleanupErr != nil {
131+
if cleanupErr := reapStrayHelpers(cleanupCtx, dir); cleanupErr != nil {
162132
return cleanupErr
163133
}
164134
}
@@ -174,6 +144,29 @@ func (h *Handler) RM(cmd *cobra.Command, args []string) error {
174144
return nil
175145
}
176146

147+
func (h *Handler) createVM(cmd *cobra.Command, image string, launch bool) error {
148+
name := requestedVMName(cmd, "macos-"+time.Now().Format("20060102-150405"))
149+
dir, err := home.VMDir(cmd, name)
150+
if err != nil {
151+
return err
152+
}
153+
return withVMLock(cliutil.CommandContext(cmd), dir, func() error {
154+
r, err := h.create(cmd, image, name)
155+
if err != nil {
156+
return err
157+
}
158+
if !launch {
159+
fmt.Println(r.Name)
160+
return nil
161+
}
162+
if err := h.launch(cmd, dir, r); err != nil {
163+
return errors.Join(err, cleanupFailedVM(cmd, dir, r))
164+
}
165+
fmt.Printf("%s (pid %d)\n", r.Name, r.PID)
166+
return nil
167+
})
168+
}
169+
177170
func (h *Handler) create(cmd *cobra.Command, image, name string) (r *record, retErr error) {
178171
rawDisks, _ := cmd.Flags().GetStringArray("data-disk")
179172
diskSpecs, err := parseDataDisks(rawDisks, nil) // fail fast before any scaffolding
@@ -330,10 +323,7 @@ func cleanupFailedVM(cmd *cobra.Command, dir string, r *record) error {
330323
errs = append(errs, err)
331324
}
332325
}
333-
if err := procutil.TerminateByCmdline(ctx, qemuBinary, dir, 0); err != nil {
334-
errs = append(errs, err)
335-
}
336-
if err := procutil.TerminateByCmdline(ctx, "qemu-nbd", dir, time.Second); err != nil {
326+
if err := reapStrayHelpers(ctx, dir); err != nil {
337327
errs = append(errs, err)
338328
}
339329
if err := os.RemoveAll(dir); err != nil {
@@ -342,6 +332,14 @@ func cleanupFailedVM(cmd *cobra.Command, dir string, r *record) error {
342332
return errors.Join(errs...)
343333
}
344334

335+
// reapStrayHelpers kills the QEMU and qemu-nbd processes still referencing dir.
336+
func reapStrayHelpers(ctx context.Context, dir string) error {
337+
return errors.Join(
338+
procutil.TerminateByCmdline(ctx, qemuBinary, dir, 0),
339+
procutil.TerminateByCmdline(ctx, "qemu-nbd", dir, time.Second),
340+
)
341+
}
342+
345343
// prepareOpenCore points r.OpenCore at the shared base, or with randomSMBIOS at a per-VM overlay whose config.plist is patched with a unique identity.
346344
func prepareOpenCore(ctx context.Context, dir, ocBase string, randomSMBIOS bool, r *record) error {
347345
if !randomSMBIOS {

cmd/vm/net_linux.go

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@ func provisionNet(cmd *cobra.Command, r *record) (tap, netns, mac string, err er
7474
return "", "", "", fmt.Errorf("prepare network: %w", err)
7575
}
7676
cfgs, err := provider.Add(ctx, r.VMID, vmCfg, network.AddRange(0, 1)...)
77+
if err == nil && len(cfgs) == 0 {
78+
err = errors.New("network add returned no NIC")
79+
}
7780
if err != nil {
81+
// Add rolls back only what it created itself; the netns from Prepare is ours to drop
82+
rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), vmCleanupTimeout)
83+
defer cancel()
84+
if delErr := provider.Delete(rctx, r.VMID); delErr != nil {
85+
log.WithFunc("cmd.vm.provisionNet").Warnf(rctx, "rollback network for %s: %v", r.VMID, delErr)
86+
}
7887
return "", "", "", fmt.Errorf("add network: %w", err)
7988
}
80-
if len(cfgs) == 0 {
81-
return "", "", "", errors.New("network add returned no NIC")
82-
}
8389
mac = cfgs[0].MAC
8490
if r.NetMode != netCNI {
8591
mac = cmp.Or(r.MAC, mac)
@@ -108,39 +114,29 @@ func teardownNet(ctx context.Context, cmd *cobra.Command, r *record) error {
108114
if err := provider.Quiesce(ctx, r.VMID); err != nil {
109115
logger.Warnf(ctx, "quiesce network for %s: %v", r.VMID, err)
110116
}
111-
if _, err := provider.Delete(ctx, []string{r.VMID}); err != nil {
117+
if err := provider.Delete(ctx, r.VMID); err != nil {
112118
return fmt.Errorf("teardown network for %s: %w", r.VMID, err)
113119
}
114120
return nil
115121
}
116122

117-
// quiesceNet downs a stopped VM's owned NICs so a dead VMM's carrier-less TAP can't storm host softirqs via the tc mirred redirect; unquiesceNet reverses it on start.
118-
func quiesceNet(cmd *cobra.Command, r *record) {
123+
// toggleNet downs a stopped VM's owned NICs so a dead VMM's carrier-less TAP can't storm host softirqs via the tc mirred redirect, and brings them back up on start.
124+
func toggleNet(cmd *cobra.Command, r *record, up bool) {
119125
if !r.TapOwned {
120126
return
121127
}
122128
ctx := cliutil.CommandContext(cmd)
123-
logger := log.WithFunc("cmd.vm.quiesceNet")
124-
if provider, err := newProvider(cmd, r); err != nil {
125-
logger.Warnf(ctx, "quiesce network for %s: %v", r.VMID, err)
126-
} else if err := provider.Quiesce(ctx, r.VMID); err != nil {
127-
logger.Warnf(ctx, "quiesce network for %s: %v", r.VMID, err)
129+
logger := log.WithFunc("cmd.vm.toggleNet")
130+
verb, toggle := "quiesce", network.Network.Quiesce
131+
if up {
132+
verb, toggle = "unquiesce", network.Network.Unquiesce
128133
}
129-
setTapLink(ctx, r, false)
130-
}
131-
132-
func unquiesceNet(cmd *cobra.Command, r *record) {
133-
if !r.TapOwned {
134-
return
135-
}
136-
ctx := cliutil.CommandContext(cmd)
137-
logger := log.WithFunc("cmd.vm.unquiesceNet")
138134
if provider, err := newProvider(cmd, r); err != nil {
139-
logger.Warnf(ctx, "unquiesce network for %s: %v", r.VMID, err)
140-
} else if err := provider.Unquiesce(ctx, r.VMID); err != nil {
141-
logger.Warnf(ctx, "unquiesce network for %s: %v", r.VMID, err)
135+
logger.Warnf(ctx, "%s network for %s: %v", verb, r.VMID, err)
136+
} else if err := toggle(provider, ctx, r.VMID); err != nil {
137+
logger.Warnf(ctx, "%s network for %s: %v", verb, r.VMID, err)
142138
}
143-
setTapLink(ctx, r, true)
139+
setTapLink(ctx, r, up)
144140
}
145141

146142
// setTapLink flips a host-netns TAP's admin state: cocoon's bridge backend no-ops Quiesce, so the toggle lives here; a CNI TAP is inside a netns and is the provider's job.

cmd/vm/net_other.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ func provisionNet(_ *cobra.Command, _ *record) (tap, netns, mac string, err erro
1717

1818
func teardownNet(_ context.Context, _ *cobra.Command, _ *record) error { return nil }
1919

20-
func quiesceNet(_ *cobra.Command, _ *record) {}
21-
22-
func unquiesceNet(_ *cobra.Command, _ *record) {}
20+
func toggleNet(_ *cobra.Command, _ *record, _ bool) {}
2321

2422
func launchCmd(_ *record, args []string) *exec.Cmd {
2523
return exec.Command(qemuBinary, args...)

cmd/vm/query.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ func (h *Handler) List(cmd *cobra.Command, _ []string) error {
2828
}
2929
}
3030
return cliutil.OutputFormatted(cmd, recs, func(w *tabwriter.Writer) {
31-
fmt.Fprintln(w, "NAME\tSTATE\tCPU\tMEM\tNET\tVNC\tSSH\tIMAGE\tCREATED") //nolint:errcheck
31+
fmt.Fprintln(w, "NAME\tSTATE\tCPU\tMEM\tNET\tVNC\tSSH\tIMAGE\tCREATED") //nolint:errcheck // the tabwriter flush reports the write error
3232
for _, r := range recs {
33-
fmt.Fprintf(w, "%s\t%s\t%d\t%sM\t%s\t%s\t%s\t%s\t%s\n", //nolint:errcheck
33+
fmt.Fprintf(w, "%s\t%s\t%d\t%sM\t%s\t%s\t%s\t%s\t%s\n", //nolint:errcheck // the tabwriter flush reports the write error
3434
r.Name, vmState(r), r.CPUs, r.Memory, cmp.Or(r.NetMode, netUser),
3535
vncCol(r), sshCol(r), r.Image, formatTime(r.Created))
3636
}

0 commit comments

Comments
 (0)