Skip to content

Commit e6d93e9

Browse files
committed
qemu: experimental support for S390X
Supporting S390X is useful for testing a program with big-endian. Signed-off-by: Akihiro Suda <[email protected]>
1 parent ddc0081 commit e6d93e9

22 files changed

+143
-35
lines changed

Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ config GUESTAGENT_ARCH_RISCV64
3131
Build lima-guestagent for "riscv64" Arch
3232
default y
3333

34+
config GUESTAGENT_ARCH_S390X
35+
bool "guestagent Arch: s390x"
36+
help
37+
Build lima-guestagent for "s390x" Arch
38+
default y
39+
3440
config GUESTAGENT_COMPRESS
3541
bool "guestagent compress"
3642
help

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ LINUX_GUESTAGENT_PATH_COMMON = _output/share/lima/lima-guestagent.Linux-
272272
# How to add architecture specific guestagent:
273273
# 1. Add the architecture to GUESTAGENT_ARCHS
274274
# 2. Add ENVS_$(*_GUESTAGENT_PATH_COMMON)<arch> to set GOOS, GOARCH, and other necessary environment variables
275-
LINUX_GUESTAGENT_ARCHS = aarch64 armv7l riscv64 x86_64
275+
LINUX_GUESTAGENT_ARCHS = aarch64 armv7l riscv64 s390x x86_64
276276

277277
ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y)
278278
ALL_GUESTAGENTS_NOT_COMPRESSED += $(addprefix $(LINUX_GUESTAGENT_PATH_COMMON),$(LINUX_GUESTAGENT_ARCHS))
@@ -321,6 +321,7 @@ additional-guestagents: $(ADDITIONAL_GUESTAGENTS)
321321
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)aarch64 = CGO_ENABLED=0 GOOS=linux GOARCH=arm64
322322
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)armv7l = CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7
323323
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)riscv64 = CGO_ENABLED=0 GOOS=linux GOARCH=riscv64
324+
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)s390x = CGO_ENABLED=0 GOOS=linux GOARCH=s390x
324325
ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)x86_64 = CGO_ENABLED=0 GOOS=linux GOARCH=amd64
325326
$(ALL_GUESTAGENTS_NOT_COMPRESSED): $(call dependencies_for_cmd,lima-guestagent) $$(call force_build_with_gunzip,$$@) | _output/share/lima
326327
$(ENVS_$@) $(GO_BUILD) -o $@ ./cmd/lima-guestagent

cmd/limactl/editflags/editflags.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ func RegisterCreate(cmd *cobra.Command, commentPrefix string) {
7373
registerEdit(cmd, commentPrefix)
7474
flags := cmd.Flags()
7575

76-
flags.String("arch", "", commentPrefix+"machine architecture (x86_64, aarch64, riscv64)") // colima-compatible
76+
flags.String("arch", "", commentPrefix+"machine architecture (x86_64, aarch64, riscv64, armv7l, s390x)") // colima-compatible
7777
_ = cmd.RegisterFlagCompletionFunc("arch", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
78-
return []string{"x86_64", "aarch64", "riscv64"}, cobra.ShellCompDirectiveNoFileComp
78+
return []string{"x86_64", "aarch64", "riscv64", "armv7l", "s390x"}, cobra.ShellCompDirectiveNoFileComp
7979
})
8080

8181
flags.String("containerd", "", commentPrefix+"containerd mode (user, system, user+system, none)")

config.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ CONFIG_GUESTAGENT_ARCH_X8664=y
33
CONFIG_GUESTAGENT_ARCH_AARCH64=y
44
CONFIG_GUESTAGENT_ARCH_ARMV7L=y
55
CONFIG_GUESTAGENT_ARCH_RISCV64=y
6+
CONFIG_GUESTAGENT_ARCH_S390X=y
67
CONFIG_GUESTAGENT_COMPRESS=n

