|
1 | 1 | package qemu
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bytes" |
4 | 5 | "errors"
|
5 | 6 | "fmt"
|
| 7 | + "io/fs" |
6 | 8 | "os"
|
7 | 9 | "os/exec"
|
8 | 10 | "path/filepath"
|
@@ -136,15 +138,64 @@ func appendArgsIfNoConflict(args []string, k, v string) []string {
|
136 | 138 | }
|
137 | 139 | return append(args, k, v)
|
138 | 140 | }
|
| 141 | + |
| 142 | +type features struct { |
| 143 | + // NetdevHelp is the output of `qemu-system-x86_64 -accel help` |
| 144 | + // e.g. "Accelerators supported in QEMU binary:\ntcg\nhax\nhvf\n" |
| 145 | + // Not machine-readable, but checking strings.Contains() should be fine. |
| 146 | + AccelHelp []byte |
| 147 | + // NetdevHelp is the output of `qemu-system-x86_64 -netdev help` |
| 148 | + // e.g. "Available netdev backend types:\nsocket\nhubport\ntap\nuser\nvde\nbridge\vhost-user\n" |
| 149 | + // Not machine-readable, but checking strings.Contains() should be fine. |
| 150 | + NetdevHelp []byte |
| 151 | +} |
| 152 | + |
| 153 | +func inspectFeatures(exe string) (*features, error) { |
| 154 | + var ( |
| 155 | + f features |
| 156 | + stdout bytes.Buffer |
| 157 | + stderr bytes.Buffer |
| 158 | + ) |
| 159 | + cmd := exec.Command(exe, "-M", "none", "-accel", "help") |
| 160 | + cmd.Stdout = &stdout |
| 161 | + cmd.Stderr = &stderr |
| 162 | + if err := cmd.Run(); err != nil { |
| 163 | + return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q", cmd.Args, stdout.String(), stderr.String()) |
| 164 | + } |
| 165 | + f.AccelHelp = stdout.Bytes() |
| 166 | + |
| 167 | + cmd = exec.Command(exe, "-M", "none", "-netdev", "help") |
| 168 | + cmd.Stdout = &stdout |
| 169 | + cmd.Stderr = &stderr |
| 170 | + if err := cmd.Run(); err != nil { |
| 171 | + return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q", cmd.Args, stdout.String(), stderr.String()) |
| 172 | + } |
| 173 | + f.NetdevHelp = stdout.Bytes() |
| 174 | + return &f, nil |
| 175 | +} |
| 176 | + |
139 | 177 | func Cmdline(cfg Config) (string, []string, error) {
|
140 | 178 | y := cfg.LimaYAML
|
141 | 179 | exe, args, err := getExe(y.Arch)
|
142 | 180 | if err != nil {
|
143 | 181 | return "", nil, err
|
144 | 182 | }
|
145 | 183 |
|
| 184 | + features, err := inspectFeatures(exe) |
| 185 | + if err != nil { |
| 186 | + return "", nil, err |
| 187 | + } |
| 188 | + |
146 | 189 | // Architecture
|
147 | 190 | accel := getAccel(y.Arch)
|
| 191 | + if !strings.Contains(string(features.AccelHelp), accel) { |
| 192 | + errStr := fmt.Sprintf("accelerator %q is not supported by %s", accel, exe) |
| 193 | + if accel == "hvf" && y.Arch == limayaml.AARCH64 { |
| 194 | + errStr += " ( Hint: as of August 2021, qemu-system-aarch64 on ARM Mac needs to be patched for enabling hvf accelerator," |
| 195 | + errStr += " see https://gist.github.com/nrjdalal/e70249bb5d2e9d844cc203fd11f74c55 )" |
| 196 | + } |
| 197 | + return "", nil, errors.New(errStr) |
| 198 | + } |
148 | 199 | switch y.Arch {
|
149 | 200 | case limayaml.X8664:
|
150 | 201 | // NOTE: "-cpu host" seems to cause kernel panic
|
@@ -202,11 +253,29 @@ func Cmdline(cfg Config) (string, []string, error) {
|
202 | 253 | args = append(args, "-netdev", fmt.Sprintf("user,id=net0,net=%s,dhcpstart=%s,hostfwd=tcp:127.0.0.1:%d-:22",
|
203 | 254 | qemuconst.SlirpNetwork, qemuconst.SlirpIPAddress, y.SSH.LocalPort))
|
204 | 255 | args = append(args, "-device", "virtio-net-pci,netdev=net0,mac="+limayaml.MACAddress(cfg.InstanceDir))
|
| 256 | + if len(y.Network.VDE) > 0 && !strings.Contains(string(features.NetdevHelp), "vde") { |
| 257 | + return "", nil, fmt.Errorf("netdev \"vde\" is not supported by %s ( Hint: recompile QEMU with `configure --enable-vde` )", exe) |
| 258 | + } |
205 | 259 | for i, vde := range y.Network.VDE {
|
206 | 260 | // VDE4 accepts VNL like vde:///var/run/vde.ctl as well as file path like /var/run/vde.ctl .
|
207 | 261 | // VDE2 only accepts the latter form.
|
208 | 262 | // VDE2 supports macOS but VDE4 does not yet, so we trim vde:// prefix here for VDE2 compatibility.
|
209 | 263 | vdeSock := strings.TrimPrefix(vde.VNL, "vde://")
|
| 264 | + if !strings.Contains(vdeSock, "://") { |
| 265 | + if _, err := os.Stat(vdeSock); err != nil { |
| 266 | + return "", nil, fmt.Errorf("cannot use VNL %q: %w", vde.VNL, err) |
| 267 | + } |
| 268 | + // vdeSock is a directory, unless vde.SwitchPort == 65535 (PTP) |
| 269 | + actualSocket := filepath.Join(vdeSock, "ctl") |
| 270 | + if vde.SwitchPort == 65535 { // PTP |
| 271 | + actualSocket = vdeSock |
| 272 | + } |
| 273 | + if st, err := os.Stat(actualSocket); err != nil { |
| 274 | + return "", nil, fmt.Errorf("cannot use VNL %q: failed to stat %q: %w", vde.VNL, actualSocket, err) |
| 275 | + } else if st.Mode()&fs.ModeSocket == 0 { |
| 276 | + return "", nil, fmt.Errorf("cannot use VNL %q: %q is not a socket: %w", vde.VNL, actualSocket, err) |
| 277 | + } |
| 278 | + } |
210 | 279 | args = append(args, "-netdev", fmt.Sprintf("vde,id=net%d,sock=%s", i+1, vdeSock))
|
211 | 280 | args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, vde.MACAddress))
|
212 | 281 | }
|
|
0 commit comments