pkg/guestagent/guestagent_linux.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/lima-vm/lima/pkg/guestagent/procnettcp"
2121
"github.com/lima-vm/lima/pkg/guestagent/timesync"
2222
"github.com/sirupsen/logrus"
23-
"golang.org/x/sys/cpu"
2423
"google.golang.org/protobuf/types/known/timestamppb"
2524
)
2625

@@ -220,9 +219,6 @@ func (a *agent) Events(ctx context.Context, ch chan *api.Event) {
220219
}
221220

222221
func (a *agent) LocalPorts(_ context.Context) ([]*api.IPPort, error) {
223-
if cpu.IsBigEndian {
224-
return nil, errors.New("big endian architecture is unsupported, because I don't know how /proc/net/tcp looks like on big endian hosts")
225-
}
226222
var res []*api.IPPort
227223
tcpParsed, err := procnettcp.ParseFiles()
228224
if err != nil {

pkg/guestagent/procnettcp/procnettcp.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"net"
1313
"strconv"
1414
"strings"
15+
16+
"golang.org/x/sys/cpu"
1517
)
1618

1719
type Kind = string
@@ -40,6 +42,10 @@ type Entry struct {
4042
}
4143

4244
func Parse(r io.Reader, kind Kind) ([]Entry, error) {
45+
return ParseWithEndian(r, kind, cpu.IsBigEndian)
46+
}
47+
48+
func ParseWithEndian(r io.Reader, kind Kind, isBE bool) ([]Entry, error) {
4349
switch kind {
4450
case TCP, TCP6, UDP, UDP6:
4551
default:
@@ -72,7 +78,7 @@ func Parse(r io.Reader, kind Kind) ([]Entry, error) {
7278
default:
7379
// localAddress is like "0100007F:053A"
7480
localAddress := fields[fieldNames["local_address"]]
75-
ip, port, err := ParseAddress(localAddress)
81+
ip, port, err := ParseAddressWithEndian(localAddress, isBE)
7682
if err != nil {
7783
return entries, err
7884
}
@@ -99,17 +105,24 @@ func Parse(r io.Reader, kind Kind) ([]Entry, error) {
99105
return entries, nil
100106
}
101107

102-
// ParseAddress parses a string, e.g.,
108+
// ParseAddress parses a string.
109+
//
110+
// Little endian hosts:
103111
// "0100007F:0050" (127.0.0.1:80)
104112
// "000080FE00000000FF57A6705DC771FE:0050" ([fe80::70a6:57ff:fe71:c75d]:80)
105113
// "00000000000000000000000000000000:0050" (0.0.0.0:80)
106114
//
107-
// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000
115+
// Big endian hosts:
116+
// "7F000001:0050" (127.0.0.1:80)
117+
// "FE8000000000000070A657FFFE71C75D:0050" ([fe80::70a6:57ff:fe71:c75d]:80)
118+
// "00000000000000000000000000000000:0050" (0.0.0.0:80)
108119
//
109-
// ParseAddress is expected to be used for /proc/net/{tcp,tcp6} entries on
110-
// little endian machines.
111-
// Not sure how those entries look like on big endian machines.
120+
// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000
112121
func ParseAddress(s string) (net.IP, uint16, error) {
122+
return ParseAddressWithEndian(s, cpu.IsBigEndian)
123+
}
124+
125+
func ParseAddressWithEndian(s string, isBE bool) (net.IP, uint16, error) {
113126
split := strings.SplitN(s, ":", 2)
114127
if len(split) != 2 {
115128
return nil, 0, fmt.Errorf("unparsable address %q", s)
@@ -124,12 +137,18 @@ func ParseAddress(s string) (net.IP, uint16, error) {
124137
ipBytes := make([]byte, len(split[0])/2) // 4 bytes (8 chars) or 16 bytes (32 chars)
125138
for i := 0; i < len(split[0])/8; i++ {
126139
quartet := split[0][8*i : 8*(i+1)]
127-
quartetLE, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes
140+
quartetB, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes, on little endian hosts
128141
if err != nil {
129142
return nil, 0, fmt.Errorf("unparsable address %q: unparsable quartet %q: %w", s, quartet, err)
130143
}
131-
for j := 0; j < len(quartetLE); j++ {
132-
ipBytes[4*i+len(quartetLE)-1-j] = quartetLE[j]
144+
if isBE {
145+
for j := 0; j < len(quartetB); j++ {
146+
ipBytes[4*i+j] = quartetB[j]
147+
}
148+
} else {
149+
for j := 0; j < len(quartetB); j++ {
150+
ipBytes[4*i+len(quartetB)-1-j] = quartetB[j]
151+
}
133152
}
134153
}
135154
ip := net.IP(ipBytes)

pkg/guestagent/procnettcp/procnettcp_test.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
)
1313

1414
func TestParseTCP(t *testing.T) {
15+
const isBE = false
1516
procNetTCP := ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
1617
0: 0100007F:8AEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 28152 1 0000000000000000 100 0 0 10 0
1718
1: 0103000A:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 31474 1 0000000000000000 100 0 0 10 5
@@ -20,7 +21,7 @@ func TestParseTCP(t *testing.T) {
2021
4: 0100007F:053A 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 31430 1 0000000000000000 100 0 0 10 0
2122
5: 0B3CA8C0:0016 690AA8C0:F705 01 00000000:00000000 02:00028D8B 00000000 0 0 32989 4 0000000000000000 20 4 31 10 19
2223
`
23-
entries, err := Parse(strings.NewReader(procNetTCP), TCP)
24+
entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP, isBE)
2425
assert.NilError(t, err)
2526
t.Log(entries)
2627

@@ -34,9 +35,10 @@ func TestParseTCP(t *testing.T) {
3435
}
3536

3637
func TestParseTCP6(t *testing.T) {
38+
const isBE = false
3739
procNetTCP := ` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
3840
0: 000080FE00000000FF57A6705DC771FE:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 850222 1 0000000000000000 100 0 0 10 0`
39-
entries, err := Parse(strings.NewReader(procNetTCP), TCP6)
41+
entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP6, isBE)
4042
assert.NilError(t, err)
4143
t.Log(entries)
4244

@@ -46,12 +48,13 @@ func TestParseTCP6(t *testing.T) {
4648
}
4749

4850
func TestParseTCP6Zero(t *testing.T) {
51+
const isBE = false
4952
procNetTCP := ` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
5053
0: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 33825 1 0000000000000000 100 0 0 10 0
5154
1: 00000000000000000000000000000000:006F 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 26772 1 0000000000000000 100 0 0 10 0
5255
2: 00000000000000000000000000000000:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 1210901 1 0000000000000000 100 0 0 10 0
5356
`
54-
entries, err := Parse(strings.NewReader(procNetTCP), TCP6)
57+
entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP6, isBE)
5558
assert.NilError(t, err)
5659
t.Log(entries)
5760

@@ -61,13 +64,14 @@ func TestParseTCP6Zero(t *testing.T) {
6164
}
6265

6366
func TestParseUDP(t *testing.T) {
67+
const isBE = false
6468
procNetTCP := ` sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
6569
716: 3600007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 991 0 2964 2 0000000000000000 0
6670
716: 3500007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000 991 0 2962 2 0000000000000000 0
6771
731: 0369A8C0:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000 998 0 29132 2 0000000000000000 0
6872
731: 0F05A8C0:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000 998 0 4049 2 0000000000000000 0
6973
1768: 00000000:1451 00000000:0000 07 00000000:00000000 00:00000000 00000000 502 0 28364 2 0000000000000000 0 `
70-
entries, err := Parse(strings.NewReader(procNetTCP), UDP)
74+
entries, err := ParseWithEndian(strings.NewReader(procNetTCP), UDP, isBE)
7175
assert.NilError(t, err)
7276
t.Log(entries)
7377

@@ -79,6 +83,7 @@ func TestParseUDP(t *testing.T) {
7983
func TestParseAddress(t *testing.T) {
8084
tests := []struct {
8185
input string
86+
bigEndian bool
8287
expectedIP net.IP
8388
expectedPort uint16
8489
expectedErrSubstr string
@@ -88,13 +93,31 @@ func TestParseAddress(t *testing.T) {
8893
expectedIP: net.IPv4(127, 0, 0, 1),
8994
expectedPort: 80,
9095
},
96+
{
97+
input: "7F000001:0050",
98+
bigEndian: true,
99+
expectedIP: net.IPv4(127, 0, 0, 1),
100+
expectedPort: 80,
101+
},
91102
{
92103
input: "000080FE00000000FF57A6705DC771FE:0050",
93104
expectedIP: net.ParseIP("fe80::70a6:57ff:fe71:c75d"),
94105
expectedPort: 80,
95106
},
107+
{
108+
input: "FE8000000000000070A657FFFE71C75D:0050",
109+
bigEndian: true,
110+
expectedIP: net.ParseIP("fe80::70a6:57ff:fe71:c75d"),
111+
expectedPort: 80,
112+
},
113+
{
114+
input: "00000000000000000000000000000000:0050",
115+
expectedIP: net.IPv6zero,
116+
expectedPort: 80,
117+
},
96118
{
97119
input: "00000000000000000000000000000000:0050",
120+
bigEndian: true,
98121
expectedIP: net.IPv6zero,
99122
expectedPort: 80,
100123
},
@@ -126,7 +149,7 @@ func TestParseAddress(t *testing.T) {
126149

127150
for _, test := range tests {
128151
t.Run(test.input, func(t *testing.T) {
129-
ip, port, err := ParseAddress(test.input)
152+
ip, port, err := ParseAddressWithEndian(test.input, test.bigEndian)
130153
if test.expectedErrSubstr != "" {
131154
assert.ErrorContains(t, err, test.expectedErrSubstr)
132155
} else {

pkg/limayaml/defaults.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func defaultCPUType() CPUType {
6464
// Since https://github.com/lima-vm/lima/pull/494, we use qemu64 cpu for better emulation of x86_64.
6565
X8664: "qemu64",
6666
RISCV64: "rv64", // FIXME: what is the right choice for riscv64?
67+
S390X: "qemu", // FIXME: what is the right choice for s390x?
6768
}
6869
for arch := range cpuType {
6970
if IsNativeArch(arch) && IsAccelOS() {
@@ -1047,6 +1048,8 @@ func NewArch(arch string) Arch {
10471048
return arch
10481049
case "riscv64":
10491050
return RISCV64
1051+
case "s390x":
1052+
return S390X
10501053
default:
10511054
logrus.Warnf("Unknown arch: %s", arch)
10521055
return arch
@@ -1237,7 +1240,8 @@ func IsNativeArch(arch Arch) bool {
12371240
nativeAARCH64 := arch == AARCH64 && runtime.GOARCH == "arm64"
12381241
nativeARMV7L := arch == ARMV7L && runtime.GOARCH == "arm" && goarm() == 7
12391242
nativeRISCV64 := arch == RISCV64 && runtime.GOARCH == "riscv64"
1240-
return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativeRISCV64
1243+
nativeS390X := arch == S390X && runtime.GOARCH == "s390x"
1244+
return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativeRISCV64 || nativeS390X
12411245
}
12421246

12431247
func unique(s []string) []string {

pkg/limayaml/defaults_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ func TestFillDefault(t *testing.T) {
5050
arch = ARMV7L
5151
case "riscv64":
5252
arch = RISCV64
53+
case "s390x":
54+
arch = S390X
5355
default:
5456
t.Skipf("unknown GOARCH: %s", runtime.GOARCH)
5557
}
@@ -330,6 +332,7 @@ func TestFillDefault(t *testing.T) {
330332
ARMV7L: "armhf",
331333
X8664: "amd64",
332334
RISCV64: "riscv64",
335+
S390X: "s390x",
333336
},
334337
CPUs: ptr.Of(7),
335338
Memory: ptr.Of("5GiB"),
@@ -542,6 +545,7 @@ func TestFillDefault(t *testing.T) {
542545
ARMV7L: "armv8",
543546
X8664: "pentium",
544547
RISCV64: "sifive-u54",
548+
S390X: "z14",
545549
},
546550
CPUs: ptr.Of(12),
547551
Memory: ptr.Of("7GiB"),

pkg/limayaml/limayaml.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const (
7777
AARCH64 Arch = "aarch64"
7878
ARMV7L Arch = "armv7l"
7979
RISCV64 Arch = "riscv64"
80+
S390X Arch = "s390x"
8081

8182
REVSSHFS MountType = "reverse-sshfs"
8283
NINEP MountType = "9p"
@@ -90,7 +91,7 @@ const (
9091

9192
var (
9293
OSTypes = []OS{LINUX}
93-
ArchTypes = []Arch{X8664, AARCH64, ARMV7L, RISCV64}
94+
ArchTypes = []Arch{X8664, AARCH64, ARMV7L, RISCV64, S390X}
9495
MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount}
9596
VMTypes = []VMType{QEMU, VZ, WSL2}
9697
)

pkg/limayaml/validate.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ func validateFileObject(f File, fieldName string) error {
3434
// f.Location does NOT need to be accessible, so we do NOT check os.Stat(f.Location)
3535
}
3636
switch f.Arch {
37-
case X8664, AARCH64, ARMV7L, RISCV64:
37+
case X8664, AARCH64, ARMV7L, RISCV64, S390X:
3838
default:
39-
return fmt.Errorf("field `arch` must be %q, %q, %q, or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, f.Arch)
39+
return fmt.Errorf("field `arch` must be %q, %q, %q, %q, or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, S390X, f.Arch)
4040
}
4141
if f.Digest != "" {
4242
if !f.Digest.Algorithm().Available() {
@@ -76,9 +76,9 @@ func Validate(y *LimaYAML, warn bool) error {
7676
return fmt.Errorf("field `os` must be %q; got %q", LINUX, *y.OS)
7777
}
7878
switch *y.Arch {
79-
case X8664, AARCH64, ARMV7L, RISCV64:
79+
case X8664, AARCH64, ARMV7L, RISCV64, S390X:
8080
default:
81-
return fmt.Errorf("field `arch` must be %q, %q, %q or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, *y.Arch)
81+
return fmt.Errorf("field `arch` must be %q, %q, %q, %q or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, S390X, *y.Arch)
8282
}
8383

8484
switch *y.VMType {
@@ -124,7 +124,7 @@ func Validate(y *LimaYAML, warn bool) error {
124124

125125
for arch := range y.CPUType {
126126
switch arch {
127-
case AARCH64, X8664, ARMV7L, RISCV64:
127+
case AARCH64, X8664, ARMV7L, RISCV64, S390X:
128128
// these are the only supported architectures
129129
default:
130130
return fmt.Errorf("field `cpuType` uses unsupported arch %q", arch)
@@ -540,8 +540,9 @@ func warnExperimental(y *LimaYAML) {
540540
if *y.MountType == VIRTIOFS && runtime.GOOS == "linux" {
541541
logrus.Warn("`mountType: virtiofs` on Linux is experimental")
542542
}
543-
if *y.Arch == RISCV64 {
544-
logrus.Warn("`arch: riscv64` is experimental")
543+
switch *y.Arch {
544+
case RISCV64, ARMV7L, S390X:
545+
logrus.Warnf("`arch: %s ` is experimental", *y.Arch)
545546
}
546547
if y.Video.Display != nil && strings.Contains(*y.Video.Display, "vnc") {
547548
logrus.Warn("`video.display: vnc` is experimental")

0 commit comments

Comments
 (0)