From 382693927fcbaf1737239e4c058d7ce57b2cae04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:47:39 +0000 Subject: [PATCH 001/109] build(deps): bump github.com/cloudflare/circl from 1.3.3 to 1.3.7 Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.3 to 1.3.7. - [Release notes](https://github.com/cloudflare/circl/releases) - [Commits](https://github.com/cloudflare/circl/compare/v1.3.3...v1.3.7) --- updated-dependencies: - dependency-name: github.com/cloudflare/circl dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../cloudflare/circl/ecc/goldilocks/twist.go | 2 +- .../cloudflare/circl/internal/sha3/keccakf.go | 12 +++++- .../cloudflare/circl/internal/sha3/sha3.go | 11 +++-- .../cloudflare/circl/internal/sha3/shake.go | 40 +++++++++++++++++++ .../cloudflare/circl/math/primes.go | 34 ++++++++++++++++ .../cloudflare/circl/sign/ed25519/ed25519.go | 2 +- vendor/modules.txt | 2 +- 9 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 vendor/github.com/cloudflare/circl/math/primes.go diff --git a/go.mod b/go.mod index 9928e9a1a2..c801d6f075 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/charmbracelet/bubbles v0.15.1-0.20230123181021-a6a12c4a31eb // indirect github.com/charmbracelet/bubbletea v0.24.1 // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index c1c977475e..c3823b30be 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go index 8cd4e333b9..83d7cdadd3 100644 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go +++ b/vendor/github.com/cloudflare/circl/ecc/goldilocks/twist.go @@ -9,7 +9,7 @@ import ( fp "github.com/cloudflare/circl/math/fp448" ) -// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogeneous to Goldilocks. +// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogenous to Goldilocks. type twistCurve struct{} // Identity returns the identity point. diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go b/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go index ab19d0ad12..1755fd1e6d 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/keccakf.go @@ -6,13 +6,21 @@ package sha3 // KeccakF1600 applies the Keccak permutation to a 1600b-wide // state represented as a slice of 25 uint64s. +// If turbo is true, applies the 12-round variant instead of the +// regular 24-round variant. // nolint:funlen -func KeccakF1600(a *[25]uint64) { +func KeccakF1600(a *[25]uint64, turbo bool) { // Implementation translated from Keccak-inplace.c // in the keccak reference code. var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 - for i := 0; i < 24; i += 4 { + i := 0 + + if turbo { + i = 12 + } + + for ; i < 24; i += 4 { // Combines the 5 steps in each round into 2 steps. // Unrolls 4 rounds per loop and spreads some steps across rounds. diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go b/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go index b35cd006b0..a0df5aa6c5 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/sha3.go @@ -51,6 +51,7 @@ type State struct { // Specific to SHA-3 and SHAKE. outputLen int // the default output size in bytes state spongeDirection // whether the sponge is absorbing or squeezing + turbo bool // Whether we're using 12 rounds instead of 24 } // BlockSize returns the rate of sponge underlying this hash function. @@ -86,11 +87,11 @@ func (d *State) permute() { xorIn(d, d.buf()) d.bufe = 0 d.bufo = 0 - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) case spongeSqueezing: // If we're squeezing, we need to apply the permutation before // copying more output. - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) d.bufe = d.rate d.bufo = 0 copyOut(d, d.buf()) @@ -136,7 +137,7 @@ func (d *State) Write(p []byte) (written int, err error) { // The fast path; absorb a full "rate" bytes of input and apply the permutation. xorIn(d, p[:d.rate]) p = p[d.rate:] - KeccakF1600(&d.a) + KeccakF1600(&d.a, d.turbo) } else { // The slow path; buffer the input until we can fill the sponge, and then xor it in. todo := d.rate - bufl @@ -193,3 +194,7 @@ func (d *State) Sum(in []byte) []byte { _, _ = dup.Read(hash) return append(in, hash...) } + +func (d *State) IsAbsorbing() bool { + return d.state == spongeAbsorbing +} diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go b/vendor/github.com/cloudflare/circl/internal/sha3/shake.go index b92c5b7d78..77817f758c 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/shake.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/shake.go @@ -57,6 +57,17 @@ func NewShake128() State { return State{rate: rate128, dsbyte: dsbyteShake} } +// NewTurboShake128 creates a new TurboSHAKE128 variable-output-length ShakeHash. +// Its generic security strength is 128 bits against all attacks if at +// least 32 bytes of its output are used. +// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. +func NewTurboShake128(D byte) State { + if D == 0 || D > 0x7f { + panic("turboshake: D out of range") + } + return State{rate: rate128, dsbyte: D, turbo: true} +} + // NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. // Its generic security strength is 256 bits against all attacks if // at least 64 bytes of its output are used. @@ -64,6 +75,17 @@ func NewShake256() State { return State{rate: rate256, dsbyte: dsbyteShake} } +// NewTurboShake256 creates a new TurboSHAKE256 variable-output-length ShakeHash. +// Its generic security strength is 256 bits against all attacks if +// at least 64 bytes of its output are used. +// D is the domain separation byte and must be between 0x01 and 0x7f inclusive. +func NewTurboShake256(D byte) State { + if D == 0 || D > 0x7f { + panic("turboshake: D out of range") + } + return State{rate: rate256, dsbyte: D, turbo: true} +} + // ShakeSum128 writes an arbitrary-length digest of data into hash. func ShakeSum128(hash, data []byte) { h := NewShake128() @@ -77,3 +99,21 @@ func ShakeSum256(hash, data []byte) { _, _ = h.Write(data) _, _ = h.Read(hash) } + +// TurboShakeSum128 writes an arbitrary-length digest of data into hash. +func TurboShakeSum128(hash, data []byte, D byte) { + h := NewTurboShake128(D) + _, _ = h.Write(data) + _, _ = h.Read(hash) +} + +// TurboShakeSum256 writes an arbitrary-length digest of data into hash. +func TurboShakeSum256(hash, data []byte, D byte) { + h := NewTurboShake256(D) + _, _ = h.Write(data) + _, _ = h.Read(hash) +} + +func (d *State) SwitchDS(D byte) { + d.dsbyte = D +} diff --git a/vendor/github.com/cloudflare/circl/math/primes.go b/vendor/github.com/cloudflare/circl/math/primes.go new file mode 100644 index 0000000000..158fd83a7a --- /dev/null +++ b/vendor/github.com/cloudflare/circl/math/primes.go @@ -0,0 +1,34 @@ +package math + +import ( + "crypto/rand" + "io" + "math/big" +) + +// IsSafePrime reports whether p is (probably) a safe prime. +// The prime p=2*q+1 is safe prime if both p and q are primes. +// Note that ProbablyPrime is not suitable for judging primes +// that an adversary may have crafted to fool the test. +func IsSafePrime(p *big.Int) bool { + pdiv2 := new(big.Int).Rsh(p, 1) + return p.ProbablyPrime(20) && pdiv2.ProbablyPrime(20) +} + +// SafePrime returns a number of the given bit length that is a safe prime with high probability. +// The number returned p=2*q+1 is a safe prime if both p and q are primes. +// SafePrime will return error for any error returned by rand.Read or if bits < 2. +func SafePrime(random io.Reader, bits int) (*big.Int, error) { + one := big.NewInt(1) + p := new(big.Int) + for { + q, err := rand.Prime(random, bits-1) + if err != nil { + return nil, err + } + p.Lsh(q, 1).Add(p, one) + if p.ProbablyPrime(20) { + return p, nil + } + } +} diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go b/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go index 08ca65d799..2c73c26fb1 100644 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go +++ b/vendor/github.com/cloudflare/circl/sign/ed25519/ed25519.go @@ -1,7 +1,7 @@ // Package ed25519 implements Ed25519 signature scheme as described in RFC-8032. // // This package provides optimized implementations of the three signature -// variants and maintaining closer compatiblilty with crypto/ed25519. +// variants and maintaining closer compatibility with crypto/ed25519. // // | Scheme Name | Sign Function | Verification | Context | // |-------------|-------------------|---------------|-------------------| diff --git a/vendor/modules.txt b/vendor/modules.txt index 4fcea8b07c..d548f84cdc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -67,7 +67,7 @@ github.com/charmbracelet/bubbletea # github.com/charmbracelet/lipgloss v0.7.1 ## explicit; go 1.17 github.com/charmbracelet/lipgloss -# github.com/cloudflare/circl v1.3.3 +# github.com/cloudflare/circl v1.3.7 ## explicit; go 1.19 github.com/cloudflare/circl/dh/x25519 github.com/cloudflare/circl/dh/x448 From df70864a17c1c128cac01f56eea3077395ad9fe0 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 10 Jan 2024 18:49:42 -0800 Subject: [PATCH 002/109] Increase test timeout to 15m Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c911cf022b..2863135f6e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,6 +58,7 @@ jobs: GOCOVERDIR=$(pwd)/gocov \ VMTEST_GO_PROFILE=vmcoverage.txt runvmtest -- \ go test -v -covermode=atomic ${{ matrix.extra-arg }} \ + -timeout=15m \ -coverprofile=coverage.txt ./${{ matrix.pattern }} - uses: codecov/codecov-action@v4-beta From 0f777223d5ea9e0de317b4ce5e1b6f7b8ecad306 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 10 Jan 2024 18:52:44 -0800 Subject: [PATCH 003/109] dhclient: ensure DHCPv6 address is routable if using non-multicast address Signed-off-by: Chris Koch --- pkg/dhclient/dhclient.go | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/pkg/dhclient/dhclient.go b/pkg/dhclient/dhclient.go index 33170b9b68..9b9bc343f6 100644 --- a/pkg/dhclient/dhclient.go +++ b/pkg/dhclient/dhclient.go @@ -27,9 +27,9 @@ import ( "golang.org/x/sys/unix" ) -// isIpv6LinkReady returns true if the interface has a link-local address +// isIPv6LinkReady returns true if the interface has a link-local address // which is not tentative. -func isIpv6LinkReady(l netlink.Link) (bool, error) { +func isIPv6LinkReady(l netlink.Link) (bool, error) { addrs, err := netlink.AddrList(l, netlink.FAMILY_V6) if err != nil { return false, err @@ -45,6 +45,31 @@ func isIpv6LinkReady(l netlink.Link) (bool, error) { return false, nil } +// isIPv6RouteReady returns true if serverAddr is reachable. +func isIPv6RouteReady(l netlink.Link, serverAddr net.IP) (bool, error) { + if serverAddr.IsMulticast() { + return true, nil + } + + routes, err := netlink.RouteList(l, netlink.FAMILY_V6) + if err != nil { + return false, err + } + for _, route := range routes { + if route.LinkIndex != l.Attrs().Index { + continue + } + // Default route. + if route.Dst == nil { + return true, nil + } + if route.Dst.Contains(serverAddr) { + return true, nil + } + } + return false, nil +} + // IfUp ensures the given network interface is up and returns the link object. func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { start := time.Now() @@ -230,7 +255,7 @@ func lease6(ctx context.Context, iface netlink.Link, c Config, linkUpTimeout tim // Hardcode the timeout to 30s for now. linkTimeout := time.After(linkUpTimeout) for { - if ready, err := isIpv6LinkReady(iface); err != nil { + if ready, err := isIPv6LinkReady(iface); err != nil { return nil, err } else if ready { break @@ -239,12 +264,29 @@ func lease6(ctx context.Context, iface netlink.Link, c Config, linkUpTimeout tim case <-time.After(100 * time.Millisecond): continue case <-linkTimeout: - return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") case <-ctx.Done(): return nil, errors.New("timeout after waiting for a non-tentative IPv6 address") } } + // If user specified a non-multicast address, make sure it's routable before we start. + if c.V6ServerAddr != nil { + for { + if ready, err := isIPv6RouteReady(iface, c.V6ServerAddr.IP); err != nil { + return nil, err + } else if ready { + break + } + select { + case <-time.After(100 * time.Millisecond): + continue + case <-linkTimeout: + case <-ctx.Done(): + return nil, errors.New("timeout after waiting for a route") + } + } + } + mods := []nclient6.ClientOpt{ nclient6.WithTimeout(c.Timeout), nclient6.WithRetry(c.Retries), From 02dfe97d856d24138aa1594690792748303ef384 Mon Sep 17 00:00:00 2001 From: Ronald G Minnich Date: Wed, 10 Jan 2024 21:46:39 -0800 Subject: [PATCH 004/109] fix the mtd test if /dev/mtd0 can not be opened Signed-off-by: Ronald G Minnich --- pkg/mount/mtd/linux_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/mount/mtd/linux_test.go b/pkg/mount/mtd/linux_test.go index 4cbf0facf4..6644d1f3cf 100644 --- a/pkg/mount/mtd/linux_test.go +++ b/pkg/mount/mtd/linux_test.go @@ -15,19 +15,20 @@ var ( ) func TestOpen(t *testing.T) { - if _, err := os.Stat(DevName); err != nil { + m, err := NewDev(DevName) + if err != nil { tmpDir := t.TempDir() testmtd, err := os.CreateTemp(tmpDir, "testmtd") if err != nil { t.Errorf(`os.Create(tmpDir, "testmtd")=file, %q, want file, nil`, err) } DevName = testmtd.Name() + m, err = NewDev(DevName) + if err != nil { + t.Fatal(err) + } } - m, err := NewDev(DevName) - if err != nil { - t.Fatal(err) - } defer m.Close() if _, err := m.QueueWriteAt([]byte(testString), 0); err != nil { From 4046fb053f8eb5535bd38f8d314c7ec909331d07 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Thu, 11 Jan 2024 09:13:36 -0800 Subject: [PATCH 005/109] flash/op: add String() and Bytes() for OpCode Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 4 +- pkg/flash/flash_linux.go | 19 ++++---- pkg/flash/op/op.go | 71 +++++++++++++++++++++++------ pkg/flash/op/op_test.go | 58 +++++++++++++++++++++++ pkg/flash/spimock/spimock_linux.go | 2 +- 5 files changed, 127 insertions(+), 27 deletions(-) create mode 100644 pkg/flash/op/op_test.go diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index 191ef0aa16..c22bb8c0ed 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -90,12 +90,12 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) // transfers to ensure that happens. transfers := []spidev.Transfer{ { - Tx: []byte{op.PRDRES}, + Tx: op.PRDRES.Bytes(), Rx: make([]byte, 1), CSChange: true, }, { - Tx: []byte{op.ReadJEDECID, 0, 0, 0}, + Tx: append(op.ReadJEDECID.Bytes(), 0, 0, 0), Rx: make([]byte, 4), }, } diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 5a2e68cd73..74797abb25 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -115,7 +115,7 @@ func (f *Flash) ReadAt(p []byte, off int64) (int, error) { // Split the transfer into maxTransferSize chunks. for i := 0; i < len(p); i += maxTransferSize { if err := f.spi.Transfer([]spidev.Transfer{ - {Tx: append([]byte{op.Read}, f.prepareAddress(off+int64(i))...)}, + {Tx: append(op.Read.Bytes(), f.prepareAddress(off+int64(i))...)}, {Rx: p[i:min(int64(i)+maxTransferSize, int64(len(p)))]}, }); err != nil { return i, err @@ -129,9 +129,9 @@ func (f *Flash) ReadAt(p []byte, off int64) (int, error) { func (f *Flash) writeAt(p []byte, off int64) (int, error) { if err := f.spi.Transfer([]spidev.Transfer{ // Enable writing. - {Tx: []byte{op.WriteEnable}, CSChange: true}, + {Tx: op.WriteEnable.Bytes(), CSChange: true}, // Send the address. - {Tx: append([]byte{op.PageProgram}, f.prepareAddress(off)...)}, + {Tx: append(op.PageProgram.Bytes(), f.prepareAddress(off)...)}, // Send the data. {Tx: p}, }); err != nil { @@ -212,11 +212,11 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { if err := f.spi.Transfer([]spidev.Transfer{ // Enable writing. { - Tx: []byte{op.WriteEnable}, + Tx: op.WriteEnable.Bytes(), CSChange: true, }, // Send the address. - {Tx: append([]byte{opcode}, f.prepareAddress(off+i)...)}, + {Tx: append(opcode.Bytes(), f.prepareAddress(off+i)...)}, }); err != nil { return i, err } @@ -228,7 +228,7 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { // ReadJEDECID reads the flash chip's JEDEC ID. func (f *Flash) ReadJEDECID() (uint32, error) { - tx := []byte{op.ReadJEDECID} + tx := op.ReadJEDECID.Bytes() rx := make([]byte, 3) if err := f.spi.Transfer([]spidev.Transfer{ @@ -262,13 +262,12 @@ func (f *SFDPReader) ReadAt(p []byte, off int64) (int, error) { return 0, io.EOF } p = p[:min(int64(len(p)), sfdpMaxAddress-off)] - tx := []byte{ - op.ReadSFDP, + tx := append(op.ReadSFDP.Bytes(), // offset, 3-bytes, big-endian - byte((off >> 16) & 0xff), byte((off >> 8) & 0xff), byte(off & 0xff), + byte((off>>16)&0xff), byte((off>>8)&0xff), byte(off&0xff), // dummy 0xff 0xff, - } + ) if err := f.spi.Transfer([]spidev.Transfer{ {Tx: tx}, {Rx: p}, diff --git a/pkg/flash/op/op.go b/pkg/flash/op/op.go index 9acc3cce80..e28b57c733 100644 --- a/pkg/flash/op/op.go +++ b/pkg/flash/op/op.go @@ -6,29 +6,72 @@ // the beginning of a SPI transaction. package op +import "fmt" + +type OpCode byte + const ( // PageProgram programs a page on the flash chip. - PageProgram byte = 0x02 + PageProgram OpCode = 0x02 // Read reads from the flash chip. - Read byte = 0x03 + Read OpCode = 0x03 // WriteDisable disables writing. - WriteDisable byte = 0x04 + WriteDisable OpCode = 0x04 // ReadStatus reads the status register. - ReadStatus byte = 0x05 + ReadStatus OpCode = 0x05 // WriteEnable enables writing. - WriteEnable byte = 0x06 + WriteEnable OpCode = 0x06 // SectorErase erases a sector to the value 0xff. - SectorErase byte = 0x20 + SectorErase OpCode = 0x20 // ReadSFDP reads from the SFDP. - ReadSFDP byte = 0x5a + ReadSFDP OpCode = 0x5a // ReadID reads the JEDEC ID. - ReadJEDECID byte = 0x9f + ReadJEDECID OpCode = 0x9f // PRD/RES - PRDRES = 0xab - // Enter4BA enters 4-byte addressing mode. - Enter4BA byte = 0xb7 + PRDRES OpCode = 0xab + // AAI is auto address increment + AAI OpCode = 0xad + // Enter4BA enters 4-OpCode addressing mode. + Enter4BA OpCode = 0xb7 // BlockErase erases a block to the value 0xff. - BlockErase byte = 0xd8 - // Exit4BA exits 4-byte addressing mode. - Exit4BA byte = 0xe9 + BlockErase OpCode = 0xd8 + // Exit4BA exits 4-OpCode addressing mode. + Exit4BA OpCode = 0xe9 ) + +func (o OpCode) String() string { + switch o { + case PageProgram: + return "PageProgram" + case Read: + return "Read" + case WriteDisable: + return "WriteDisable" + case ReadStatus: + return "ReadStatus" + case WriteEnable: + return "WriteEnable" + case SectorErase: + return "SectorErase" + case ReadSFDP: + return "ReadSFDP" + case ReadJEDECID: + return "ReadJEDECID" + case PRDRES: + return "PRDRES" + case AAI: + return "AAI" + case Enter4BA: + return "Enter4BA" + case BlockErase: + return "BlockErase" + case Exit4BA: + return "Exit4BA" + default: + return fmt.Sprintf("Unknown(%02x)", byte(o)) + } +} + +func (o OpCode) Bytes() []byte { + return []byte{byte(o)} +} diff --git a/pkg/flash/op/op_test.go b/pkg/flash/op/op_test.go new file mode 100644 index 0000000000..30a717e75a --- /dev/null +++ b/pkg/flash/op/op_test.go @@ -0,0 +1,58 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package op_test + +import ( + "bytes" + "fmt" + "testing" + + "github.com/u-root/u-root/pkg/flash/op" +) + +func TestString(t *testing.T) { + bad := op.OpCode(0xff) + tests := []struct { + opcode op.OpCode + expected string + }{ + {op.PageProgram, "PageProgram"}, + {op.Read, "Read"}, + {op.WriteDisable, "WriteDisable"}, + {op.ReadStatus, "ReadStatus"}, + {op.WriteEnable, "WriteEnable"}, + {op.SectorErase, "SectorErase"}, + {op.ReadSFDP, "ReadSFDP"}, + {op.ReadJEDECID, "ReadJEDECID"}, + {op.PRDRES, "PRDRES"}, + {op.Enter4BA, "Enter4BA"}, + {op.BlockErase, "BlockErase"}, + {op.Exit4BA, "Exit4BA"}, + {bad, fmt.Sprintf("Unknown(%02x)", byte(bad))}, + } + + for _, tt := range tests { + actual := tt.opcode.String() + if actual != tt.expected { + t.Errorf("String() for %v: expected %s, got %s", tt.opcode, tt.expected, actual) + } + } +} + +func TestBytes(t *testing.T) { + tests := []struct { + opcode op.OpCode + expected []byte + }{ + {op.PageProgram, []byte{byte(op.PageProgram)}}, + } + + for _, tt := range tests { + actual := tt.opcode.Bytes() + if !bytes.Equal(actual, tt.expected) { + t.Errorf("Bytes() for %v: expected %v, got %v", tt.opcode, tt.expected, actual) + } + } +} diff --git a/pkg/flash/spimock/spimock_linux.go b/pkg/flash/spimock/spimock_linux.go index 7ee26dbda1..c38a29998d 100644 --- a/pkg/flash/spimock/spimock_linux.go +++ b/pkg/flash/spimock/spimock_linux.go @@ -197,7 +197,7 @@ func (s *MockSPI) Transfer(transfers []spidev.Transfer) error { if err != nil { return err } - switch o { + switch op.OpCode(o) { case op.PageProgram: if !s.IsWriteEnabled { break From 7159185cb1424030611769dc048c362e9b1653ef Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Thu, 11 Jan 2024 09:52:40 -0800 Subject: [PATCH 006/109] flash: Chip struct and Lookup() and String() for it Signed-off-by: Ronald G. Minnich --- pkg/flash/chips/chips.go | 102 ++++++++++++++++++++++++++++++++++ pkg/flash/chips/chips_test.go | 73 ++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 pkg/flash/chips/chips.go create mode 100644 pkg/flash/chips/chips_test.go diff --git a/pkg/flash/chips/chips.go b/pkg/flash/chips/chips.go new file mode 100644 index 0000000000..7a0a8f4136 --- /dev/null +++ b/pkg/flash/chips/chips.go @@ -0,0 +1,102 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package chips contains chips known to work with the flash tool. +package chips + +import ( + "fmt" + "os" + + "github.com/u-root/u-root/pkg/flash/op" +) + +const ( + k = 1024 + m = 1024 * 1024 +) + +// EraseBlock defines the size and the opcode used for an erase block. +// In Flash Chips, the erase region is aligned to the size. +type EraseBlock struct { + Size int + Op op.OpCode +} + +// Chip defines a chip. +type Chip struct { + Vendor string + Chip string + ID int + ArraySize int64 + PageSize int64 + SectorSize int64 + BlockSize int64 + Is4BA bool + EraseBlocks []EraseBlock + + Unlock op.OpCode + Write op.OpCode + Read op.OpCode +} + +// String implements string for Chip. +func (c *Chip) String() string { + return fmt.Sprintf("Vendor:%s Chip:%s ID:%06x Size:%d 4BA:%v", c.Vendor, c.Chip, c.ID, c.ArraySize, c.Is4BA) +} + +// Lookup finds a Chip by id, returning os.ErrNotExist if it is not found. +func Lookup(id int) (*Chip, error) { + for _, c := range Chips { + if c.ID == id { + return &c, nil + } + } + return nil, os.ErrNotExist +} + +// Chips are all the chips we know about. +// Note that the test assumes that SST25VF016B +// is first. +var Chips = []Chip{ + { + Vendor: "SST", + Chip: "SST25VF016B", + ID: 0xbf2541, + ArraySize: 2 * m, + // This is the real page size. + // The kernel gets an error on the ioctl. + // PageSize: 256 * 1024, + PageSize: 1, // make it 1 until we get AAI 1024, + SectorSize: 4 * k, + BlockSize: 64 * k, + Is4BA: false, + EraseBlocks: []EraseBlock{ + { + Size: 4 * k, + Op: 0x20, + }, + { + Size: 32 * k, + Op: 0x52, + }, + { + Size: 64 * k, + Op: 0xD8, + }, + { + Size: 2 * m, + Op: 0x60, + }, + { + Size: 2 * m, + Op: 0xc7, + }, + }, + + Unlock: op.WriteEnable, + Write: op.AAI, + Read: op.Read, + }, +} diff --git a/pkg/flash/chips/chips_test.go b/pkg/flash/chips/chips_test.go new file mode 100644 index 0000000000..eeca5aab57 --- /dev/null +++ b/pkg/flash/chips/chips_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package chips_test + +import ( + "errors" + "os" + "reflect" + "testing" + + "github.com/u-root/u-root/pkg/flash/chips" +) + +func TestLookup(t *testing.T) { + tests := []struct { + name string + id int + want *chips.Chip + err error + }{ + { + name: "Valid ID", + id: 0xbf2541, + want: &chips.Chips[0], // Assume [0] is the SST25VF016B chip + err: nil, + }, + { + name: "Non-existent ID", + id: 0xdeadbeef, + want: nil, + err: os.ErrNotExist, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := chips.Lookup(tt.id) + + if !errors.Is(err, tt.err) { + t.Fatalf("Lookup(%06x) got %v, want %v", tt.id, err, tt.err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("Lookup(%d) got %v, want %v", tt.id, got, tt.want) + } + }) + } +} + +func TestString(t *testing.T) { + tests := []struct { + name string + chip chips.Chip + want string + }{ + { + name: "SST25VF016B", + chip: chips.Chips[0], // Assume [0] is the SST25VF016B chip + want: "Vendor:SST Chip:SST25VF016B ID:bf2541 Size:2097152 4BA:false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.chip.String() + if got != tt.want { + t.Errorf("String() got %v, want %v", got, tt.want) + } + }) + } +} From bc840e98b936e8e51525d190549cf5adec534584 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Thu, 11 Jan 2024 14:32:44 -0800 Subject: [PATCH 007/109] flash/chips: make ID a type Signed-off-by: Ronald G. Minnich --- pkg/flash/chips/chips.go | 6 ++++-- pkg/flash/chips/chips_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/flash/chips/chips.go b/pkg/flash/chips/chips.go index 7a0a8f4136..d70ee2f03f 100644 --- a/pkg/flash/chips/chips.go +++ b/pkg/flash/chips/chips.go @@ -24,11 +24,13 @@ type EraseBlock struct { Op op.OpCode } +type ID int + // Chip defines a chip. type Chip struct { Vendor string Chip string - ID int + ID ID ArraySize int64 PageSize int64 SectorSize int64 @@ -47,7 +49,7 @@ func (c *Chip) String() string { } // Lookup finds a Chip by id, returning os.ErrNotExist if it is not found. -func Lookup(id int) (*Chip, error) { +func Lookup(id ID) (*Chip, error) { for _, c := range Chips { if c.ID == id { return &c, nil diff --git a/pkg/flash/chips/chips_test.go b/pkg/flash/chips/chips_test.go index eeca5aab57..b5dbc2909d 100644 --- a/pkg/flash/chips/chips_test.go +++ b/pkg/flash/chips/chips_test.go @@ -16,7 +16,7 @@ import ( func TestLookup(t *testing.T) { tests := []struct { name string - id int + id chips.ID want *chips.Chip err error }{ From 668999a35bb528af4bfad6cddbfd9ef65f7c9dfc Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 12 Jan 2024 13:38:32 -0800 Subject: [PATCH 008/109] pkg/flash: update flash to use chips package Check for the chip ID first, before trying to read the SFDP. If it is found, use those values for density and so on. In any case, always try to read the sfdp. As time goes by, more and more chips will implement it, and it will go from mostly failing to mostly working. Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 2 + pkg/flash/flash_linux.go | 104 +++++++++++++++++----------- pkg/flash/flash_linux_test.go | 13 ++-- pkg/flash/spimock/spimock_linux.go | 8 +++ pkg/spidev/spidev_linux.go | 68 ++++++++++++++++-- pkg/spidev/spidev_linux_test.go | 42 +++++++++++ 6 files changed, 187 insertions(+), 50 deletions(-) diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index c22bb8c0ed..aad6a03d93 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -32,6 +32,7 @@ import ( flag "github.com/spf13/pflag" "github.com/u-root/u-root/pkg/flash" + "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" @@ -41,6 +42,7 @@ type spi interface { Transfer([]spidev.Transfer) error SetSpeedHz(uint32) error Close() error + ID() (chips.ID, error) } var ( diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 74797abb25..96cc6ad964 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -11,6 +11,7 @@ import ( "fmt" "io" + "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" @@ -22,6 +23,7 @@ const sfdpMaxAddress = (1 << 24) - 1 // SPI interface for the underlying calls to the SPI driver. type SPI interface { Transfer(transfers []spidev.Transfer) error + ID() (chips.ID, error) } // Flash provides operations for SPI flash chips. @@ -29,21 +31,12 @@ type Flash struct { // spi is the underlying SPI device. spi SPI - // is4ba is true if 4-byte addressing mode is enabled. - is4ba bool - - // size is the size of the flash chip in bytes. - size int64 + // Chip is derived from SFDP or looking up + // the chip via the ID. + chips.Chip // sfdp is cached. sfdp *sfdp.SFDP - - // JEDEC ID is cached. - id uint32 - - pageSize int64 - sectorSize int64 - blockSize int64 } // New creates a new flash device from a SPI interface. @@ -53,36 +46,60 @@ func New(spi SPI) (*Flash, error) { } var err error + var id chips.ID + id, err = f.spi.ID() + if err != nil { + return nil, fmt.Errorf("Can not ID chip: %w", err) + } + c, err := chips.Lookup(id) + if err == nil { + f.Chip = *c + // Even when we have a chip, there is still + // benefit to trying to get an SFDP. + // Further, the package as written wants + // some sort of empty sfdp to exist, and this + // is the easiest way to do it. + f.sfdp, _ = sfdp.Read(f.SFDPReader()) + return f, nil + } + f.sfdp, err = sfdp.Read(f.SFDPReader()) if err != nil { - return nil, fmt.Errorf("could not read sfdp: %v", err) + return nil, fmt.Errorf("chip %#x: chip not known, and no SFDP: %w", id, err) } + if err = f.FillFromSFDP(); err != nil { + return nil, fmt.Errorf("chip %#x: chip not known, and no SFDP: %w", id, err) + } + + return f, nil +} +func (f *Flash) FillFromSFDP() error { density, err := f.SFDP().Param(sfdp.ParamFlashMemoryDensity) if err != nil { - return nil, fmt.Errorf("flash chip SFDP does not have density param") + return fmt.Errorf("flash chip SFDP does not have density param") } if density >= 0x80000000 { - return nil, fmt.Errorf("unsupported flash density: %#x", density) + return fmt.Errorf("unsupported flash density: %#x", density) } - f.size = (density + 1) / 8 + f.ArraySize = (density + 1) / 8 // Assume 4ba if address if size requires 4 bytes. - if f.size >= 0x1000000 { - f.is4ba = true + if f.ArraySize >= 0x1000000 { + f.Is4BA = true } // TODO - f.pageSize = 256 - f.sectorSize = 4096 - f.blockSize = 65536 + f.PageSize = 256 + f.SectorSize = 4096 + f.BlockSize = 65536 - return f, nil + return nil } // Size returns the size of the flash chip in bytes. func (f *Flash) Size() int64 { - return f.size + return f.ArraySize } const maxTransferSize = 4096 @@ -98,7 +115,7 @@ func min(x, y int64) int64 { func (f *Flash) prepareAddress(addr int64) []byte { data := make([]byte, 4) binary.BigEndian.PutUint32(data, uint32(addr)) - if f.is4ba { + if f.Is4BA { return data } return data[1:] @@ -107,10 +124,10 @@ func (f *Flash) prepareAddress(addr int64) []byte { // ReadAt reads from the flash chip. func (f *Flash) ReadAt(p []byte, off int64) (int, error) { // This is a valid implementation of io.ReaderAt. - if off < 0 || off > f.size { + if off < 0 || off > f.ArraySize { return 0, io.EOF } - p = p[:min(int64(len(p)), f.size-off)] + p = p[:min(int64(len(p)), f.ArraySize-off)] // Split the transfer into maxTransferSize chunks. for i := 0; i < len(p); i += maxTransferSize { @@ -129,11 +146,13 @@ func (f *Flash) ReadAt(p []byte, off int64) (int, error) { func (f *Flash) writeAt(p []byte, off int64) (int, error) { if err := f.spi.Transfer([]spidev.Transfer{ // Enable writing. + {Tx: op.PRDRES.Bytes(), CSChange: true}, {Tx: op.WriteEnable.Bytes(), CSChange: true}, // Send the address. {Tx: append(op.PageProgram.Bytes(), f.prepareAddress(off)...)}, // Send the data. - {Tx: p}, + {Tx: p, CSChange: true}, + {Tx: op.WriteDisable.Bytes(), CSChange: true}, }); err != nil { return 0, err } @@ -148,13 +167,13 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { // what you want instead! func (f *Flash) WriteAt(p []byte, off int64) (int, error) { // This is a valid implementation of io.WriterAt. - if off < 0 || off > f.size { + if off < 0 || off > f.ArraySize { return 0, io.EOF } - p = p[:min(int64(len(p)), f.size-off)] + p = p[:min(int64(len(p)), f.ArraySize-off)] // Special case where no page boundaries are crossed. - if off%f.pageSize+int64(len(p)) <= f.pageSize { + if off%f.PageSize+int64(len(p)) <= f.PageSize { return f.writeAt(p, off) } @@ -162,16 +181,16 @@ func (f *Flash) WriteAt(p []byte, off int64) (int, error) { // 1. A partial page before the first aligned offset. (optional) // 2. All the aligned pages in the middle. // 3. A partial page after the last aligned offset. (optional) - firstAlignedOff := (off + f.pageSize - 1) / f.pageSize * f.pageSize - lastAlignedOff := (off + int64(len(p))) / f.pageSize * f.pageSize + firstAlignedOff := (off + f.PageSize - 1) / f.PageSize * f.PageSize + lastAlignedOff := (off + int64(len(p))) / f.PageSize * f.PageSize if off != firstAlignedOff { if n, err := f.writeAt(p[:firstAlignedOff-off], off); err != nil { return n, err } } - for i := firstAlignedOff; i < lastAlignedOff; i += f.pageSize { - if _, err := f.writeAt(p[i:i+f.pageSize], off+i); err != nil { + for i := firstAlignedOff; i < lastAlignedOff; i += f.PageSize { + if _, err := f.writeAt(p[i:i+f.PageSize], off+i); err != nil { return int(i), err } } @@ -191,22 +210,22 @@ func (f *Flash) ProgramAt(p []byte, off int64) (int, error) { // EraseAt erases n bytes from offset off. Both parameters must be aligned to // sectorSize. func (f *Flash) EraseAt(n int64, off int64) (int64, error) { - if off < 0 || off > f.size || off+n > f.size { + if off < 0 || off > f.ArraySize || off+n > f.ArraySize { return 0, io.EOF } - if (off%f.sectorSize != 0) || (n%f.sectorSize != 0) { + if (off%f.SectorSize != 0) || (n%f.SectorSize != 0) { return 0, fmt.Errorf("len(p) and off must be multiple of the sector size") } for i := int64(0); i < n; { opcode := op.SectorErase - eraseSize := f.sectorSize + eraseSize := f.SectorSize // Optimization to erase faster. - if i%f.blockSize == 0 && n-i > f.blockSize { + if i%f.BlockSize == 0 && n-i > f.BlockSize { opcode = op.BlockErase - eraseSize = f.blockSize + eraseSize = f.BlockSize } if err := f.spi.Transfer([]spidev.Transfer{ @@ -262,12 +281,13 @@ func (f *SFDPReader) ReadAt(p []byte, off int64) (int, error) { return 0, io.EOF } p = p[:min(int64(len(p)), sfdpMaxAddress-off)] - tx := append(op.ReadSFDP.Bytes(), + tx := []byte{ + byte(op.ReadSFDP), // offset, 3-bytes, big-endian - byte((off>>16)&0xff), byte((off>>8)&0xff), byte(off&0xff), + byte((off >> 16) & 0xff), byte((off >> 8) & 0xff), byte(off & 0xff), // dummy 0xff 0xff, - ) + } if err := f.spi.Transfer([]spidev.Transfer{ {Tx: tx}, {Rx: p}, diff --git a/pkg/flash/flash_linux_test.go b/pkg/flash/flash_linux_test.go index e40824777f..5aa6169628 100644 --- a/pkg/flash/flash_linux_test.go +++ b/pkg/flash/flash_linux_test.go @@ -15,6 +15,7 @@ import ( // TestSFDPReader tests reading arbitrary offsets from the SFDP. func TestSFDPReader(t *testing.T) { + fakeErr := errors.New("fake transfer error") for _, tt := range []struct { name string readOffset int64 @@ -40,16 +41,16 @@ func TestSFDPReader(t *testing.T) { name: "transfer error", readOffset: 0x10, readSize: 4, - forceTransferErr: errors.New("fake transfer error"), - wantNewErr: errors.New("could not read sfdp: fake transfer error"), + forceTransferErr: fakeErr, + wantNewErr: fakeErr, }, } { t.Run(tt.name, func(t *testing.T) { s := spimock.New() s.ForceTransferErr = tt.forceTransferErr f, err := New(s) - if gotErrString, wantErrString := fmt.Sprint(err), fmt.Sprint(tt.wantNewErr); gotErrString != wantErrString { - t.Errorf("flash.New() err = %q; want %q", gotErrString, wantErrString) + if !errors.Is(err, tt.wantNewErr) { + t.Errorf("flash.New() err = %v; want %v", err, tt.forceTransferErr) } if err != nil { return @@ -80,6 +81,10 @@ func TestSFDPReadDWORD(t *testing.T) { } sfdp := f.SFDP() + if sfdp == nil { + t.Fatalf("f.SFDP: got nil, want value") + } + dword, err := sfdp.Dword(0, 0) if err != nil { t.Error(err) diff --git a/pkg/flash/spimock/spimock_linux.go b/pkg/flash/spimock/spimock_linux.go index c38a29998d..a9fcb54150 100644 --- a/pkg/flash/spimock/spimock_linux.go +++ b/pkg/flash/spimock/spimock_linux.go @@ -12,6 +12,7 @@ import ( "io" "os" + "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/spidev" "golang.org/x/sys/unix" @@ -277,3 +278,10 @@ func (s *MockSPI) SetSpeedHz(hz uint32) error { s.SpeedHz = hz return nil } + +func (s *MockSPI) ID() (chips.ID, error) { + if s.ForceTransferErr != nil { + return -1, s.ForceTransferErr + } + return 0xbf2541, nil +} diff --git a/pkg/spidev/spidev_linux.go b/pkg/spidev/spidev_linux.go index 72e63efd0f..c89960f558 100644 --- a/pkg/spidev/spidev_linux.go +++ b/pkg/spidev/spidev_linux.go @@ -7,13 +7,17 @@ package spidev import ( + "bytes" "encoding/binary" "fmt" + "io" "math" "os" "runtime" "unsafe" + "github.com/u-root/u-root/pkg/flash/chips" + "github.com/u-root/u-root/pkg/flash/op" "golang.org/x/sys/unix" ) @@ -141,6 +145,12 @@ type Transfer struct { WordDelayUSecs uint8 } +func (t *Transfer) String() string { + var x [8]byte + n, _ := io.ReadAtLeast(bytes.NewBuffer(t.Tx), x[:], len(x)) + return fmt.Sprintf("%#02x...[:%d](%s)", x[:n], len(t.Tx), op.OpCode(x[0]).String()) +} + // ErrTxOverflow is returned if the Transfer buffer is too large. type ErrTxOverflow struct { TxLen, TxMax int @@ -177,24 +187,46 @@ type SPI struct { f *os.File // Used for mocking. syscall func(trap, a1, a2 uintptr, a3 unsafe.Pointer) (r1, r2 uintptr, err unix.Errno) + // logger allows logging + logger func(string, ...any) +} + +type opt func(s *SPI) error + +// WithLogger returns an opt which can be used in Open to add +// a logger. A common usage would be: +// spidev.Open("/dev/spidev0.0", WithLogger(log.Printf)) +func WithLogger(l func(string, ...any)) opt { + return func(s *SPI) error { + s.logger = l + return nil + } } // Open opens a new SPI device. dev is a filename such as "/dev/spidev0.0". // Remember to call Close() once done. -func Open(dev string) (*SPI, error) { +func Open(dev string, opts ...opt) (*SPI, error) { f, err := os.OpenFile(dev, os.O_RDWR, 0) if err != nil { return nil, err } - return &SPI{ - f: f, + s := &SPI{ + f: f, + logger: func(string, ...any) {}, // log.Printf, + //logger: log.Printf, // a3 must be an unsafe.Pointer instead of a uintptr, otherwise // we cannot mock out in the test without creating a race // condition. See `go doc unsafe.Pointer`. syscall: func(trap, a1, a2 uintptr, a3 unsafe.Pointer) (r1, r2 uintptr, err unix.Errno) { return unix.Syscall(trap, a1, a2, uintptr(a3)) }, - }, err + } + for _, o := range opts { + if err := o(s); err != nil { + return nil, err + } + } + return s, nil } // Close closes the SPI device. @@ -208,6 +240,7 @@ func (s *SPI) Transfer(transfers []Transfer) error { // Convert []Transfer to []iocTransfer. it := make([]iocTransfer, len(transfers)) for i, t := range transfers { + s.logger("%d:%s", i, t.String()) it[i] = iocTransfer{ speedHz: t.SpeedHz, delayUSecs: t.DelayUSecs, @@ -301,3 +334,30 @@ func (s *SPI) SetSpeedHz(hz uint32) error { } return nil } + +// ID gets the 24-bit id as an int +func (s *SPI) ID() (chips.ID, error) { + // Wake it up, then get the id. + // PRDRES is not universally handled on all devices, but that's ok. + // but CE MUST drop, so we structure this as two separate + // transfers to ensure that happens. + var id [4]byte + transfers := []Transfer{ + { + Tx: []byte{byte(op.PRDRES)}, + Rx: make([]byte, 1), + CSChange: true, + }, + { + Tx: []byte{byte(op.ReadJEDECID), 0, 0, 0}, + Rx: id[:], + }, + } + + if err := s.Transfer(transfers); err != nil { + return -1, err + } + + id[0] = 0 + return chips.ID(binary.BigEndian.Uint32(id[:])), nil +} diff --git a/pkg/spidev/spidev_linux_test.go b/pkg/spidev/spidev_linux_test.go index 7ab7552a9d..0cedd3f9cc 100644 --- a/pkg/spidev/spidev_linux_test.go +++ b/pkg/spidev/spidev_linux_test.go @@ -13,6 +13,8 @@ import ( "testing" "unsafe" + "github.com/u-root/u-root/pkg/flash/chips" + "github.com/u-root/u-root/pkg/flash/op" "golang.org/x/sys/unix" ) @@ -130,6 +132,20 @@ func TestGetters(t *testing.T) { } { m.forceErrno = tt.forceErrno + t.Run("ID"+tt.name, func(t *testing.T) { + m, err := s.ID() + if !errors.Is(err, tt.wantErr) { + t.Errorf("Mode() got error %q; want error %q", err, tt.wantErr) + } + if err != nil { + return + } + want := chips.ID(0) + if m != want { + t.Errorf("ID() = %#v; want %#v", m, want) + } + }) + t.Run("Mode"+tt.name, func(t *testing.T) { m, err := s.Mode() if !errors.Is(err, tt.wantErr) { @@ -243,6 +259,32 @@ func TestSetters(t *testing.T) { } } +func TestTransferString(t *testing.T) { + for _, tt := range []struct { + n string + t Transfer + s string + }{ + { + n: "empty", + t: Transfer{}, + s: "00...[:0](Unknown(00))", + }, + { + n: "Read with no data", + t: Transfer{Tx: op.Read.Bytes()}, + s: "0x03...[:1](Read)", + }, + } { + t.Run(tt.n, func(t *testing.T) { + s := tt.t.String() + if s != tt.s { + t.Fatalf("got %q, want %q", s, tt.s) + } + }) + } +} + // TestTransfer tests multiple scenarios involving the Transfer method. func TestTransfer(t *testing.T) { // To avoid OOMing the CI, we set the maxTransferSize to a smaller From 00ccdc3561b1e55c35dfa07155595a32939943a2 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 12 Jan 2024 23:23:17 -0800 Subject: [PATCH 009/109] pkg/flash:remove caps in error message Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 96cc6ad964..18c9138161 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -49,7 +49,7 @@ func New(spi SPI) (*Flash, error) { var id chips.ID id, err = f.spi.ID() if err != nil { - return nil, fmt.Errorf("Can not ID chip: %w", err) + return nil, fmt.Errorf("can not ID chip: %w", err) } c, err := chips.Lookup(id) if err == nil { From 1540d3afc15f83dbb6cad077a9fd348d700b85b1 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Sat, 13 Jan 2024 09:03:54 +0200 Subject: [PATCH 010/109] pkg/acpi: lift loop condition and remove Sprintf Signed-off-by: Siarhiej Siemianczuk --- pkg/acpi/raw.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/acpi/raw.go b/pkg/acpi/raw.go index 9929d0a668..887fb22c2e 100644 --- a/pkg/acpi/raw.go +++ b/pkg/acpi/raw.go @@ -31,10 +31,7 @@ var _ = Table(&Raw{}) // NewRaw returns a new Raw []Table fron a given byte slice. func NewRaw(b []byte) ([]Table, error) { var tab []Table - for { - if len(b) == 0 { - break - } + for len(b) != 0 { if len(b) < headerLength { return nil, fmt.Errorf("NewRaw: byte slice is only %d bytes and must be at least %d bytes", len(b), headerLength) } @@ -102,7 +99,7 @@ func (r *Raw) TableData() []byte { // Sig returns the table signature. func (r *Raw) Sig() string { - return fmt.Sprintf("%s", r.data[:4]) + return string(r.data[:4]) } // Len returns the total table length. From a660b435f051a1855727c47f5ae2d372428bb9f5 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sat, 13 Jan 2024 08:27:22 -0800 Subject: [PATCH 011/109] fwtools/spidev: use the ID function from pkg spidev Remove the code in the command, use ID from the package. Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 25 ++++-------------------- cmds/fwtools/spidev/spidev_linux_test.go | 4 ++-- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index aad6a03d93..da88e368b2 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -33,16 +33,15 @@ import ( flag "github.com/spf13/pflag" "github.com/u-root/u-root/pkg/flash" "github.com/u-root/u-root/pkg/flash/chips" - "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" ) type spi interface { Transfer([]spidev.Transfer) error + ID() (chips.ID, error) SetSpeedHz(uint32) error Close() error - ID() (chips.ID, error) } var ( @@ -86,27 +85,11 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) // Currently, only the raw subcommand is supported. switch cmd { case "id": - // Wake it up, then get the id. - // PRDRES is not universally handled on all devices, but that's ok. - // but CE MUST drop, so we structure this as two separate - // transfers to ensure that happens. - transfers := []spidev.Transfer{ - { - Tx: op.PRDRES.Bytes(), - Rx: make([]byte, 1), - CSChange: true, - }, - { - Tx: append(op.ReadJEDECID.Bytes(), 0, 0, 0), - Rx: make([]byte, 4), - }, - } - - if err := s.Transfer(transfers); err != nil { + id, err := s.ID() + if err != nil { return err } - - fmt.Fprintf(output, "%02x\n", transfers[1].Rx[1:]) + fmt.Fprintf(output, "%02x\n", id) return nil case "raw": diff --git a/cmds/fwtools/spidev/spidev_linux_test.go b/cmds/fwtools/spidev/spidev_linux_test.go index 9ee3990d6e..783d2b9934 100644 --- a/cmds/fwtools/spidev/spidev_linux_test.go +++ b/cmds/fwtools/spidev/spidev_linux_test.go @@ -33,7 +33,7 @@ func TestRun(t *testing.T) { { name: "id", args: []string{"id"}, - wantOutputRegex: regexp.MustCompile("[0-9a-fA-F]{6}\n"), + wantOutputRegex: regexp.MustCompile("[0-9a-fA-F]*\n"), }, { name: "id failing IO", @@ -119,7 +119,7 @@ func TestRun(t *testing.T) { got := run(tt.args, openFakeSpi, bytes.NewBuffer(tt.input), output) if !errors.Is(got, tt.err) { - t.Errorf("run(): %v != %v", got, tt.err) + t.Fatalf("run(): %v != %v", got, tt.err) } gotOutputString := output.String() From ab3534910cedb5709e20ba4c217293633c34d596 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Sun, 14 Jan 2024 07:55:31 +0200 Subject: [PATCH 012/109] pkg/impi/ocp: remove comparison with boolean constants Signed-off-by: Siarhiej Siemianczuk --- pkg/ipmi/ocp/ocp_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ipmi/ocp/ocp_linux.go b/pkg/ipmi/ocp/ocp_linux.go index c2545860ed..33a53e665f 100644 --- a/pkg/ipmi/ocp/ocp_linux.go +++ b/pkg/ipmi/ocp/ocp_linux.go @@ -341,12 +341,12 @@ func GetOemIpmiBootDriveInfo(si *smbios.Info) (*BootDriveInfo, error) { const bootDriveName = "Boot_Drive" var info BootDriveInfo - if boardManufacturerID, ok := OENMap[t1.Manufacturer]; ok == true { + if boardManufacturerID, ok := OENMap[t1.Manufacturer]; ok { info.ManufacturerID = boardManufacturerID } for index := 0; index < len(t9); index++ { - if strings.Contains(t9[index].SlotDesignation, bootDriveName) == false { + if !strings.Contains(t9[index].SlotDesignation, bootDriveName) { continue } From 447fb2a253936ed5b296bd516a8c66322cd095cf Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 03:42:00 +0000 Subject: [PATCH 013/109] Update vmtest Signed-off-by: Chris Koch --- go.mod | 2 +- go.sum | 6 +- vendor/github.com/hugelgupf/vmtest/README.md | 130 +++++++++++++++++- vendor/github.com/hugelgupf/vmtest/gotest.go | 37 +---- .../hugelgupf/vmtest/qemu/devices.go | 90 ++++++++++-- .../github.com/hugelgupf/vmtest/qemu/qemu.go | 62 ++++++--- .../gouinit/{gouinit.go => main_linux.go} | 39 ++++-- .../{shelluinit.go => main_linux.go} | 0 vendor/github.com/hugelgupf/vmtest/vmtest.go | 11 +- vendor/modules.txt | 4 +- 10 files changed, 290 insertions(+), 91 deletions(-) rename vendor/github.com/hugelgupf/vmtest/vminit/gouinit/{gouinit.go => main_linux.go} (93%) rename vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/{shelluinit.go => main_linux.go} (100%) diff --git a/go.mod b/go.mod index c801d6f075..28d2a63851 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.9.1-0.20230914180155-ee6cbcd136f8 github.com/google/uuid v1.3.0 - github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f + github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 github.com/kevinburke/ssh_config v1.1.0 diff --git a/go.sum b/go.sum index c3823b30be..86c9509d59 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFa github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f h1:ov45/OzrJG8EKbGjn7jJZQJTN7Z1t73sFYNIRd64YlI= -github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f/go.mod h1:JoDrYMZpDPYo6uH9/f6Peqms3zNNWT2XiGgioMOIGuI= +github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea h1:rSO4GiZ/EThUOkl1kFLB+DOGxa3oEgT8d8ZhrYbDIW8= +github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea/go.mod h1:3YxP4j/kQh5BzoobzCeSIVZOlz4te/CGVRxS9/NrwGU= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 h1:h+RKaNPjka7LRJGoeub/IQBdXSoEaJjfADkBq02hvjw= @@ -174,8 +174,6 @@ github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4AN github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nanmu42/limitio v1.0.0 h1:dpopBYPwUyLOPv+vsGja0iax+dG0SP9paTEmz+Sy7KU= github.com/nanmu42/limitio v1.0.0/go.mod h1:8H40zQ7pqxzbwZ9jxsK2hDoE06TH5ziybtApt1io8So= -github.com/ncruces/go-fs v0.2.2 h1:ak7h7jdihotXtXqjrBb2YZViJ+n41tLIqMG9ZY7bJMQ= -github.com/ncruces/go-fs v0.2.2/go.mod h1:07xkoGj//ID8iICNv3rcD2PtMjia3mABv1yZzdq7qZ8= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330 h1:zJBTzBuTR7EdFzmCGx0xp0pbOOb82sAh0+YUK4JTDEI= github.com/orangecms/go-framebuffer v0.0.0-20200613202404-a0700d90c330/go.mod h1:3Myb/UszJY32F2G7yGkUtcW/ejHpjlGfYLim7cv2uKA= diff --git a/vendor/github.com/hugelgupf/vmtest/README.md b/vendor/github.com/hugelgupf/vmtest/README.md index 1ec85c44de..298c936022 100644 --- a/vendor/github.com/hugelgupf/vmtest/README.md +++ b/vendor/github.com/hugelgupf/vmtest/README.md @@ -1,10 +1,91 @@ ## vmtest -[![CircleCI](https://circleci.com/gh/hugelgupf/vmtest.svg?style=svg)](https://circleci.com/gh/hugelgupf/vmtest) [![Go Report Card](https://goreportcard.com/badge/github.com/hugelgupf/vmtest)](https://goreportcard.com/report/github.com/hugelgupf/vmtest) [![GoDoc](https://godoc.org/github.com/hugelgupf/vmtest?status.svg)](https://godoc.org/github.com/hugelgupf/vmtest) -Fun stuff coming +vmtest is a Go API for launching QEMU VMs. + +* [The `qemu` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/qemu) + contains APIs for + + * launching QEMU processes + * configuring QEMU devices (such as a shared 9P directory, networking, + serial logging, etc) + * running tasks (goroutines) bound to the VM process lifetime, and + * using expect-scripting to check for outputs. + +* [The `uqemu` package](https://pkg.go.dev/github.com/hugelgupf/vmtest/uqemu) + can be used to configure a u-root initramfs to be used as the boot root file + system. + +* [The `vmtest` package](https://pkg.go.dev/github.com/hugelgupf/vmtest) + contains + + * a `testing.TB` wrapper around the `qemu` API with some safe defaults + (logging serial console to `t.Logf`, etc) + * an API for running shell scripts in the guest + * an API for running Go unit tests in the guest and collecting their + results. + +Out of these, the `vmtest` API is still the most raw and being iterated on. + +## Running Tests + +The `qemu` API picks up the following values from env vars by default: + +* `VMTEST_QEMU`: QEMU binary + arguments (e.g. + `VMTEST_QEMU="qemu-system-x86_64 -enable-kvm"`) +* `VMTEST_KERNEL`: Kernel to boot. +* `VMTEST_ARCH`: Guest architecture (same as GOARCH values). Must match the QEMU + binary supplied. If not supplied, defaults to `runtime.GOARCH`, i.e. it + matches the host's GOARCH. +* `VMTEST_TIMEOUT`: Timeout value (e.g. `1m20s` -- parsed by Go's + `time.ParseDuration`). +* `VMTEST_INITRAMFS`: Initramfs to boot. + +These values can be overriden in the Go API, but typically only +`VMTEST_INITRAMFS` and `VMTEST_TIMEOUT` are. + +The `runvmtest` tool automatically downloads `VMTEST_QEMU` and +`VMTEST_KERNEL` for use with tests based on a provided `VMTEST_ARCH`. E.g. + +```sh +go install github.com/hugelgupf/vmtest/tools/runvmtest@latest + +# See how it works: +runvmtest -- bash -c "echo \$VMTEST_KERNEL -- \$VMTEST_QEMU" + +# Intended usage: +runvmtest -- go test -v ./tests/gohello + +# Or run an Arm64 guest: +VMTEST_ARCH=arm64 runvmtest -- go test -v ./tests/gohello +``` + +You can also override one or both, which will just be passed through: + +```sh +# Will only download VMTEST_KERNEL. +VMTEST_ARCH=arm64 VMTEST_QEMU="qemu-system-aarch64 -enable-kvm" runvmtest -- go test -v ./tests/gohello +``` + +To keep the artifacts around locally to reproduce the same test: + +```s +runvmtest --keep-artifacts -- go test -v ./tests/gohello +``` + +The default kernel and QEMU supplied by `runvmtest` may of course not work well +for your tests. You can configure `runvmtest` to supply your own `VMTEST_KERNEL` +and `VMTEST_QEMU` -- but also any additional environment variables. See +[`runvmtest` configuration](#custom-runvmtest-configuration). + +To build your own kernel or QEMU, check out +[images/kernel-arm64](./images/kernel-arm64) for building a kernel-image-only +Docker image, and [images/qemu](./images/qemu/Dockerfile) for how we build a +Docker image with just QEMU binaries and their dependencies. + +## Writing Tests ### Example: qemu API @@ -46,7 +127,6 @@ func TestStartVM(t *testing.T) { ```go func TestStartVM(t *testing.T) { - l := &ulogtest.Logger{TB: t} initramfs := uroot.Opts{ TempDir: t.TempDir(), InitCmd: "init", @@ -62,7 +142,7 @@ func TestStartVM(t *testing.T) { } vm, err := qemu.Start( qemu.ArchUseEnvv, - uqemu.WithUrootInitramfs(l, initramfs, filepath.Join(t.TempDir(), "initramfs.cpio")), + uqemu.WithUrootInitramfsT(t, initramfs), // Other options... ) @@ -87,8 +167,50 @@ func TestStartVM(t *testing.T) { return exec.CommandContext(ctx, "sleep", "900").Run() }, ), + + // Task that runs when the VM exits. + qemu.WithTask(qemu.Cleanup(func() error { + // Do something. + return fmt.Errorf("this is returned by vm.Wait()") + })), + + // Task that only runs when VM starts. + qemu.WithTask(qemu.WaitVMStarted(...)), ) // ... } ``` +### Example: vmtest API + +See [tests/startexample](./tests/startexample/vm_test.go) + +### Example: Go unit tests in VM + +See [tests/gobench](./tests/gobench/bench_test.go) + +## Custom runvmtest configuration + +`runvmtest` tries to read a config from `.vmtest.yaml` in the current working +directory or any of its parents. + +Given this is a Go-based test framework, the recommendation would be to place +`.vmtest.yaml` in the same directory as your `go.mod` so that the config is +available anywhere `go test` is for that module. + +`runvmtest` can be configured to set up any number of environment variables. +Config format looks like this: + +``` +VMTEST_ARCH: + ENV_VAR: + container: + template: "{{.somedir}} -foobar {{.somefile}}" + files: + somefile: + directories: + somedir: +``` + +Check out the example in +[tools/runvmtest/example-vmtest.yaml](./tools/runvmtest/example-vmtest.yaml). diff --git a/vendor/github.com/hugelgupf/vmtest/gotest.go b/vendor/github.com/hugelgupf/vmtest/gotest.go index 63dfeb29f2..f2de2150c9 100644 --- a/vendor/github.com/hugelgupf/vmtest/gotest.go +++ b/vendor/github.com/hugelgupf/vmtest/gotest.go @@ -19,6 +19,7 @@ import ( "github.com/hugelgupf/vmtest/testtmp" "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/uroot" + "github.com/u-root/uio/cp" "golang.org/x/tools/go/packages" ) @@ -158,21 +159,11 @@ func RunGoTestsInVM(t testing.TB, pkgs []string, opts ...GoTestOpt) { } // Set up u-root build options. - env := golang.Default(golang.DisableCGO(), golang.WithGOARCH(string(qemu.GuestArch().Arch()))) + env := golang.Default(golang.DisableCGO(), golang.WithGOARCH(string(qemu.GuestArch()))) // Statically build tests and add them to the temporary directory. testDir := filepath.Join(sharedDir, "tests") - if len(vmCoverProfile) > 0 { - f, err := os.Create(filepath.Join(sharedDir, "coverage.profile")) - if err != nil { - t.Fatalf("Could not create coverage file %v", err) - } - if err := f.Close(); err != nil { - t.Fatalf("Could not close coverage.profile: %v", err) - } - } - // Compile the Go tests. Place the test binaries in a directory that // will be shared with the VM using 9P. for _, pkg := range goOpts.Packages { @@ -222,34 +213,14 @@ func RunGoTestsInVM(t testing.TB, pkgs []string, opts ...GoTestOpt) { CollectKernelCoverage(), }, goOpts.VMOpts...)...) - if _, err := vm.Console.ExpectString("TESTS PASSED MARKER"); err != nil { - t.Errorf("Waiting for 'TESTS PASSED MARKER' signal: %v", err) - } - if err := vm.Wait(); err != nil { t.Errorf("VM exited with %v", err) } // Collect Go coverage. if len(vmCoverProfile) > 0 { - cov, err := os.Open(filepath.Join(sharedDir, "coverage.profile")) - if err != nil { - t.Fatalf("No coverage file shared from VM: %v", err) - } - - out, err := os.OpenFile(vmCoverProfile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) - if err != nil { - t.Fatalf("Could not open vmcoverageprofile: %v", err) - } - - if _, err := io.Copy(out, cov); err != nil { - t.Fatalf("Error copying coverage: %s", err) - } - if err := out.Close(); err != nil { - t.Fatalf("Could not close vmcoverageprofile: %v", err) - } - if err := cov.Close(); err != nil { - t.Fatalf("Could not close coverage.profile: %v", err) + if err := cp.Copy(filepath.Join(sharedDir, "coverage.profile"), vmCoverProfile); err != nil { + t.Errorf("Could not copy coverage file: %v", err) } } diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go index bbc7532c55..46ba5a48bd 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go @@ -20,6 +20,15 @@ import ( "github.com/hugelgupf/vmtest/internal/eventchannel" ) +// ErrInvalidDir is used when no directory is specified for file sharing. +var ErrInvalidDir = errors.New("no directory specified") + +// ErrInvalidTag is used when no tag is specified for 9P file system sharing. +var ErrInvalidTag = errors.New("no tag specified for 9P file system") + +// ErrIsNotDir is used when the directory specified for file sharing is not a directory. +var ErrIsNotDir = errors.New("file system sharing requires directory") + // IDAllocator is used to ensure no overlapping QEMU option IDs. type IDAllocator struct { // maps a prefix to the maximum used suffix number. @@ -46,7 +55,16 @@ func (a *IDAllocator) ID(prefix string) string { func ReadOnlyDirectory(dir string) Fn { return func(alloc *IDAllocator, opts *Options) error { if len(dir) == 0 { - return nil + return ErrInvalidDir + } + if fi, err := os.Stat(dir); err != nil { + return fmt.Errorf("cannot access directory %s to be shared with guest: %w", dir, err) + } else if !fi.IsDir() { + return &os.PathError{ + Op: "9P-directory-sharing", + Path: dir, + Err: fmt.Errorf("%w: is %s", ErrIsNotDir, fi.Mode().Type()), + } } drive := alloc.ID("drive") @@ -65,8 +83,8 @@ func ReadOnlyDirectory(dir string) Fn { // IDEBlockDevice emulates an AHCI/IDE block device. func IDEBlockDevice(file string) Fn { return func(alloc *IDAllocator, opts *Options) error { - if len(file) == 0 { - return nil + if _, err := os.Stat(file); err != nil { + return fmt.Errorf("cannot access file %s to be shared with guest: %w", file, err) } drive := alloc.ID("drive") @@ -105,15 +123,19 @@ func P9BootDirectory(dir string) Fn { func p9Directory(dir string, boot bool, tag string) Fn { return func(alloc *IDAllocator, opts *Options) error { if len(dir) == 0 { - return fmt.Errorf("no directory specified for shared 9P file system") + return fmt.Errorf("%w for shared 9P file system", ErrInvalidDir) } if len(tag) == 0 { - return fmt.Errorf("a tag must be specified for 9P file system") + return ErrInvalidTag } if fi, err := os.Stat(dir); err != nil { - return fmt.Errorf("cannot access directory %s to be shared with guest: %v", dir, err) + return fmt.Errorf("cannot access directory %s to be shared with guest: %w", dir, err) } else if !fi.IsDir() { - return fmt.Errorf("directory %s to be shared with guest is not a directory, is %s", dir, fi.Mode().Type()) + return &os.PathError{ + Op: "9P-directory-sharing", + Path: dir, + Err: fmt.Errorf("%w: is %s", ErrIsNotDir, fi.Mode().Type()), + } } var id string @@ -146,9 +168,6 @@ func p9Directory(dir string, boot bool, tag string) Fn { "rootfstype=9p", "rootflags=trans=virtio,version=9p2000.L", ) - } else { - // seen as an env var by the init process - opts.AppendKernel("UROOT_USE_9P=1") } return nil } @@ -203,7 +222,10 @@ func ServeHTTP(s *http.Server, l net.Listener) Fn { }) opts.Tasks = append(opts.Tasks, func(ctx context.Context, n *Notifications) error { // Wait for VM exit. - <-n.VMExited + select { + case <-n.VMExited: + case <-ctx.Done(): + } // Stop HTTP server. return s.Close() }) @@ -395,3 +417,49 @@ func Cleanup(f func() error) Task { return f() } } + +// ByArch applies only the Fn config function applicable to the VM guest +// architecture. +func ByArch(m map[Arch]Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + a := opts.Arch() + fn, ok := m[a] + if !ok { + return nil + } + return fn(alloc, opts) + } +} + +// IfNotArch applies fn only if the VM guest arch is not the given arch. +func IfNotArch(arch Arch, fn Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + if opts.Arch() == arch { + return nil + } + return fn(alloc, opts) + } +} + +// IfArch applies fn only if the VM guest arch is the given arch. +func IfArch(arch Arch, fn Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + if opts.Arch() == arch { + return fn(alloc, opts) + } + return nil + } +} + +// All applies all given configurators in order. If an error occurs, it returns +// the error early. +func All(fn ...Fn) Fn { + return func(alloc *IDAllocator, opts *Options) error { + for _, f := range fn { + if err := f(alloc, opts); err != nil { + return err + } + } + return nil + } +} diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go index 0d7c0cdd3a..d11c802be5 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go @@ -40,12 +40,12 @@ import ( // ErrKernelRequiredForArgs is returned when KernelArgs is populated but Kernel is empty. var ErrKernelRequiredForArgs = errors.New("KernelArgs can only be used when Kernel is also specified due to how QEMU bootloader works") -// ErrNoArch is returned when neither Arch nor VMTEST_ARCH are set. -var ErrNoArch = errors.New("no guest architecture specified -- guest arch is required to decide some QEMU command-line arguments") - // ErrUnsupportedArch is returned when an unsupported guest architecture value is used. var ErrUnsupportedArch = errors.New("unsupported guest architecture specified -- guest arch is required to decide some QEMU command-line arguments") +// ErrInvalidTimeout is returned when VMTEST_TIMEOUT could not be parsed. +var ErrInvalidTimeout = errors.New("could not parse VMTEST_TIMEOUT") + // Arch is the QEMU guest architecture. type Arch string @@ -65,6 +65,9 @@ const ( // ArchArm is the arm 32bit architecture. ArchArm Arch = "arm" + + // ArchRiscv64 is the riscv 64bit architecture. + ArchRiscv64 Arch = "riscv64" ) // SupportedArches are the supported guest architecture values. @@ -73,6 +76,7 @@ var SupportedArches = []Arch{ ArchI386, ArchArm64, ArchArm, + ArchRiscv64, } // GuestArch returns the Guest architecture under test. Either VMTEST_ARCH or @@ -89,14 +93,6 @@ func (g Arch) Valid() bool { return slices.Contains(SupportedArches, g) } -// Arch returns the guest architecture. -func (g Arch) Arch() Arch { - if g == ArchUseEnvv { - g = GuestArch() - } - return g -} - // Fn is a QEMU configuration option supplied to Start or OptionsFor. // // Fns rely on a QEMU architecture already having been determined. @@ -179,7 +175,7 @@ func OptionsFor(arch Arch, fns ...Fn) (*Options, error) { var err error vmTimeout, err = time.ParseDuration(d) if err != nil { - return nil, fmt.Errorf("invalid VMTEST_TIMEOUT value: %w", err) + return nil, fmt.Errorf("%w: %v", ErrInvalidTimeout, err) } } @@ -192,7 +188,7 @@ func OptionsFor(arch Arch, fns ...Fn) (*Options, error) { QEMUArgs: []string{"-nographic"}, } - if err := o.setArch(arch.Arch()); err != nil { + if err := o.setArch(arch); err != nil { return nil, err } @@ -233,7 +229,7 @@ type Options struct { // If empty, VMTEST_QEMU_ARCH env var will be used. arch Arch - // QEMUCommand is QEMU binary to invoke and some additonal args. + // QEMUCommand is QEMU binary to invoke and some additional args. // // If empty, the VMTEST_QEMU env var will be used. QEMUCommand string @@ -262,6 +258,8 @@ type Options struct { // QEMU subprocess exits. When the context is canceled, the QEMU // subprocess is expected to exit as well, and when the QEMU subprocess // exits, the context is canceled. + // + // Tasks may depend on ExtraFiles or SerialOutput to be closed to exit. Tasks []Task // Additional QEMU cmdline arguments. @@ -373,9 +371,22 @@ func (o *Options) Start(ctx context.Context) (*VM, error) { if err := cmd.Start(); err != nil { // Cancel tasks. cancel() - // Wait for tasks to exit. Some day we'll report their errors - // with errors.Join. + + // Unblock tasks that may depend on these files. + vm.Console.Close() + for _, w := range vm.Options.SerialOutput { + w.Close() + } + for _, c := range o.ExtraFiles { + c.Close() + } + + // Wait for tasks to exit. _ = vm.taskWG.Wait() + + // Close these after tasks have exited to guarantee that tasks + // use context cancelation or closing of their inputs to unblock. + vm.notifs.closeAll() return nil, err } vm.notifs.vmStarted() @@ -403,8 +414,8 @@ func (o *Options) Start(ctx context.Context) (*VM, error) { } func (o *Options) setArch(arch Arch) error { - if len(arch) == 0 { - return ErrNoArch + if arch == ArchUseEnvv { + arch = GuestArch() } if !arch.Valid() { return fmt.Errorf("%w: %s", ErrUnsupportedArch, arch) @@ -517,6 +528,14 @@ func (v *VM) Waited() bool { func (v *VM) Wait() error { v.waitCalled.Store(true) + // If there is a lot of output after the last user's Expect call (or + // there are no Expect calls at all), the pty buffer may fill up and + // the guest is blocked from writing anything and from continuing + // execution. + // + // Therefore, drain! EOF should happen when the guest exits. + _, _ = v.Console.ExpectEOF() + <-v.wait v.waitMu.Lock() @@ -565,3 +584,10 @@ func (n notifications) vmExited(err error) { close(m.VMExited) } } + +func (n notifications) closeAll() { + for _, m := range n { + close(m.VMStarted) + close(m.VMExited) + } +} diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go similarity index 93% rename from vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go rename to vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go index c6c5c31d39..8057fb55f2 100644 --- a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/gouinit.go +++ b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go @@ -75,18 +75,36 @@ func AppendFile(srcFile, targetFile string) error { func runTest() error { flag.Parse() - cleanup, err := guest.MountSharedDir() + // If these fail, the host will be missing the "Done" event from + // testEvents, or possibly even the errors.json file and fail. + mp, err := guest.Mount9PDir("/gotestdata", "gotests") if err != nil { return err } - defer cleanup() - defer guest.CollectKernelCoverage() + defer func() { _ = mp.Unmount(0) }() - mp, err := guest.Mount9PDir("/gotestdata", "gotests") + testEvents, err := guest.EventChannel[testevent.ErrorEvent]("/gotestdata/errors.json") if err != nil { return err } - defer func() { _ = mp.Unmount(0) }() + defer testEvents.Close() + + if err := run(testEvents); err != nil { + _ = testEvents.Emit(testevent.ErrorEvent{ + Error: fmt.Sprintf("running tests failed: %v", err), + }) + return err + } + return nil +} + +func run(testEvents *guest.Emitter[testevent.ErrorEvent]) error { + cleanup, err := guest.MountSharedDir() + if err != nil { + return err + } + defer cleanup() + defer guest.CollectKernelCoverage() var envv []string if tag := os.Getenv("VMTEST_GOCOVERDIR"); tag != "" { @@ -105,12 +123,6 @@ func runTest() error { } defer goTestEvents.Close() - testEvents, err := guest.EventChannel[testevent.ErrorEvent]("/gotestdata/errors.json") - if err != nil { - return err - } - defer testEvents.Close() - return walkTests("/gotestdata/tests", func(path, pkgName string) { // Send the kill signal with a 500ms grace period. ctx, cancel := context.WithTimeout(context.Background(), *individualTestTimeout+500*time.Millisecond) @@ -198,11 +210,10 @@ func runTest() error { } func main() { + flag.Parse() + if err := runTest(); err != nil { log.Printf("Tests failed: %v", err) - } else { - // The test infra is expecting this exact print. - log.Print("TESTS PASSED MARKER") } if err := unix.Reboot(unix.LINUX_REBOOT_CMD_POWER_OFF); err != nil { diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/shelluinit.go b/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go similarity index 100% rename from vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/shelluinit.go rename to vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go diff --git a/vendor/github.com/hugelgupf/vmtest/vmtest.go b/vendor/github.com/hugelgupf/vmtest/vmtest.go index 57b7455744..facf438399 100644 --- a/vendor/github.com/hugelgupf/vmtest/vmtest.go +++ b/vendor/github.com/hugelgupf/vmtest/vmtest.go @@ -178,7 +178,12 @@ func WithInitramfsFiles(files ...string) Opt { } } -// WithSharedDir shares a directory with the VM. +// WithSharedDir shares a directory with the QEMU VM using 9P using the +// tag "tmpdir". +// +// guest.MountSharedDir mounts this directory at /testdata. +// +// If none is set, no directory is shared with the guest by default. func WithSharedDir(dir string) Opt { return func(_ testing.TB, v *VMOptions) error { v.SharedDir = dir @@ -224,9 +229,7 @@ func startVM(t testing.TB, o *VMOptions) *qemu.VM { qemu.LogSerialByLine(qemu.PrintLineWithPrefix(o.ConsoleOutputPrefix, t.Logf)), // Tests use this env var to identify they are running inside a // vmtest using SkipIfNotInVM. - // - // Older tests use the presence of uroot.vmtest in the kernel command-line. - qemu.WithAppendKernel("VMTEST_IN_GUEST=1", "uroot.vmtest"), + qemu.WithAppendKernel("VMTEST_IN_GUEST=1"), qemu.VirtioRandom(), } if o.SharedDir != "" { diff --git a/vendor/modules.txt b/vendor/modules.txt index d548f84cdc..31f00187c0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -127,8 +127,8 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror -# github.com/hugelgupf/vmtest v0.0.0-20240102225328-693afabdd27f -## explicit; go 1.18 +# github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea +## explicit; go 1.21 github.com/hugelgupf/vmtest github.com/hugelgupf/vmtest/guest github.com/hugelgupf/vmtest/internal/eventchannel From 246859908c50c1d068d25ff971860757795a053d Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 03:43:14 +0000 Subject: [PATCH 014/109] gotest: fix blocklist Signed-off-by: Chris Koch --- integration/gotests/gotest_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/integration/gotests/gotest_test.go b/integration/gotests/gotest_test.go index ad1a2f74c5..aa32a808d0 100644 --- a/integration/gotests/gotest_test.go +++ b/integration/gotests/gotest_test.go @@ -10,6 +10,7 @@ package integration import ( "os" "os/exec" + "slices" "strings" "testing" "time" @@ -34,7 +35,7 @@ func testPkgs(t *testing.T) []string { if err != nil { t.Fatal(err) } - pkgs := strings.Fields(strings.TrimSpace(string(out))) + allPkgs := strings.Fields(strings.TrimSpace(string(out))) // TODO: Some tests do not run properly in QEMU at the moment. They are // blocklisted. These tests fail for mostly two reasons: @@ -88,14 +89,13 @@ func testPkgs(t *testing.T) []string { "github.com/u-root/u-root/pkg/vfile", ) } - for i := 0; i < len(pkgs); i++ { - for _, b := range blocklist { - if pkgs[i] == b { - pkgs = append(pkgs[:i], pkgs[i+1:]...) - } + + var pkgs []string + for _, p := range allPkgs { + if !slices.Contains(blocklist, p) { + pkgs = append(pkgs, p) } } - return pkgs } From e9808c604d9756a640c56d05d8776e7cafd88d68 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 03:43:50 +0000 Subject: [PATCH 015/109] Replace uroot.vmtest with VMTEST_IN_GUEST Signed-off-by: Chris Koch --- pkg/testutil/testutil.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 5f68623d7b..96b4f87a33 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -15,7 +15,6 @@ import ( "time" "github.com/u-root/gobusybox/src/pkg/golang" - "github.com/u-root/u-root/pkg/cmdline" ) // CheckError is a helper function for tests @@ -154,11 +153,8 @@ func Run(m *testing.M, mainFn func()) { } // SkipIfInVMTest skips a test if it's being executed in a u-root test VM. -// -// See pkg/vmtest/integration.go which starts the VM with the uroot.vmtest in -// the kernel cmdline. func SkipIfInVMTest(t *testing.T) { - if cmdline.ContainsFlag("uroot.vmtest") { + if os.Getenv("VMTEST_IN_GUEST") == "1" { t.Skipf("Skipping test since we are in a u-root test VM") } } From 786434264cf637d6317bb40d6e0ec68c8dc39d38 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 03:44:21 +0000 Subject: [PATCH 016/109] Remove 15 minute timeout from VM tests Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2863135f6e..c911cf022b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,6 @@ jobs: GOCOVERDIR=$(pwd)/gocov \ VMTEST_GO_PROFILE=vmcoverage.txt runvmtest -- \ go test -v -covermode=atomic ${{ matrix.extra-arg }} \ - -timeout=15m \ -coverprofile=coverage.txt ./${{ matrix.pattern }} - uses: codecov/codecov-action@v4-beta From dd87f4446cec66b8e934642f2c623366235eae82 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 04:21:11 +0000 Subject: [PATCH 017/109] Run arm tests on Go 1.21 Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c911cf022b..5002841db7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,6 +20,7 @@ jobs: vmarch: [ 'amd64', 'arm64' ] pattern: [ 'integration/generic-tests/...', 'integration/gotests/...' ] extra-arg: [ '' ] + goarm: [ '' ] include: # Only run these with VM arch amd64 for now. - go-version: '1.21.x' @@ -29,10 +30,11 @@ jobs: vmarch: 'amd64' pattern: 'pkg/...' extra-arg: '-coverpkg=./pkg/...' - # This doesn't pass on 1.21.x for some reason yet. - - go-version: '1.20' + # QEMU's -M virt only supports GOARM=5 + - go-version: '1.21.x' vmarch: arm pattern: 'integration/generic-tests/...' + goarm: '5' # Root dir tests - go-version: '1.21.x' vmarch: 'amd64' @@ -41,6 +43,7 @@ jobs: env: GO_VERSION: ${{ matrix.go-version }} VMTEST_ARCH: ${{ matrix.vmarch }} + GOARM: ${{ matrix.goarm }} steps: - uses: actions/checkout@v4 From e1cd18df2094dccfb7af5d9eb069382bd4a5f99e Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 02:45:23 +0000 Subject: [PATCH 018/109] Enable Go VM tests on arm Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 16 ++++++++-------- integration/gotests/gotest_test.go | 22 ++++++++++++++++------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5002841db7..8fe70c79e0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,29 +17,29 @@ jobs: strategy: matrix: go-version: [ '1.21.x' ] - vmarch: [ 'amd64', 'arm64' ] + vmarch: [ 'amd64', 'arm', 'arm64' ] pattern: [ 'integration/generic-tests/...', 'integration/gotests/...' ] - extra-arg: [ '' ] - goarm: [ '' ] include: # Only run these with VM arch amd64 for now. - go-version: '1.21.x' vmarch: 'amd64' pattern: 'cmds/...' + - go-version: '1.21.x' vmarch: 'amd64' pattern: 'pkg/...' extra-arg: '-coverpkg=./pkg/...' - # QEMU's -M virt only supports GOARM=5 - - go-version: '1.21.x' - vmarch: arm - pattern: 'integration/generic-tests/...' - goarm: '5' + # Root dir tests - go-version: '1.21.x' vmarch: 'amd64' pattern: '.' + # QEMU's -M virt only supports GOARM=5, so add goarm=5 only for + # arm configs + - vmarch: arm + goarm: '5' + env: GO_VERSION: ${{ matrix.go-version }} VMTEST_ARCH: ${{ matrix.vmarch }} diff --git a/integration/gotests/gotest_test.go b/integration/gotests/gotest_test.go index aa32a808d0..2feefd9ede 100644 --- a/integration/gotests/gotest_test.go +++ b/integration/gotests/gotest_test.go @@ -76,9 +76,9 @@ func testPkgs(t *testing.T) []string { "github.com/u-root/u-root/pkg/tss", "github.com/u-root/u-root/pkg/syscallfilter", } - if qemu.GuestArch() == qemu.ArchArm64 { - blocklist = append( - blocklist, + switch qemu.GuestArch() { + case qemu.ArchArm64: + blocklist = append(blocklist, "github.com/u-root/u-root/pkg/strace", // These tests run in 1-2 seconds on x86, but run @@ -88,6 +88,17 @@ func testPkgs(t *testing.T) []string { "github.com/u-root/u-root/cmds/exp/cbmem", "github.com/u-root/u-root/pkg/vfile", ) + + case qemu.ArchArm: + blocklist = append(blocklist, + "github.com/u-root/u-root/cmds/exp/cbmem", + + // These 4 tests do not compile on arm. + "github.com/u-root/u-root/pkg/boot/kexec", + "github.com/u-root/u-root/pkg/flash/chips", + "github.com/u-root/u-root/pkg/mount/gpt", + "github.com/u-root/u-root/pkg/mount/mtd", + ) } var pkgs []string @@ -102,8 +113,6 @@ func testPkgs(t *testing.T) []string { // TestGoTest effectively runs "go test ./..." inside a QEMU instance. The // tests run as root and can do all sorts of things not possible otherwise. func TestGoTest(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - pkgs := testPkgs(t) vmtest.RunGoTestsInVM(t, pkgs, @@ -127,7 +136,8 @@ func TestGoTest(t *testing.T) { // e.g. pkg/mount/gpt/gpt_test.go need to claim 4.29G // // disk = make([]byte, 0x100000000) - qemu.ArbitraryArgs("-m", "6G"), + qemu.IfNotArch(qemu.ArchArm, qemu.ArbitraryArgs("-m", "6G")), + qemu.IfArch(qemu.ArchArm, qemu.ArbitraryArgs("-m", "3G")), // aarch64 VMs start at 1970-01-01 without RTC explicitly set. qemu.ArbitraryArgs("-rtc", "base=localtime,clock=vm"), From 74fd0db9928b63ffd03b01cd51686395b978b8e0 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Mon, 15 Jan 2024 04:54:42 +0000 Subject: [PATCH 019/109] Enable DHCP/PXE tests for Arm Signed-off-by: Chris Koch --- integration/generic-tests/dhclient_test.go | 6 ------ integration/generic-tests/pxeboot_test.go | 3 --- 2 files changed, 9 deletions(-) diff --git a/integration/generic-tests/dhclient_test.go b/integration/generic-tests/dhclient_test.go index 17f99a38f8..7130c36c57 100644 --- a/integration/generic-tests/dhclient_test.go +++ b/integration/generic-tests/dhclient_test.go @@ -25,8 +25,6 @@ import ( // TestDhclientQEMU4 uses QEMU's DHCP server to test dhclient. func TestDhclientQEMU4(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - // Create the file to download dir := t.TempDir() want := "Hello, world!" @@ -86,8 +84,6 @@ func TestDhclientQEMU4(t *testing.T) { } func TestDhclientTimesOut(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - testCmds := []string{ "dhclient -v -retry 2 -timeout 10", "echo \"DHCP timed out\"", @@ -125,8 +121,6 @@ func TestDhclientTimesOut(t *testing.T) { } func TestDhclient6(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - serverCmds := []string{ "ip link set eth0 up", "pxeserver -6 -your-ip6=fec0::3 -4=false", diff --git a/integration/generic-tests/pxeboot_test.go b/integration/generic-tests/pxeboot_test.go index 88e5fedd76..5cadc61206 100644 --- a/integration/generic-tests/pxeboot_test.go +++ b/integration/generic-tests/pxeboot_test.go @@ -20,9 +20,6 @@ import ( // TestPxeboot runs a server and client to test pxebooting a node. func TestPxeboot4(t *testing.T) { - // TODO: support arm - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - serverCmds := []string{ "ip addr add 192.168.0.1/24 dev eth0", "ip link set eth0 up", From 7d76d5d91b10a216cae143d6930e71173b5a8090 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sat, 13 Jan 2024 13:38:40 -0800 Subject: [PATCH 020/109] flash: used the SPI ID function insteand of hand coding ReadID Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 18c9138161..af11e7c8f4 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -247,18 +247,8 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { // ReadJEDECID reads the flash chip's JEDEC ID. func (f *Flash) ReadJEDECID() (uint32, error) { - tx := op.ReadJEDECID.Bytes() - rx := make([]byte, 3) - - if err := f.spi.Transfer([]spidev.Transfer{ - {Tx: tx}, - {Rx: rx}, - }); err != nil { - return 0, err - } - - // Little-endian - return (uint32(rx[0]) << 16) | (uint32(rx[1]) << 8) | uint32(rx[2]), nil + id, err := f.spi.ID() + return uint32(id), err } // SFDPReader is used to read from the SFDP address space. From 41730c75bb0ea9f67b9465d4e0ce4e67738e010d Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sat, 13 Jan 2024 13:55:58 -0800 Subject: [PATCH 021/109] pkg/spidev: set safe initial settings when opened It is critical to set some settings to "safe" values when starting up. For now, the only one we've found is speed. The spidev command was setting it, but it is more properly set in spidev Open() Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 10 ++++++---- cmds/fwtools/spidev/spidev_linux_test.go | 2 +- pkg/spidev/spidev_linux.go | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index da88e368b2..f18922ecb6 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -13,7 +13,7 @@ // Options: // // -D DEV: spidev device (default /dev/spidev0.0) -// -s SPEED: max speed in Hz (default 500000) +// -s SPEED: max speed in Hz (default whatever the spi package sets) // // Description: // @@ -59,7 +59,7 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) // Parse args. fs := flag.NewFlagSet("spidev", flag.ContinueOnError) dev := fs.StringP("device", "D", "/dev/spidev0.0", "spidev device") - speed := fs.Uint32P("speed", "s", 500000, "max speed in Hz") + speed := fs.Uint32P("speed", "s", 0, "max speed in Hz") if err := fs.Parse(args); err != nil { if err == flag.ErrHelp { return fmt.Errorf("%w:", errCommand) @@ -77,8 +77,10 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) return err } defer s.Close() - if err := s.SetSpeedHz(*speed); err != nil { - return err + if *speed != 0 { + if err := s.SetSpeedHz(*speed); err != nil { + return err + } } cmd := fs.Arg(0) diff --git a/cmds/fwtools/spidev/spidev_linux_test.go b/cmds/fwtools/spidev/spidev_linux_test.go index 783d2b9934..8d14334bc6 100644 --- a/cmds/fwtools/spidev/spidev_linux_test.go +++ b/cmds/fwtools/spidev/spidev_linux_test.go @@ -71,7 +71,7 @@ func TestRun(t *testing.T) { }, { name: "setspeedhz error", - args: []string{"raw"}, + args: []string{"-s", "1", "raw"}, input: []byte("abcd"), ForceSetSpeedHzErr: os.ErrInvalid, err: os.ErrInvalid, diff --git a/pkg/spidev/spidev_linux.go b/pkg/spidev/spidev_linux.go index c89960f558..d8cd1dacbc 100644 --- a/pkg/spidev/spidev_linux.go +++ b/pkg/spidev/spidev_linux.go @@ -203,6 +203,16 @@ func WithLogger(l func(string, ...any)) opt { } } +// safe sets "safe" settings for initial SPI operation. +// Some SPI settings are required for proper initial +// operation. +func (s *SPI) safe() error { + if err := s.SetSpeedHz(500000); err != nil { + return err + } + return nil +} + // Open opens a new SPI device. dev is a filename such as "/dev/spidev0.0". // Remember to call Close() once done. func Open(dev string, opts ...opt) (*SPI, error) { @@ -221,6 +231,11 @@ func Open(dev string, opts ...opt) (*SPI, error) { return unix.Syscall(trap, a1, a2, uintptr(a3)) }, } + + if err := s.safe(); err != nil { + return nil, err + } + for _, o := range opts { if err := o(s); err != nil { return nil, err From 2458675b4cf5e4095f3eac356e3422ab402d7480 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sun, 14 Jan 2024 08:12:15 -0800 Subject: [PATCH 022/109] spidev: allow settings in safe() to fail This came about due to a test. But, while safe() is an attempt to make things safe, hardware being hardware, setting may not succeed, and hardware might work anyway. Signed-off-by: Ronald G. Minnich --- pkg/spidev/spidev_linux.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/spidev/spidev_linux.go b/pkg/spidev/spidev_linux.go index d8cd1dacbc..b04ef149be 100644 --- a/pkg/spidev/spidev_linux.go +++ b/pkg/spidev/spidev_linux.go @@ -203,14 +203,14 @@ func WithLogger(l func(string, ...any)) opt { } } -// safe sets "safe" settings for initial SPI operation. -// Some SPI settings are required for proper initial -// operation. -func (s *SPI) safe() error { +// safe tries to set "safe" settings for initial SPI operation. +// However, settings may not succeed, for $REASONS$. +// Hardware is highly variable. +// If there is an error, log it, and continue. +func (s *SPI) safe() { if err := s.SetSpeedHz(500000); err != nil { - return err + s.logger("warning only: speeding set to %d err %v", 500000, err) } - return nil } // Open opens a new SPI device. dev is a filename such as "/dev/spidev0.0". @@ -232,15 +232,14 @@ func Open(dev string, opts ...opt) (*SPI, error) { }, } - if err := s.safe(); err != nil { - return nil, err - } - for _, o := range opts { if err := o(s); err != nil { return nil, err } } + + s.safe() + return s, nil } From e3a0c1cab0eacf9e0df9e88832f2d096b3db5c02 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sun, 14 Jan 2024 08:23:17 -0800 Subject: [PATCH 023/109] spidev: tidy up error message in safe() Signed-off-by: Ronald G. Minnich --- pkg/spidev/spidev_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/spidev/spidev_linux.go b/pkg/spidev/spidev_linux.go index b04ef149be..902ea293e8 100644 --- a/pkg/spidev/spidev_linux.go +++ b/pkg/spidev/spidev_linux.go @@ -209,7 +209,7 @@ func WithLogger(l func(string, ...any)) opt { // If there is an error, log it, and continue. func (s *SPI) safe() { if err := s.SetSpeedHz(500000); err != nil { - s.logger("warning only: speeding set to %d err %v", 500000, err) + s.logger("warning only: set speed to %d HZ err %v", 500000, err) } } From 4740ac13e8c73376791967a198baf7c5e903ff95 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sun, 14 Jan 2024 12:24:26 -0800 Subject: [PATCH 024/109] spidev command: add a note stating why it is ok to set speed Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index f18922ecb6..3baa8d5c35 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -77,6 +77,11 @@ func run(args []string, spiOpen spiOpenFunc, input io.Reader, output io.Writer) return err } defer s.Close() + + // Note that spidev.Open sets a safe default speed, known to + // work, that is conservative. In some cases, users might wish + // to override that speed. Since the speed can be set any number + // of times, this is a safe operation. if *speed != 0 { if err := s.SetSpeedHz(*speed); err != nil { return err From 4478dd7851dc33c62f3cc4685e1977113c85c845 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Sun, 14 Jan 2024 17:09:41 +0200 Subject: [PATCH 025/109] pkg/gpio: wrap errors and remove second trim Signed-off-by: Siarhiej Siemianczuk --- pkg/gpio/gpio_linux.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/gpio/gpio_linux.go b/pkg/gpio/gpio_linux.go index 309da956a0..ed4c5f4f9d 100644 --- a/pkg/gpio/gpio_linux.go +++ b/pkg/gpio/gpio_linux.go @@ -49,7 +49,7 @@ func readInt(filename string) (int, error) { baseStr := strings.TrimSpace(string(buf)) num, err := strconv.Atoi(baseStr) if err != nil { - return 0, fmt.Errorf("could not convert %s contents %s to integer: %v", filename, baseStr, err) + return 0, fmt.Errorf("could not convert %s contents %s to integer: %w", filename, baseStr, err) } return num, nil } @@ -68,25 +68,25 @@ func GetPinID(controller string, pin uint) (int, error) { // Get label (name of the controller) buf, err := os.ReadFile(filepath.Join(c, "label")) if err != nil { - return 0, fmt.Errorf("failed to read label of %s: %v", c, err) + return 0, fmt.Errorf("failed to read label of %s: %w", c, err) } label := strings.TrimSpace(string(buf)) // Check that this is the controller we want - if strings.TrimSpace(label) != controller { + if label != controller { continue } // Get base offset (the first GPIO managed by this chip) base, err := readInt(filepath.Join(c, "base")) if err != nil { - return 0, fmt.Errorf("failed to read base: %v", err) + return 0, fmt.Errorf("failed to read base: %w", err) } // Get the number of GPIOs managed by this chip. ngpio, err := readInt(filepath.Join(c, "ngpio")) if err != nil { - return 0, fmt.Errorf("failed to read number of gpios: %v", err) + return 0, fmt.Errorf("failed to read number of gpios: %w", err) } if int(pin) >= ngpio { return 0, fmt.Errorf("requested pin %d of controller %s, but controller only has %d pins", pin, controller, ngpio) @@ -108,7 +108,7 @@ func SetOutputValue(pin int, val Value) error { } defer outFile.Close() if _, err := outFile.WriteString(dir); err != nil { - return fmt.Errorf("failed to set gpio %d to %s: %v", pin, dir, err) + return fmt.Errorf("failed to set gpio %d to %s: %w", pin, dir, err) } return nil } @@ -119,7 +119,7 @@ func ReadValue(pin int) (Value, error) { path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "value") buf, err := os.ReadFile(path) if err != nil { - return Low, fmt.Errorf("failed to read value of gpio %d: %v", pin, err) + return Low, fmt.Errorf("failed to read value of gpio %d: %w", pin, err) } switch string(buf) { case "0\n": From 25faeb4566c826ea11bc725f291af8f46d893cc5 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Thu, 4 Jan 2024 19:55:13 +0200 Subject: [PATCH 026/109] WIP ping icmp Signed-off-by: Siarhiej Siemianczuk --- cmds/core/ping/ping.go | 225 ++++++------ cmds/core/ping/ping_linux.go | 23 -- cmds/core/ping/ping_other.go | 13 - cmds/core/ping/ping_test.go | 359 +++++++++---------- vendor/golang.org/x/net/icmp/dstunreach.go | 59 +++ vendor/golang.org/x/net/icmp/echo.go | 173 +++++++++ vendor/golang.org/x/net/icmp/endpoint.go | 113 ++++++ vendor/golang.org/x/net/icmp/extension.go | 170 +++++++++ vendor/golang.org/x/net/icmp/helper_posix.go | 75 ++++ vendor/golang.org/x/net/icmp/interface.go | 322 +++++++++++++++++ vendor/golang.org/x/net/icmp/ipv4.go | 69 ++++ vendor/golang.org/x/net/icmp/ipv6.go | 23 ++ vendor/golang.org/x/net/icmp/listen_posix.go | 105 ++++++ vendor/golang.org/x/net/icmp/listen_stub.go | 35 ++ vendor/golang.org/x/net/icmp/message.go | 162 +++++++++ vendor/golang.org/x/net/icmp/messagebody.go | 52 +++ vendor/golang.org/x/net/icmp/mpls.go | 77 ++++ vendor/golang.org/x/net/icmp/multipart.go | 129 +++++++ vendor/golang.org/x/net/icmp/packettoobig.go | 43 +++ vendor/golang.org/x/net/icmp/paramprob.go | 72 ++++ vendor/golang.org/x/net/icmp/sys_freebsd.go | 11 + vendor/golang.org/x/net/icmp/timeexceeded.go | 57 +++ vendor/modules.txt | 1 + 23 files changed, 2023 insertions(+), 345 deletions(-) delete mode 100644 cmds/core/ping/ping_linux.go delete mode 100644 cmds/core/ping/ping_other.go create mode 100644 vendor/golang.org/x/net/icmp/dstunreach.go create mode 100644 vendor/golang.org/x/net/icmp/echo.go create mode 100644 vendor/golang.org/x/net/icmp/endpoint.go create mode 100644 vendor/golang.org/x/net/icmp/extension.go create mode 100644 vendor/golang.org/x/net/icmp/helper_posix.go create mode 100644 vendor/golang.org/x/net/icmp/interface.go create mode 100644 vendor/golang.org/x/net/icmp/ipv4.go create mode 100644 vendor/golang.org/x/net/icmp/ipv6.go create mode 100644 vendor/golang.org/x/net/icmp/listen_posix.go create mode 100644 vendor/golang.org/x/net/icmp/listen_stub.go create mode 100644 vendor/golang.org/x/net/icmp/message.go create mode 100644 vendor/golang.org/x/net/icmp/messagebody.go create mode 100644 vendor/golang.org/x/net/icmp/mpls.go create mode 100644 vendor/golang.org/x/net/icmp/multipart.go create mode 100644 vendor/golang.org/x/net/icmp/packettoobig.go create mode 100644 vendor/golang.org/x/net/icmp/paramprob.go create mode 100644 vendor/golang.org/x/net/icmp/sys_freebsd.go create mode 100644 vendor/golang.org/x/net/icmp/timeexceeded.go diff --git a/cmds/core/ping/ping.go b/cmds/core/ping/ping.go index fb3e05ffab..1ed5e14a7f 100644 --- a/cmds/core/ping/ping.go +++ b/cmds/core/ping/ping.go @@ -21,9 +21,10 @@ package main import ( - "encoding/binary" + "bytes" "flag" "fmt" + "io" "log" "math" "net" @@ -31,158 +32,142 @@ import ( "time" "github.com/u-root/u-root/pkg/uroot/util" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" ) const usage = "ping [-V] [-6] [-c count] [-i interval] [-s packetsize] [-w deadline] [-a audible] destination" -var ( - net6 = flag.Bool("6", false, "use ipv4 (means ip4:icmp) or 6 (ip6:ipv6-icmp)") - packetSize = flag.Int("s", 64, "Data size") - iter = flag.Uint64("c", math.MaxUint64, "# iterations") - intv = flag.Int("i", 1000, "interval in milliseconds") - wtf = flag.Int("w", 100, "wait time in milliseconds") - audible = flag.Bool("a", false, "Audible rings a bell when a packet is received") -) - -const ( - ICMP_TYPE_ECHO_REQUEST = 8 - ICMP_TYPE_ECHO_REPLY = 0 - ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET = 20 -) - -const ( - ICMP6_TYPE_ECHO_REQUEST = 128 - ICMP6_TYPE_ECHO_REPLY = 129 - ICMP6_ECHO_REPLY_HEADER_IPV6_OFFSET = 40 -) - -type Ping struct { - dial func(string, string) (net.Conn, error) +type params struct { + packetSize int + intv int + wtf int + iter uint64 + host string + net6 bool + audible bool } -func New() *Ping { - return &Ping{ - dial: net.Dial, - } +type cmd struct { + stdout io.Writer + conn net.PacketConn + params } -func cksum(bs []byte) uint16 { - sum := uint32(0) - - for k := 0; k < len(bs)/2; k++ { - sum += uint32(bs[k*2]) << 8 - sum += uint32(bs[k*2+1]) - } - if len(bs)%2 != 0 { - sum += uint32(bs[len(bs)-1]) << 8 +func command(stdin io.Writer, p params) (*cmd, error) { + netname, address := "ip4:icmp", "0.0.0.0" + if p.net6 { + netname, address = "ip6:ipv6-icmp", "::1" } - sum = (sum >> 16) + (sum & 0xffff) - sum = (sum >> 16) + (sum & 0xffff) - if sum == 0xffff { - sum = 0 + conn, err := icmp.ListenPacket(netname, address) + if err != nil { + return nil, fmt.Errorf("can't setup %s socket on %s: %v", netname, address, err) } - return ^uint16(sum) + return &cmd{stdin, conn, p}, nil } -func (p *Ping) ping1(net6 bool, host string, i uint64, waitFor time.Duration) (string, error) { - netname := "ip4:icmp" - // todo: just figure out if it's an ip6 address and go from there. - if net6 { - netname = "ip6:ipv6-icmp" +func (c *cmd) run() error { + defer c.conn.Close() + if c.packetSize < 8 { + return fmt.Errorf("packet size too small (must be >= 8): %v", c.packetSize) + } + + network := "ip4" + if c.net6 { + network = "ip6" } - c, err := p.dial(netname, host) + + addr, err := net.ResolveIPAddr(network, c.host) if err != nil { - return "", fmt.Errorf("net.Dial(%v %v) failed: %v", netname, host, err) + return fmt.Errorf("failed to resolve address: %v", err) } - defer c.Close() - if net6 { - ipc := c.(*net.IPConn) - if err := setupICMPv6Socket(ipc); err != nil { - return "", fmt.Errorf("failed to set up the ICMPv6 connection: %w", err) + interval := time.Duration(c.intv) + waitFor := time.Duration(c.wtf) * time.Millisecond + for i := uint64(0); i < c.iter; i++ { + msg, err := c.ping(addr, i+1, waitFor) + if err != nil { + return fmt.Errorf("ping failed: %v", err) } + if c.audible { + msg = "\a" + msg + } + fmt.Fprintf(c.stdout, "%s\n", msg) + time.Sleep(time.Millisecond * interval) } - // Send ICMP Echo Request - c.SetDeadline(time.Now().Add(waitFor)) - msg := make([]byte, *packetSize) - if net6 { - msg[0] = ICMP6_TYPE_ECHO_REQUEST - } else { - msg[0] = ICMP_TYPE_ECHO_REQUEST - } - msg[1] = 0 - binary.BigEndian.PutUint16(msg[6:], uint16(i)) - binary.BigEndian.PutUint16(msg[4:], uint16(i>>16)) - binary.BigEndian.PutUint16(msg[2:], cksum(msg)) - if _, err := c.Write(msg[:]); err != nil { - return "", fmt.Errorf("write failed: %v", err) + return nil +} + +func (c *cmd) ping(addr *net.IPAddr, i uint64, waitFor time.Duration) (string, error) { + c.conn.SetDeadline(time.Now().Add(waitFor)) + + var echoRequestType icmp.Type = ipv4.ICMPTypeEcho + if c.net6 { + echoRequestType = ipv6.ICMPTypeEchoRequest } - // Get ICMP Echo Reply - c.SetDeadline(time.Now().Add(waitFor)) - rmsg := make([]byte, *packetSize+256) - before := time.Now() - amt, err := c.Read(rmsg[:]) - if err != nil { - return "", fmt.Errorf("read failed: %v", err) + wm := icmp.Message{Type: echoRequestType, Code: 0, Body: &icmp.Echo{ + ID: os.Getpid() & 0xffff, + Seq: int(i), + Data: bytes.Repeat([]byte{1}, c.packetSize)}, } - latency := time.Since(before) - if !net6 { - rmsg = rmsg[ICMP_ECHO_REPLY_HEADER_IPV4_OFFSET:] + wb, err := wm.Marshal(nil) + if err != nil { + return "", fmt.Errorf("icmp.Message.Marshal failed: %v", err) } - if net6 { - if rmsg[0] != ICMP6_TYPE_ECHO_REPLY { - return "", fmt.Errorf("bad ICMPv6 echo reply type, got %d, want %d", rmsg[0], ICMP6_TYPE_ECHO_REPLY) - } - } else { - if rmsg[0] != ICMP_TYPE_ECHO_REPLY { - return "", fmt.Errorf("bad ICMP echo reply type, got %d, want %d", rmsg[0], ICMP_TYPE_ECHO_REPLY) - } + + startTime := time.Now() + _, err = c.conn.WriteTo(wb, addr) + if err != nil { + return "", fmt.Errorf("conn.Write failed: %v", err) } - cks := binary.BigEndian.Uint16(rmsg[2:]) - binary.BigEndian.PutUint16(rmsg[2:], 0) - // only validate the checksum for IPv4. For IPv6 this *should* be done by the - // TCP stack (and do we need to validate the checksum anyway?) - if !net6 && cks != cksum(rmsg) { - return "", fmt.Errorf("bad ICMP checksum: %v (expected %v)", cks, cksum(rmsg)) + + rb := make([]byte, 1500) + n, _, err := c.conn.ReadFrom(rb) + if err != nil { + return "", fmt.Errorf("conn.Read failed: %v", err) } - id := binary.BigEndian.Uint16(rmsg[4:]) - seq := binary.BigEndian.Uint16(rmsg[6:]) - rseq := uint64(id)<<16 + uint64(seq) - if rseq != i { - return "", fmt.Errorf("wrong sequence number %v (expected %v)", rseq, i) + + latency := time.Since(startTime) + + var echoReplyType icmp.Type = ipv4.ICMPTypeEchoReply + if c.net6 { + echoReplyType = ipv6.ICMPTypeEchoReply } - return fmt.Sprintf("%d bytes from %v: icmp_seq=%v, time=%v", amt, host, i, latency), nil -} + msg, err := icmp.ParseMessage(echoReplyType.Protocol(), rb[:n]) + if err != nil { + return "", fmt.Errorf("icmp.ParseMessage failed: %v", err) + } -func ping(host string) error { - if *packetSize < 8 { - return fmt.Errorf("packet size too small (must be >= 8): %v", *packetSize) + echoReply, ok := msg.Body.(*icmp.Echo) + if !ok { + return "", fmt.Errorf("got %+v; want echo reply", msg) } - interval := time.Duration(*intv) - p := New() - // ping needs to run forever if count is not specified, so default value is MaxUint64 - waitFor := time.Duration(*wtf) * time.Millisecond - for i := uint64(0); i < *iter; i++ { - msg, err := p.ping1(*net6, host, i+1, waitFor) - if err != nil { - return fmt.Errorf("ping failed: %v", err) - } - if *audible { - msg = "\a" + msg - } - log.Print(msg) - time.Sleep(time.Millisecond * interval) + if echoReply.ID != os.Getpid()&0xffff { + return "", fmt.Errorf("got id %v; want %v", echoReply.ID, os.Getpid()&0xffff) + } + if echoReply.Seq != int(i) { + return "", fmt.Errorf("got seq %v; want %v", echoReply.Seq, i) } - return nil + return fmt.Sprintf("%d bytes from %v: icmp_seq=%v time=%v", n, c.host, i, latency), nil } func main() { + var ( + net6 = flag.Bool("6", false, "use ipv4 (means ip4:icmp) or 6 (ip6:ipv6-icmp)") + packetSize = flag.Int("s", 56, "Data size") + iter = flag.Uint64("c", math.MaxUint64, "# iterations") + intv = flag.Int("i", 1000, "interval in milliseconds") + wtf = flag.Int("w", 100, "wait time in milliseconds") + audible = flag.Bool("a", false, "Audible rings a bell when a packet is received") + ) + flag.Usage = util.Usage(flag.Usage, usage) flag.Parse() // options without parameters (right now just: -hV) @@ -191,7 +176,11 @@ func main() { os.Exit(1) } host := flag.Args()[0] - if err := ping(host); err != nil { + cmd, err := command(os.Stdout, params{*packetSize, *intv, *wtf, *iter, host, *net6, *audible}) + if err != nil { + log.Fatal(err) + } + if err := cmd.run(); err != nil { log.Fatal(err) } } diff --git a/cmds/core/ping/ping_linux.go b/cmds/core/ping/ping_linux.go deleted file mode 100644 index f53f5a2686..0000000000 --- a/cmds/core/ping/ping_linux.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -package main - -import ( - "fmt" - "net" - - "golang.org/x/sys/unix" -) - -func setupICMPv6Socket(c *net.IPConn) error { - file, err := c.File() - if err != nil { - return fmt.Errorf("net.IPConn.File failed: %w", err) - } - // we want the stack to return us the network error if any occurred - if err := unix.SetsockoptInt(int(file.Fd()), unix.SOL_IPV6, unix.IPV6_RECVERR, 1); err != nil { - return fmt.Errorf("failed to set sock opt IPV6_RECVERR: %w", err) - } - return nil -} diff --git a/cmds/core/ping/ping_other.go b/cmds/core/ping/ping_other.go deleted file mode 100644 index e98fb9ea49..0000000000 --- a/cmds/core/ping/ping_other.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build !tinygo && !linux -// +build !tinygo,!linux - -package main - -import ( - "errors" - "net" -) - -func setupICMPv6Socket(c *net.IPConn) error { - return errors.New("setting up ICMPv6 socket only supported on Linux") -} diff --git a/cmds/core/ping/ping_test.go b/cmds/core/ping/ping_test.go index 19cbabb7e8..d2ea83c606 100644 --- a/cmds/core/ping/ping_test.go +++ b/cmds/core/ping/ping_test.go @@ -5,222 +5,199 @@ package main import ( - "fmt" + "bytes" "net" + "strconv" + "strings" "testing" "time" + + "github.com/hugelgupf/vmtest/guest" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" ) -type myConn struct { - net.IPConn - testRun string +type testConn struct { + lastMessage []byte } -func (M *myConn) Read(b []byte) (int, error) { - if M.testRun == "error in read" { - return 0, fmt.Errorf("err") +func (tc *testConn) ReadFrom(b []byte) (int, net.Addr, error) { + m, err := icmp.ParseMessage(ipv4.ICMPTypeEcho.Protocol(), tc.lastMessage) + if err != nil { + return 0, nil, err } - if M.testRun == "rmsg[0] != ICMP_TYPE_ECHO_REPLY" { - b[20] = 0xff + + body := m.Body.(*icmp.Echo) + + respone := icmp.Message{ + Type: ipv4.ICMPTypeEchoReply, + Code: 0, + Body: &icmp.Echo{ + ID: body.ID, + Seq: body.Seq, + Data: body.Data, + }, } - if M.testRun == "!net6 && cks != cksum(rmsg)" { - return 0, nil + + resp, err := respone.Marshal(nil) + if err != nil { + return 0, nil, err } - b[22] = 0xff - b[23] = 0xff - return 0, nil + + n := copy(b, resp) + return n, nil, nil } -func (M *myConn) Write(b []byte) (int, error) { - if M.testRun == "error in write" { - return 0, fmt.Errorf("err") - } - return 0, nil +func (tc *testConn) WriteTo(b []byte, addr net.Addr) (int, error) { + tc.lastMessage = b + return len(b), nil } -// Test cksum -func TestCkSum(t *testing.T) { - for _, tt := range []struct { - name string - input []byte - want uint16 - }{ - { - name: "ultimate test, triggers the sum == 0xffff and len(bs)%2 !=0", - input: []byte{0xff, 0xff, 0x00}, - want: 65535, - }, - { - name: "another input", - input: []byte{0xfe, 0xfe, 0xfe, 0xfe}, - want: 514, - }, - { - name: "empty input", - input: []byte{}, - want: 65535, - }, - } { - if got := cksum(tt.input); got != tt.want { - t.Errorf("cksum() = '%d', want: '%d'", got, tt.want) - } - } +func (tc *testConn) Close() error { + return nil +} +func (tc *testConn) LocalAddr() net.Addr { + return nil } -// Test Ping1 -func TestPing1(t *testing.T) { - for _, tt := range []struct { - name string - p Ping - net6 bool - host string - i uint64 - waitFor time.Duration - want error - }{ - { - name: "ping1 without error", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "ping1 without error"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf(""), - }, - { - name: "error in dial", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return nil, fmt.Errorf("err") - }, - }, - net6: false, - host: "test.com", - i: 1, - waitFor: time.Minute, - want: fmt.Errorf("net.Dial(%v %v) failed: %v", "ip4:icmp", "test.com", fmt.Errorf("err")), - }, - { - name: "error in write", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "error in write"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("write failed: err"), - }, - { - name: "error in read", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "error in read"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("read failed: err"), - }, - { - name: "rmsg[0] != ICMP_TYPE_ECHO_REPLY", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "rmsg[0] != ICMP_TYPE_ECHO_REPLY"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("bad ICMP echo reply type, got %d, want %d", 0xff, ICMP_TYPE_ECHO_REPLY), - }, - { - name: "!net6 && cks != cksum(rmsg)", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "!net6 && cks != cksum(rmsg)"}, nil - }, - }, - net6: false, - host: "test.com", - i: 0, - waitFor: time.Minute, - want: fmt.Errorf("bad ICMP checksum: %v (expected %v)", 0, 65535), - }, - { - name: "rseq != i", - p: Ping{ - dial: func(s1, s2 string) (net.Conn, error) { - return &myConn{testRun: "rseq != i"}, nil - }, - }, - net6: false, - host: "test.com", - i: 1, - waitFor: time.Minute, - want: fmt.Errorf("wrong sequence number %v (expected %v)", 0, 1), - }, - } { - t.Run(tt.name, func(t *testing.T) { - if _, got := tt.p.ping1(tt.net6, tt.host, tt.i, tt.waitFor); got != nil { - if got.Error() != tt.want.Error() { - t.Errorf("ping1() = '%s', want: '%s'", got, tt.want) - } +func (tc *testConn) SetDeadline(t time.Time) error { + return nil +} + +func (tc *testConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (tc *testConn) SetWriteDeadline(t time.Time) error { + return nil +} + +type pingOutputLine struct { + addr string + size int + seq int + audible bool +} + +func parsePingLines(t *testing.T, output []byte) []pingOutputLine { + t.Helper() + var lines []pingOutputLine + + for _, line := range bytes.Split(output, []byte("\n")) { + if len(line) == 0 { + continue + } + + var pl pingOutputLine + sp := strings.Split(string(line), " ") + if sp[0][0] == '\a' { + pl.audible = true + size, err := strconv.Atoi(sp[0][1:]) + if err != nil { + t.Fatalf("size atoi failed: %v", err) } - }) + pl.size = size + } else { + size, err := strconv.Atoi(sp[0]) + if err != nil { + t.Fatalf("size atoi failed: %v", err) + } + pl.size = size + } + + pl.addr = sp[3][:len(sp[3])-1] + sp = strings.Split(sp[4], "=") + sqn, err := strconv.Atoi(sp[1]) + if err != nil { + t.Fatalf("address atoi failed: %v", err) + } + pl.seq = sqn + + lines = append(lines, pl) } + + return lines } -// Test refactored ping() func TestPing(t *testing.T) { - for _, tt := range []struct { - name string - packetSize int - audible bool - host string - waitFor time.Duration - want error - }{ - { - name: "packetSize < 8", - packetSize: 7, - host: "test.com", - audible: true, - waitFor: time.Minute, - want: fmt.Errorf("packet size too small (must be >= 8): %v", 7), - }, - { - name: "ping with error", - packetSize: 8, - host: "", + var paConn net.PacketConn = &testConn{} + paConn.Close() + + stdout := &bytes.Buffer{} + cmd := &cmd{ + stdout: stdout, + conn: paConn, + params: params{ + host: "1.1.1.1", + packetSize: 56, + intv: 1000, + wtf: 100, + iter: 1, + net6: false, audible: true, - waitFor: time.Minute, - want: fmt.Errorf("ping failed: net.Dial(ip4:icmp ) failed: dial ip4:icmp: missing address"), }, - } { - t.Run(tt.name, func(t *testing.T) { - *packetSize = tt.packetSize - *audible = tt.audible - if got := ping(tt.host); got != nil { - if got.Error() != tt.want.Error() { - t.Errorf("ping() = '%s', want: '%s'", got, tt.want) - } - } - }) + } + + err := cmd.run() + if err != nil { + t.Error(err) + } + + lines := parsePingLines(t, stdout.Bytes()) + if len(lines) != 1 { + t.Errorf("expected 1 line, got %d", len(lines)) + } + + if lines[0].size != 64 { + t.Errorf("expected size 64 (56 + header), got %d", lines[0].size) + } + if !lines[0].audible { + t.Errorf("expected audible, got %v", lines[0].audible) + } + if lines[0].seq != 1 { + t.Errorf("expected seq 1, got %d", lines[0].seq) + } + if lines[0].addr != "1.1.1.1" { + t.Errorf("expected addr, got %s", lines[0].addr) } } -// This test gets the coverage higher and does not test any functionality. -func TestNew(t *testing.T) { - _ = New() +func TestRawPing(t *testing.T) { + guest.SkipIfNotInVM(t) + stdout := &bytes.Buffer{} + cmd, err := command(stdout, params{ + packetSize: 56, + host: "127.0.0.1", + intv: 1000, + wtf: 100, + iter: 1, + }) + if err != nil { + t.Errorf("command() failed: %v", err) + } + + err = cmd.run() + if err != nil { + t.Errorf("run() failed: %v", err) + } + + lines := parsePingLines(t, stdout.Bytes()) + + if len(lines) != 1 { + t.Errorf("expected 1 line, got %d", len(lines)) + } + + if lines[0].size != 64 { + t.Errorf("expected size 64 (56 + header), got %d", lines[0].size) + } + if lines[0].audible { + t.Errorf("expected no audible, got %v", lines[0].audible) + } + if lines[0].seq != 1 { + t.Errorf("expected seq 1, got %d", lines[0].seq) + } + if lines[0].addr != "127.0.0.1" { + t.Errorf("expected addr, got %s", lines[0].addr) + } } diff --git a/vendor/golang.org/x/net/icmp/dstunreach.go b/vendor/golang.org/x/net/icmp/dstunreach.go new file mode 100644 index 0000000000..8615cf54a4 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/dstunreach.go @@ -0,0 +1,59 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// A DstUnreach represents an ICMP destination unreachable message +// body. +type DstUnreach struct { + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *DstUnreach) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *DstUnreach) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeDestinationUnreachable + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeDestinationUnreachable + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) +} + +// parseDstUnreach parses b as an ICMP destination unreachable message +// body. +func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &DstUnreach{} + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/echo.go b/vendor/golang.org/x/net/icmp/echo.go new file mode 100644 index 0000000000..b591864278 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/echo.go @@ -0,0 +1,173 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// An Echo represents an ICMP echo request or reply message body. +type Echo struct { + ID int // identifier + Seq int // sequence number + Data []byte // data +} + +// Len implements the Len method of MessageBody interface. +func (p *Echo) Len(proto int) int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *Echo) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + binary.BigEndian.PutUint16(b[2:4], uint16(p.Seq)) + copy(b[4:], p.Data) + return b, nil +} + +// parseEcho parses b as an ICMP echo request or reply message body. +func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) { + bodyLen := len(b) + if bodyLen < 4 { + return nil, errMessageTooShort + } + p := &Echo{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(binary.BigEndian.Uint16(b[2:4]))} + if bodyLen > 4 { + p.Data = make([]byte, bodyLen-4) + copy(p.Data, b[4:]) + } + return p, nil +} + +// An ExtendedEchoRequest represents an ICMP extended echo request +// message body. +type ExtendedEchoRequest struct { + ID int // identifier + Seq int // sequence number + Local bool // must be true when identifying by name or index + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *ExtendedEchoRequest) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeExtendedEchoRequest + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeExtendedEchoRequest + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions) + if err != nil { + return nil, err + } + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + b[2] = byte(p.Seq) + if p.Local { + b[3] |= 0x01 + } + return b, nil +} + +// parseExtendedEchoRequest parses b as an ICMP extended echo request +// message body. +func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])} + if b[3]&0x01 != 0 { + p.Local = true + } + var err error + _, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} + +// An ExtendedEchoReply represents an ICMP extended echo reply message +// body. +type ExtendedEchoReply struct { + ID int // identifier + Seq int // sequence number + State int // 3-bit state working together with Message.Code + Active bool // probed interface is active + IPv4 bool // probed interface runs IPv4 + IPv6 bool // probed interface runs IPv6 +} + +// Len implements the Len method of MessageBody interface. +func (p *ExtendedEchoReply) Len(proto int) int { + if p == nil { + return 0 + } + return 4 +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4) + binary.BigEndian.PutUint16(b[:2], uint16(p.ID)) + b[2] = byte(p.Seq) + b[3] = byte(p.State<<5) & 0xe0 + if p.Active { + b[3] |= 0x04 + } + if p.IPv4 { + b[3] |= 0x02 + } + if p.IPv6 { + b[3] |= 0x01 + } + return b, nil +} + +// parseExtendedEchoReply parses b as an ICMP extended echo reply +// message body. +func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ExtendedEchoReply{ + ID: int(binary.BigEndian.Uint16(b[:2])), + Seq: int(b[2]), + State: int(b[3]) >> 5, + } + if b[3]&0x04 != 0 { + p.Active = true + } + if b[3]&0x02 != 0 { + p.IPv4 = true + } + if b[3]&0x01 != 0 { + p.IPv6 = true + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/endpoint.go b/vendor/golang.org/x/net/icmp/endpoint.go new file mode 100644 index 0000000000..47f5b698d5 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/endpoint.go @@ -0,0 +1,113 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "net" + "runtime" + "time" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +var _ net.PacketConn = &PacketConn{} + +// A PacketConn represents a packet network endpoint that uses either +// ICMPv4 or ICMPv6. +type PacketConn struct { + c net.PacketConn + p4 *ipv4.PacketConn + p6 *ipv6.PacketConn +} + +func (c *PacketConn) ok() bool { return c != nil && c.c != nil } + +// IPv4PacketConn returns the ipv4.PacketConn of c. +// It returns nil when c is not created as the endpoint for ICMPv4. +func (c *PacketConn) IPv4PacketConn() *ipv4.PacketConn { + if !c.ok() { + return nil + } + return c.p4 +} + +// IPv6PacketConn returns the ipv6.PacketConn of c. +// It returns nil when c is not created as the endpoint for ICMPv6. +func (c *PacketConn) IPv6PacketConn() *ipv6.PacketConn { + if !c.ok() { + return nil + } + return c.p6 +} + +// ReadFrom reads an ICMP message from the connection. +func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + if !c.ok() { + return 0, nil, errInvalidConn + } + // Please be informed that ipv4.NewPacketConn enables + // IP_STRIPHDR option by default on Darwin. + // See golang.org/issue/9395 for further information. + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && c.p4 != nil { + n, _, peer, err := c.p4.ReadFrom(b) + return n, peer, err + } + return c.c.ReadFrom(b) +} + +// WriteTo writes the ICMP message b to dst. +// The provided dst must be net.UDPAddr when c is a non-privileged +// datagram-oriented ICMP endpoint. +// Otherwise it must be net.IPAddr. +func (c *PacketConn) WriteTo(b []byte, dst net.Addr) (int, error) { + if !c.ok() { + return 0, errInvalidConn + } + return c.c.WriteTo(b, dst) +} + +// Close closes the endpoint. +func (c *PacketConn) Close() error { + if !c.ok() { + return errInvalidConn + } + return c.c.Close() +} + +// LocalAddr returns the local network address. +func (c *PacketConn) LocalAddr() net.Addr { + if !c.ok() { + return nil + } + return c.c.LocalAddr() +} + +// SetDeadline sets the read and write deadlines associated with the +// endpoint. +func (c *PacketConn) SetDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetDeadline(t) +} + +// SetReadDeadline sets the read deadline associated with the +// endpoint. +func (c *PacketConn) SetReadDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetReadDeadline(t) +} + +// SetWriteDeadline sets the write deadline associated with the +// endpoint. +func (c *PacketConn) SetWriteDeadline(t time.Time) error { + if !c.ok() { + return errInvalidConn + } + return c.c.SetWriteDeadline(t) +} diff --git a/vendor/golang.org/x/net/icmp/extension.go b/vendor/golang.org/x/net/icmp/extension.go new file mode 100644 index 0000000000..eeb85c3fc0 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/extension.go @@ -0,0 +1,170 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// An Extension represents an ICMP extension. +type Extension interface { + // Len returns the length of ICMP extension. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Len(proto int) int + + // Marshal returns the binary encoding of ICMP extension. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Marshal(proto int) ([]byte, error) +} + +const extensionVersion = 2 + +func validExtensionHeader(b []byte) bool { + v := int(b[0]&0xf0) >> 4 + s := binary.BigEndian.Uint16(b[2:4]) + if s != 0 { + s = checksum(b) + } + if v != extensionVersion || s != 0 { + return false + } + return true +} + +// parseExtensions parses b as a list of ICMP extensions. +// The length attribute l must be the length attribute field in +// received icmp messages. +// +// It will return a list of ICMP extensions and an adjusted length +// attribute that represents the length of the padded original +// datagram field. Otherwise, it returns an error. +func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) { + // Still a lot of non-RFC 4884 compliant implementations are + // out there. Set the length attribute l to 128 when it looks + // inappropriate for backwards compatibility. + // + // A minimal extension at least requires 8 octets; 4 octets + // for an extension header, and 4 octets for a single object + // header. + // + // See RFC 4884 for further information. + switch typ { + case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest: + if len(b) < 8 || !validExtensionHeader(b) { + return nil, -1, errNoExtension + } + l = 0 + default: + if 128 > l || l+8 > len(b) { + l = 128 + } + if l+8 > len(b) { + return nil, -1, errNoExtension + } + if !validExtensionHeader(b[l:]) { + if l == 128 { + return nil, -1, errNoExtension + } + l = 128 + if !validExtensionHeader(b[l:]) { + return nil, -1, errNoExtension + } + } + } + var exts []Extension + for b = b[l+4:]; len(b) >= 4; { + ol := int(binary.BigEndian.Uint16(b[:2])) + if 4 > ol || ol > len(b) { + break + } + switch b[2] { + case classMPLSLabelStack: + ext, err := parseMPLSLabelStack(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + case classInterfaceInfo: + ext, err := parseInterfaceInfo(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + case classInterfaceIdent: + ext, err := parseInterfaceIdent(b[:ol]) + if err != nil { + return nil, -1, err + } + exts = append(exts, ext) + default: + ext := &RawExtension{Data: make([]byte, ol)} + copy(ext.Data, b[:ol]) + exts = append(exts, ext) + } + b = b[ol:] + } + return exts, l, nil +} + +func validExtensions(typ Type, exts []Extension) bool { + switch typ { + case ipv4.ICMPTypeDestinationUnreachable, ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeParameterProblem, + ipv6.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeTimeExceeded: + for i := range exts { + switch exts[i].(type) { + case *MPLSLabelStack, *InterfaceInfo, *RawExtension: + default: + return false + } + } + return true + case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest: + var n int + for i := range exts { + switch exts[i].(type) { + case *InterfaceIdent: + n++ + case *RawExtension: + default: + return false + } + } + // Not a single InterfaceIdent object or a combo of + // RawExtension and InterfaceIdent objects is not + // allowed. + if n == 1 && len(exts) > 1 { + return false + } + return true + default: + return false + } +} + +// A RawExtension represents a raw extension. +// +// A raw extension is excluded from message processing and can be used +// to construct applications such as protocol conformance testing. +type RawExtension struct { + Data []byte // data +} + +// Len implements the Len method of Extension interface. +func (p *RawExtension) Len(proto int) int { + if p == nil { + return 0 + } + return len(p.Data) +} + +// Marshal implements the Marshal method of Extension interface. +func (p *RawExtension) Marshal(proto int) ([]byte, error) { + return p.Data, nil +} diff --git a/vendor/golang.org/x/net/icmp/helper_posix.go b/vendor/golang.org/x/net/icmp/helper_posix.go new file mode 100644 index 0000000000..f625483f06 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/helper_posix.go @@ -0,0 +1,75 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows + +package icmp + +import ( + "net" + "strconv" + "syscall" +) + +func sockaddr(family int, address string) (syscall.Sockaddr, error) { + switch family { + case syscall.AF_INET: + a, err := net.ResolveIPAddr("ip4", address) + if err != nil { + return nil, err + } + if len(a.IP) == 0 { + a.IP = net.IPv4zero + } + if a.IP = a.IP.To4(); a.IP == nil { + return nil, net.InvalidAddrError("non-ipv4 address") + } + sa := &syscall.SockaddrInet4{} + copy(sa.Addr[:], a.IP) + return sa, nil + case syscall.AF_INET6: + a, err := net.ResolveIPAddr("ip6", address) + if err != nil { + return nil, err + } + if len(a.IP) == 0 { + a.IP = net.IPv6unspecified + } + if a.IP.Equal(net.IPv4zero) { + a.IP = net.IPv6unspecified + } + if a.IP = a.IP.To16(); a.IP == nil || a.IP.To4() != nil { + return nil, net.InvalidAddrError("non-ipv6 address") + } + sa := &syscall.SockaddrInet6{ZoneId: zoneToUint32(a.Zone)} + copy(sa.Addr[:], a.IP) + return sa, nil + default: + return nil, net.InvalidAddrError("unexpected family") + } +} + +func zoneToUint32(zone string) uint32 { + if zone == "" { + return 0 + } + if ifi, err := net.InterfaceByName(zone); err == nil { + return uint32(ifi.Index) + } + n, err := strconv.Atoi(zone) + if err != nil { + return 0 + } + return uint32(n) +} + +func last(s string, b byte) int { + i := len(s) + for i--; i >= 0; i-- { + if s[i] == b { + break + } + } + return i +} diff --git a/vendor/golang.org/x/net/icmp/interface.go b/vendor/golang.org/x/net/icmp/interface.go new file mode 100644 index 0000000000..b3dd72fb0a --- /dev/null +++ b/vendor/golang.org/x/net/icmp/interface.go @@ -0,0 +1,322 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + "net" + "strings" + + "golang.org/x/net/internal/iana" +) + +const ( + classInterfaceInfo = 2 +) + +const ( + attrMTU = 1 << iota + attrName + attrIPAddr + attrIfIndex +) + +// An InterfaceInfo represents interface and next-hop identification. +type InterfaceInfo struct { + Class int // extension object class number + Type int // extension object sub-type + Interface *net.Interface + Addr *net.IPAddr +} + +func (ifi *InterfaceInfo) nameLen() int { + if len(ifi.Interface.Name) > 63 { + return 64 + } + l := 1 + len(ifi.Interface.Name) + return (l + 3) &^ 3 +} + +func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) { + l = 4 + if ifi.Interface != nil && ifi.Interface.Index > 0 { + attrs |= attrIfIndex + l += 4 + if len(ifi.Interface.Name) > 0 { + attrs |= attrName + l += ifi.nameLen() + } + if ifi.Interface.MTU > 0 { + attrs |= attrMTU + l += 4 + } + } + if ifi.Addr != nil { + switch proto { + case iana.ProtocolICMP: + if ifi.Addr.IP.To4() != nil { + attrs |= attrIPAddr + l += 4 + net.IPv4len + } + case iana.ProtocolIPv6ICMP: + if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + attrs |= attrIPAddr + l += 4 + net.IPv6len + } + } + } + return +} + +// Len implements the Len method of Extension interface. +func (ifi *InterfaceInfo) Len(proto int) int { + _, l := ifi.attrsAndLen(proto) + return l +} + +// Marshal implements the Marshal method of Extension interface. +func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) { + attrs, l := ifi.attrsAndLen(proto) + b := make([]byte, l) + if err := ifi.marshal(proto, b, attrs, l); err != nil { + return nil, err + } + return b, nil +} + +func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error { + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classInterfaceInfo, byte(ifi.Type) + for b = b[4:]; len(b) > 0 && attrs != 0; { + switch { + case attrs&attrIfIndex != 0: + b = ifi.marshalIfIndex(proto, b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b = ifi.marshalIPAddr(proto, b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b = ifi.marshalName(proto, b) + attrs &^= attrName + case attrs&attrMTU != 0: + b = ifi.marshalMTU(proto, b) + attrs &^= attrMTU + } + } + return nil +} + +func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte { + binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.Index)) + return b[4:] +} + +func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.Index = int(binary.BigEndian.Uint32(b[:4])) + return b[4:], nil +} + +func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte { + switch proto { + case iana.ProtocolICMP: + binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4)) + copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4()) + b = b[4+net.IPv4len:] + case iana.ProtocolIPv6ICMP: + binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6)) + copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16()) + b = b[4+net.IPv6len:] + } + return b +} + +func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + afi := int(binary.BigEndian.Uint16(b[:2])) + b = b[4:] + switch afi { + case iana.AddrFamilyIPv4: + if len(b) < net.IPv4len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv4len) + copy(ifi.Addr.IP, b[:net.IPv4len]) + b = b[net.IPv4len:] + case iana.AddrFamilyIPv6: + if len(b) < net.IPv6len { + return nil, errMessageTooShort + } + ifi.Addr.IP = make(net.IP, net.IPv6len) + copy(ifi.Addr.IP, b[:net.IPv6len]) + b = b[net.IPv6len:] + } + return b, nil +} + +func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte { + l := byte(ifi.nameLen()) + b[0] = l + copy(b[1:], []byte(ifi.Interface.Name)) + return b[l:] +} + +func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) { + if 4 > len(b) || len(b) < int(b[0]) { + return nil, errMessageTooShort + } + l := int(b[0]) + if l%4 != 0 || 4 > l || l > 64 { + return nil, errInvalidExtension + } + var name [63]byte + copy(name[:], b[1:l]) + ifi.Interface.Name = strings.Trim(string(name[:]), "\000") + return b[l:], nil +} + +func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte { + binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.MTU)) + return b[4:] +} + +func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + ifi.Interface.MTU = int(binary.BigEndian.Uint32(b[:4])) + return b[4:], nil +} + +func parseInterfaceInfo(b []byte) (Extension, error) { + ifi := &InterfaceInfo{ + Class: int(b[2]), + Type: int(b[3]), + } + if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 { + ifi.Interface = &net.Interface{} + } + if ifi.Type&attrIPAddr != 0 { + ifi.Addr = &net.IPAddr{} + } + attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU) + for b = b[4:]; len(b) > 0 && attrs != 0; { + var err error + switch { + case attrs&attrIfIndex != 0: + b, err = ifi.parseIfIndex(b) + attrs &^= attrIfIndex + case attrs&attrIPAddr != 0: + b, err = ifi.parseIPAddr(b) + attrs &^= attrIPAddr + case attrs&attrName != 0: + b, err = ifi.parseName(b) + attrs &^= attrName + case attrs&attrMTU != 0: + b, err = ifi.parseMTU(b) + attrs &^= attrMTU + } + if err != nil { + return nil, err + } + } + if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil { + ifi.Addr.Zone = ifi.Interface.Name + } + return ifi, nil +} + +const ( + classInterfaceIdent = 3 + typeInterfaceByName = 1 + typeInterfaceByIndex = 2 + typeInterfaceByAddress = 3 +) + +// An InterfaceIdent represents interface identification. +type InterfaceIdent struct { + Class int // extension object class number + Type int // extension object sub-type + Name string // interface name + Index int // interface index + AFI int // address family identifier; see address family numbers in IANA registry + Addr []byte // address +} + +// Len implements the Len method of Extension interface. +func (ifi *InterfaceIdent) Len(_ int) int { + switch ifi.Type { + case typeInterfaceByName: + l := len(ifi.Name) + if l > 255 { + l = 255 + } + return 4 + (l+3)&^3 + case typeInterfaceByIndex: + return 4 + 4 + case typeInterfaceByAddress: + return 4 + 4 + (len(ifi.Addr)+3)&^3 + default: + return 4 + } +} + +// Marshal implements the Marshal method of Extension interface. +func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) { + b := make([]byte, ifi.Len(proto)) + if err := ifi.marshal(proto, b); err != nil { + return nil, err + } + return b, nil +} + +func (ifi *InterfaceIdent) marshal(proto int, b []byte) error { + l := ifi.Len(proto) + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classInterfaceIdent, byte(ifi.Type) + switch ifi.Type { + case typeInterfaceByName: + copy(b[4:], ifi.Name) + case typeInterfaceByIndex: + binary.BigEndian.PutUint32(b[4:4+4], uint32(ifi.Index)) + case typeInterfaceByAddress: + binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI)) + b[4+2] = byte(len(ifi.Addr)) + copy(b[4+4:], ifi.Addr) + } + return nil +} + +func parseInterfaceIdent(b []byte) (Extension, error) { + ifi := &InterfaceIdent{ + Class: int(b[2]), + Type: int(b[3]), + } + switch ifi.Type { + case typeInterfaceByName: + ifi.Name = strings.Trim(string(b[4:]), "\x00") + case typeInterfaceByIndex: + if len(b[4:]) < 4 { + return nil, errInvalidExtension + } + ifi.Index = int(binary.BigEndian.Uint32(b[4 : 4+4])) + case typeInterfaceByAddress: + if len(b[4:]) < 4 { + return nil, errInvalidExtension + } + ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2])) + l := int(b[4+2]) + if len(b[4+4:]) < l { + return nil, errInvalidExtension + } + ifi.Addr = make([]byte, l) + copy(ifi.Addr, b[4+4:]) + } + return ifi, nil +} diff --git a/vendor/golang.org/x/net/icmp/ipv4.go b/vendor/golang.org/x/net/icmp/ipv4.go new file mode 100644 index 0000000000..0ad40fef2f --- /dev/null +++ b/vendor/golang.org/x/net/icmp/ipv4.go @@ -0,0 +1,69 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + "net" + "runtime" + + "golang.org/x/net/internal/socket" + "golang.org/x/net/ipv4" +) + +// freebsdVersion is set in sys_freebsd.go. +// See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html. +var freebsdVersion uint32 + +// ParseIPv4Header returns the IPv4 header of the IPv4 packet that +// triggered an ICMP error message. +// This is found in the Data field of the ICMP error message body. +// +// The provided b must be in the format used by a raw ICMP socket on +// the local system. +// This may differ from the wire format, and the format used by a raw +// IP socket, depending on the system. +// +// To parse an IPv6 header, use ipv6.ParseHeader. +func ParseIPv4Header(b []byte) (*ipv4.Header, error) { + if len(b) < ipv4.HeaderLen { + return nil, errHeaderTooShort + } + hdrlen := int(b[0]&0x0f) << 2 + if hdrlen > len(b) { + return nil, errBufferTooShort + } + h := &ipv4.Header{ + Version: int(b[0] >> 4), + Len: hdrlen, + TOS: int(b[1]), + ID: int(binary.BigEndian.Uint16(b[4:6])), + FragOff: int(binary.BigEndian.Uint16(b[6:8])), + TTL: int(b[8]), + Protocol: int(b[9]), + Checksum: int(binary.BigEndian.Uint16(b[10:12])), + Src: net.IPv4(b[12], b[13], b[14], b[15]), + Dst: net.IPv4(b[16], b[17], b[18], b[19]), + } + switch runtime.GOOS { + case "darwin", "ios": + h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + case "freebsd": + if freebsdVersion >= 1000000 { + h.TotalLen = int(binary.BigEndian.Uint16(b[2:4])) + } else { + h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4])) + } + default: + h.TotalLen = int(binary.BigEndian.Uint16(b[2:4])) + } + h.Flags = ipv4.HeaderFlags(h.FragOff&0xe000) >> 13 + h.FragOff = h.FragOff & 0x1fff + if hdrlen-ipv4.HeaderLen > 0 { + h.Options = make([]byte, hdrlen-ipv4.HeaderLen) + copy(h.Options, b[ipv4.HeaderLen:]) + } + return h, nil +} diff --git a/vendor/golang.org/x/net/icmp/ipv6.go b/vendor/golang.org/x/net/icmp/ipv6.go new file mode 100644 index 0000000000..2e8cfeb131 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/ipv6.go @@ -0,0 +1,23 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "net" + + "golang.org/x/net/internal/iana" +) + +const ipv6PseudoHeaderLen = 2*net.IPv6len + 8 + +// IPv6PseudoHeader returns an IPv6 pseudo header for checksum +// calculation. +func IPv6PseudoHeader(src, dst net.IP) []byte { + b := make([]byte, ipv6PseudoHeaderLen) + copy(b, src.To16()) + copy(b[net.IPv6len:], dst.To16()) + b[len(b)-1] = byte(iana.ProtocolIPv6ICMP) + return b +} diff --git a/vendor/golang.org/x/net/icmp/listen_posix.go b/vendor/golang.org/x/net/icmp/listen_posix.go new file mode 100644 index 0000000000..b7cb15b7dc --- /dev/null +++ b/vendor/golang.org/x/net/icmp/listen_posix.go @@ -0,0 +1,105 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows + +package icmp + +import ( + "net" + "os" + "runtime" + "syscall" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option + +// ListenPacket listens for incoming ICMP packets addressed to +// address. See net.Dial for the syntax of address. +// +// For non-privileged datagram-oriented ICMP endpoints, network must +// be "udp4" or "udp6". The endpoint allows to read, write a few +// limited ICMP messages such as echo request and echo reply. +// Currently only Darwin and Linux support this. +// +// Examples: +// +// ListenPacket("udp4", "192.168.0.1") +// ListenPacket("udp4", "0.0.0.0") +// ListenPacket("udp6", "fe80::1%en0") +// ListenPacket("udp6", "::") +// +// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" +// followed by a colon and an ICMP protocol number or name. +// +// Examples: +// +// ListenPacket("ip4:icmp", "192.168.0.1") +// ListenPacket("ip4:1", "0.0.0.0") +// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") +// ListenPacket("ip6:58", "::") +func ListenPacket(network, address string) (*PacketConn, error) { + var family, proto int + switch network { + case "udp4": + family, proto = syscall.AF_INET, iana.ProtocolICMP + case "udp6": + family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP + default: + i := last(network, ':') + if i < 0 { + i = len(network) + } + switch network[:i] { + case "ip4": + proto = iana.ProtocolICMP + case "ip6": + proto = iana.ProtocolIPv6ICMP + } + } + var cerr error + var c net.PacketConn + switch family { + case syscall.AF_INET, syscall.AF_INET6: + s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto) + if err != nil { + return nil, os.NewSyscallError("socket", err) + } + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && family == syscall.AF_INET { + if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil { + syscall.Close(s) + return nil, os.NewSyscallError("setsockopt", err) + } + } + sa, err := sockaddr(family, address) + if err != nil { + syscall.Close(s) + return nil, err + } + if err := syscall.Bind(s, sa); err != nil { + syscall.Close(s) + return nil, os.NewSyscallError("bind", err) + } + f := os.NewFile(uintptr(s), "datagram-oriented icmp") + c, cerr = net.FilePacketConn(f) + f.Close() + default: + c, cerr = net.ListenPacket(network, address) + } + if cerr != nil { + return nil, cerr + } + switch proto { + case iana.ProtocolICMP: + return &PacketConn{c: c, p4: ipv4.NewPacketConn(c)}, nil + case iana.ProtocolIPv6ICMP: + return &PacketConn{c: c, p6: ipv6.NewPacketConn(c)}, nil + default: + return &PacketConn{c: c}, nil + } +} diff --git a/vendor/golang.org/x/net/icmp/listen_stub.go b/vendor/golang.org/x/net/icmp/listen_stub.go new file mode 100644 index 0000000000..7b76be1cb3 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/listen_stub.go @@ -0,0 +1,35 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows + +package icmp + +// ListenPacket listens for incoming ICMP packets addressed to +// address. See net.Dial for the syntax of address. +// +// For non-privileged datagram-oriented ICMP endpoints, network must +// be "udp4" or "udp6". The endpoint allows to read, write a few +// limited ICMP messages such as echo request and echo reply. +// Currently only Darwin and Linux support this. +// +// Examples: +// +// ListenPacket("udp4", "192.168.0.1") +// ListenPacket("udp4", "0.0.0.0") +// ListenPacket("udp6", "fe80::1%en0") +// ListenPacket("udp6", "::") +// +// For privileged raw ICMP endpoints, network must be "ip4" or "ip6" +// followed by a colon and an ICMP protocol number or name. +// +// Examples: +// +// ListenPacket("ip4:icmp", "192.168.0.1") +// ListenPacket("ip4:1", "0.0.0.0") +// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0") +// ListenPacket("ip6:58", "::") +func ListenPacket(network, address string) (*PacketConn, error) { + return nil, errNotImplemented +} diff --git a/vendor/golang.org/x/net/icmp/message.go b/vendor/golang.org/x/net/icmp/message.go new file mode 100644 index 0000000000..40db65d0cd --- /dev/null +++ b/vendor/golang.org/x/net/icmp/message.go @@ -0,0 +1,162 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package icmp provides basic functions for the manipulation of +// messages used in the Internet Control Message Protocols, +// ICMPv4 and ICMPv6. +// +// ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443. +// Multi-part message support for ICMP is defined in RFC 4884. +// ICMP extensions for MPLS are defined in RFC 4950. +// ICMP extensions for interface and next-hop identification are +// defined in RFC 5837. +// PROBE: A utility for probing interfaces is defined in RFC 8335. +package icmp // import "golang.org/x/net/icmp" + +import ( + "encoding/binary" + "errors" + "net" + "runtime" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// BUG(mikio): This package is not implemented on JS, NaCl and Plan 9. + +var ( + errInvalidConn = errors.New("invalid connection") + errInvalidProtocol = errors.New("invalid protocol") + errMessageTooShort = errors.New("message too short") + errHeaderTooShort = errors.New("header too short") + errBufferTooShort = errors.New("buffer too short") + errInvalidBody = errors.New("invalid body") + errNoExtension = errors.New("no extension") + errInvalidExtension = errors.New("invalid extension") + errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) +) + +func checksum(b []byte) uint16 { + csumcv := len(b) - 1 // checksum coverage + s := uint32(0) + for i := 0; i < csumcv; i += 2 { + s += uint32(b[i+1])<<8 | uint32(b[i]) + } + if csumcv&1 == 0 { + s += uint32(b[csumcv]) + } + s = s>>16 + s&0xffff + s = s + s>>16 + return ^uint16(s) +} + +// A Type represents an ICMP message type. +type Type interface { + Protocol() int +} + +// A Message represents an ICMP message. +type Message struct { + Type Type // type, either ipv4.ICMPType or ipv6.ICMPType + Code int // code + Checksum int // checksum + Body MessageBody // body +} + +// Marshal returns the binary encoding of the ICMP message m. +// +// For an ICMPv4 message, the returned message always contains the +// calculated checksum field. +// +// For an ICMPv6 message, the returned message contains the calculated +// checksum field when psh is not nil, otherwise the kernel will +// compute the checksum field during the message transmission. +// When psh is not nil, it must be the pseudo header for IPv6. +func (m *Message) Marshal(psh []byte) ([]byte, error) { + var mtype byte + switch typ := m.Type.(type) { + case ipv4.ICMPType: + mtype = byte(typ) + case ipv6.ICMPType: + mtype = byte(typ) + default: + return nil, errInvalidProtocol + } + b := []byte{mtype, byte(m.Code), 0, 0} + proto := m.Type.Protocol() + if proto == iana.ProtocolIPv6ICMP && psh != nil { + b = append(psh, b...) + } + if m.Body != nil && m.Body.Len(proto) != 0 { + mb, err := m.Body.Marshal(proto) + if err != nil { + return nil, err + } + b = append(b, mb...) + } + if proto == iana.ProtocolIPv6ICMP { + if psh == nil { // cannot calculate checksum here + return b, nil + } + off, l := 2*net.IPv6len, len(b)-len(psh) + binary.BigEndian.PutUint32(b[off:off+4], uint32(l)) + } + s := checksum(b) + // Place checksum back in header; using ^= avoids the + // assumption the checksum bytes are zero. + b[len(psh)+2] ^= byte(s) + b[len(psh)+3] ^= byte(s >> 8) + return b[len(psh):], nil +} + +var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){ + ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach, + ipv4.ICMPTypeTimeExceeded: parseTimeExceeded, + ipv4.ICMPTypeParameterProblem: parseParamProb, + + ipv4.ICMPTypeEcho: parseEcho, + ipv4.ICMPTypeEchoReply: parseEcho, + ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, + ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, + + ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach, + ipv6.ICMPTypePacketTooBig: parsePacketTooBig, + ipv6.ICMPTypeTimeExceeded: parseTimeExceeded, + ipv6.ICMPTypeParameterProblem: parseParamProb, + + ipv6.ICMPTypeEchoRequest: parseEcho, + ipv6.ICMPTypeEchoReply: parseEcho, + ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest, + ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply, +} + +// ParseMessage parses b as an ICMP message. +// The provided proto must be either the ICMPv4 or ICMPv6 protocol +// number. +func ParseMessage(proto int, b []byte) (*Message, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + var err error + m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))} + switch proto { + case iana.ProtocolICMP: + m.Type = ipv4.ICMPType(b[0]) + case iana.ProtocolIPv6ICMP: + m.Type = ipv6.ICMPType(b[0]) + default: + return nil, errInvalidProtocol + } + if fn, ok := parseFns[m.Type]; !ok { + m.Body, err = parseRawBody(proto, b[4:]) + } else { + m.Body, err = fn(proto, m.Type, b[4:]) + } + if err != nil { + return nil, err + } + return m, nil +} diff --git a/vendor/golang.org/x/net/icmp/messagebody.go b/vendor/golang.org/x/net/icmp/messagebody.go new file mode 100644 index 0000000000..e2d9bfa01b --- /dev/null +++ b/vendor/golang.org/x/net/icmp/messagebody.go @@ -0,0 +1,52 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +// A MessageBody represents an ICMP message body. +type MessageBody interface { + // Len returns the length of ICMP message body. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Len(proto int) int + + // Marshal returns the binary encoding of ICMP message body. + // The provided proto must be either the ICMPv4 or ICMPv6 + // protocol number. + Marshal(proto int) ([]byte, error) +} + +// A RawBody represents a raw message body. +// +// A raw message body is excluded from message processing and can be +// used to construct applications such as protocol conformance +// testing. +type RawBody struct { + Data []byte // data +} + +// Len implements the Len method of MessageBody interface. +func (p *RawBody) Len(proto int) int { + if p == nil { + return 0 + } + return len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *RawBody) Marshal(proto int) ([]byte, error) { + return p.Data, nil +} + +// parseRawBody parses b as an ICMP message body. +func parseRawBody(proto int, b []byte) (MessageBody, error) { + p := &RawBody{Data: make([]byte, len(b))} + copy(p.Data, b) + return p, nil +} + +// A DefaultMessageBody represents the default message body. +// +// Deprecated: Use RawBody instead. +type DefaultMessageBody = RawBody diff --git a/vendor/golang.org/x/net/icmp/mpls.go b/vendor/golang.org/x/net/icmp/mpls.go new file mode 100644 index 0000000000..f9f4841bce --- /dev/null +++ b/vendor/golang.org/x/net/icmp/mpls.go @@ -0,0 +1,77 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "encoding/binary" + +// MPLSLabel represents an MPLS label stack entry. +type MPLSLabel struct { + Label int // label value + TC int // traffic class; formerly experimental use + S bool // bottom of stack + TTL int // time to live +} + +const ( + classMPLSLabelStack = 1 + typeIncomingMPLSLabelStack = 1 +) + +// MPLSLabelStack represents an MPLS label stack. +type MPLSLabelStack struct { + Class int // extension object class number + Type int // extension object sub-type + Labels []MPLSLabel +} + +// Len implements the Len method of Extension interface. +func (ls *MPLSLabelStack) Len(proto int) int { + return 4 + (4 * len(ls.Labels)) +} + +// Marshal implements the Marshal method of Extension interface. +func (ls *MPLSLabelStack) Marshal(proto int) ([]byte, error) { + b := make([]byte, ls.Len(proto)) + if err := ls.marshal(proto, b); err != nil { + return nil, err + } + return b, nil +} + +func (ls *MPLSLabelStack) marshal(proto int, b []byte) error { + l := ls.Len(proto) + binary.BigEndian.PutUint16(b[:2], uint16(l)) + b[2], b[3] = classMPLSLabelStack, typeIncomingMPLSLabelStack + off := 4 + for _, ll := range ls.Labels { + b[off], b[off+1], b[off+2] = byte(ll.Label>>12), byte(ll.Label>>4&0xff), byte(ll.Label<<4&0xf0) + b[off+2] |= byte(ll.TC << 1 & 0x0e) + if ll.S { + b[off+2] |= 0x1 + } + b[off+3] = byte(ll.TTL) + off += 4 + } + return nil +} + +func parseMPLSLabelStack(b []byte) (Extension, error) { + ls := &MPLSLabelStack{ + Class: int(b[2]), + Type: int(b[3]), + } + for b = b[4:]; len(b) >= 4; b = b[4:] { + ll := MPLSLabel{ + Label: int(b[0])<<12 | int(b[1])<<4 | int(b[2])>>4, + TC: int(b[2]&0x0e) >> 1, + TTL: int(b[3]), + } + if b[2]&0x1 != 0 { + ll.S = true + } + ls.Labels = append(ls.Labels, ll) + } + return ls, nil +} diff --git a/vendor/golang.org/x/net/icmp/multipart.go b/vendor/golang.org/x/net/icmp/multipart.go new file mode 100644 index 0000000000..c7b72bf3dd --- /dev/null +++ b/vendor/golang.org/x/net/icmp/multipart.go @@ -0,0 +1,129 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "golang.org/x/net/internal/iana" + +// multipartMessageBodyDataLen takes b as an original datagram and +// exts as extensions, and returns a required length for message body +// and a required length for a padded original datagram in wire +// format. +func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) { + bodyLen = 4 // length of leading octets + var extLen int + var rawExt bool // raw extension may contain an empty object + for _, ext := range exts { + extLen += ext.Len(proto) + if _, ok := ext.(*RawExtension); ok { + rawExt = true + } + } + if extLen > 0 && withOrigDgram { + dataLen = multipartMessageOrigDatagramLen(proto, b) + } else { + dataLen = len(b) + } + if extLen > 0 || rawExt { + bodyLen += 4 // length of extension header + } + bodyLen += dataLen + extLen + return bodyLen, dataLen +} + +// multipartMessageOrigDatagramLen takes b as an original datagram, +// and returns a required length for a padded original datagram in wire +// format. +func multipartMessageOrigDatagramLen(proto int, b []byte) int { + roundup := func(b []byte, align int) int { + // According to RFC 4884, the padded original datagram + // field must contain at least 128 octets. + if len(b) < 128 { + return 128 + } + r := len(b) + return (r + align - 1) &^ (align - 1) + } + switch proto { + case iana.ProtocolICMP: + return roundup(b, 4) + case iana.ProtocolIPv6ICMP: + return roundup(b, 8) + default: + return len(b) + } +} + +// marshalMultipartMessageBody takes data as an original datagram and +// exts as extesnsions, and returns a binary encoding of message body. +// It can be used for non-multipart message bodies when exts is nil. +func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) { + bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts) + b := make([]byte, bodyLen) + copy(b[4:], data) + if len(exts) > 0 { + b[4+dataLen] = byte(extensionVersion << 4) + off := 4 + dataLen + 4 // leading octets, data, extension header + for _, ext := range exts { + switch ext := ext.(type) { + case *MPLSLabelStack: + if err := ext.marshal(proto, b[off:]); err != nil { + return nil, err + } + off += ext.Len(proto) + case *InterfaceInfo: + attrs, l := ext.attrsAndLen(proto) + if err := ext.marshal(proto, b[off:], attrs, l); err != nil { + return nil, err + } + off += ext.Len(proto) + case *InterfaceIdent: + if err := ext.marshal(proto, b[off:]); err != nil { + return nil, err + } + off += ext.Len(proto) + case *RawExtension: + copy(b[off:], ext.Data) + off += ext.Len(proto) + } + } + s := checksum(b[4+dataLen:]) + b[4+dataLen+2] ^= byte(s) + b[4+dataLen+3] ^= byte(s >> 8) + if withOrigDgram { + switch proto { + case iana.ProtocolICMP: + b[1] = byte(dataLen / 4) + case iana.ProtocolIPv6ICMP: + b[0] = byte(dataLen / 8) + } + } + } + return b, nil +} + +// parseMultipartMessageBody parses b as either a non-multipart +// message body or a multipart message body. +func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) { + var l int + switch proto { + case iana.ProtocolICMP: + l = 4 * int(b[1]) + case iana.ProtocolIPv6ICMP: + l = 8 * int(b[0]) + } + if len(b) == 4 { + return nil, nil, nil + } + exts, l, err := parseExtensions(typ, b[4:], l) + if err != nil { + l = len(b) - 4 + } + var data []byte + if l > 0 { + data = make([]byte, l) + copy(data, b[4:]) + } + return data, exts, nil +} diff --git a/vendor/golang.org/x/net/icmp/packettoobig.go b/vendor/golang.org/x/net/icmp/packettoobig.go new file mode 100644 index 0000000000..afbf24f1ba --- /dev/null +++ b/vendor/golang.org/x/net/icmp/packettoobig.go @@ -0,0 +1,43 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "encoding/binary" + +// A PacketTooBig represents an ICMP packet too big message body. +type PacketTooBig struct { + MTU int // maximum transmission unit of the nexthop link + Data []byte // data, known as original datagram field +} + +// Len implements the Len method of MessageBody interface. +func (p *PacketTooBig) Len(proto int) int { + if p == nil { + return 0 + } + return 4 + len(p.Data) +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *PacketTooBig) Marshal(proto int) ([]byte, error) { + b := make([]byte, 4+len(p.Data)) + binary.BigEndian.PutUint32(b[:4], uint32(p.MTU)) + copy(b[4:], p.Data) + return b, nil +} + +// parsePacketTooBig parses b as an ICMP packet too big message body. +func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) { + bodyLen := len(b) + if bodyLen < 4 { + return nil, errMessageTooShort + } + p := &PacketTooBig{MTU: int(binary.BigEndian.Uint32(b[:4]))} + if bodyLen > 4 { + p.Data = make([]byte, bodyLen-4) + copy(p.Data, b[4:]) + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/paramprob.go b/vendor/golang.org/x/net/icmp/paramprob.go new file mode 100644 index 0000000000..f16fd33ec2 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/paramprob.go @@ -0,0 +1,72 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "encoding/binary" + + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" +) + +// A ParamProb represents an ICMP parameter problem message body. +type ParamProb struct { + Pointer uintptr // offset within the data where the error was detected + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *ParamProb) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *ParamProb) Marshal(proto int) ([]byte, error) { + switch proto { + case iana.ProtocolICMP: + if !validExtensions(ipv4.ICMPTypeParameterProblem, p.Extensions) { + return nil, errInvalidExtension + } + b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) + if err != nil { + return nil, err + } + b[0] = byte(p.Pointer) + return b, nil + case iana.ProtocolIPv6ICMP: + b := make([]byte, p.Len(proto)) + binary.BigEndian.PutUint32(b[:4], uint32(p.Pointer)) + copy(b[4:], p.Data) + return b, nil + default: + return nil, errInvalidProtocol + } +} + +// parseParamProb parses b as an ICMP parameter problem message body. +func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &ParamProb{} + if proto == iana.ProtocolIPv6ICMP { + p.Pointer = uintptr(binary.BigEndian.Uint32(b[:4])) + p.Data = make([]byte, len(b)-4) + copy(p.Data, b[4:]) + return p, nil + } + p.Pointer = uintptr(b[0]) + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/golang.org/x/net/icmp/sys_freebsd.go b/vendor/golang.org/x/net/icmp/sys_freebsd.go new file mode 100644 index 0000000000..c75f3ddaa7 --- /dev/null +++ b/vendor/golang.org/x/net/icmp/sys_freebsd.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import "syscall" + +func init() { + freebsdVersion, _ = syscall.SysctlUint32("kern.osreldate") +} diff --git a/vendor/golang.org/x/net/icmp/timeexceeded.go b/vendor/golang.org/x/net/icmp/timeexceeded.go new file mode 100644 index 0000000000..ffa986fdea --- /dev/null +++ b/vendor/golang.org/x/net/icmp/timeexceeded.go @@ -0,0 +1,57 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package icmp + +import ( + "golang.org/x/net/internal/iana" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" +) + +// A TimeExceeded represents an ICMP time exceeded message body. +type TimeExceeded struct { + Data []byte // data, known as original datagram field + Extensions []Extension // extensions +} + +// Len implements the Len method of MessageBody interface. +func (p *TimeExceeded) Len(proto int) int { + if p == nil { + return 0 + } + l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions) + return l +} + +// Marshal implements the Marshal method of MessageBody interface. +func (p *TimeExceeded) Marshal(proto int) ([]byte, error) { + var typ Type + switch proto { + case iana.ProtocolICMP: + typ = ipv4.ICMPTypeTimeExceeded + case iana.ProtocolIPv6ICMP: + typ = ipv6.ICMPTypeTimeExceeded + default: + return nil, errInvalidProtocol + } + if !validExtensions(typ, p.Extensions) { + return nil, errInvalidExtension + } + return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions) +} + +// parseTimeExceeded parses b as an ICMP time exceeded message body. +func parseTimeExceeded(proto int, typ Type, b []byte) (MessageBody, error) { + if len(b) < 4 { + return nil, errMessageTooShort + } + p := &TimeExceeded{} + var err error + p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b) + if err != nil { + return nil, err + } + return p, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 31f00187c0..d7e5104d79 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -333,6 +333,7 @@ golang.org/x/mod/semver # golang.org/x/net v0.19.0 ## explicit; go 1.18 golang.org/x/net/bpf +golang.org/x/net/icmp golang.org/x/net/internal/iana golang.org/x/net/internal/socket golang.org/x/net/ipv4 From 2b4e33c7aee6f1647767791796ab82dc97b575a7 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Wed, 17 Jan 2024 20:05:19 +0200 Subject: [PATCH 027/109] pkg/tarutil: move test data to testdata testdata ignored by go tool Signed-off-by: Siarhiej Siemianczuk --- pkg/tarutil/tar_test.go | 30 ++++++++++----------- pkg/tarutil/{ => testdata}/test.tar | Bin pkg/tarutil/{ => testdata}/test0/a.txt | 0 pkg/tarutil/{ => testdata}/test0/dir/b.txt | 0 pkg/tarutil/{ => testdata}/test1/a1.txt | 0 pkg/tarutil/{ => testdata}/test2.txt | 0 6 files changed, 15 insertions(+), 15 deletions(-) rename pkg/tarutil/{ => testdata}/test.tar (100%) rename pkg/tarutil/{ => testdata}/test0/a.txt (100%) rename pkg/tarutil/{ => testdata}/test0/dir/b.txt (100%) rename pkg/tarutil/{ => testdata}/test1/a1.txt (100%) rename pkg/tarutil/{ => testdata}/test2.txt (100%) diff --git a/pkg/tarutil/tar_test.go b/pkg/tarutil/tar_test.go index 0b1901734e..dfe828ec25 100644 --- a/pkg/tarutil/tar_test.go +++ b/pkg/tarutil/tar_test.go @@ -45,7 +45,7 @@ func TestExtractDir(t *testing.T) { {"a.txt", "hello\n"}, {"dir/b.txt", "world\n"}, } - extractAndCompare(t, "test.tar", files) + extractAndCompare(t, "testdata/test.tar", files) } func TestCreateTarSingleFile(t *testing.T) { @@ -57,7 +57,7 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatal(err) } - if err := CreateTar(f, []string{"test0"}, nil); err != nil { + if err := CreateTar(f, []string{"testdata/test0"}, nil); err != nil { f.Close() t.Fatal(err) } @@ -69,10 +69,10 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatalf("system tar could not parse the file: %v", err) } - expected := `test0 -test0/a.txt -test0/dir -test0/dir/b.txt + expected := `testdata/test0 +testdata/test0/a.txt +testdata/test0/dir +testdata/test0/dir/b.txt ` if string(out) != expected { t.Fatalf("got %q, want %q", string(out), expected) @@ -88,7 +88,7 @@ func TestCreateTarMultFiles(t *testing.T) { if err != nil { t.Fatal(err) } - files := []string{"test0", "test1", "test2.txt"} + files := []string{"testdata/test0", "testdata/test1", "testdata/test2.txt"} if err := CreateTar(f, files, nil); err != nil { f.Close() t.Fatal(err) @@ -101,13 +101,13 @@ func TestCreateTarMultFiles(t *testing.T) { if err != nil { t.Fatalf("system tar could not parse the file: %v", err) } - expected := `test0 -test0/a.txt -test0/dir -test0/dir/b.txt -test1 -test1/a1.txt -test2.txt + expected := `testdata/test0 +testdata/test0/a.txt +testdata/test0/dir +testdata/test0/dir/b.txt +testdata/test1 +testdata/test1/a1.txt +testdata/test2.txt ` if string(out) != expected { t.Fatalf("got %q, want %q", string(out), expected) @@ -168,7 +168,7 @@ func TestCreateTarProcfsFile(t *testing.T) { } func TestListArchive(t *testing.T) { - f, err := os.Open("test.tar") + f, err := os.Open("testdata/test.tar") if err != nil { t.Fatal(err) } diff --git a/pkg/tarutil/test.tar b/pkg/tarutil/testdata/test.tar similarity index 100% rename from pkg/tarutil/test.tar rename to pkg/tarutil/testdata/test.tar diff --git a/pkg/tarutil/test0/a.txt b/pkg/tarutil/testdata/test0/a.txt similarity index 100% rename from pkg/tarutil/test0/a.txt rename to pkg/tarutil/testdata/test0/a.txt diff --git a/pkg/tarutil/test0/dir/b.txt b/pkg/tarutil/testdata/test0/dir/b.txt similarity index 100% rename from pkg/tarutil/test0/dir/b.txt rename to pkg/tarutil/testdata/test0/dir/b.txt diff --git a/pkg/tarutil/test1/a1.txt b/pkg/tarutil/testdata/test1/a1.txt similarity index 100% rename from pkg/tarutil/test1/a1.txt rename to pkg/tarutil/testdata/test1/a1.txt diff --git a/pkg/tarutil/test2.txt b/pkg/tarutil/testdata/test2.txt similarity index 100% rename from pkg/tarutil/test2.txt rename to pkg/tarutil/testdata/test2.txt From 5e702f0ce6a680c28767f4537c4bf1b9a2752430 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Thu, 18 Jan 2024 08:02:15 +0200 Subject: [PATCH 028/109] pkg/tarutil: increase test coverage alter one test to not recurse and verbose filtering, remove two filters NoFilter is the same as not empty list, wrapping errors Signed-off-by: Siarhiej Siemianczuk --- pkg/tarutil/tar.go | 16 ++++------------ pkg/tarutil/tar_test.go | 11 +++++------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/pkg/tarutil/tar.go b/pkg/tarutil/tar.go index 0f19abb14a..76587c1deb 100644 --- a/pkg/tarutil/tar.go +++ b/pkg/tarutil/tar.go @@ -83,10 +83,10 @@ func ExtractDir(tarFile io.Reader, dir string, opts *Opts) error { fi, err := os.Stat(dir) if os.IsNotExist(err) { if err := os.Mkdir(dir, os.ModePerm); err != nil { - return fmt.Errorf("could not create directory %s: %v", dir, err) + return fmt.Errorf("could not create directory %s: %w", dir, err) } } else if err != nil || !fi.IsDir() { - return fmt.Errorf("could not stat directory %s: %v", dir, err) + return fmt.Errorf("could not stat directory %s: %w", dir, err) } return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error { @@ -196,10 +196,7 @@ func CreateTar(tarFile io.Writer, files []string, opts *Opts) error { return err } } - if err := tw.Close(); err != nil { - return err - } - return nil + return tw.Close() } func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { @@ -248,7 +245,7 @@ func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { } if err := os.Chmod(path, fi.Mode()&os.ModePerm); err != nil { - return fmt.Errorf("error setting mode %#o on %q: %v", + return fmt.Errorf("error setting mode %#o on %q: %w", fi.Mode()&os.ModePerm, path, err) } // TODO: also set ownership, etc... @@ -260,11 +257,6 @@ func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error { // the file is omitted. type Filter func(hdr *tar.Header) bool -// NoFilter does not filter or modify any files. -func NoFilter(hdr *tar.Header) bool { - return true -} - // VerboseFilter prints the name of every file. func VerboseFilter(hdr *tar.Header) bool { fmt.Println(hdr.Name) diff --git a/pkg/tarutil/tar_test.go b/pkg/tarutil/tar_test.go index dfe828ec25..a042a5df36 100644 --- a/pkg/tarutil/tar_test.go +++ b/pkg/tarutil/tar_test.go @@ -57,7 +57,10 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatal(err) } - if err := CreateTar(f, []string{"testdata/test0"}, nil); err != nil { + if err := CreateTar(f, []string{"testdata/test0"}, &Opts{ + NoRecursion: true, + Filters: []Filter{VerboseFilter, VerboseLogFilter}, + }); err != nil { f.Close() t.Fatal(err) } @@ -69,11 +72,7 @@ func TestCreateTarSingleFile(t *testing.T) { if err != nil { t.Fatalf("system tar could not parse the file: %v", err) } - expected := `testdata/test0 -testdata/test0/a.txt -testdata/test0/dir -testdata/test0/dir/b.txt -` + expected := "testdata/test0\n" if string(out) != expected { t.Fatalf("got %q, want %q", string(out), expected) } From 3d52f7b9733f17d5df50b4c3b8fe947f2ab39c41 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Mon, 22 Jan 2024 19:43:23 +0200 Subject: [PATCH 029/109] pkg/dhclient: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/dhclient/dhclient.go | 4 ++-- pkg/dhclient/dhcp4.go | 6 +++--- pkg/dhclient/dhcp6.go | 2 +- pkg/dhclient/iface.go | 2 +- pkg/dhclient/iscsiuri.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/dhclient/dhclient.go b/pkg/dhclient/dhclient.go index 9b9bc343f6..fe18c01d90 100644 --- a/pkg/dhclient/dhclient.go +++ b/pkg/dhclient/dhclient.go @@ -79,7 +79,7 @@ func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { // ethernet can just vanish. iface, err := netlink.LinkByName(ifname) if err != nil { - return nil, fmt.Errorf("cannot get interface %q by name: %v", ifname, err) + return nil, fmt.Errorf("cannot get interface %q by name: %w", ifname, err) } // Check if link is actually operational. @@ -90,7 +90,7 @@ func IfUp(ifname string, linkUpTimeout time.Duration) (netlink.Link, error) { } if err := netlink.LinkSetUp(iface); err != nil { - return nil, fmt.Errorf("interface %q: %v can't make it up: %v", ifname, iface, err) + return nil, fmt.Errorf("interface %q: %v can't make it up: %w", ifname, iface, err) } time.Sleep(100 * time.Millisecond) } diff --git a/pkg/dhclient/dhcp4.go b/pkg/dhclient/dhcp4.go index de4c76e5fa..e0fb70f1ab 100644 --- a/pkg/dhclient/dhcp4.go +++ b/pkg/dhclient/dhcp4.go @@ -78,7 +78,7 @@ func (p *Packet4) Configure() error { IPNet: l, } if err := netlink.AddrReplace(p.iface, dst); err != nil { - return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) + return fmt.Errorf("add/replace %s to %v: %w", dst, p.iface, err) } // RFC 3442 notes that if classless static routes are available, they @@ -96,7 +96,7 @@ func (p *Packet4) Configure() error { } if err := netlink.RouteReplace(r); err != nil { - return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) + return fmt.Errorf("%s: add %s: %w", p.iface.Attrs().Name, r, err) } } } else if gw := p.P.Router(); len(gw) > 0 { @@ -106,7 +106,7 @@ func (p *Packet4) Configure() error { } if err := netlink.RouteReplace(r); err != nil { - return fmt.Errorf("%s: add %s: %v", p.iface.Attrs().Name, r, err) + return fmt.Errorf("%s: add %s: %w", p.iface.Attrs().Name, r, err) } } diff --git a/pkg/dhclient/dhcp6.go b/pkg/dhclient/dhcp6.go index 9c950fd2f5..a8b6d7aa11 100644 --- a/pkg/dhclient/dhcp6.go +++ b/pkg/dhclient/dhcp6.go @@ -79,7 +79,7 @@ func (p *Packet6) Configure() error { if err := netlink.AddrReplace(p.iface, dst); err != nil { if os.IsExist(err) { - return fmt.Errorf("add/replace %s to %v: %v", dst, p.iface, err) + return fmt.Errorf("add/replace %s to %v: %w", dst, p.iface, err) } } diff --git a/pkg/dhclient/iface.go b/pkg/dhclient/iface.go index e9dd5f9c55..c39f991217 100644 --- a/pkg/dhclient/iface.go +++ b/pkg/dhclient/iface.go @@ -41,7 +41,7 @@ func Interfaces(ifName string) ([]netlink.Link, error) { ifnames, err := netlink.LinkList() if err != nil { - return nil, fmt.Errorf("can not get list of link names: %v", err) + return nil, fmt.Errorf("can not get list of link names: %w", err) } var filteredIfs []netlink.Link diff --git a/pkg/dhclient/iscsiuri.go b/pkg/dhclient/iscsiuri.go index f0f977f740..bbecc7dceb 100644 --- a/pkg/dhclient/iscsiuri.go +++ b/pkg/dhclient/iscsiuri.go @@ -94,7 +94,7 @@ func ParseISCSIURI(s string) (*net.TCPAddr, string, error) { if len(tok) > 0 { pv, err := strconv.Atoi(tok) if err != nil { - return nil, "", fmt.Errorf("iSCSI URI %q has invalid port: %v", s, err) + return nil, "", fmt.Errorf("iSCSI URI %q has invalid port: %w", s, err) } port = pv } @@ -103,7 +103,7 @@ func ParseISCSIURI(s string) (*net.TCPAddr, string, error) { } } if i.err != nil { - return nil, "", fmt.Errorf("iSCSI URI %q failed to parse: %v", s, i.err) + return nil, "", fmt.Errorf("iSCSI URI %q failed to parse: %w", s, i.err) } if magic != "iscsi" { return nil, "", fmt.Errorf("iSCSI URI %q is missing iscsi scheme prefix, have %s", s, magic) From 085feb90a6d616746050f172f17ed2be90ae0b87 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 23 Jan 2024 18:53:02 +0200 Subject: [PATCH 030/109] pkg/gpio: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/gpio/gpio_linux.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/gpio/gpio_linux.go b/pkg/gpio/gpio_linux.go index ed4c5f4f9d..8d5603e742 100644 --- a/pkg/gpio/gpio_linux.go +++ b/pkg/gpio/gpio_linux.go @@ -44,7 +44,7 @@ func readInt(filename string) (int, error) { // Get base offset (the first GPIO managed by this chip) buf, err := os.ReadFile(filename) if err != nil { - return 0, fmt.Errorf("failed to read integer out of %s: %v", filename, err) + return 0, fmt.Errorf("failed to read integer out of %s: %w", filename, err) } baseStr := strings.TrimSpace(string(buf)) num, err := strconv.Atoi(baseStr) @@ -104,7 +104,7 @@ func SetOutputValue(pin int, val Value) error { path := filepath.Join(gpioPath, fmt.Sprintf("gpio%d", pin), "direction") outFile, err := os.OpenFile(path, os.O_WRONLY, 0) if err != nil { - return fmt.Errorf("failed to open %s: %v", path, err) + return fmt.Errorf("failed to open %s: %w", path, err) } defer outFile.Close() if _, err := outFile.WriteString(dir); err != nil { @@ -135,11 +135,11 @@ func Export(pin int) error { path := filepath.Join(gpioPath, "export") outFile, err := os.OpenFile(path, os.O_WRONLY, 0) if err != nil { - return fmt.Errorf("failed to open %s: %v", path, err) + return fmt.Errorf("failed to open %s: %w", path, err) } defer outFile.Close() if _, err := outFile.WriteString(strconv.Itoa(pin)); err != nil { - return fmt.Errorf("failed to export gpio %d: %v", pin, err) + return fmt.Errorf("failed to export gpio %d: %w", pin, err) } return nil } From cce038e187a11f9f420671a6af7b666fa64ddf56 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 23 Jan 2024 19:23:51 +0200 Subject: [PATCH 031/109] pkg/acpi: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/acpi/acpi.go | 2 +- pkg/acpi/bios.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/acpi/acpi.go b/pkg/acpi/acpi.go index 6a63daa341..e0137f23eb 100644 --- a/pkg/acpi/acpi.go +++ b/pkg/acpi/acpi.go @@ -112,7 +112,7 @@ func String(t Table) string { func WriteTables(w io.Writer, tab Table, tabs ...Table) error { for _, tt := range append([]Table{tab}, tabs...) { if _, err := w.Write(tt.Data()); err != nil { - return fmt.Errorf("writing %s: %v", tt.Sig(), err) + return fmt.Errorf("writing %s: %w", tt.Sig(), err) } } return nil diff --git a/pkg/acpi/bios.go b/pkg/acpi/bios.go index cf104c57ce..644800b331 100644 --- a/pkg/acpi/bios.go +++ b/pkg/acpi/bios.go @@ -45,7 +45,7 @@ func ReadBiosTables() (*BiosTable, error) { Debug("Check Table at %#x", a) t, err := ReadRawTable(a) if err != nil { - return nil, fmt.Errorf("%#x:%v", a, err) + return nil, fmt.Errorf("%#x:%w", a, err) } Debug("Add table %s", String(t)) bios.Tables = append(bios.Tables, t) @@ -73,7 +73,7 @@ func ReadBiosTables() (*BiosTable, error) { // Fix that. t, err = ReadRawTable(int64(uint32(dsdt))) if err != nil { - return nil, fmt.Errorf("%#x:%v", uint64(dsdt), err) + return nil, fmt.Errorf("%#x:%w", uint64(dsdt), err) } Debug("Add table %s", String(t)) bios.Tables = append(bios.Tables, t) From 9f50c29eb28943f3ab2a4f1881c8283e607f6c73 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 23 Jan 2024 19:24:12 +0200 Subject: [PATCH 032/109] pkg/fb: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/fb/fb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/fb/fb.go b/pkg/fb/fb.go index e57ed68498..f8e9fd6e02 100644 --- a/pkg/fb/fb.go +++ b/pkg/fb/fb.go @@ -62,7 +62,7 @@ func DrawImageAt(img image.Image, posx int, posy int) error { DrawOnBufAt(buf, img, posx, posy, stride, bpp) err = os.WriteFile(fbdev, buf, 0o600) if err != nil { - return fmt.Errorf("error writing to framebuffer: %v", err) + return fmt.Errorf("error writing to framebuffer: %w", err) } return nil } @@ -103,7 +103,7 @@ func DrawScaledImageAt(img image.Image, posx int, posy int, factor int) error { DrawScaledOnBufAt(buf, img, posx, posy, factor, stride, bpp) err = os.WriteFile(fbdev, buf, 0o600) if err != nil { - return fmt.Errorf("error writing to framebuffer: %v", err) + return fmt.Errorf("error writing to framebuffer: %w", err) } return nil } From 46827e0f92a7da8abf32b2ea6ad0fcbebeaf1959 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 23 Jan 2024 18:58:25 +0200 Subject: [PATCH 033/109] pkg/cpio: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/cpio/fs_unix.go | 2 +- pkg/cpio/fs_windows.go | 2 +- pkg/cpio/newc.go | 4 ++-- pkg/cpio/utils.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/cpio/fs_unix.go b/pkg/cpio/fs_unix.go index 29d1f242a3..eef60fdfc2 100644 --- a/pkg/cpio/fs_unix.go +++ b/pkg/cpio/fs_unix.go @@ -124,7 +124,7 @@ func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error { // mode 755. if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 { if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err) + return fmt.Errorf("CreateFileInRoot %q: %w", f.Name, err) } } diff --git a/pkg/cpio/fs_windows.go b/pkg/cpio/fs_windows.go index bdaf3d99f2..1c6d43c36b 100644 --- a/pkg/cpio/fs_windows.go +++ b/pkg/cpio/fs_windows.go @@ -86,7 +86,7 @@ func CreateFileInRoot(f Record, rootDir string, forcePriv bool) error { // mode 755. if _, err := os.Stat(dir); os.IsNotExist(err) && len(dir) > 0 { if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("CreateFileInRoot %q: %v", f.Name, err) + return fmt.Errorf("CreateFileInRoot %q: %w", f.Name, err) } } diff --git a/pkg/cpio/newc.go b/pkg/cpio/newc.go index 44e616e559..9e767e83ed 100644 --- a/pkg/cpio/newc.go +++ b/pkg/cpio/newc.go @@ -259,7 +259,7 @@ func (r *reader) read(p []byte) error { } if err != nil || n != len(p) { - return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %v", r.pos, n, len(p), err) + return fmt.Errorf("ReadAt(pos = %d): got %d, want %d bytes; error %w", r.pos, n, len(p), err) } r.pos += int64(n) @@ -290,7 +290,7 @@ func (r *reader) ReadRecord() (Record, error) { // Decode hex header fields. dst := make([]byte, binary.Size(hdr)) if _, err := hex.Decode(dst, buf[magicLen:]); err != nil { - return Record{}, fmt.Errorf("reader: error decoding hex: %v", err) + return Record{}, fmt.Errorf("reader: error decoding hex: %w", err) } if err := binary.Read(bytes.NewReader(dst), binary.BigEndian, &hdr); err != nil { return Record{}, err diff --git a/pkg/cpio/utils.go b/pkg/cpio/utils.go index 988c1a5f16..71dde815b7 100644 --- a/pkg/cpio/utils.go +++ b/pkg/cpio/utils.go @@ -131,7 +131,7 @@ func (dw *DedupWriter) WriteRecord(rec Record) error { func WriteRecords(w RecordWriter, files []Record) error { for _, f := range files { if err := w.WriteRecord(f); err != nil { - return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) + return fmt.Errorf("WriteRecords: writing %q got %w", f.Info.Name, err) } } return nil @@ -190,7 +190,7 @@ func WriteRecordsAndDirs(rw RecordWriter, files []Record) error { } recs = append(recs, f) if err := WriteRecords(rw, recs); err != nil { - return fmt.Errorf("WriteRecords: writing %q got %v", f.Info.Name, err) + return fmt.Errorf("WriteRecords: writing %q got %w", f.Info.Name, err) } } return nil From 56724877d66b2832cde97a64fe3ab6f1f0baf181 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 01:18:59 +0000 Subject: [PATCH 034/109] Add new kernel, multiboot, uefipayload pkg Docker images Signed-off-by: Chris Koch --- .circleci/images/builduploadall.sh | 8 - .circleci/images/kernel-amd64/Dockerfile | 35 + .../config_linux.txt | 0 .circleci/images/kernel-arm/Dockerfile | 38 + .circleci/images/kernel-arm/config_linux.txt | 141 ++ .circleci/images/kernel-arm64/Dockerfile | 38 + .../config_linux.txt | 6 + .../multiboot-test-kernel-amd64/Dockerfile | 28 + .circleci/images/test-image-amd64/Dockerfile | 101 - .circleci/images/test-image-arm/Dockerfile | 70 - .../test-image-arm/config_linux5.10.0_arm.txt | 2124 ----------------- .circleci/images/test-image-arm64/Dockerfile | 73 - .circleci/images/uefipayload-amd64/Dockerfile | 34 + 13 files changed, 320 insertions(+), 2376 deletions(-) create mode 100644 .circleci/images/kernel-amd64/Dockerfile rename .circleci/images/{test-image-amd64 => kernel-amd64}/config_linux.txt (100%) create mode 100644 .circleci/images/kernel-arm/Dockerfile create mode 100644 .circleci/images/kernel-arm/config_linux.txt create mode 100644 .circleci/images/kernel-arm64/Dockerfile rename .circleci/images/{test-image-arm64 => kernel-arm64}/config_linux.txt (96%) create mode 100644 .circleci/images/multiboot-test-kernel-amd64/Dockerfile delete mode 100644 .circleci/images/test-image-amd64/Dockerfile delete mode 100644 .circleci/images/test-image-arm/Dockerfile delete mode 100644 .circleci/images/test-image-arm/config_linux5.10.0_arm.txt delete mode 100644 .circleci/images/test-image-arm64/Dockerfile create mode 100644 .circleci/images/uefipayload-amd64/Dockerfile diff --git a/.circleci/images/builduploadall.sh b/.circleci/images/builduploadall.sh index 6e7a7dfb31..41543367c3 100755 --- a/.circleci/images/builduploadall.sh +++ b/.circleci/images/builduploadall.sh @@ -10,14 +10,6 @@ if [[ $VC != "3" ]]; then exit 1 fi -for GOARCH in amd64 arm arm64; do - ( - cd test-image-$GOARCH - docker build . -t uroottest/test-image-$GOARCH:$VERSION - docker push uroottest/test-image-$GOARCH:$VERSION - ) -done - # Tamago has slightly different requirements; until we are sure why, # do a slightly custom build diff --git a/.circleci/images/kernel-amd64/Dockerfile b/.circleci/images/kernel-amd64/Dockerfile new file mode 100644 index 0000000000..0974e21e50 --- /dev/null +++ b/.circleci/images/kernel-amd64/Dockerfile @@ -0,0 +1,35 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + `# Linux kernel build deps` \ + libelf-dev; + +# Create working directory +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; + +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config; + +RUN cd linux; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/x86_64/boot/bzImage /bzImage diff --git a/.circleci/images/test-image-amd64/config_linux.txt b/.circleci/images/kernel-amd64/config_linux.txt similarity index 100% rename from .circleci/images/test-image-amd64/config_linux.txt rename to .circleci/images/kernel-amd64/config_linux.txt diff --git a/.circleci/images/kernel-arm/Dockerfile b/.circleci/images/kernel-arm/Dockerfile new file mode 100644 index 0000000000..303e4183a2 --- /dev/null +++ b/.circleci/images/kernel-arm/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2018-2023 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + gcc-arm-linux-gnueabi \ + `# Linux kernel build deps` \ + libelf-dev; + +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.1.68 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git + +# Copy config file +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config + +RUN cd linux; \ + export ARCH=arm; \ + export CROSS_COMPILE=arm-linux-gnueabi-; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/arm/boot/zImage /zImage diff --git a/.circleci/images/kernel-arm/config_linux.txt b/.circleci/images/kernel-arm/config_linux.txt new file mode 100644 index 0000000000..034da7259c --- /dev/null +++ b/.circleci/images/kernel-arm/config_linux.txt @@ -0,0 +1,141 @@ +# If you copy this to your Linux directory, run +# make olddefconfig +# to fill in the blanks. + +CONFIG_ARCH_MULTIPLATFORM=y +CONFIG_ARCH_MULTI_V7=y +CONFIG_ARCH_MULTI_V6_V7=y +CONFIG_ARCH_VIRT=y +CONFIG_RETPOLINE=n + +# CONFIG_ACPI_CUSTOM_DSDT_FILE="" +# CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y + +# Minimal kernel config needed for Go and serial port: +CONFIG_BINFMT_ELF=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_DEVTMPFS=y +CONFIG_TMPFS=y +CONFIG_EPOLL=y +CONFIG_FUTEX=y +CONFIG_PRINTK=y +CONFIG_PROC_FS=y +CONFIG_TTY=y +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y + +# Always print to serial +CONFIG_CMDLINE_BOOL=y +CONFIG_CMDLINE="console=ttyAMA0" + +# Add /dev/mem for io command: +CONFIG_DEVMEM=y +CONFIG_STRICT_DEVMEM=n + +# vfat filesystem: +CONFIG_BLOCK=y +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_BLK_DEV_NVME=y +CONFIG_BLK_DEV_SD=y +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_NLS=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ISO8859_1=y +CONFIG_MSDOS_FS=y +CONFIG_EXT4_FS=y + +# 9P filesystem +CONFIG_NET_9P=y +CONFIG_NET_9P_VIRTIO=y +CONFIG_9P_FS=y +# virtio-9p-device on qemu-system-arm uses virtio MMIO. +CONFIG_VIRTIO_MMIO=y + +CONFIG_PCI=y +CONFIG_VIRTIO_PCI=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCI_IOV=y +CONFIG_HOTPLUG_PCI=y +CONFIG_HOTPLUG_PCI_ACPI=y +CONFIG_PCI_AARDVARK=y +CONFIG_PCI_TEGRA=y +CONFIG_PCIE_RCAR=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCI_XGENE=y +CONFIG_PCIE_ALTERA=y +CONFIG_PCIE_ALTERA_MSI=y +CONFIG_PCI_HOST_THUNDER_PEM=y +CONFIG_PCI_HOST_THUNDER_ECAM=y +CONFIG_PCIE_ROCKCHIP_HOST=m +CONFIG_PCI_LAYERSCAPE=y +CONFIG_PCI_HISI=y +CONFIG_PCIE_QCOM=y +CONFIG_PCIE_ARMADA_8K=y +CONFIG_PCIE_KIRIN=y +CONFIG_PCIE_HISI_STB=y + +# Loop device for tcz: + +CONFIG_BLK_DEV_LOOP=y +CONFIG_MISC_FILESYSTEMS=y +CONFIG_SQUASHFS=y + +# Virtio Networking + random + storage +CONFIG_VIRTIO_PCI=y +CONFIG_HW_RANDOM_VIRTIO=y +CONFIG_CRYPTO_DEV_VIRTIO=y +CONFIG_VIRTIO_BLK=y +CONFIG_VIRTIO_SCSI=y +CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y + +# Networking +CONFIG_NET=y +CONFIG_INET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_IPV6=y +CONFIG_NET_CORE=y +CONFIG_NETDEVICES=y +CONFIG_ETHERNET=y +CONFIG_E1000=y + +# GPIO test - mock GPIO libraries +CONFIG_GPIOLIB=y +CONFIG_GPIO_MOCKUP=y +CONFIG_GPIO_SYSFS=y + +# For the kernel doing the kexec'ing +CONFIG_CRYPTO=y +CONFIG_KEXEC=y +CONFIG_KEXEC_FILE=y + +# For the kernel being kexec'ed +CONFIG_RELOCATABLE=y + +# Enable ACPI +CONFIG_ACPI=y + +# pkg/efivarfs (and its test) require immutable bit on xattr +CONFIG_TMPFS_XATTR=y + +# v6.0 has a missing dependency, and PCIE_KIRIN is "y" in the defconfig. +# Compilation fails if you use this and run `make olddefconfig` without setting +# CONFIG_PCIE_KIRIN=n explicitly. +CONFIG_PCIE_KIRIN=n + +# Enable time in guest. QEMU uses PL031 to set RTC. pkg/boot/fit requires +# current time signature checks. +CONFIG_RTC_CLASS=y +CONFIG_ARM_RZN1=y +CONFIG_RTC_DRV_PL031=y + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y diff --git a/.circleci/images/kernel-arm64/Dockerfile b/.circleci/images/kernel-arm64/Dockerfile new file mode 100644 index 0000000000..b8c6978c93 --- /dev/null +++ b/.circleci/images/kernel-arm64/Dockerfile @@ -0,0 +1,38 @@ +# Copyright 2020-2023 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + `# Linux dependencies` \ + bc \ + bison \ + flex \ + gcc \ + git \ + make \ + gcc-aarch64-linux-gnu \ + libssl-dev \ + `# Linux kernel build deps` \ + libelf-dev; + +WORKDIR /root + +# Build linux +RUN git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; + +COPY config_linux.txt linux/.config +RUN chmod 0444 linux/.config; + +RUN cd linux; \ + export ARCH=arm64; \ + export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-; \ + make olddefconfig; \ + make -j$(($(nproc) * 2 + 1)); + +FROM scratch +COPY --from=base /root/linux/arch/arm64/boot/Image /Image diff --git a/.circleci/images/test-image-arm64/config_linux.txt b/.circleci/images/kernel-arm64/config_linux.txt similarity index 96% rename from .circleci/images/test-image-arm64/config_linux.txt rename to .circleci/images/kernel-arm64/config_linux.txt index 9edbf80085..9b4a8342cb 100644 --- a/.circleci/images/test-image-arm64/config_linux.txt +++ b/.circleci/images/kernel-arm64/config_linux.txt @@ -86,6 +86,7 @@ CONFIG_CRYPTO_DEV_VIRTIO=y CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_SCSI=y CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y # Networking CONFIG_NET=y @@ -127,3 +128,8 @@ CONFIG_PCIE_KIRIN=n CONFIG_RTC_CLASS=y CONFIG_ARM_RZN1=y CONFIG_RTC_DRV_PL031=y + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y diff --git a/.circleci/images/multiboot-test-kernel-amd64/Dockerfile b/.circleci/images/multiboot-test-kernel-amd64/Dockerfile new file mode 100644 index 0000000000..9e594aaa36 --- /dev/null +++ b/.circleci/images/multiboot-test-kernel-amd64/Dockerfile @@ -0,0 +1,28 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling as base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + make \ + gcc-multilib \ + gzip \ + gcc; + +# Create working directory +WORKDIR /root + +# Build Multiboot kernel +RUN git clone --depth=1 https://github.com/u-root/multiboot-test-kernel; +RUN cd multiboot-test-kernel; \ + git checkout f5d529c940603ea9398a488f910142529387e4a6; \ + make; + +FROM scratch +COPY --from=base /root/multiboot-test-kernel/kernel ./mb/kernel +COPY --from=base /root/multiboot-test-kernel/kernel.gz ./mb/kernel.gz diff --git a/.circleci/images/test-image-amd64/Dockerfile b/.circleci/images/test-image-amd64/Dockerfile deleted file mode 100644 index 8682fa5695..0000000000 --- a/.circleci/images/test-image-amd64/Dockerfile +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2018-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc \ - git \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev \ - `# Multiboot kernel build deps` \ - gcc-multilib \ - gzip \ - `# Edk2 build deps` \ - uuid-dev \ - nasm \ - bash \ - iasl && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - make olddefconfig; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/x86_64/boot/bzImage bzImage; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=x86_64-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/x86_64-softmmu/qemu-system-x86_64 .; \ - rm -rf qemu/ - -# Build Multiboot kernel -RUN set -eux; \ - git clone --depth=1 \ - https://github.com/u-root/multiboot-test-kernel; \ - cd multiboot-test-kernel; \ - git checkout 1c7e4f4722077dcab308cd1df9818eab011e58c4; \ - make; \ - cd ~; \ - cp multiboot-test-kernel/kernel.gz ./; \ - rm -rf multiboot-test-kernel/ - -SHELL ["/bin/bash", "-c"] -RUN set -ex; \ - git clone --branch uefipayload-2023 --recursive \ - https://github.com/linuxboot/edk2 uefipayload; \ - cd uefipayload; \ - source ./edksetup.sh; \ - make -C BaseTools; \ - build -a X64 -p UefiPayloadPkg/UefiPayloadPkg.dsc -b DEBUG \ - -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; \ - cp Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd ~/; \ - cd ~; \ - rm -rf uefipayload/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/bzImage -ENV UROOT_QEMU "/home/circleci/qemu-system-x86_64 -L /home/circleci/pc-bios -m 1G" -ENV UROOT_TESTARCH amd64 diff --git a/.circleci/images/test-image-arm/Dockerfile b/.circleci/images/test-image-arm/Dockerfile deleted file mode 100644 index 3153acae71..0000000000 --- a/.circleci/images/test-image-arm/Dockerfile +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2018-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc-arm-linux-gnueabi \ - git \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux5.10.0_arm.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v5.10 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - export ARCH=arm; \ - export CROSS_COMPILE=/usr/bin/arm-linux-gnueabi-; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/arm/boot/zImage zImage; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=arm-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/arm-softmmu/qemu-system-arm .; \ - rm -rf qemu/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/zImage -ENV UROOT_QEMU "/home/circleci/qemu-system-arm -M virt -L /home/circleci/pc-bios" -ENV UROOT_TESTARCH arm diff --git a/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt b/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt deleted file mode 100644 index 7fda1b8223..0000000000 --- a/.circleci/images/test-image-arm/config_linux5.10.0_arm.txt +++ /dev/null @@ -1,2124 +0,0 @@ -# -# Automatically generated file; DO NOT EDIT. -# Linux/arm 5.2.0 Kernel Configuration -# - -# -# Compiler: arm-linux-gnueabi-gcc (Debian 8.3.0-6) 8.3.0 -# -CONFIG_CC_IS_GCC=y -CONFIG_GCC_VERSION=80300 -CONFIG_CLANG_VERSION=0 -CONFIG_CC_HAS_ASM_GOTO=y -CONFIG_CC_HAS_WARN_MAYBE_UNINITIALIZED=y -CONFIG_CC_DISABLE_WARN_MAYBE_UNINITIALIZED=y -CONFIG_IRQ_WORK=y -CONFIG_BUILDTIME_EXTABLE_SORT=y - -# -# General setup -# -CONFIG_BROKEN_ON_SMP=y -CONFIG_INIT_ENV_ARG_LIMIT=32 -# CONFIG_COMPILE_TEST is not set -CONFIG_LOCALVERSION="" -# CONFIG_LOCALVERSION_AUTO is not set -CONFIG_BUILD_SALT="" -CONFIG_HAVE_KERNEL_GZIP=y -CONFIG_HAVE_KERNEL_LZMA=y -CONFIG_HAVE_KERNEL_XZ=y -CONFIG_HAVE_KERNEL_LZO=y -CONFIG_HAVE_KERNEL_LZ4=y -# CONFIG_KERNEL_GZIP is not set -# CONFIG_KERNEL_LZMA is not set -CONFIG_KERNEL_XZ=y -# CONFIG_KERNEL_LZO is not set -# CONFIG_KERNEL_LZ4 is not set -CONFIG_DEFAULT_HOSTNAME="(none)" -CONFIG_SWAP=y -# CONFIG_SYSVIPC is not set -# CONFIG_POSIX_MQUEUE is not set -CONFIG_CROSS_MEMORY_ATTACH=y -# CONFIG_USELIB is not set -# CONFIG_AUDIT is not set -CONFIG_HAVE_ARCH_AUDITSYSCALL=y - -# -# IRQ subsystem -# -CONFIG_GENERIC_IRQ_PROBE=y -CONFIG_GENERIC_IRQ_SHOW=y -CONFIG_GENERIC_IRQ_SHOW_LEVEL=y -CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y -CONFIG_HARDIRQS_SW_RESEND=y -CONFIG_IRQ_DOMAIN=y -CONFIG_IRQ_DOMAIN_HIERARCHY=y -CONFIG_GENERIC_MSI_IRQ=y -CONFIG_GENERIC_MSI_IRQ_DOMAIN=y -CONFIG_HANDLE_DOMAIN_IRQ=y -CONFIG_IRQ_FORCED_THREADING=y -CONFIG_SPARSE_IRQ=y -# end of IRQ subsystem - -CONFIG_GENERIC_IRQ_MULTI_HANDLER=y -CONFIG_ARCH_CLOCKSOURCE_DATA=y -CONFIG_GENERIC_TIME_VSYSCALL=y -CONFIG_GENERIC_CLOCKEVENTS=y - -# -# Timers subsystem -# -CONFIG_HZ_PERIODIC=y -# CONFIG_NO_HZ_IDLE is not set -# CONFIG_NO_HZ is not set -# CONFIG_HIGH_RES_TIMERS is not set -# end of Timers subsystem - -CONFIG_PREEMPT_NONE=y -# CONFIG_PREEMPT_VOLUNTARY is not set -# CONFIG_PREEMPT is not set - -# -# CPU/Task time and stats accounting -# -CONFIG_TICK_CPU_ACCOUNTING=y -# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set -# CONFIG_IRQ_TIME_ACCOUNTING is not set -# CONFIG_PSI is not set -# end of CPU/Task time and stats accounting - -# -# RCU Subsystem -# -CONFIG_TINY_RCU=y -# CONFIG_RCU_EXPERT is not set -CONFIG_SRCU=y -CONFIG_TINY_SRCU=y -# end of RCU Subsystem - -# CONFIG_IKCONFIG is not set -CONFIG_LOG_BUF_SHIFT=17 -CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13 -CONFIG_GENERIC_SCHED_CLOCK=y - -# -# Scheduler features -# -# end of Scheduler features - -# CONFIG_CGROUPS is not set -# CONFIG_CHECKPOINT_RESTORE is not set -# CONFIG_SCHED_AUTOGROUP is not set -# CONFIG_RELAY is not set -CONFIG_BLK_DEV_INITRD=y -CONFIG_INITRAMFS_SOURCE="" -CONFIG_RD_GZIP=y -CONFIG_RD_BZIP2=y -CONFIG_RD_LZMA=y -CONFIG_RD_XZ=y -CONFIG_RD_LZO=y -CONFIG_RD_LZ4=y -# CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE is not set -CONFIG_CC_OPTIMIZE_FOR_SIZE=y -CONFIG_SYSCTL=y -CONFIG_HAVE_UID16=y -CONFIG_BPF=y -CONFIG_EXPERT=y -# CONFIG_MULTIUSER is not set -# CONFIG_SGETMASK_SYSCALL is not set -# CONFIG_SYSFS_SYSCALL is not set -# CONFIG_SYSCTL_SYSCALL is not set -# CONFIG_FHANDLE is not set -# CONFIG_POSIX_TIMERS is not set -CONFIG_PRINTK=y -CONFIG_PRINTK_NMI=y -# CONFIG_BUG is not set -# CONFIG_BASE_FULL is not set -CONFIG_FUTEX=y -CONFIG_FUTEX_PI=y -CONFIG_EPOLL=y -# CONFIG_SIGNALFD is not set -# CONFIG_TIMERFD is not set -# CONFIG_EVENTFD is not set -CONFIG_SHMEM=y -# CONFIG_AIO is not set -CONFIG_IO_URING=y -# CONFIG_ADVISE_SYSCALLS is not set -# CONFIG_MEMBARRIER is not set -# CONFIG_KALLSYMS is not set -# CONFIG_BPF_SYSCALL is not set -# CONFIG_USERFAULTFD is not set -CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y -# CONFIG_RSEQ is not set -CONFIG_EMBEDDED=y -CONFIG_HAVE_PERF_EVENTS=y -CONFIG_PERF_USE_VMALLOC=y -# CONFIG_PC104 is not set - -# -# Kernel Performance Events And Counters -# -# CONFIG_PERF_EVENTS is not set -# end of Kernel Performance Events And Counters - -# CONFIG_VM_EVENT_COUNTERS is not set -# CONFIG_COMPAT_BRK is not set -# CONFIG_SLAB is not set -# CONFIG_SLUB is not set -CONFIG_SLOB=y -# CONFIG_SLAB_MERGE_DEFAULT is not set -# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set -# CONFIG_PROFILING is not set -# end of General setup - -CONFIG_ARM=y -CONFIG_ARM_HAS_SG_CHAIN=y -CONFIG_SYS_SUPPORTS_APM_EMULATION=y -CONFIG_HAVE_PROC_CPU=y -CONFIG_STACKTRACE_SUPPORT=y -CONFIG_LOCKDEP_SUPPORT=y -CONFIG_TRACE_IRQFLAGS_SUPPORT=y -CONFIG_FIX_EARLYCON_MEM=y -CONFIG_GENERIC_HWEIGHT=y -CONFIG_GENERIC_CALIBRATE_DELAY=y -CONFIG_ARCH_SUPPORTS_UPROBES=y -CONFIG_ARM_PATCH_PHYS_VIRT=y -CONFIG_PGTABLE_LEVELS=2 - -# -# System Type -# -CONFIG_MMU=y -CONFIG_ARCH_MMAP_RND_BITS_MIN=8 -CONFIG_ARCH_MMAP_RND_BITS_MAX=16 -CONFIG_ARCH_MULTIPLATFORM=y -# CONFIG_ARCH_EBSA110 is not set -# CONFIG_ARCH_EP93XX is not set -# CONFIG_ARCH_FOOTBRIDGE is not set -# CONFIG_ARCH_NETX is not set -# CONFIG_ARCH_IOP13XX is not set -# CONFIG_ARCH_IOP32X is not set -# CONFIG_ARCH_IOP33X is not set -# CONFIG_ARCH_IXP4XX is not set -# CONFIG_ARCH_DOVE is not set -# CONFIG_ARCH_KS8695 is not set -# CONFIG_ARCH_W90X900 is not set -# CONFIG_ARCH_LPC32XX is not set -# CONFIG_ARCH_PXA is not set -# CONFIG_ARCH_RPC is not set -# CONFIG_ARCH_SA1100 is not set -# CONFIG_ARCH_S3C24XX is not set -# CONFIG_ARCH_DAVINCI is not set -# CONFIG_ARCH_OMAP1 is not set - -# -# Multiple platform selection -# - -# -# CPU Core family selection -# -# CONFIG_ARCH_MULTI_V6 is not set -CONFIG_ARCH_MULTI_V7=y -CONFIG_ARCH_MULTI_V6_V7=y -# end of Multiple platform selection - -CONFIG_ARCH_VIRT=y -# CONFIG_ARCH_ACTIONS is not set -# CONFIG_ARCH_ALPINE is not set -# CONFIG_ARCH_ARTPEC is not set -# CONFIG_ARCH_AT91 is not set -# CONFIG_ARCH_BCM is not set -# CONFIG_ARCH_BERLIN is not set -# CONFIG_ARCH_DIGICOLOR is not set -# CONFIG_ARCH_EXYNOS is not set -# CONFIG_ARCH_HIGHBANK is not set -# CONFIG_ARCH_HISI is not set -# CONFIG_ARCH_MXC is not set -# CONFIG_ARCH_KEYSTONE is not set -# CONFIG_ARCH_MEDIATEK is not set -# CONFIG_ARCH_MESON is not set -# CONFIG_ARCH_MILBEAUT is not set -# CONFIG_ARCH_MMP is not set -# CONFIG_ARCH_MVEBU is not set -# CONFIG_ARCH_NPCM is not set - -# -# TI OMAP/AM/DM/DRA Family -# -# CONFIG_ARCH_OMAP3 is not set -# CONFIG_ARCH_OMAP4 is not set -# CONFIG_SOC_OMAP5 is not set -# CONFIG_SOC_AM33XX is not set -# CONFIG_SOC_AM43XX is not set -# CONFIG_SOC_DRA7XX is not set -# end of TI OMAP/AM/DM/DRA Family - -# CONFIG_ARCH_SIRF is not set -# CONFIG_ARCH_QCOM is not set -# CONFIG_ARCH_RDA is not set -# CONFIG_ARCH_REALVIEW is not set -# CONFIG_ARCH_ROCKCHIP is not set -# CONFIG_ARCH_S5PV210 is not set -# CONFIG_ARCH_RENESAS is not set -# CONFIG_ARCH_SOCFPGA is not set -# CONFIG_PLAT_SPEAR is not set -# CONFIG_ARCH_STI is not set -# CONFIG_ARCH_STM32 is not set -# CONFIG_ARCH_SUNXI is not set -# CONFIG_ARCH_TANGO is not set -# CONFIG_ARCH_TEGRA is not set -# CONFIG_ARCH_UNIPHIER is not set -# CONFIG_ARCH_U8500 is not set -# CONFIG_ARCH_VEXPRESS is not set -# CONFIG_ARCH_WM8850 is not set -# CONFIG_ARCH_ZX is not set -# CONFIG_ARCH_ZYNQ is not set - -# -# Processor Type -# -CONFIG_CPU_V7=y -CONFIG_CPU_THUMB_CAPABLE=y -CONFIG_CPU_32v6K=y -CONFIG_CPU_32v7=y -CONFIG_CPU_ABRT_EV7=y -CONFIG_CPU_PABRT_V7=y -CONFIG_CPU_CACHE_V7=y -CONFIG_CPU_CACHE_VIPT=y -CONFIG_CPU_COPY_V6=y -CONFIG_CPU_TLB_V7=y -CONFIG_CPU_HAS_ASID=y -CONFIG_CPU_CP15=y -CONFIG_CPU_CP15_MMU=y - -# -# Processor Features -# -# CONFIG_ARM_LPAE is not set -CONFIG_ARM_THUMB=y -# CONFIG_ARM_THUMBEE is not set -CONFIG_ARM_VIRT_EXT=y -# CONFIG_SWP_EMULATE is not set -# CONFIG_CPU_BIG_ENDIAN is not set -# CONFIG_CPU_ICACHE_DISABLE is not set -# CONFIG_CPU_DCACHE_DISABLE is not set -# CONFIG_CPU_BPREDICT_DISABLE is not set -CONFIG_CPU_SPECTRE=y -CONFIG_HARDEN_BRANCH_PREDICTOR=y -CONFIG_KUSER_HELPERS=y -CONFIG_VDSO=y -CONFIG_OUTER_CACHE=y -CONFIG_OUTER_CACHE_SYNC=y -CONFIG_MIGHT_HAVE_CACHE_L2X0=y -CONFIG_CACHE_L2X0=y -# CONFIG_PL310_ERRATA_588369 is not set -# CONFIG_PL310_ERRATA_727915 is not set -# CONFIG_PL310_ERRATA_753970 is not set -# CONFIG_PL310_ERRATA_769419 is not set -CONFIG_ARM_L1_CACHE_SHIFT_6=y -CONFIG_ARM_L1_CACHE_SHIFT=6 -CONFIG_ARM_DMA_MEM_BUFFERABLE=y -CONFIG_ARM_HEAVY_MB=y -CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y -CONFIG_DEBUG_ALIGN_RODATA=y -# CONFIG_ARM_ERRATA_430973 is not set -# CONFIG_ARM_ERRATA_720789 is not set -# CONFIG_ARM_ERRATA_754322 is not set -# CONFIG_ARM_ERRATA_775420 is not set -# CONFIG_ARM_ERRATA_773022 is not set -# CONFIG_ARM_ERRATA_818325_852422 is not set -# CONFIG_ARM_ERRATA_821420 is not set -# CONFIG_ARM_ERRATA_825619 is not set -# CONFIG_ARM_ERRATA_857271 is not set -# CONFIG_ARM_ERRATA_852421 is not set -# CONFIG_ARM_ERRATA_852423 is not set -# CONFIG_ARM_ERRATA_857272 is not set -# end of System Type - -# -# Bus support -# -# CONFIG_ARM_ERRATA_814220 is not set -# end of Bus support - -# -# Kernel Features -# -CONFIG_HAVE_SMP=y -# CONFIG_SMP is not set -CONFIG_HAVE_ARM_ARCH_TIMER=y -CONFIG_VMSPLIT_3G=y -# CONFIG_VMSPLIT_3G_OPT is not set -# CONFIG_VMSPLIT_2G is not set -# CONFIG_VMSPLIT_1G is not set -CONFIG_PAGE_OFFSET=0xC0000000 -CONFIG_ARM_PSCI=y -CONFIG_ARCH_NR_GPIO=0 -CONFIG_HZ_FIXED=0 -CONFIG_HZ_100=y -# CONFIG_HZ_200 is not set -# CONFIG_HZ_250 is not set -# CONFIG_HZ_300 is not set -# CONFIG_HZ_500 is not set -# CONFIG_HZ_1000 is not set -CONFIG_HZ=100 -CONFIG_THUMB2_KERNEL=y -CONFIG_ARM_PATCH_IDIV=y -CONFIG_AEABI=y -CONFIG_HAVE_ARCH_PFN_VALID=y -# CONFIG_HIGHMEM is not set -CONFIG_CPU_SW_DOMAIN_PAN=y -CONFIG_ARCH_WANT_GENERAL_HUGETLB=y -CONFIG_FORCE_MAX_ZONEORDER=11 -CONFIG_ALIGNMENT_TRAP=y -# CONFIG_UACCESS_WITH_MEMCPY is not set -# CONFIG_SECCOMP is not set -# CONFIG_PARAVIRT is not set -# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set -# CONFIG_XEN is not set -# end of Kernel Features - -# -# Boot options -# -CONFIG_USE_OF=y -# CONFIG_ATAGS is not set -CONFIG_ZBOOT_ROM_TEXT=0 -CONFIG_ZBOOT_ROM_BSS=0 -# CONFIG_ARM_APPENDED_DTB is not set -CONFIG_CMDLINE="" -# CONFIG_KEXEC is not set -# CONFIG_CRASH_DUMP is not set -CONFIG_AUTO_ZRELADDR=y -# CONFIG_EFI is not set -# end of Boot options - -# -# CPU Power Management -# - -# -# CPU Frequency scaling -# -# CONFIG_CPU_FREQ is not set -# end of CPU Frequency scaling - -# -# CPU Idle -# -# CONFIG_CPU_IDLE is not set -# end of CPU Idle -# end of CPU Power Management - -# -# Floating point emulation -# - -# -# At least one emulation must be selected -# -# CONFIG_VFP is not set -# end of Floating point emulation - -# -# Power management options -# -# CONFIG_SUSPEND is not set -# CONFIG_HIBERNATION is not set -# CONFIG_PM is not set -# CONFIG_APM_EMULATION is not set -CONFIG_ARCH_SUSPEND_POSSIBLE=y -CONFIG_ARM_CPU_SUSPEND=y -CONFIG_ARCH_HIBERNATION_POSSIBLE=y -# end of Power management options - -# -# Firmware Drivers -# -# CONFIG_FIRMWARE_MEMMAP is not set -# CONFIG_TRUSTED_FOUNDATIONS is not set -CONFIG_HAVE_ARM_SMCCC=y -CONFIG_ARM_PSCI_FW=y -# CONFIG_GOOGLE_FIRMWARE is not set - -# -# Tegra firmware driver -# -# end of Tegra firmware driver -# end of Firmware Drivers - -# CONFIG_ARM_CRYPTO is not set -# CONFIG_VIRTUALIZATION is not set - -# -# General architecture-dependent options -# -CONFIG_HAVE_OPROFILE=y -# CONFIG_JUMP_LABEL is not set -CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y -CONFIG_ARCH_USE_BUILTIN_BSWAP=y -CONFIG_HAVE_KPROBES=y -CONFIG_HAVE_KRETPROBES=y -CONFIG_HAVE_NMI=y -CONFIG_HAVE_ARCH_TRACEHOOK=y -CONFIG_HAVE_DMA_CONTIGUOUS=y -CONFIG_GENERIC_SMP_IDLE_THREAD=y -CONFIG_GENERIC_IDLE_POLL_SETUP=y -CONFIG_ARCH_HAS_FORTIFY_SOURCE=y -CONFIG_ARCH_HAS_KEEPINITRD=y -CONFIG_ARCH_HAS_SET_MEMORY=y -CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y -CONFIG_ARCH_32BIT_OFF_T=y -CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y -CONFIG_HAVE_RSEQ=y -CONFIG_HAVE_CLK=y -CONFIG_HAVE_PERF_REGS=y -CONFIG_HAVE_PERF_USER_STACK_DUMP=y -CONFIG_HAVE_ARCH_JUMP_LABEL=y -CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y -CONFIG_HAVE_ARCH_SECCOMP_FILTER=y -CONFIG_HAVE_STACKPROTECTOR=y -CONFIG_CC_HAS_STACKPROTECTOR_NONE=y -# CONFIG_STACKPROTECTOR is not set -CONFIG_HAVE_CONTEXT_TRACKING=y -CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y -CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y -CONFIG_HAVE_MOD_ARCH_SPECIFIC=y -CONFIG_MODULES_USE_ELF_REL=y -CONFIG_ARCH_HAS_ELF_RANDOMIZE=y -CONFIG_HAVE_ARCH_MMAP_RND_BITS=y -CONFIG_HAVE_EXIT_THREAD=y -CONFIG_ARCH_MMAP_RND_BITS=8 -CONFIG_CLONE_BACKWARDS=y -CONFIG_OLD_SIGSUSPEND3=y -CONFIG_OLD_SIGACTION=y -CONFIG_64BIT_TIME=y -CONFIG_COMPAT_32BIT_TIME=y -CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y -CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y -CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y -CONFIG_STRICT_KERNEL_RWX=y -CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y -CONFIG_ARCH_HAS_PHYS_TO_DMA=y -CONFIG_REFCOUNT_FULL=y - -# -# GCOV-based kernel profiling -# -CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y -# end of GCOV-based kernel profiling - -CONFIG_PLUGIN_HOSTCC="" -CONFIG_HAVE_GCC_PLUGINS=y -# end of General architecture-dependent options - -CONFIG_RT_MUTEXES=y -CONFIG_BASE_SMALL=1 -# CONFIG_MODULES is not set -CONFIG_BLOCK=y -CONFIG_BLK_SCSI_REQUEST=y -CONFIG_BLK_DEV_BSG=y -# CONFIG_BLK_DEV_BSGLIB is not set -# CONFIG_BLK_DEV_INTEGRITY is not set -# CONFIG_BLK_DEV_ZONED is not set -# CONFIG_BLK_CMDLINE_PARSER is not set -# CONFIG_BLK_WBT is not set -# CONFIG_BLK_SED_OPAL is not set - -# -# Partition Types -# -# CONFIG_PARTITION_ADVANCED is not set -CONFIG_MSDOS_PARTITION=y -CONFIG_EFI_PARTITION=y -# end of Partition Types - -CONFIG_BLK_MQ_VIRTIO=y - -# -# IO Schedulers -# -CONFIG_MQ_IOSCHED_DEADLINE=y -CONFIG_MQ_IOSCHED_KYBER=y -# CONFIG_IOSCHED_BFQ is not set -# end of IO Schedulers - -CONFIG_INLINE_SPIN_UNLOCK_IRQ=y -CONFIG_INLINE_READ_UNLOCK=y -CONFIG_INLINE_READ_UNLOCK_IRQ=y -CONFIG_INLINE_WRITE_UNLOCK=y -CONFIG_INLINE_WRITE_UNLOCK_IRQ=y -CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y - -# -# Executable file formats -# -CONFIG_BINFMT_ELF=y -# CONFIG_BINFMT_ELF_FDPIC is not set -CONFIG_ELFCORE=y -# CONFIG_BINFMT_SCRIPT is not set -CONFIG_ARCH_HAS_BINFMT_FLAT=y -# CONFIG_BINFMT_FLAT is not set -CONFIG_BINFMT_FLAT_ARGVP_ENVP_ON_STACK=y -# CONFIG_BINFMT_MISC is not set -# CONFIG_COREDUMP is not set -# end of Executable file formats - -# -# Memory Management options -# -CONFIG_FLATMEM=y -CONFIG_FLAT_NODE_MEM_MAP=y -CONFIG_ARCH_KEEP_MEMBLOCK=y -CONFIG_SPLIT_PTLOCK_CPUS=4 -CONFIG_COMPACTION=y -CONFIG_MIGRATION=y -# CONFIG_KSM is not set -CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 -CONFIG_NEED_PER_CPU_KM=y -# CONFIG_CLEANCACHE is not set -# CONFIG_FRONTSWAP is not set -# CONFIG_CMA is not set -# CONFIG_ZPOOL is not set -# CONFIG_ZBUD is not set -# CONFIG_ZSMALLOC is not set -CONFIG_GENERIC_EARLY_IOREMAP=y -# CONFIG_PERCPU_STATS is not set -# CONFIG_GUP_BENCHMARK is not set -# end of Memory Management options - -CONFIG_NET=y - -# -# Networking options -# -# CONFIG_PACKET is not set -# CONFIG_UNIX is not set -# CONFIG_TLS is not set -# CONFIG_XFRM_USER is not set -# CONFIG_NET_KEY is not set -CONFIG_INET=y -# CONFIG_IP_MULTICAST is not set -# CONFIG_IP_ADVANCED_ROUTER is not set -# CONFIG_IP_PNP is not set -# CONFIG_NET_IPIP is not set -# CONFIG_NET_IPGRE_DEMUX is not set -CONFIG_NET_IP_TUNNEL=y -# CONFIG_SYN_COOKIES is not set -# CONFIG_NET_IPVTI is not set -# CONFIG_NET_FOU is not set -# CONFIG_NET_FOU_IP_TUNNELS is not set -# CONFIG_INET_AH is not set -# CONFIG_INET_ESP is not set -# CONFIG_INET_IPCOMP is not set -CONFIG_INET_TUNNEL=y -CONFIG_INET_DIAG=y -CONFIG_INET_TCP_DIAG=y -# CONFIG_INET_UDP_DIAG is not set -# CONFIG_INET_RAW_DIAG is not set -# CONFIG_INET_DIAG_DESTROY is not set -# CONFIG_TCP_CONG_ADVANCED is not set -CONFIG_TCP_CONG_CUBIC=y -CONFIG_DEFAULT_TCP_CONG="cubic" -# CONFIG_TCP_MD5SIG is not set -CONFIG_IPV6=y -# CONFIG_IPV6_ROUTER_PREF is not set -# CONFIG_IPV6_OPTIMISTIC_DAD is not set -# CONFIG_INET6_AH is not set -# CONFIG_INET6_ESP is not set -# CONFIG_INET6_IPCOMP is not set -# CONFIG_IPV6_MIP6 is not set -# CONFIG_IPV6_VTI is not set -CONFIG_IPV6_SIT=y -# CONFIG_IPV6_SIT_6RD is not set -CONFIG_IPV6_NDISC_NODETYPE=y -# CONFIG_IPV6_TUNNEL is not set -# CONFIG_IPV6_MULTIPLE_TABLES is not set -# CONFIG_IPV6_MROUTE is not set -# CONFIG_IPV6_SEG6_LWTUNNEL is not set -# CONFIG_IPV6_SEG6_HMAC is not set -# CONFIG_NETWORK_SECMARK is not set -# CONFIG_NETWORK_PHY_TIMESTAMPING is not set -# CONFIG_NETFILTER is not set -# CONFIG_BPFILTER is not set -# CONFIG_IP_DCCP is not set -# CONFIG_IP_SCTP is not set -# CONFIG_RDS is not set -# CONFIG_TIPC is not set -# CONFIG_ATM is not set -# CONFIG_L2TP is not set -# CONFIG_BRIDGE is not set -CONFIG_HAVE_NET_DSA=y -# CONFIG_NET_DSA is not set -# CONFIG_VLAN_8021Q is not set -# CONFIG_DECNET is not set -# CONFIG_LLC2 is not set -# CONFIG_ATALK is not set -# CONFIG_X25 is not set -# CONFIG_LAPB is not set -# CONFIG_PHONET is not set -# CONFIG_6LOWPAN is not set -# CONFIG_IEEE802154 is not set -# CONFIG_NET_SCHED is not set -# CONFIG_DCB is not set -# CONFIG_BATMAN_ADV is not set -# CONFIG_OPENVSWITCH is not set -# CONFIG_VSOCKETS is not set -# CONFIG_NETLINK_DIAG is not set -# CONFIG_MPLS is not set -# CONFIG_NET_NSH is not set -# CONFIG_HSR is not set -# CONFIG_NET_SWITCHDEV is not set -# CONFIG_NET_L3_MASTER_DEV is not set -# CONFIG_NET_NCSI is not set -CONFIG_NET_RX_BUSY_POLL=y - -# -# Network testing -# -# CONFIG_NET_PKTGEN is not set -# end of Network testing -# end of Networking options - -# CONFIG_HAMRADIO is not set -# CONFIG_CAN is not set -# CONFIG_BT is not set -# CONFIG_AF_RXRPC is not set -# CONFIG_AF_KCM is not set -CONFIG_WIRELESS=y -# CONFIG_CFG80211 is not set - -# -# CFG80211 needs to be enabled for MAC80211 -# -CONFIG_MAC80211_STA_HASH_MAX_SIZE=0 -# CONFIG_WIMAX is not set -# CONFIG_RFKILL is not set -CONFIG_NET_9P=y -CONFIG_NET_9P_VIRTIO=y -# CONFIG_NET_9P_DEBUG is not set -# CONFIG_CAIF is not set -# CONFIG_CEPH_LIB is not set -# CONFIG_NFC is not set -# CONFIG_PSAMPLE is not set -# CONFIG_NET_IFE is not set -# CONFIG_LWTUNNEL is not set -CONFIG_DST_CACHE=y -CONFIG_GRO_CELLS=y -CONFIG_FAILOVER=y -CONFIG_HAVE_EBPF_JIT=y - -# -# Device Drivers -# -CONFIG_ARM_AMBA=y -CONFIG_HAVE_PCI=y -# CONFIG_PCI is not set -# CONFIG_PCCARD is not set - -# -# Generic Driver Options -# -# CONFIG_UEVENT_HELPER is not set -CONFIG_DEVTMPFS=y -# CONFIG_DEVTMPFS_MOUNT is not set -# CONFIG_STANDALONE is not set -# CONFIG_PREVENT_FIRMWARE_BUILD is not set - -# -# Firmware loader -# -# CONFIG_FW_LOADER is not set -# end of Firmware loader - -# CONFIG_ALLOW_DEV_COREDUMP is not set -# CONFIG_DEBUG_DRIVER is not set -# CONFIG_DEBUG_DEVRES is not set -# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set -CONFIG_GENERIC_CPU_AUTOPROBE=y -# end of Generic Driver Options - -# -# Bus devices -# -# CONFIG_BRCMSTB_GISB_ARB is not set -# CONFIG_VEXPRESS_CONFIG is not set -# end of Bus devices - -# CONFIG_CONNECTOR is not set -# CONFIG_GNSS is not set -# CONFIG_MTD is not set -CONFIG_DTC=y -CONFIG_OF=y -# CONFIG_OF_UNITTEST is not set -CONFIG_OF_FLATTREE=y -CONFIG_OF_EARLY_FLATTREE=y -CONFIG_OF_ADDRESS=y -CONFIG_OF_IRQ=y -CONFIG_OF_NET=y -CONFIG_OF_RESERVED_MEM=y -# CONFIG_OF_OVERLAY is not set -CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y -# CONFIG_PARPORT is not set -CONFIG_BLK_DEV=y -# CONFIG_BLK_DEV_NULL_BLK is not set -CONFIG_BLK_DEV_LOOP=y -CONFIG_BLK_DEV_LOOP_MIN_COUNT=8 -# CONFIG_BLK_DEV_CRYPTOLOOP is not set -# CONFIG_BLK_DEV_DRBD is not set -# CONFIG_BLK_DEV_NBD is not set -# CONFIG_BLK_DEV_RAM is not set -# CONFIG_CDROM_PKTCDVD is not set -# CONFIG_ATA_OVER_ETH is not set -# CONFIG_VIRTIO_BLK is not set -# CONFIG_BLK_DEV_RBD is not set - -# -# NVME Support -# -# CONFIG_NVME_FC is not set -# end of NVME Support - -# -# Misc devices -# -# CONFIG_DUMMY_IRQ is not set -# CONFIG_ENCLOSURE_SERVICES is not set -# CONFIG_SRAM is not set -# CONFIG_PVPANIC is not set -# CONFIG_C2PORT is not set - -# -# EEPROM support -# -# CONFIG_EEPROM_93CX6 is not set -# end of EEPROM support - -# -# Texas Instruments shared transport line discipline -# -# end of Texas Instruments shared transport line discipline - -# -# Altera FPGA firmware download module (requires I2C) -# - -# -# Intel MIC & related support -# - -# -# Intel MIC Bus Driver -# - -# -# SCIF Bus Driver -# - -# -# VOP Bus Driver -# -# CONFIG_VOP_BUS is not set - -# -# Intel MIC Host Driver -# - -# -# Intel MIC Card Driver -# - -# -# SCIF Driver -# - -# -# Intel MIC Coprocessor State Management (COSM) Drivers -# - -# -# VOP Driver -# -# end of Intel MIC & related support - -# CONFIG_ECHO is not set -# end of Misc devices - -# -# SCSI device support -# -CONFIG_SCSI_MOD=y -# CONFIG_RAID_ATTRS is not set -CONFIG_SCSI=y -CONFIG_SCSI_DMA=y -CONFIG_SCSI_PROC_FS=y - -# -# SCSI support type (disk, tape, CD-ROM) -# -CONFIG_BLK_DEV_SD=y -# CONFIG_CHR_DEV_ST is not set -# CONFIG_CHR_DEV_OSST is not set -# CONFIG_BLK_DEV_SR is not set -# CONFIG_CHR_DEV_SG is not set -# CONFIG_CHR_DEV_SCH is not set -# CONFIG_SCSI_CONSTANTS is not set -# CONFIG_SCSI_LOGGING is not set -# CONFIG_SCSI_SCAN_ASYNC is not set - -# -# SCSI Transports -# -# CONFIG_SCSI_SPI_ATTRS is not set -# CONFIG_SCSI_FC_ATTRS is not set -# CONFIG_SCSI_ISCSI_ATTRS is not set -# CONFIG_SCSI_SAS_ATTRS is not set -# CONFIG_SCSI_SAS_LIBSAS is not set -# CONFIG_SCSI_SRP_ATTRS is not set -# end of SCSI Transports - -CONFIG_SCSI_LOWLEVEL=y -# CONFIG_ISCSI_TCP is not set -# CONFIG_ISCSI_BOOT_SYSFS is not set -# CONFIG_SCSI_UFSHCD is not set -# CONFIG_SCSI_DEBUG is not set -# CONFIG_SCSI_VIRTIO is not set -# CONFIG_SCSI_DH is not set -# end of SCSI device support - -CONFIG_ATA=y -CONFIG_ATA_VERBOSE_ERROR=y -CONFIG_SATA_PMP=y - -# -# Controllers with non-SFF native interface -# -# CONFIG_SATA_AHCI_PLATFORM is not set -# CONFIG_AHCI_CEVA is not set -# CONFIG_AHCI_QORIQ is not set -CONFIG_ATA_SFF=y - -# -# SFF controllers with custom DMA interface -# -CONFIG_ATA_BMDMA=y - -# -# SATA SFF controllers with BMDMA -# - -# -# PATA SFF controllers with BMDMA -# - -# -# PIO-only SFF controllers -# -# CONFIG_PATA_PLATFORM is not set - -# -# Generic fallback / legacy drivers -# -# CONFIG_MD is not set -# CONFIG_TARGET_CORE is not set -CONFIG_NETDEVICES=y -CONFIG_NET_CORE=y -# CONFIG_BONDING is not set -# CONFIG_DUMMY is not set -# CONFIG_EQUALIZER is not set -# CONFIG_NET_TEAM is not set -# CONFIG_MACVLAN is not set -# CONFIG_IPVLAN is not set -# CONFIG_VXLAN is not set -# CONFIG_GENEVE is not set -# CONFIG_GTP is not set -# CONFIG_MACSEC is not set -# CONFIG_NETCONSOLE is not set -# CONFIG_TUN is not set -# CONFIG_TUN_VNET_CROSS_LE is not set -# CONFIG_VETH is not set -CONFIG_VIRTIO_NET=y -# CONFIG_NLMON is not set - -# -# CAIF transport drivers -# - -# -# Distributed Switch Architecture drivers -# -# end of Distributed Switch Architecture drivers - -CONFIG_ETHERNET=y -CONFIG_NET_VENDOR_ALACRITECH=y -# CONFIG_ALTERA_TSE is not set -CONFIG_NET_VENDOR_AMAZON=y -CONFIG_NET_VENDOR_AQUANTIA=y -CONFIG_NET_VENDOR_ARC=y -CONFIG_NET_VENDOR_AURORA=y -# CONFIG_AURORA_NB8800 is not set -CONFIG_NET_VENDOR_BROADCOM=y -# CONFIG_B44 is not set -# CONFIG_BCMGENET is not set -# CONFIG_SYSTEMPORT is not set -CONFIG_NET_VENDOR_CADENCE=y -# CONFIG_MACB is not set -CONFIG_NET_VENDOR_CAVIUM=y -CONFIG_NET_VENDOR_CIRRUS=y -# CONFIG_CS89x0 is not set -CONFIG_NET_VENDOR_CORTINA=y -# CONFIG_GEMINI_ETHERNET is not set -# CONFIG_DM9000 is not set -# CONFIG_DNET is not set -CONFIG_NET_VENDOR_EZCHIP=y -# CONFIG_EZCHIP_NPS_MANAGEMENT_ENET is not set -CONFIG_NET_VENDOR_FARADAY=y -# CONFIG_FTMAC100 is not set -# CONFIG_FTGMAC100 is not set -CONFIG_NET_VENDOR_HISILICON=y -# CONFIG_HIX5HD2_GMAC is not set -# CONFIG_HISI_FEMAC is not set -# CONFIG_HIP04_ETH is not set -# CONFIG_HNS is not set -# CONFIG_HNS_DSAF is not set -# CONFIG_HNS_ENET is not set -CONFIG_NET_VENDOR_HUAWEI=y -CONFIG_NET_VENDOR_I825XX=y -CONFIG_NET_VENDOR_INTEL=y -CONFIG_NET_VENDOR_MARVELL=y -# CONFIG_MVMDIO is not set -CONFIG_NET_VENDOR_MICREL=y -# CONFIG_KS8851_MLL is not set -CONFIG_NET_VENDOR_MICROCHIP=y -CONFIG_NET_VENDOR_MICROSEMI=y -CONFIG_NET_VENDOR_NATSEMI=y -CONFIG_NET_VENDOR_NETRONOME=y -CONFIG_NET_VENDOR_NI=y -# CONFIG_NI_XGE_MANAGEMENT_ENET is not set -CONFIG_NET_VENDOR_8390=y -# CONFIG_AX88796 is not set -# CONFIG_ETHOC is not set -CONFIG_NET_VENDOR_QUALCOMM=y -# CONFIG_QCOM_EMAC is not set -# CONFIG_RMNET is not set -CONFIG_NET_VENDOR_RENESAS=y -CONFIG_NET_VENDOR_ROCKER=y -CONFIG_NET_VENDOR_SAMSUNG=y -# CONFIG_SXGBE_ETH is not set -CONFIG_NET_VENDOR_SEEQ=y -CONFIG_NET_VENDOR_SOLARFLARE=y -CONFIG_NET_VENDOR_SMSC=y -# CONFIG_SMC911X is not set -# CONFIG_SMSC911X is not set -CONFIG_NET_VENDOR_SOCIONEXT=y -CONFIG_NET_VENDOR_STMICRO=y -# CONFIG_STMMAC_ETH is not set -CONFIG_NET_VENDOR_SYNOPSYS=y -# CONFIG_DWC_XLGMAC is not set -CONFIG_NET_VENDOR_VIA=y -# CONFIG_VIA_RHINE is not set -# CONFIG_VIA_VELOCITY is not set -CONFIG_NET_VENDOR_WIZNET=y -# CONFIG_WIZNET_W5100 is not set -# CONFIG_WIZNET_W5300 is not set -# CONFIG_MDIO_DEVICE is not set -# CONFIG_PHYLIB is not set -# CONFIG_PPP is not set -# CONFIG_SLIP is not set - -# -# Host-side USB support is needed for USB Network Adapter support -# -CONFIG_WLAN=y -# CONFIG_WIRELESS_WDS is not set -CONFIG_WLAN_VENDOR_ADMTEK=y -CONFIG_WLAN_VENDOR_ATH=y -# CONFIG_ATH_DEBUG is not set -CONFIG_WLAN_VENDOR_ATMEL=y -CONFIG_WLAN_VENDOR_BROADCOM=y -CONFIG_WLAN_VENDOR_CISCO=y -CONFIG_WLAN_VENDOR_INTEL=y -CONFIG_WLAN_VENDOR_INTERSIL=y -# CONFIG_HOSTAP is not set -CONFIG_WLAN_VENDOR_MARVELL=y -CONFIG_WLAN_VENDOR_MEDIATEK=y -CONFIG_WLAN_VENDOR_RALINK=y -CONFIG_WLAN_VENDOR_REALTEK=y -CONFIG_WLAN_VENDOR_RSI=y -CONFIG_WLAN_VENDOR_ST=y -CONFIG_WLAN_VENDOR_TI=y -CONFIG_WLAN_VENDOR_ZYDAS=y -CONFIG_WLAN_VENDOR_QUANTENNA=y - -# -# Enable WiMAX (Networking options) to see the WiMAX drivers -# -# CONFIG_WAN is not set -CONFIG_NET_FAILOVER=y -# CONFIG_ISDN is not set -# CONFIG_NVM is not set - -# -# Input device support -# -CONFIG_INPUT=y -# CONFIG_INPUT_FF_MEMLESS is not set -# CONFIG_INPUT_POLLDEV is not set -# CONFIG_INPUT_SPARSEKMAP is not set -# CONFIG_INPUT_MATRIXKMAP is not set - -# -# Userland interfaces -# -# CONFIG_INPUT_MOUSEDEV is not set -# CONFIG_INPUT_JOYDEV is not set -# CONFIG_INPUT_EVDEV is not set -# CONFIG_INPUT_EVBUG is not set - -# -# Input Device Drivers -# -CONFIG_INPUT_KEYBOARD=y -CONFIG_KEYBOARD_ATKBD=y -# CONFIG_KEYBOARD_LKKBD is not set -# CONFIG_KEYBOARD_NEWTON is not set -# CONFIG_KEYBOARD_OPENCORES is not set -# CONFIG_KEYBOARD_SAMSUNG is not set -# CONFIG_KEYBOARD_STOWAWAY is not set -# CONFIG_KEYBOARD_SUNKBD is not set -# CONFIG_KEYBOARD_OMAP4 is not set -# CONFIG_KEYBOARD_XTKBD is not set -# CONFIG_KEYBOARD_BCM is not set -CONFIG_INPUT_MOUSE=y -CONFIG_MOUSE_PS2=y -CONFIG_MOUSE_PS2_ALPS=y -CONFIG_MOUSE_PS2_BYD=y -CONFIG_MOUSE_PS2_LOGIPS2PP=y -CONFIG_MOUSE_PS2_SYNAPTICS=y -CONFIG_MOUSE_PS2_CYPRESS=y -CONFIG_MOUSE_PS2_TRACKPOINT=y -# CONFIG_MOUSE_PS2_ELANTECH is not set -# CONFIG_MOUSE_PS2_SENTELIC is not set -# CONFIG_MOUSE_PS2_TOUCHKIT is not set -CONFIG_MOUSE_PS2_FOCALTECH=y -# CONFIG_MOUSE_SERIAL is not set -# CONFIG_MOUSE_VSXXXAA is not set -# CONFIG_INPUT_JOYSTICK is not set -# CONFIG_INPUT_TABLET is not set -# CONFIG_INPUT_TOUCHSCREEN is not set -# CONFIG_INPUT_MISC is not set -# CONFIG_RMI4_CORE is not set - -# -# Hardware I/O ports -# -CONFIG_SERIO=y -CONFIG_SERIO_SERPORT=y -# CONFIG_SERIO_AMBAKMI is not set -CONFIG_SERIO_LIBPS2=y -# CONFIG_SERIO_RAW is not set -# CONFIG_SERIO_ALTERA_PS2 is not set -# CONFIG_SERIO_PS2MULT is not set -# CONFIG_SERIO_ARC_PS2 is not set -# CONFIG_SERIO_APBPS2 is not set -# CONFIG_USERIO is not set -# CONFIG_GAMEPORT is not set -# end of Hardware I/O ports -# end of Input device support - -# -# Character devices -# -CONFIG_TTY=y -CONFIG_VT=y -CONFIG_CONSOLE_TRANSLATIONS=y -CONFIG_VT_CONSOLE=y -CONFIG_HW_CONSOLE=y -# CONFIG_VT_HW_CONSOLE_BINDING is not set -CONFIG_UNIX98_PTYS=y -CONFIG_LEGACY_PTYS=y -CONFIG_LEGACY_PTY_COUNT=256 -# CONFIG_SERIAL_NONSTANDARD is not set -# CONFIG_N_GSM is not set -# CONFIG_TRACE_SINK is not set -# CONFIG_NULL_TTY is not set -CONFIG_LDISC_AUTOLOAD=y -CONFIG_DEVMEM=y -# CONFIG_DEVKMEM is not set - -# -# Serial drivers -# -CONFIG_SERIAL_EARLYCON=y -# CONFIG_SERIAL_8250 is not set - -# -# Non-8250 serial port support -# -# CONFIG_SERIAL_AMBA_PL010 is not set -CONFIG_SERIAL_AMBA_PL011=y -CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -# CONFIG_SERIAL_EARLYCON_ARM_SEMIHOST is not set -# CONFIG_SERIAL_UARTLITE is not set -CONFIG_SERIAL_CORE=y -CONFIG_SERIAL_CORE_CONSOLE=y -# CONFIG_SERIAL_SIFIVE is not set -# CONFIG_SERIAL_SCCNXP is not set -# CONFIG_SERIAL_BCM63XX is not set -# CONFIG_SERIAL_ALTERA_JTAGUART is not set -# CONFIG_SERIAL_ALTERA_UART is not set -# CONFIG_SERIAL_XILINX_PS_UART is not set -# CONFIG_SERIAL_ARC is not set -# CONFIG_SERIAL_FSL_LPUART is not set -# CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set -# CONFIG_SERIAL_ST_ASC is not set -# end of Serial drivers - -# CONFIG_SERIAL_DEV_BUS is not set -# CONFIG_TTY_PRINTK is not set -# CONFIG_HVC_DCC is not set -# CONFIG_VIRTIO_CONSOLE is not set -# CONFIG_IPMI_HANDLER is not set -# CONFIG_HW_RANDOM is not set -# CONFIG_RAW_DRIVER is not set -# CONFIG_TCG_TPM is not set -# CONFIG_XILLYBUS is not set -# end of Character devices - -# -# I2C support -# -# CONFIG_I2C is not set -# end of I2C support - -# CONFIG_I3C is not set -# CONFIG_SPI is not set -# CONFIG_SPMI is not set -# CONFIG_HSI is not set -# CONFIG_PPS is not set - -# -# PTP clock support -# - -# -# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. -# -# end of PTP clock support - -# CONFIG_PINCTRL is not set -CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y -# CONFIG_GPIOLIB is not set -# CONFIG_W1 is not set -# CONFIG_POWER_AVS is not set -# CONFIG_POWER_RESET is not set -# CONFIG_POWER_SUPPLY is not set -# CONFIG_HWMON is not set -# CONFIG_THERMAL is not set -# CONFIG_WATCHDOG is not set -CONFIG_SSB_POSSIBLE=y -# CONFIG_SSB is not set -CONFIG_BCMA_POSSIBLE=y -# CONFIG_BCMA is not set - -# -# Multifunction device drivers -# -# CONFIG_MFD_ATMEL_FLEXCOM is not set -# CONFIG_MFD_ATMEL_HLCDC is not set -# CONFIG_MFD_CROS_EC is not set -# CONFIG_MFD_MADERA is not set -# CONFIG_MFD_HI6421_PMIC is not set -# CONFIG_HTC_PASIC3 is not set -# CONFIG_MFD_KEMPLD is not set -# CONFIG_MFD_MT6397 is not set -# CONFIG_MFD_PM8XXX is not set -# CONFIG_MFD_SM501 is not set -# CONFIG_ABX500_CORE is not set -# CONFIG_MFD_SYSCON is not set -# CONFIG_MFD_TI_AM335X_TSCADC is not set -# CONFIG_MFD_T7L66XB is not set -# CONFIG_MFD_TC6387XB is not set -# CONFIG_MFD_TC6393XB is not set -# CONFIG_MFD_TQMX86 is not set -# end of Multifunction device drivers - -# CONFIG_REGULATOR is not set -# CONFIG_RC_CORE is not set -# CONFIG_MEDIA_SUPPORT is not set - -# -# Graphics support -# -# CONFIG_IMX_IPUV3_CORE is not set -# CONFIG_DRM is not set -# CONFIG_DRM_DP_CEC is not set - -# -# ARM devices -# -# end of ARM devices - -# -# ACP (Audio CoProcessor) Configuration -# -# end of ACP (Audio CoProcessor) Configuration - -# -# Frame buffer Devices -# -# CONFIG_FB is not set -# end of Frame buffer Devices - -# -# Backlight & LCD device support -# -CONFIG_LCD_CLASS_DEVICE=y -# CONFIG_LCD_PLATFORM is not set -CONFIG_BACKLIGHT_CLASS_DEVICE=y -CONFIG_BACKLIGHT_GENERIC=y -# CONFIG_BACKLIGHT_PM8941_WLED is not set -# end of Backlight & LCD device support - -# -# Console display driver support -# -CONFIG_DUMMY_CONSOLE=y -# end of Console display driver support -# end of Graphics support - -# CONFIG_SOUND is not set - -# -# HID support -# -CONFIG_HID=y -# CONFIG_HID_BATTERY_STRENGTH is not set -# CONFIG_HIDRAW is not set -# CONFIG_UHID is not set -CONFIG_HID_GENERIC=y - -# -# Special HID drivers -# -# CONFIG_HID_A4TECH is not set -# CONFIG_HID_ACRUX is not set -# CONFIG_HID_APPLE is not set -# CONFIG_HID_AUREAL is not set -# CONFIG_HID_BELKIN is not set -# CONFIG_HID_CHERRY is not set -# CONFIG_HID_CHICONY is not set -# CONFIG_HID_COUGAR is not set -# CONFIG_HID_MACALLY is not set -# CONFIG_HID_CMEDIA is not set -# CONFIG_HID_CYPRESS is not set -# CONFIG_HID_DRAGONRISE is not set -# CONFIG_HID_EMS_FF is not set -# CONFIG_HID_ELECOM is not set -# CONFIG_HID_EZKEY is not set -# CONFIG_HID_GEMBIRD is not set -# CONFIG_HID_GFRM is not set -# CONFIG_HID_KEYTOUCH is not set -# CONFIG_HID_KYE is not set -# CONFIG_HID_WALTOP is not set -# CONFIG_HID_VIEWSONIC is not set -# CONFIG_HID_GYRATION is not set -# CONFIG_HID_ICADE is not set -# CONFIG_HID_ITE is not set -# CONFIG_HID_JABRA is not set -# CONFIG_HID_TWINHAN is not set -# CONFIG_HID_KENSINGTON is not set -# CONFIG_HID_LCPOWER is not set -# CONFIG_HID_LENOVO is not set -# CONFIG_HID_LOGITECH is not set -# CONFIG_HID_MAGICMOUSE is not set -# CONFIG_HID_MALTRON is not set -# CONFIG_HID_MAYFLASH is not set -# CONFIG_HID_REDRAGON is not set -# CONFIG_HID_MICROSOFT is not set -# CONFIG_HID_MONTEREY is not set -# CONFIG_HID_MULTITOUCH is not set -# CONFIG_HID_NTI is not set -# CONFIG_HID_ORTEK is not set -# CONFIG_HID_PANTHERLORD is not set -# CONFIG_HID_PETALYNX is not set -# CONFIG_HID_PICOLCD is not set -# CONFIG_HID_PLANTRONICS is not set -# CONFIG_HID_PRIMAX is not set -# CONFIG_HID_SAITEK is not set -# CONFIG_HID_SAMSUNG is not set -# CONFIG_HID_SPEEDLINK is not set -# CONFIG_HID_STEAM is not set -# CONFIG_HID_STEELSERIES is not set -# CONFIG_HID_SUNPLUS is not set -# CONFIG_HID_RMI is not set -# CONFIG_HID_GREENASIA is not set -# CONFIG_HID_SMARTJOYPLUS is not set -# CONFIG_HID_TIVO is not set -# CONFIG_HID_TOPSEED is not set -# CONFIG_HID_THRUSTMASTER is not set -# CONFIG_HID_UDRAW_PS3 is not set -# CONFIG_HID_XINMO is not set -# CONFIG_HID_ZEROPLUS is not set -# CONFIG_HID_ZYDACRON is not set -# CONFIG_HID_SENSOR_HUB is not set -# CONFIG_HID_ALPS is not set -# end of Special HID drivers -# end of HID support - -CONFIG_USB_OHCI_LITTLE_ENDIAN=y -# CONFIG_USB_SUPPORT is not set -# CONFIG_UWB is not set -# CONFIG_MMC is not set -# CONFIG_MEMSTICK is not set -# CONFIG_NEW_LEDS is not set -# CONFIG_ACCESSIBILITY is not set -# CONFIG_INFINIBAND is not set -CONFIG_EDAC_ATOMIC_SCRUB=y -CONFIG_EDAC_SUPPORT=y -CONFIG_RTC_LIB=y -# CONFIG_RTC_CLASS is not set -# CONFIG_DMADEVICES is not set - -# -# DMABUF options -# -# CONFIG_SYNC_FILE is not set -# end of DMABUF options - -# CONFIG_AUXDISPLAY is not set -# CONFIG_UIO is not set -# CONFIG_VIRT_DRIVERS is not set -CONFIG_VIRTIO=y -CONFIG_VIRTIO_MENU=y -# CONFIG_VIRTIO_BALLOON is not set -CONFIG_VIRTIO_INPUT=y -CONFIG_VIRTIO_MMIO=y -CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y - -# -# Microsoft Hyper-V guest support -# -# end of Microsoft Hyper-V guest support - -# CONFIG_STAGING is not set -# CONFIG_GOLDFISH is not set -# CONFIG_CHROME_PLATFORMS is not set -# CONFIG_MELLANOX_PLATFORM is not set -CONFIG_CLKDEV_LOOKUP=y -CONFIG_HAVE_CLK_PREPARE=y -CONFIG_COMMON_CLK=y - -# -# Common Clock Framework -# -# CONFIG_CLK_HSDK is not set -# CONFIG_CLK_QORIQ is not set -# CONFIG_COMMON_CLK_FIXED_MMIO is not set -# end of Common Clock Framework - -# CONFIG_HWSPINLOCK is not set - -# -# Clock Source drivers -# -CONFIG_TIMER_OF=y -CONFIG_TIMER_PROBE=y -CONFIG_ARM_ARCH_TIMER=y -CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y -# end of Clock Source drivers - -# CONFIG_MAILBOX is not set -CONFIG_IOMMU_SUPPORT=y - -# -# Generic IOMMU Pagetable Support -# -# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set -# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set -# end of Generic IOMMU Pagetable Support - -# CONFIG_ARM_SMMU is not set - -# -# Remoteproc drivers -# -# CONFIG_REMOTEPROC is not set -# end of Remoteproc drivers - -# -# Rpmsg drivers -# -# CONFIG_RPMSG_VIRTIO is not set -# end of Rpmsg drivers - -# CONFIG_SOUNDWIRE is not set - -# -# SOC (System On Chip) specific Drivers -# - -# -# Amlogic SoC drivers -# -# end of Amlogic SoC drivers - -# -# Aspeed SoC drivers -# -# end of Aspeed SoC drivers - -# -# Broadcom SoC drivers -# -# CONFIG_SOC_BRCMSTB is not set -# end of Broadcom SoC drivers - -# -# NXP/Freescale QorIQ SoC drivers -# -# end of NXP/Freescale QorIQ SoC drivers - -# -# i.MX SoC drivers -# -# end of i.MX SoC drivers - -# -# IXP4xx SoC drivers -# -# CONFIG_IXP4XX_QMGR is not set -# CONFIG_IXP4XX_NPE is not set -# end of IXP4xx SoC drivers - -# -# Qualcomm SoC drivers -# -# end of Qualcomm SoC drivers - -# CONFIG_SOC_TI is not set - -# -# Xilinx SoC drivers -# -# CONFIG_XILINX_VCU is not set -# end of Xilinx SoC drivers -# end of SOC (System On Chip) specific Drivers - -# CONFIG_PM_DEVFREQ is not set -# CONFIG_EXTCON is not set -# CONFIG_MEMORY is not set -# CONFIG_IIO is not set -# CONFIG_PWM is not set - -# -# IRQ chip support -# -CONFIG_IRQCHIP=y -CONFIG_ARM_GIC=y -CONFIG_ARM_GIC_MAX_NR=1 -CONFIG_ARM_GIC_V3=y -CONFIG_ARM_GIC_V3_ITS=y -# CONFIG_AL_FIC is not set -CONFIG_PARTITION_PERCPU=y -# end of IRQ chip support - -# CONFIG_IPACK_BUS is not set -# CONFIG_RESET_CONTROLLER is not set - -# -# PHY Subsystem -# -# CONFIG_GENERIC_PHY is not set -# CONFIG_BCM_KONA_USB2_PHY is not set -# CONFIG_PHY_CADENCE_DP is not set -# CONFIG_PHY_CADENCE_DPHY is not set -# CONFIG_PHY_FSL_IMX8MQ_USB is not set -# CONFIG_PHY_PXA_28NM_HSIC is not set -# CONFIG_PHY_PXA_28NM_USB2 is not set -# end of PHY Subsystem - -# CONFIG_POWERCAP is not set -# CONFIG_MCB is not set -# CONFIG_RAS is not set - -# -# Android -# -# CONFIG_ANDROID is not set -# end of Android - -# CONFIG_DAX is not set -# CONFIG_NVMEM is not set - -# -# HW tracing support -# -# CONFIG_STM is not set -# CONFIG_INTEL_TH is not set -# end of HW tracing support - -# CONFIG_FPGA is not set -# CONFIG_FSI is not set -# CONFIG_TEE is not set -# CONFIG_SIOX is not set -# CONFIG_SLIMBUS is not set -# CONFIG_INTERCONNECT is not set -# CONFIG_COUNTER is not set -# end of Device Drivers - -# -# File systems -# -CONFIG_DCACHE_WORD_ACCESS=y -# CONFIG_VALIDATE_FS_PARSER is not set -# CONFIG_EXT2_FS is not set -# CONFIG_EXT3_FS is not set -# CONFIG_EXT4_FS is not set -# CONFIG_REISERFS_FS is not set -# CONFIG_JFS_FS is not set -# CONFIG_XFS_FS is not set -# CONFIG_GFS2_FS is not set -# CONFIG_BTRFS_FS is not set -# CONFIG_NILFS2_FS is not set -# CONFIG_F2FS_FS is not set -# CONFIG_EXPORTFS_BLOCK_OPS is not set -# CONFIG_FILE_LOCKING is not set -# CONFIG_FS_ENCRYPTION is not set -# CONFIG_DNOTIFY is not set -# CONFIG_INOTIFY_USER is not set -# CONFIG_FANOTIFY is not set -# CONFIG_QUOTA is not set -# CONFIG_AUTOFS4_FS is not set -# CONFIG_AUTOFS_FS is not set -# CONFIG_FUSE_FS is not set -# CONFIG_OVERLAY_FS is not set - -# -# Caches -# -# CONFIG_FSCACHE is not set -# end of Caches - -# -# CD-ROM/DVD Filesystems -# -# CONFIG_ISO9660_FS is not set -# CONFIG_UDF_FS is not set -# end of CD-ROM/DVD Filesystems - -# -# DOS/FAT/NT Filesystems -# -CONFIG_FAT_FS=y -CONFIG_MSDOS_FS=y -CONFIG_VFAT_FS=y -CONFIG_FAT_DEFAULT_CODEPAGE=437 -CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" -# CONFIG_FAT_DEFAULT_UTF8 is not set -# CONFIG_NTFS_FS is not set -# end of DOS/FAT/NT Filesystems - -# -# Pseudo filesystems -# -CONFIG_PROC_FS=y -CONFIG_PROC_SYSCTL=y -CONFIG_PROC_PAGE_MONITOR=y -# CONFIG_PROC_CHILDREN is not set -# CONFIG_SYSFS is not set -CONFIG_TMPFS=y -# CONFIG_CONFIGFS_FS is not set -# end of Pseudo filesystems - -CONFIG_MISC_FILESYSTEMS=y -# CONFIG_ORANGEFS_FS is not set -# CONFIG_ADFS_FS is not set -# CONFIG_AFFS_FS is not set -# CONFIG_HFS_FS is not set -# CONFIG_HFSPLUS_FS is not set -# CONFIG_BEFS_FS is not set -# CONFIG_BFS_FS is not set -# CONFIG_EFS_FS is not set -# CONFIG_CRAMFS is not set -CONFIG_SQUASHFS=y -CONFIG_SQUASHFS_FILE_CACHE=y -# CONFIG_SQUASHFS_FILE_DIRECT is not set -CONFIG_SQUASHFS_DECOMP_SINGLE=y -# CONFIG_SQUASHFS_DECOMP_MULTI is not set -# CONFIG_SQUASHFS_DECOMP_MULTI_PERCPU is not set -# CONFIG_SQUASHFS_XATTR is not set -CONFIG_SQUASHFS_ZLIB=y -# CONFIG_SQUASHFS_LZ4 is not set -# CONFIG_SQUASHFS_LZO is not set -# CONFIG_SQUASHFS_XZ is not set -# CONFIG_SQUASHFS_ZSTD is not set -# CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set -# CONFIG_SQUASHFS_EMBEDDED is not set -CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 -# CONFIG_VXFS_FS is not set -# CONFIG_MINIX_FS is not set -# CONFIG_OMFS_FS is not set -# CONFIG_HPFS_FS is not set -# CONFIG_QNX4FS_FS is not set -# CONFIG_QNX6FS_FS is not set -# CONFIG_ROMFS_FS is not set -# CONFIG_PSTORE is not set -# CONFIG_SYSV_FS is not set -# CONFIG_UFS_FS is not set -CONFIG_NETWORK_FILESYSTEMS=y -# CONFIG_CEPH_FS is not set -# CONFIG_CIFS is not set -# CONFIG_CODA_FS is not set -# CONFIG_AFS_FS is not set -CONFIG_9P_FS=y -# CONFIG_9P_FS_POSIX_ACL is not set -# CONFIG_9P_FS_SECURITY is not set -CONFIG_NLS=y -CONFIG_NLS_DEFAULT="iso8859-1" -CONFIG_NLS_CODEPAGE_437=y -# CONFIG_NLS_CODEPAGE_737 is not set -# CONFIG_NLS_CODEPAGE_775 is not set -# CONFIG_NLS_CODEPAGE_850 is not set -# CONFIG_NLS_CODEPAGE_852 is not set -# CONFIG_NLS_CODEPAGE_855 is not set -# CONFIG_NLS_CODEPAGE_857 is not set -# CONFIG_NLS_CODEPAGE_860 is not set -# CONFIG_NLS_CODEPAGE_861 is not set -# CONFIG_NLS_CODEPAGE_862 is not set -# CONFIG_NLS_CODEPAGE_863 is not set -# CONFIG_NLS_CODEPAGE_864 is not set -# CONFIG_NLS_CODEPAGE_865 is not set -# CONFIG_NLS_CODEPAGE_866 is not set -# CONFIG_NLS_CODEPAGE_869 is not set -# CONFIG_NLS_CODEPAGE_936 is not set -# CONFIG_NLS_CODEPAGE_950 is not set -# CONFIG_NLS_CODEPAGE_932 is not set -# CONFIG_NLS_CODEPAGE_949 is not set -# CONFIG_NLS_CODEPAGE_874 is not set -# CONFIG_NLS_ISO8859_8 is not set -# CONFIG_NLS_CODEPAGE_1250 is not set -# CONFIG_NLS_CODEPAGE_1251 is not set -# CONFIG_NLS_ASCII is not set -CONFIG_NLS_ISO8859_1=y -# CONFIG_NLS_ISO8859_2 is not set -# CONFIG_NLS_ISO8859_3 is not set -# CONFIG_NLS_ISO8859_4 is not set -# CONFIG_NLS_ISO8859_5 is not set -# CONFIG_NLS_ISO8859_6 is not set -# CONFIG_NLS_ISO8859_7 is not set -# CONFIG_NLS_ISO8859_9 is not set -# CONFIG_NLS_ISO8859_13 is not set -# CONFIG_NLS_ISO8859_14 is not set -# CONFIG_NLS_ISO8859_15 is not set -# CONFIG_NLS_KOI8_R is not set -# CONFIG_NLS_KOI8_U is not set -# CONFIG_NLS_MAC_ROMAN is not set -# CONFIG_NLS_MAC_CELTIC is not set -# CONFIG_NLS_MAC_CENTEURO is not set -# CONFIG_NLS_MAC_CROATIAN is not set -# CONFIG_NLS_MAC_CYRILLIC is not set -# CONFIG_NLS_MAC_GAELIC is not set -# CONFIG_NLS_MAC_GREEK is not set -# CONFIG_NLS_MAC_ICELAND is not set -# CONFIG_NLS_MAC_INUIT is not set -# CONFIG_NLS_MAC_ROMANIAN is not set -# CONFIG_NLS_MAC_TURKISH is not set -# CONFIG_NLS_UTF8 is not set -# CONFIG_UNICODE is not set -# end of File systems - -# -# Security options -# -# CONFIG_KEYS is not set -# CONFIG_SECURITY_DMESG_RESTRICT is not set -# CONFIG_SECURITYFS is not set -# CONFIG_FORTIFY_SOURCE is not set -# CONFIG_STATIC_USERMODEHELPER is not set -CONFIG_DEFAULT_SECURITY_DAC=y -CONFIG_LSM="yama,loadpin,safesetid,integrity" - -# -# Kernel hardening options -# - -# -# Memory initialization -# -CONFIG_INIT_STACK_NONE=y -# end of Memory initialization -# end of Kernel hardening options -# end of Security options - -CONFIG_CRYPTO=y - -# -# Crypto core or helper -# -CONFIG_CRYPTO_ALGAPI=y -CONFIG_CRYPTO_ALGAPI2=y -CONFIG_CRYPTO_AEAD=y -CONFIG_CRYPTO_AEAD2=y -CONFIG_CRYPTO_BLKCIPHER=y -CONFIG_CRYPTO_BLKCIPHER2=y -CONFIG_CRYPTO_HASH2=y -CONFIG_CRYPTO_RNG2=y -# CONFIG_CRYPTO_MANAGER is not set -# CONFIG_CRYPTO_USER is not set -# CONFIG_CRYPTO_NULL is not set -CONFIG_CRYPTO_NULL2=y -# CONFIG_CRYPTO_CRYPTD is not set -# CONFIG_CRYPTO_AUTHENC is not set -CONFIG_CRYPTO_ENGINE=y - -# -# Public-key cryptography -# -# CONFIG_CRYPTO_RSA is not set -# CONFIG_CRYPTO_DH is not set -# CONFIG_CRYPTO_ECDH is not set -# CONFIG_CRYPTO_ECRDSA is not set - -# -# Authenticated Encryption with Associated Data -# -# CONFIG_CRYPTO_CCM is not set -# CONFIG_CRYPTO_GCM is not set -# CONFIG_CRYPTO_CHACHA20POLY1305 is not set -# CONFIG_CRYPTO_AEGIS128 is not set -# CONFIG_CRYPTO_AEGIS128L is not set -# CONFIG_CRYPTO_AEGIS256 is not set -# CONFIG_CRYPTO_MORUS640 is not set -# CONFIG_CRYPTO_MORUS1280 is not set -# CONFIG_CRYPTO_SEQIV is not set -# CONFIG_CRYPTO_ECHAINIV is not set - -# -# Block modes -# -# CONFIG_CRYPTO_CBC is not set -# CONFIG_CRYPTO_CFB is not set -# CONFIG_CRYPTO_CTR is not set -# CONFIG_CRYPTO_CTS is not set -# CONFIG_CRYPTO_ECB is not set -# CONFIG_CRYPTO_LRW is not set -# CONFIG_CRYPTO_OFB is not set -# CONFIG_CRYPTO_PCBC is not set -# CONFIG_CRYPTO_XTS is not set -# CONFIG_CRYPTO_KEYWRAP is not set -# CONFIG_CRYPTO_ADIANTUM is not set - -# -# Hash modes -# -# CONFIG_CRYPTO_CMAC is not set -# CONFIG_CRYPTO_HMAC is not set -# CONFIG_CRYPTO_XCBC is not set -# CONFIG_CRYPTO_VMAC is not set - -# -# Digest -# -# CONFIG_CRYPTO_CRC32C is not set -# CONFIG_CRYPTO_CRC32 is not set -# CONFIG_CRYPTO_XXHASH is not set -# CONFIG_CRYPTO_CRCT10DIF is not set -# CONFIG_CRYPTO_GHASH is not set -# CONFIG_CRYPTO_POLY1305 is not set -# CONFIG_CRYPTO_MD4 is not set -# CONFIG_CRYPTO_MD5 is not set -# CONFIG_CRYPTO_MICHAEL_MIC is not set -# CONFIG_CRYPTO_RMD128 is not set -# CONFIG_CRYPTO_RMD160 is not set -# CONFIG_CRYPTO_RMD256 is not set -# CONFIG_CRYPTO_RMD320 is not set -# CONFIG_CRYPTO_SHA1 is not set -# CONFIG_CRYPTO_SHA256 is not set -# CONFIG_CRYPTO_SHA512 is not set -# CONFIG_CRYPTO_SHA3 is not set -# CONFIG_CRYPTO_SM3 is not set -# CONFIG_CRYPTO_STREEBOG is not set -# CONFIG_CRYPTO_TGR192 is not set -# CONFIG_CRYPTO_WP512 is not set - -# -# Ciphers -# -CONFIG_CRYPTO_AES=y -# CONFIG_CRYPTO_AES_TI is not set -# CONFIG_CRYPTO_ANUBIS is not set -# CONFIG_CRYPTO_ARC4 is not set -# CONFIG_CRYPTO_BLOWFISH is not set -# CONFIG_CRYPTO_CAMELLIA is not set -# CONFIG_CRYPTO_CAST5 is not set -# CONFIG_CRYPTO_CAST6 is not set -# CONFIG_CRYPTO_DES is not set -# CONFIG_CRYPTO_FCRYPT is not set -# CONFIG_CRYPTO_KHAZAD is not set -# CONFIG_CRYPTO_SALSA20 is not set -# CONFIG_CRYPTO_CHACHA20 is not set -# CONFIG_CRYPTO_SEED is not set -# CONFIG_CRYPTO_SERPENT is not set -# CONFIG_CRYPTO_SM4 is not set -# CONFIG_CRYPTO_TEA is not set -# CONFIG_CRYPTO_TWOFISH is not set - -# -# Compression -# -# CONFIG_CRYPTO_DEFLATE is not set -# CONFIG_CRYPTO_LZO is not set -# CONFIG_CRYPTO_842 is not set -# CONFIG_CRYPTO_LZ4 is not set -# CONFIG_CRYPTO_LZ4HC is not set -# CONFIG_CRYPTO_ZSTD is not set - -# -# Random Number Generation -# -# CONFIG_CRYPTO_ANSI_CPRNG is not set -# CONFIG_CRYPTO_DRBG_MENU is not set -# CONFIG_CRYPTO_JITTERENTROPY is not set -# CONFIG_CRYPTO_USER_API_HASH is not set -# CONFIG_CRYPTO_USER_API_SKCIPHER is not set -# CONFIG_CRYPTO_USER_API_RNG is not set -# CONFIG_CRYPTO_USER_API_AEAD is not set -CONFIG_CRYPTO_HW=y -CONFIG_CRYPTO_DEV_VIRTIO=y -# CONFIG_CRYPTO_DEV_CCREE is not set - -# -# Certificates for signature checking -# -# end of Certificates for signature checking - -# -# Library routines -# -# CONFIG_PACKING is not set -CONFIG_BITREVERSE=y -CONFIG_HAVE_ARCH_BITREVERSE=y -CONFIG_GENERIC_STRNCPY_FROM_USER=y -CONFIG_GENERIC_STRNLEN_USER=y -CONFIG_GENERIC_NET_UTILS=y -# CONFIG_CORDIC is not set -CONFIG_RATIONAL=y -CONFIG_GENERIC_PCI_IOMAP=y -CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y -# CONFIG_CRC_CCITT is not set -# CONFIG_CRC16 is not set -# CONFIG_CRC_T10DIF is not set -# CONFIG_CRC_ITU_T is not set -CONFIG_CRC32=y -# CONFIG_CRC32_SELFTEST is not set -CONFIG_CRC32_SLICEBY8=y -# CONFIG_CRC32_SLICEBY4 is not set -# CONFIG_CRC32_SARWATE is not set -# CONFIG_CRC32_BIT is not set -# CONFIG_CRC64 is not set -# CONFIG_CRC4 is not set -# CONFIG_CRC7 is not set -# CONFIG_LIBCRC32C is not set -# CONFIG_CRC8 is not set -# CONFIG_RANDOM32_SELFTEST is not set -CONFIG_ZLIB_INFLATE=y -CONFIG_LZO_DECOMPRESS=y -CONFIG_LZ4_DECOMPRESS=y -CONFIG_XZ_DEC=y -CONFIG_XZ_DEC_X86=y -CONFIG_XZ_DEC_POWERPC=y -CONFIG_XZ_DEC_IA64=y -CONFIG_XZ_DEC_ARM=y -CONFIG_XZ_DEC_ARMTHUMB=y -CONFIG_XZ_DEC_SPARC=y -CONFIG_XZ_DEC_BCJ=y -# CONFIG_XZ_DEC_TEST is not set -CONFIG_DECOMPRESS_GZIP=y -CONFIG_DECOMPRESS_BZIP2=y -CONFIG_DECOMPRESS_LZMA=y -CONFIG_DECOMPRESS_XZ=y -CONFIG_DECOMPRESS_LZO=y -CONFIG_DECOMPRESS_LZ4=y -CONFIG_GENERIC_ALLOCATOR=y -CONFIG_HAS_IOMEM=y -CONFIG_HAS_IOPORT_MAP=y -CONFIG_HAS_DMA=y -CONFIG_NEED_DMA_MAP_STATE=y -CONFIG_DMA_DECLARE_COHERENT=y -CONFIG_ARCH_HAS_SETUP_DMA_OPS=y -CONFIG_ARCH_HAS_TEARDOWN_DMA_OPS=y -CONFIG_DMA_REMAP=y -# CONFIG_DMA_API_DEBUG is not set -CONFIG_GLOB=y -# CONFIG_GLOB_SELFTEST is not set -CONFIG_NLATTR=y -# CONFIG_DDR is not set -# CONFIG_IRQ_POLL is not set -CONFIG_LIBFDT=y -CONFIG_SG_POOL=y -CONFIG_SBITMAP=y -# CONFIG_STRING_SELFTEST is not set -# end of Library routines - -# -# Kernel hacking -# - -# -# printk and dmesg options -# -# CONFIG_PRINTK_TIME is not set -# CONFIG_PRINTK_CALLER is not set -CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 -CONFIG_CONSOLE_LOGLEVEL_QUIET=4 -CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 -# CONFIG_BOOT_PRINTK_DELAY is not set -# end of printk and dmesg options - -# -# Compile-time checks and compiler options -# -# CONFIG_DEBUG_INFO is not set -# CONFIG_ENABLE_MUST_CHECK is not set -CONFIG_FRAME_WARN=1024 -# CONFIG_STRIP_ASM_SYMS is not set -# CONFIG_READABLE_ASM is not set -# CONFIG_UNUSED_SYMBOLS is not set -# CONFIG_DEBUG_FS is not set -# CONFIG_HEADERS_CHECK is not set -# CONFIG_OPTIMIZE_INLINING is not set -# CONFIG_DEBUG_SECTION_MISMATCH is not set -# CONFIG_SECTION_MISMATCH_WARN_ONLY is not set -# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set -# end of Compile-time checks and compiler options - -# CONFIG_MAGIC_SYSRQ is not set -CONFIG_DEBUG_KERNEL=y -CONFIG_DEBUG_MISC=y - -# -# Memory Debugging -# -# CONFIG_PAGE_EXTENSION is not set -# CONFIG_DEBUG_PAGEALLOC is not set -# CONFIG_PAGE_OWNER is not set -# CONFIG_PAGE_POISONING is not set -# CONFIG_DEBUG_RODATA_TEST is not set -# CONFIG_DEBUG_OBJECTS is not set -CONFIG_HAVE_DEBUG_KMEMLEAK=y -# CONFIG_DEBUG_KMEMLEAK is not set -# CONFIG_DEBUG_STACK_USAGE is not set -# CONFIG_DEBUG_VM is not set -CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y -# CONFIG_DEBUG_VIRTUAL is not set -# CONFIG_DEBUG_MEMORY_INIT is not set -CONFIG_CC_HAS_KASAN_GENERIC=y -CONFIG_KASAN_STACK=1 -# end of Memory Debugging - -CONFIG_ARCH_HAS_KCOV=y -CONFIG_CC_HAS_SANCOV_TRACE_PC=y -# CONFIG_KCOV is not set -# CONFIG_DEBUG_SHIRQ is not set - -# -# Debug Lockups and Hangs -# -# CONFIG_SOFTLOCKUP_DETECTOR is not set -# CONFIG_DETECT_HUNG_TASK is not set -# CONFIG_WQ_WATCHDOG is not set -# end of Debug Lockups and Hangs - -# CONFIG_PANIC_ON_OOPS is not set -CONFIG_PANIC_ON_OOPS_VALUE=0 -CONFIG_PANIC_TIMEOUT=0 -CONFIG_SCHED_DEBUG=y -# CONFIG_SCHEDSTATS is not set -# CONFIG_SCHED_STACK_END_CHECK is not set -# CONFIG_DEBUG_TIMEKEEPING is not set - -# -# Lock Debugging (spinlocks, mutexes, etc...) -# -CONFIG_LOCK_DEBUGGING_SUPPORT=y -# CONFIG_PROVE_LOCKING is not set -# CONFIG_LOCK_STAT is not set -# CONFIG_DEBUG_RT_MUTEXES is not set -# CONFIG_DEBUG_SPINLOCK is not set -# CONFIG_DEBUG_MUTEXES is not set -# CONFIG_DEBUG_WW_MUTEX_SLOWPATH is not set -# CONFIG_DEBUG_RWSEMS is not set -# CONFIG_DEBUG_LOCK_ALLOC is not set -# CONFIG_DEBUG_ATOMIC_SLEEP is not set -# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set -# CONFIG_LOCK_TORTURE_TEST is not set -# CONFIG_WW_MUTEX_SELFTEST is not set -# end of Lock Debugging (spinlocks, mutexes, etc...) - -# CONFIG_STACKTRACE is not set -# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set -# CONFIG_DEBUG_KOBJECT is not set -# CONFIG_DEBUG_LIST is not set -# CONFIG_DEBUG_PLIST is not set -# CONFIG_DEBUG_SG is not set -# CONFIG_DEBUG_NOTIFIERS is not set -# CONFIG_DEBUG_CREDENTIALS is not set - -# -# RCU Debugging -# -# CONFIG_RCU_PERF_TEST is not set -# CONFIG_RCU_TORTURE_TEST is not set -# CONFIG_RCU_TRACE is not set -# CONFIG_RCU_EQS_DEBUG is not set -# end of RCU Debugging - -# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set -# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set -# CONFIG_NOTIFIER_ERROR_INJECTION is not set -# CONFIG_FAULT_INJECTION is not set -# CONFIG_LATENCYTOP is not set -CONFIG_HAVE_FUNCTION_TRACER=y -CONFIG_HAVE_DYNAMIC_FTRACE=y -CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y -CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y -CONFIG_HAVE_SYSCALL_TRACEPOINTS=y -CONFIG_HAVE_C_RECORDMCOUNT=y -CONFIG_TRACING_SUPPORT=y -CONFIG_FTRACE=y -# CONFIG_FUNCTION_TRACER is not set -# CONFIG_PREEMPTIRQ_EVENTS is not set -# CONFIG_IRQSOFF_TRACER is not set -# CONFIG_SCHED_TRACER is not set -# CONFIG_HWLAT_TRACER is not set -# CONFIG_ENABLE_DEFAULT_TRACERS is not set -# CONFIG_FTRACE_SYSCALLS is not set -# CONFIG_TRACER_SNAPSHOT is not set -CONFIG_BRANCH_PROFILE_NONE=y -# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set -# CONFIG_PROFILE_ALL_BRANCHES is not set -# CONFIG_STACK_TRACER is not set -# CONFIG_TRACEPOINT_BENCHMARK is not set -# CONFIG_RUNTIME_TESTING_MENU is not set -# CONFIG_MEMTEST is not set -# CONFIG_BUG_ON_DATA_CORRUPTION is not set -# CONFIG_SAMPLES is not set -CONFIG_HAVE_ARCH_KGDB=y -# CONFIG_KGDB is not set -# CONFIG_UBSAN is not set -CONFIG_UBSAN_ALIGNMENT=y -CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y -# CONFIG_STRICT_DEVMEM is not set -# CONFIG_ARM_PTDUMP_DEBUGFS is not set -# CONFIG_DEBUG_WX is not set -CONFIG_UNWINDER_ARM=y -CONFIG_ARM_UNWIND=y -# CONFIG_DEBUG_USER is not set -# CONFIG_DEBUG_LL is not set -CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" -CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" -# CONFIG_PID_IN_CONTEXTIDR is not set -# CONFIG_CORESIGHT is not set -# end of Kernel hacking diff --git a/.circleci/images/test-image-arm64/Dockerfile b/.circleci/images/test-image-arm64/Dockerfile deleted file mode 100644 index 4bc53287f5..0000000000 --- a/.circleci/images/test-image-arm64/Dockerfile +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2020-2021 the u-root Authors. All rights reserved -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -FROM cimg/go:1.21 - -# Install dependencies -RUN sudo apt-get update && \ - sudo apt-get install -y --no-install-recommends \ - `# Linux dependencies` \ - bc \ - bison \ - flex \ - gcc-aarch64-linux-gnu \ - git \ - libssl-dev \ - make \ - `# QEMU dependencies` \ - libattr1-dev \ - libcap-dev \ - libcap-ng-dev \ - libfdt-dev \ - libglib2.0-dev \ - libpixman-1-dev \ - meson \ - ninja-build \ - python3 \ - qemu-efi-aarch64 \ - zlib1g-dev \ - `# Linux kernel build deps` \ - libelf-dev && \ - sudo rm -rf /var/lib/apt/lists/* - -# Create working directory -WORKDIR /home/circleci -COPY config_linux.txt .config - -# Build linux -RUN set -eux; \ - git clone --depth=1 --branch=v6.0 https://github.com/torvalds/linux; \ - sudo chmod 0444 .config; \ - mv .config linux/; \ - cd linux; \ - export ARCH=arm64; \ - export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu-; \ - make olddefconfig; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp linux/arch/arm64/boot/Image Image; \ - rm -rf linux/ - -# Build QEMU -RUN set -eux; \ - git clone --depth=1 --branch=v7.0.0 https://github.com/qemu/qemu; \ - cd qemu; \ - mkdir build; \ - cd build; \ - ../configure \ - --target-list=aarch64-softmmu \ - --enable-virtfs \ - --disable-docs \ - --disable-sdl \ - --disable-kvm; \ - make -j$(($(nproc) * 2 + 1)); \ - cd ~; \ - cp -rL qemu/build/pc-bios/ ~/pc-bios; \ - cp qemu/build/aarch64-softmmu/qemu-system-aarch64 .; \ - rm -rf qemu/ - -# Export paths to binaries. -ENV UROOT_KERNEL /home/circleci/Image -ENV UROOT_QEMU "/home/circleci/qemu-system-aarch64 -machine virt -cpu max -m 1G " -ENV UROOT_TESTARCH arm64 diff --git a/.circleci/images/uefipayload-amd64/Dockerfile b/.circleci/images/uefipayload-amd64/Dockerfile new file mode 100644 index 0000000000..60fbbf38d5 --- /dev/null +++ b/.circleci/images/uefipayload-amd64/Dockerfile @@ -0,0 +1,34 @@ +# Copyright 2018-2021 the u-root Authors. All rights reserved +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +FROM ubuntu:rolling AS base + +# Install dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + git; + +RUN git clone --branch uefipayload-2024 --recursive https://github.com/linuxboot/edk2 uefipayload; + +RUN apt-get install -y --no-install-recommends \ + make \ + python3 \ + gcc \ + g++ \ + uuid-dev \ + nasm \ + bash \ + iasl && \ + rm -rf /var/lib/apt/lists/* + +SHELL ["/bin/bash", "-c"] +RUN cd uefipayload; \ + source ./edksetup.sh; \ + make -C BaseTools; \ + build -a X64 -p UefiPayloadPkg/UefiPayloadPkg.dsc -b DEBUG \ + -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; + +FROM scratch +COPY --from=base /uefipayload/Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd /UEFIPAYLOAD.fd From 727affedce12e1a58557f5b0881401f1075cd4f0 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 01:19:25 +0000 Subject: [PATCH 035/109] Use GH Workflows to build and publish Docker images Signed-off-by: Chris Koch --- .github/workflows/test-images.yml | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/test-images.yml diff --git a/.github/workflows/test-images.yml b/.github/workflows/test-images.yml new file mode 100644 index 0000000000..74c065218f --- /dev/null +++ b/.github/workflows/test-images.yml @@ -0,0 +1,69 @@ +name: Publish test images + +on: + push: + paths: + - '.circleci/images/kernel-arm/*' + - '.circleci/images/kernel-arm64/*' + - '.circleci/images/kernel-amd64/*' + - '.circleci/images/multiboot-test-kernel-amd64/*' + - '.circleci/images/uefipayload-amd64/*' + - '.github/workflows/test-images.yml' + branches: ['main'] + pull_request: + paths: + - '.circleci/images/kernel-arm/*' + - '.circleci/images/kernel-arm64/*' + - '.circleci/images/kernel-amd64/*' + - '.circleci/images/multiboot-test-kernel-amd64/*' + - '.circleci/images/uefipayload-amd64/*' + - '.github/workflows/test-images.yml' + branches: ['main'] + +# Cancel running workflows on new push to a PR. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + +jobs: + build-image: + runs-on: ubuntu-latest + strategy: + matrix: + image: ['kernel-amd64', 'kernel-arm', 'kernel-arm64', 'uefipayload-amd64', 'multiboot-test-kernel-amd64'] + + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/test-${{ matrix.image }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: ./.circleci/images/${{ matrix.image }} + # Build for PRs, only push for main. + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From c271704c6750ff96b6d7c89b92c938a69a1d56c9 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 02:25:57 +0000 Subject: [PATCH 036/109] Retrigger build by meaningless change in the yaml Signed-off-by: Chris Koch --- .github/workflows/test-images.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-images.yml b/.github/workflows/test-images.yml index 74c065218f..f5bfcf95ba 100644 --- a/.github/workflows/test-images.yml +++ b/.github/workflows/test-images.yml @@ -31,14 +31,14 @@ env: jobs: build-image: runs-on: ubuntu-latest - strategy: - matrix: - image: ['kernel-amd64', 'kernel-arm', 'kernel-arm64', 'uefipayload-amd64', 'multiboot-test-kernel-amd64'] - permissions: contents: read packages: write + strategy: + matrix: + image: ['kernel-amd64', 'kernel-arm', 'kernel-arm64', 'uefipayload-amd64', 'multiboot-test-kernel-amd64'] + steps: - name: Checkout repository uses: actions/checkout@v3 From f8d5d3c48dfcafb1b1668516052c9d0d8efcd8ea Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 03:20:31 +0000 Subject: [PATCH 037/109] Enable console output for x86 kernel Signed-off-by: Chris Koch --- .circleci/images/kernel-amd64/config_linux.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.circleci/images/kernel-amd64/config_linux.txt b/.circleci/images/kernel-amd64/config_linux.txt index 1e3cd3ca17..aeb12e553f 100644 --- a/.circleci/images/kernel-amd64/config_linux.txt +++ b/.circleci/images/kernel-amd64/config_linux.txt @@ -22,6 +22,10 @@ CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250_CONSOLE=y CONFIG_TTY=y +# Always print to serial port +CONFIG_CMDLINE_BOOL=y +CONFIG_CMDLINE="console=ttyS0 earlyprintk=ttyS0" + # Block devices CONFIG_BLOCK=y CONFIG_ATA=y @@ -74,6 +78,7 @@ CONFIG_CRYPTO_DEV_VIRTIO=y CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_SCSI=y CONFIG_VIRTIO_NET=y +CONFIG_VIRTIO_CONSOLE=y # Networking CONFIG_NET=y @@ -126,3 +131,8 @@ CONFIG_ACPI=y CONFIG_ACPI_FPDT=y CONFIG_DEVMEM=y CONFIG_STRICT_DEVMEM=n + +# Debugging +CONFIG_DEBUG_FS=y +CONFIG_GCOV_KERNEL=y +CONFIG_GCOV_PROFILE_ALL=y From 95f50164b7003e5fe3e044a0f0389e0e99bf6a3a Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 03:17:43 +0000 Subject: [PATCH 038/109] Pin versions in vmtest.yaml and enable multiboot and uefiboot tests Signed-off-by: Chris Koch --- .vmtest.yaml | 50 +++++++++++++++++++++ integration/generic-tests/multiboot_test.go | 22 ++++++--- integration/generic-tests/uefiboot_test.go | 17 ++++--- 3 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 .vmtest.yaml diff --git a/.vmtest.yaml b/.vmtest.yaml new file mode 100644 index 0000000000..7f32e6bc80 --- /dev/null +++ b/.vmtest.yaml @@ -0,0 +1,50 @@ +amd64: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-x86_64 -L {{.qemu}}/pc-bios -m 1G" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-amd64@sha256:10ea580ef29468f6d6f4674279586d37e118b145564fd1e99b708370262961a4" + template: "{{.bzImage}}" + files: + bzImage: "/bzImage" + + UROOT_MULTIBOOT_TEST_KERNEL_DIR: + container: "ghcr.io/u-root/u-root/test-multiboot-test-kernel-amd64@sha256:0fba729eddd76a50f5cfdfdabc4389d6a6902486b1190d354813ae953cc94b52" + template: "{{.mbdir}}" + directories: + mbdir: "/mb" + + UROOT_TEST_UEFIPAYLOAD: + container: "ghcr.io/u-root/u-root/test-uefipayload-amd64@sha256:4a9a47cdce32fb6dd7610f6de9f55d64b5dc380cf95bc23561696bb48e34622a" + template: "{{.payload}}" + files: + payload: "/UEFIPAYLOAD.fd" + +arm: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-arm -M virt,highmem=off -L {{.qemu}}/pc-bios" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-arm@sha256:d185d93812738b2869f4835d5a209fcf25ffdea4fd6601fcd206dfad67c9bced" + template: "{{.zImage}}" + files: + zImage: "/zImage" + +arm64: + VMTEST_QEMU: + container: "ghcr.io/hugelgupf/vmtest/qemu:main" + template: "{{.qemu}}/bin/qemu-system-aarch64 -machine virt -cpu max -m 1G -L {{.qemu}}/pc-bios" + directories: + qemu: "/zqemu" + + VMTEST_KERNEL: + container: "ghcr.io/u-root/u-root/test-kernel-arm64@sha256:805d64653b654ddf96e307cd3d7e835e5e9b9fd67d1b7e79b73cfa8ea2ce86e4" + template: "{{.Image}}" + files: + Image: "/Image" diff --git a/integration/generic-tests/multiboot_test.go b/integration/generic-tests/multiboot_test.go index 317fa1769b..b55d47b6a6 100644 --- a/integration/generic-tests/multiboot_test.go +++ b/integration/generic-tests/multiboot_test.go @@ -10,6 +10,7 @@ package integration import ( "bytes" "encoding/json" + "io" "os" "path/filepath" "reflect" @@ -22,6 +23,14 @@ import ( "github.com/u-root/u-root/pkg/uroot" ) +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { + return nil +} + func testMultiboot(t *testing.T, kernel string) { src := filepath.Join(os.Getenv("UROOT_MULTIBOOT_TEST_KERNEL_DIR"), kernel) if _, err := os.Stat(src); err != nil && os.IsNotExist(err) { @@ -32,8 +41,9 @@ func testMultiboot(t *testing.T, kernel string) { dir := t.TempDir() testCmds := []string{ - `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb" | tee /testdata/output.json`, + `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb"`, } + var b bytes.Buffer vm := vmtest.StartVMAndRunCmds(t, testCmds, vmtest.WithSharedDir(dir), vmtest.WithMergedInitramfs(uroot.Opts{ @@ -46,6 +56,7 @@ func testMultiboot(t *testing.T, kernel string) { }, }), vmtest.WithQEMUFn( + qemu.WithSerialOutput(nopCloser{&b}), qemu.WithVMTimeout(time.Minute), ), ) @@ -56,15 +67,12 @@ func testMultiboot(t *testing.T, kernel string) { if _, err := vm.Console.ExpectString(`}`); err != nil { t.Errorf(`expected '}' = end of JSON, got error: %v`, err) } - if err := vm.Wait(); err != nil { + if err := vm.Kill(); err != nil { t.Fatal(err) } + _ = vm.Wait() - output, err := os.ReadFile(filepath.Join(dir, "output.json")) - if err != nil { - t.Fatal(err) - } - t.Log(string(output)) + output := b.Bytes() i := bytes.Index(output, []byte(multiboot.DebugPrefix)) if i == -1 { diff --git a/integration/generic-tests/uefiboot_test.go b/integration/generic-tests/uefiboot_test.go index 9979e11bdb..89b80fa8df 100644 --- a/integration/generic-tests/uefiboot_test.go +++ b/integration/generic-tests/uefiboot_test.go @@ -8,9 +8,7 @@ package integration import ( - "fmt" "os" - "path/filepath" "testing" "time" @@ -24,14 +22,15 @@ import ( func TestUefiBoot(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - payload := "UEFIPAYLOAD.fd" - src := fmt.Sprintf("/home/circleci/%v", payload) - if tk := os.Getenv("UROOT_TEST_UEFIPAYLOAD_DIR"); len(tk) > 0 { - src = filepath.Join(tk, payload) + var payload string + if tk := os.Getenv("UROOT_TEST_UEFIPAYLOAD"); len(tk) == 0 { + t.Skipf("UROOT_TEST_UEFIPAYLOAD not set to payload") + } else { + payload = tk } - if _, err := os.Stat(src); err != nil && os.IsNotExist(err) { - t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", src) + if _, err := os.Stat(payload); err != nil && os.IsNotExist(err) { + t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", payload) } testCmds := []string{ @@ -43,7 +42,7 @@ func TestUefiBoot(t *testing.T) { )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(2*time.Minute), - qemu.IDEBlockDevice(src), + qemu.IDEBlockDevice(payload), qemu.ArbitraryArgs("-machine", "q35"), qemu.ArbitraryArgs("-m", "4096"), ), From d7690dc0d73aeff857f2441eadb6ae6869022d23 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 04:26:45 +0000 Subject: [PATCH 039/109] Update vmtest to get relative timestamps Signed-off-by: Chris Koch --- go.mod | 4 +- go.sum | 4 +- vendor/github.com/hugelgupf/vmtest/README.md | 2 +- .../hugelgupf/vmtest/qemu/devices.go | 46 ++++++++++++++++--- vendor/github.com/hugelgupf/vmtest/vmtest.go | 2 +- vendor/modules.txt | 2 +- 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 28d2a63851..d97b2cba91 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.9.1-0.20230914180155-ee6cbcd136f8 github.com/google/uuid v1.3.0 - github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea + github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 github.com/kevinburke/ssh_config v1.1.0 @@ -39,6 +39,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 golang.org/x/crypto v0.17.0 + golang.org/x/net v0.19.0 golang.org/x/sys v0.15.0 golang.org/x/term v0.15.0 golang.org/x/text v0.14.0 @@ -82,7 +83,6 @@ require ( golang.org/x/arch v0.2.0 // indirect golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect golang.org/x/sync v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 86c9509d59..cdd6c9ad27 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFa github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea h1:rSO4GiZ/EThUOkl1kFLB+DOGxa3oEgT8d8ZhrYbDIW8= -github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea/go.mod h1:3YxP4j/kQh5BzoobzCeSIVZOlz4te/CGVRxS9/NrwGU= +github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e h1:Z+XcwHPLl1WJCZ4nQUkUqS0FzxRTeOrx5twtKhbVP60= +github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e/go.mod h1:3YxP4j/kQh5BzoobzCeSIVZOlz4te/CGVRxS9/NrwGU= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 h1:h+RKaNPjka7LRJGoeub/IQBdXSoEaJjfADkBq02hvjw= diff --git a/vendor/github.com/hugelgupf/vmtest/README.md b/vendor/github.com/hugelgupf/vmtest/README.md index 298c936022..782e92844c 100644 --- a/vendor/github.com/hugelgupf/vmtest/README.md +++ b/vendor/github.com/hugelgupf/vmtest/README.md @@ -106,7 +106,7 @@ func TestStartVM(t *testing.T) { qemu.WithInitramfs("./somewhere.cpio"), qemu.WithAppendKernel("console=ttyS0 earlyprintk=ttyS0"), - qemu.LogSerialByLine(qemu.PrintLineWithPrefix("vm", t.Logf)), + qemu.LogSerialByLine(qemu.DefaultPrint("vm", t.Logf)), ) if err != nil { t.Fatalf("Failed to start VM: %v", err) diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go index 46ba5a48bd..23a90868b1 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/devices.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/devices.go @@ -14,7 +14,9 @@ import ( "net/http" "os" "strings" + "sync" "syscall" + "time" "github.com/creack/pty" "github.com/hugelgupf/vmtest/internal/eventchannel" @@ -233,9 +235,12 @@ func ServeHTTP(s *http.Server, l net.Listener) Fn { } } +// LinePrinter prints one line to some output. +type LinePrinter func(line string) + // LogSerialByLine processes serial output from the guest one line at a time // and calls callback on each full line. -func LogSerialByLine(callback func(line string)) Fn { +func LogSerialByLine(callback LinePrinter) Fn { return func(alloc *IDAllocator, opts *Options) error { r, w := io.Pipe() opts.SerialOutput = append(opts.SerialOutput, w) @@ -253,12 +258,41 @@ func LogSerialByLine(callback func(line string)) Fn { } } -// PrintLineWithPrefix returns a usable callback for LogSerialByLine that -// prints a prefix and the line. Usable with any standard Go print function -// like t.Logf or fmt.Printf. -func PrintLineWithPrefix(prefix string, printer func(fmt string, arg ...any)) func(line string) { +// TS prefixes line printer output with a timestamp since the first log line. +// +// format can be any Time.Format format string. Recommendations are +// time.TimeOnly or time.DateTime. +func TS(format string, printer LinePrinter) LinePrinter { + return func(line string) { + printer(fmt.Sprintf("[%s] %s", time.Now().Format(format), line)) + } +} + +// DefaultPrint is the default LinePrinter, adding a prefix and relative timestamp. +func DefaultPrint(prefix string, printer func(fmt string, arg ...any)) LinePrinter { + return RelativeTS(Prefix(prefix, PrintLine(printer))) +} + +// RelativeTS prefixes line printer output with "[%06.4fs] " seconds since the +// first log line. +func RelativeTS(printer LinePrinter) LinePrinter { + start := sync.OnceValue(time.Now) + return func(line string) { + printer(fmt.Sprintf("[%06.4fs] %s", time.Since(start()).Seconds(), line)) + } +} + +// PrintLine is a LinePrinter that prints to a standard "formatter" like testing.TB.Logf or fmt.Printf. +func PrintLine(printer func(fmt string, arg ...any)) LinePrinter { + return func(line string) { + printer("%s", line) + } +} + +// Prefix returns a LinePrinter that prefixes the given LinePrinter with "prefix: ". +func Prefix(prefix string, printer LinePrinter) LinePrinter { return func(line string) { - printer("%s: %s", prefix, line) + printer(fmt.Sprintf("%s: %s", prefix, line)) } } diff --git a/vendor/github.com/hugelgupf/vmtest/vmtest.go b/vendor/github.com/hugelgupf/vmtest/vmtest.go index facf438399..4460c3a07a 100644 --- a/vendor/github.com/hugelgupf/vmtest/vmtest.go +++ b/vendor/github.com/hugelgupf/vmtest/vmtest.go @@ -226,7 +226,7 @@ func startVM(t testing.TB, o *VMOptions) *qemu.VM { SkipWithoutQEMU(t) qopts := []qemu.Fn{ - qemu.LogSerialByLine(qemu.PrintLineWithPrefix(o.ConsoleOutputPrefix, t.Logf)), + qemu.LogSerialByLine(qemu.DefaultPrint(o.ConsoleOutputPrefix, t.Logf)), // Tests use this env var to identify they are running inside a // vmtest using SkipIfNotInVM. qemu.WithAppendKernel("VMTEST_IN_GUEST=1"), diff --git a/vendor/modules.txt b/vendor/modules.txt index d7e5104d79..86097a45b4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -127,7 +127,7 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror -# github.com/hugelgupf/vmtest v0.0.0-20240115033909-46506b2af5ea +# github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e ## explicit; go 1.21 github.com/hugelgupf/vmtest github.com/hugelgupf/vmtest/guest From 78f063466a7f9f3a9fa2ef37ec40eed99e473c2c Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 04:33:49 +0000 Subject: [PATCH 040/109] Change CircleCI to use Go 1.21 Signed-off-by: Chris Koch --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04ff4f9756..fbb443579d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ templates: golang-template: &golang-template docker: - - image: uroottest/test-image-amd64:v5.2.0 + - image: cimg/go:1.21 working_directory: /home/circleci/go/src/github.com/u-root/u-root environment: - UROOT_SOURCE: /home/circleci/go/src/github.com/u-root/u-root From a7fbabcdfbe05f44816d28fee23a9a5189ca6a06 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 24 Jan 2024 04:37:04 +0000 Subject: [PATCH 041/109] Remove unused test scripts Signed-off-by: Chris Koch --- integration/GET_KERNEL_QEMU | 78 ------------------------------------- integration/RUNLOCAL | 24 ------------ 2 files changed, 102 deletions(-) delete mode 100755 integration/GET_KERNEL_QEMU delete mode 100755 integration/RUNLOCAL diff --git a/integration/GET_KERNEL_QEMU b/integration/GET_KERNEL_QEMU deleted file mode 100755 index 09032de719..0000000000 --- a/integration/GET_KERNEL_QEMU +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -# This script is intended to run the tests we run at circleci, -# precisely as they are run there. -# -# to do so, it: -# o creates a directory to store local artifacts retrieved from docker -# see TMP= below -# o runs the standard test container to retrieve a the qemu, kernel, and bios image -# o runs go test with a default set of tests (./...) -# -# NOTE: if you want more complex behavior, don't make this script more -# complex. Convert it to Go. Complex shell scripts suck. - -# These docker artifacts should not persist. Place them in tmp. -# tmp is in .gitignore -# I would prefer /tmp/$$. -# Docker really doesn't like this for some reason, even when -# I map it to /out inside the container. -TMP=`pwd`/tmp -mkdir -p $TMP -chmod 777 $TMP - -# The default value is amd64, but you can override it, e.g. -# UROOT_TESTARCH=arm64 bash RUNLOCAL -export UROOT_TESTARCH=${UROOT_TESTARCH:=amd64} - -case $UROOT_TESTARCH in - - "amd64") - export UROOT_QEMU="qemu-system-x86_64" - export UROOT_QEMU_OPTS="-L $TMP/pc-bios -m 1G" - export UROOT_KERNEL=bzImage - export UROOT_BIOS=pc-bios - ;; - - "arm64") - export UROOT_QEMU=qemu-system-aarch64 - export UROOT_KERNEL=Image - export UROOT_BIOS="" - export UROOT_QEMU_OPTS="" - ;; - - "arm") - export UROOT_QEMU=qemu-system-arm - export UROOT_KERNEL=zImage - export UROOT_BIOS="" - export UROOT_QEMU_OPTS='-M virt -nographic' - export UROOT_QEMU_TIMEOUT_X=10 - - ;; - - *) - echo "$UROOT_TESTARCH is not a supported architecture (amd64, arm64, arm)" - exit 1 - ;; - -esac - -# We no longer allow you to pick a kernel to run. -# Since we wish to exactly mirror what circleci does, we always use the -# kernel and qemu in the container. -# Note the docker pull only hurts a lot the first time. -# After you have run it once, further cp operations take a second or so. -# By doing it this way, we always use the latest Docker files. -CONTAINER=uroottest/test-image-${UROOT_TESTARCH} - -DIR=/home/circleci - -docker run $CONTAINER bash -c "echo \$UROOT_QEMU" -docker run $CONTAINER tar Ccf $DIR - $UROOT_KERNEL $UROOT_QEMU $UROOT_BIOS | tar Cxf $TMP - - -ls -l $TMP - -# now adjust paths and such -export UROOT_KERNEL=$TMP/$UROOT_KERNEL -export UROOT_QEMU="$TMP/$UROOT_QEMU $UROOT_QEMU_OPTS" -export UROOT_BIOS=$TMP/$UROOT_BIOS diff --git a/integration/RUNLOCAL b/integration/RUNLOCAL deleted file mode 100755 index dc1cf3b816..0000000000 --- a/integration/RUNLOCAL +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -# This script is intended to run, locally, the tests we run at circleci, -# precisely as they are run there. -# -# to do so, it: -# o creates a directory to store local artifacts retrieved from docker -# see TMP= below -# o runs a the standard test container to retrieve a the qemu, kernel, and bios image -# o runs go test with a default set of tests (./...) -# -# NOTE: if you want more complex behavior, don't make this script more -# complex. Convert it to Go. Complex shell scripts suck. - -# Take a guess: they are likely running it in this directory -UROOT_SOURCE=${UROOT_SOURCE:-${PWD}/..} -export UROOT_SOURCE - -set -e -set -x - -. GET_KERNEL_QEMU - -go test "$@" ./... From d16c117ec8564386296720b8aaef44aba847f62f Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 23 Jan 2024 20:34:54 +0000 Subject: [PATCH 042/109] Capitalize log statements Signed-off-by: Chris Koch --- pkg/sh/run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sh/run.go b/pkg/sh/run.go index 31cff75694..760acf82c3 100644 --- a/pkg/sh/run.go +++ b/pkg/sh/run.go @@ -19,10 +19,10 @@ func Run(arg0 string, args ...string) error { // RunWithLogs runs a command with stdin, stdout and stderr. This function is // more verbose than log.Run. func RunWithLogs(arg0 string, args ...string) error { - log.Printf("executing command %q with args %q", arg0, args) + log.Printf("Executing command %q with args %q...", arg0, args) err := RunWithIO(os.Stdin, os.Stdout, os.Stderr, arg0, args...) if err != nil { - log.Printf("command %q with args %q failed: %v", arg0, args, err) + log.Printf("Command %q with args %q failed: %v", arg0, args, err) } return err } From 2f485707bbc4e20c2b5df949f183ef8688b680b6 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Wed, 24 Jan 2024 20:43:28 +0200 Subject: [PATCH 043/109] cmds/exp/syscallfilter: build with more architectures support the same build tags as strace package Signed-off-by: Siarhiej Siemianczuk --- cmds/exp/syscallfilter/main_linux.go | 5 +-- pkg/syscallfilter/syscallfilter_linux.go | 35 ++++++++----------- pkg/syscallfilter/syscallfilter_linux_test.go | 5 +-- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/cmds/exp/syscallfilter/main_linux.go b/cmds/exp/syscallfilter/main_linux.go index 001e3c464d..a4578f7ea3 100644 --- a/cmds/exp/syscallfilter/main_linux.go +++ b/cmds/exp/syscallfilter/main_linux.go @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !tinygo && linux && amd64 -// +build !tinygo,linux,amd64 +//go:build !tinygo && ((linux && arm64) || (linux && amd64) || (linux && riscv64)) +// +build !tinygo +// +build linux,arm64 linux,amd64 linux,riscv64 // syscallfilter runs a command with a possibly empty set of filters: // diff --git a/pkg/syscallfilter/syscallfilter_linux.go b/pkg/syscallfilter/syscallfilter_linux.go index 445769f4cf..0096d0cb35 100644 --- a/pkg/syscallfilter/syscallfilter_linux.go +++ b/pkg/syscallfilter/syscallfilter_linux.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && amd64 -// +build linux,amd64 +//go:build (linux && arm64) || (linux && amd64) || (linux && riscv64) +// +build linux,arm64 linux,amd64 linux,riscv64 package syscallfilter @@ -100,15 +100,16 @@ func eventName(r *strace.TraceRecord) string { case strace.SyscallExit: return "X" + sysname case strace.SignalExit: - return fmt.Sprintf("SignalExit") + return "SignalExit" case strace.Exit: - return fmt.Sprintf("Exit") + return "Exit" case strace.SignalStop: - return fmt.Sprintf("SignalStop") + return "SignalStop" case strace.NewChild: - return fmt.Sprintf("NewChild") + return "NewChild" + default: + log.Panicf("Unknown event %#x from record %v", r.Event, r) } - log.Panicf("Unknown event %#x from record %v", r.Event, r) return "" } @@ -145,19 +146,13 @@ func (c *Cmd) handleEvent(t strace.Task, r *strace.TraceRecord, e []*event) erro // as created by AddActions. The slice can be empty, in which case the command // runs as normal. func (c *Cmd) Run() error { - defer func() { - // This wait may or may not be needed, since the process - // can end normally or be stopped by a filter. Hence, - // we will not check for an error. - c.Wait() - }() - if err := strace.Trace(c.Cmd, func(t strace.Task, r *strace.TraceRecord) error { - ret := c.handleEvent(t, r, c.events) - return ret - }); err != nil { - return fmt.Errorf("%v", err) - } - return nil + // This wait may or may not be needed, since the process + // can end normally or be stopped by a filter. Hence, + // we will not check for an error. + defer c.Wait() + return strace.Trace(c.Cmd, func(t strace.Task, r *strace.TraceRecord) error { + return c.handleEvent(t, r, c.events) + }) } // AddActions creates an []event as defined by a possibly empty set of actions, and diff --git a/pkg/syscallfilter/syscallfilter_linux_test.go b/pkg/syscallfilter/syscallfilter_linux_test.go index 8e33a2ec36..4b213dcc43 100644 --- a/pkg/syscallfilter/syscallfilter_linux_test.go +++ b/pkg/syscallfilter/syscallfilter_linux_test.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "strings" + "syscall" "testing" "github.com/u-root/u-root/pkg/strace" @@ -224,8 +225,8 @@ func TestEventName(t *testing.T) { r *strace.TraceRecord n string }{ - {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: 0}}, n: "Eread"}, - {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: 0}}, n: "Xread"}, + {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: syscall.SYS_READ}}, n: "Eread"}, + {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: syscall.SYS_READ}}, n: "Xread"}, {r: &strace.TraceRecord{Event: strace.SyscallEnter, Syscall: &strace.SyscallEvent{Sysno: 0xabcd}}, n: "Eabcd"}, {r: &strace.TraceRecord{Event: strace.SyscallExit, Syscall: &strace.SyscallEvent{Sysno: 0xbcde}}, n: "Xbcde"}, {r: &strace.TraceRecord{Event: strace.SignalExit}, n: "SignalExit"}, From b7f4092c2e9a86b7dfd0ebc77a8690d5d2af5b8b Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 06:08:39 +0000 Subject: [PATCH 044/109] Update vmtest Signed-off-by: Chris Koch --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/hugelgupf/vmtest/README.md | 7 +- .../github.com/hugelgupf/vmtest/coverage.go | 18 ++ .../hugelgupf/vmtest/dependencies.go | 9 +- vendor/github.com/hugelgupf/vmtest/gotest.go | 8 +- .../hugelgupf/vmtest/guest/kcov_linux.go | 23 ++ .../hugelgupf/vmtest/qemu/network/network.go | 130 --------- .../github.com/hugelgupf/vmtest/qemu/qemu.go | 7 +- .../hugelgupf/vmtest/qemu/qnetwork/network.go | 263 ++++++++++++++++++ .../github.com/hugelgupf/vmtest/shelltest.go | 33 +-- .../vmtest/vminit/gouinit/main_linux.go | 13 +- .../vmtest/vminit/shelluinit/main_linux.go | 11 +- vendor/github.com/hugelgupf/vmtest/vmtest.go | 25 ++ vendor/modules.txt | 4 +- 15 files changed, 377 insertions(+), 180 deletions(-) delete mode 100644 vendor/github.com/hugelgupf/vmtest/qemu/network/network.go create mode 100644 vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go diff --git a/go.mod b/go.mod index d97b2cba91..126e188bb4 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/go-tpm v0.9.1-0.20230914180155-ee6cbcd136f8 github.com/google/uuid v1.3.0 - github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e + github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 github.com/kevinburke/ssh_config v1.1.0 diff --git a/go.sum b/go.sum index cdd6c9ad27..244ccd806e 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/hexdigest/gowrap v1.1.7/go.mod h1:Z+nBFUDLa01iaNM+/jzoOA1JJ7sm51rnYFa github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e h1:Z+XcwHPLl1WJCZ4nQUkUqS0FzxRTeOrx5twtKhbVP60= -github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e/go.mod h1:3YxP4j/kQh5BzoobzCeSIVZOlz4te/CGVRxS9/NrwGU= +github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa h1:2q0UvEA7TSTrjU4aFrpF6u28tHat3KnCkqsy/gB86v0= +github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa/go.mod h1:Z22CpRFjhoR/NoxBeEuyeGTwMC7G5s4RfKa9Bs/j74w= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/intel-go/cpuid v0.0.0-20200819041909-2aa72927c3e2 h1:h+RKaNPjka7LRJGoeub/IQBdXSoEaJjfADkBq02hvjw= diff --git a/vendor/github.com/hugelgupf/vmtest/README.md b/vendor/github.com/hugelgupf/vmtest/README.md index 782e92844c..d8a3f0e5b4 100644 --- a/vendor/github.com/hugelgupf/vmtest/README.md +++ b/vendor/github.com/hugelgupf/vmtest/README.md @@ -39,12 +39,15 @@ The `qemu` API picks up the following values from env vars by default: * `VMTEST_ARCH`: Guest architecture (same as GOARCH values). Must match the QEMU binary supplied. If not supplied, defaults to `runtime.GOARCH`, i.e. it matches the host's GOARCH. +* `VMTEST_KERNEL_APPEND`: is added to kernel command-line arguments +* `VMTEST_QEMU_APPEND`: is added to QEMU command-line arguments. * `VMTEST_TIMEOUT`: Timeout value (e.g. `1m20s` -- parsed by Go's `time.ParseDuration`). * `VMTEST_INITRAMFS`: Initramfs to boot. -These values can be overriden in the Go API, but typically only -`VMTEST_INITRAMFS` and `VMTEST_TIMEOUT` are. +Most of these values can be overriden in the Go API, but typically only +`VMTEST_INITRAMFS` and `VMTEST_TIMEOUT` are. `VMTEST_KERNEL_APPEND` and +`VMTEST_QEMU_APPEND` are always additive. The `runvmtest` tool automatically downloads `VMTEST_QEMU` and `VMTEST_KERNEL` for use with tests based on a provided `VMTEST_ARCH`. E.g. diff --git a/vendor/github.com/hugelgupf/vmtest/coverage.go b/vendor/github.com/hugelgupf/vmtest/coverage.go index 7aa971c426..b2bfe12a54 100644 --- a/vendor/github.com/hugelgupf/vmtest/coverage.go +++ b/vendor/github.com/hugelgupf/vmtest/coverage.go @@ -14,6 +14,24 @@ import ( "github.com/hugelgupf/vmtest/testtmp" ) +// ShareGOCOVERDIR shares VMTEST_GOCOVERDIR with the guest if it's available in the +// environment. +// +// Call guest.GOCOVERDIR to set up the directory in the guest. +func ShareGOCOVERDIR() Opt { + return func(t testing.TB, v *VMOptions) error { + goCov := os.Getenv("VMTEST_GOCOVERDIR") + if goCov == "" { + return nil + } + v.QEMUOpts = append(v.QEMUOpts, + qemu.P9Directory(goCov, "gocov"), + qemu.WithAppendKernel("VMTEST_GOCOVERDIR=gocov"), + ) + return nil + } +} + // CollectKernelCoverage collects kernel coverage files for each test to // VMTEST_KERNEL_COVERAGE_DIR/{testName}/{instance}, where instance is a number // starting at 0. diff --git a/vendor/github.com/hugelgupf/vmtest/dependencies.go b/vendor/github.com/hugelgupf/vmtest/dependencies.go index f7a7168fd8..c35513de76 100644 --- a/vendor/github.com/hugelgupf/vmtest/dependencies.go +++ b/vendor/github.com/hugelgupf/vmtest/dependencies.go @@ -7,8 +7,15 @@ package vmtest // // But obviously aren't actually importable, since they are main packages. import ( + _ "github.com/u-root/u-root/cmds/core/cat" _ "github.com/u-root/u-root/cmds/core/dhclient" - _ "github.com/u-root/u-root/cmds/core/elvish" + _ "github.com/u-root/u-root/cmds/core/false" + _ "github.com/u-root/u-root/cmds/core/gosh" _ "github.com/u-root/u-root/cmds/core/init" _ "github.com/u-root/u-root/cmds/core/ip" + _ "github.com/u-root/u-root/cmds/core/ls" + _ "github.com/u-root/u-root/cmds/core/shutdown" + _ "github.com/u-root/u-root/cmds/core/sync" + _ "github.com/u-root/u-root/cmds/core/wget" + _ "github.com/u-root/u-root/cmds/exp/pxeserver" ) diff --git a/vendor/github.com/hugelgupf/vmtest/gotest.go b/vendor/github.com/hugelgupf/vmtest/gotest.go index f2de2150c9..d63bcc8732 100644 --- a/vendor/github.com/hugelgupf/vmtest/gotest.go +++ b/vendor/github.com/hugelgupf/vmtest/gotest.go @@ -198,19 +198,13 @@ func RunGoTestsInVM(t testing.TB, pkgs []string, opts ...GoTestOpt) { qemuFns := []qemu.Fn{ qemu.P9Directory(sharedDir, "gotests"), } - goCov := os.Getenv("GOCOVERDIR") - if goCov != "" { - qemuFns = append(qemuFns, - qemu.P9Directory(goCov, "gocov"), - qemu.WithAppendKernel("VMTEST_GOCOVERDIR=gocov"), - ) - } // Create the initramfs and start the VM. vm := StartVM(t, append( []Opt{ WithMergedInitramfs(initramfs), WithQEMUFn(qemuFns...), CollectKernelCoverage(), + ShareGOCOVERDIR(), }, goOpts.VMOpts...)...) if err := vm.Wait(); err != nil { diff --git a/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go b/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go index ba5aed0f57..6c1487e203 100644 --- a/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go +++ b/vendor/github.com/hugelgupf/vmtest/guest/kcov_linux.go @@ -15,6 +15,29 @@ import ( "github.com/u-root/u-root/pkg/tarutil" ) +// GOCOVERDIR sets GOCOVERDIR in the guest if it was shared by +// vmtest.ShareGOCOVERDIR. +func GOCOVERDIR() func() { + tag := os.Getenv("VMTEST_GOCOVERDIR") + if tag == "" { + return func() {} + } + + mp, err := Mount9PDir("/gocov", tag) + if err != nil { + log.Fatal(err) + } + + if err := os.Setenv("GOCOVERDIR", "/gocov"); err != nil { + log.Fatal(err) + } + return func() { + if err := mp.Unmount(0); err != nil { + log.Printf("Unmounting GOCOVERDIR: %v", err) + } + } +} + // gcovFilter filters on all files ending with a gcda or gcno extension. func gcovFilter(hdr *tar.Header) bool { if hdr.Typeflag == tar.TypeDir { diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go b/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go deleted file mode 100644 index 190e089093..0000000000 --- a/vendor/github.com/hugelgupf/vmtest/qemu/network/network.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package network provides net device configurators for use with the Go qemu -// API. -package network - -import ( - "fmt" - "net" - "sync/atomic" - - "github.com/hugelgupf/vmtest/qemu" -) - -// InterVM is a Device that can connect multiple QEMU VMs to each other. -// -// InterVM uses the QEMU socket mechanism to connect multiple VMs with a simple -// TCP socket. -type InterVM struct { - port uint16 - - // numVMs must be atomically accessed so VMs can be started in parallel - // in goroutines. - numVMs uint32 -} - -// NewInterVM creates a new QEMU network between QEMU VMs. -// -// The network is closed from the world and only between the QEMU VMs. -func NewInterVM() *InterVM { - return &InterVM{ - port: 1234, - } -} - -// Opt returns additional QEMU command-line parameters based on the net -// device ID. -type Opt func(netdev string, id *qemu.IDAllocator) []string - -// WithPCAP captures network traffic and saves it to outputFile. -func WithPCAP(outputFile string) Opt { - return func(netdev string, id *qemu.IDAllocator) []string { - return []string{ - "-object", - fmt.Sprintf("filter-dump,id=%s,netdev=%s,file=%s", id.ID("filter"), netdev, outputFile), - } - } -} - -// NewVM returns a Device that can be used with a new QEMU VM. -func (n *InterVM) NewVM(nopts ...Opt) qemu.Fn { - if n == nil { - return nil - } - - newNum := atomic.AddUint32(&n.numVMs, 1) - num := newNum - 1 - - // MAC for the virtualized NIC. - // - // This is from the range of locally administered address ranges. - mac := net.HardwareAddr{0x0e, 0x00, 0x00, 0x00, 0x00, byte(num)} - return func(alloc *qemu.IDAllocator, opts *qemu.Options) error { - devID := alloc.ID("vm") - - args := []string{"-device", fmt.Sprintf("e1000,netdev=%s,mac=%s", devID, mac)} - // Note: QEMU in CircleCI seems to in solve cases fail when using just ':1234' format. - // It fails with "address resolution failed for :1234: Name or service not known" - // hinting that this is somehow related to DNS resolution. To work around this, - // we explicitly bind to 127.0.0.1 (IPv6 [::1] is not parsed correctly by QEMU). - if num != 0 { - args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,connect=127.0.0.1:%d", devID, n.port)) - } else { - args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,listen=127.0.0.1:%d", devID, n.port)) - } - - for _, opt := range nopts { - args = append(args, opt(devID, alloc)...) - } - opts.AppendQEMU(args...) - return nil - } -} - -// IPv4HostNetwork provides QEMU user-mode networking to the host. -// -// Net must be an IPv4 network. -// It uses the e1000 NIC. -func IPv4HostNetwork(net *net.IPNet, nopts ...Opt) qemu.Fn { - return func(alloc *qemu.IDAllocator, opts *qemu.Options) error { - if net.IP.To4() == nil { - return fmt.Errorf("HostNetwork must be configured with an IPv4 address") - } - - netdevID := alloc.ID("netdev") - args := []string{ - "-device", fmt.Sprintf("e1000,netdev=%s", netdevID), - "-netdev", fmt.Sprintf("user,id=%s,net=%s,dhcpstart=%s,ipv6=off", netdevID, net, nthIP(net, 8)), - } - - for _, opt := range nopts { - args = append(args, opt(netdevID, alloc)...) - } - opts.AppendQEMU(args...) - return nil - } -} - -func inc(ip net.IP) { - for j := len(ip) - 1; j >= 0; j-- { - ip[j]++ - if ip[j] > 0 { - break - } - } -} - -func nthIP(nt *net.IPNet, n int) net.IP { - ip := make(net.IP, net.IPv4len) - copy(ip, nt.IP.To4()) - for i := 0; i < n; i++ { - inc(ip) - } - if !nt.Contains(ip) { - return nil - } - return ip -} diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go index d11c802be5..23b7280cf0 100644 --- a/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go +++ b/vendor/github.com/hugelgupf/vmtest/qemu/qemu.go @@ -14,7 +14,9 @@ // Other environment variables: // // VMTEST_ARCH (used when Arch is empty or ArchUseEnvv is set) +// VMTEST_QEMU_APPEND (always added to QEMU arguments) // VMTEST_KERNEL (used when Options.Kernel is empty) +// VMTEST_KERNEL_APPEND (always added to kernel args) // VMTEST_INITRAMFS (used when Options.Initramfs is empty) // VMTEST_TIMEOUT (used when Options.VMTimeout is empty) package qemu @@ -182,10 +184,11 @@ func OptionsFor(arch Arch, fns ...Fn) (*Options, error) { o := &Options{ QEMUCommand: os.Getenv("VMTEST_QEMU"), Kernel: os.Getenv("VMTEST_KERNEL"), + KernelArgs: os.Getenv("VMTEST_KERNEL_APPEND"), Initramfs: os.Getenv("VMTEST_INITRAMFS"), VMTimeout: vmTimeout, // Disable graphics by default. - QEMUArgs: []string{"-nographic"}, + QEMUArgs: append([]string{"-nographic"}, strings.Fields(os.Getenv("VMTEST_QEMU_APPEND"))...), } if err := o.setArch(arch); err != nil { @@ -245,6 +248,8 @@ type Options struct { Initramfs string // Extra kernel command-line arguments. + // + // VMTEST_KERNEL_APPEND env var will always be prepended. KernelArgs string // Where to send serial output. diff --git a/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go b/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go new file mode 100644 index 0000000000..5dc71b7885 --- /dev/null +++ b/vendor/github.com/hugelgupf/vmtest/qemu/qnetwork/network.go @@ -0,0 +1,263 @@ +// Copyright 2018 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package qnetwork provides net device configurators for use with the Go qemu +// API. +package qnetwork + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + + "github.com/hugelgupf/vmtest/qemu" +) + +// NIC is a QEMU NIC device string. +// +// Valid values for your QEMU can be found with `qemu-system- -device +// help` in the Network devices section. +type NIC string + +// A subset of QEMU NIC devices. +const ( + NICE1000 NIC = "e1000" + NICVirtioNet NIC = "virtio-net" +) + +// DeviceOptions are network device options. +// +// These are options for the `-device ,...` command-line arg. +type DeviceOptions struct { + // NIC is the NIC device that QEMU emulates. + NIC NIC + + // MAC is the MAC address assigned to this interface in the guest. + MAC net.HardwareAddr +} + +// SetNIC sets the NIC. +func (d *DeviceOptions) setNIC(nic NIC) { + d.NIC = nic +} + +// SetMAC sets the device's MAC. +func (d *DeviceOptions) setMAC(mac net.HardwareAddr) { + d.MAC = mac +} + +// DeviceOptioner is an interface for setting DeviceOptions members. +// +// It exists so DeviceOptions can be extensible through generics, but the +// WithNIC/WithMAC/WithPCAP functions can be the same across all Options +// structs. +type DeviceOptioner interface { + *UserOptions | *DeviceOptions + + setNIC(NIC) + setMAC(net.HardwareAddr) +} + +// Opt is a configurer useed with either *DeviceOptions or *UserOptions. +type Opt[DO DeviceOptioner] func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error + +// WithPCAP captures network traffic and saves it to outputFile. +func WithPCAP[DO DeviceOptioner](outputFile string) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error { + qopts.AppendQEMU( + "-object", + fmt.Sprintf("filter-dump,id=%s,netdev=%s,file=%s", id.ID("filter"), netdev, outputFile), + ) + return nil + } +} + +// WithNIC changes the default NIC device QEMU emulates from e1000 to the given value. +func WithNIC[DO DeviceOptioner](nic NIC) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts DO) error { + opts.setNIC(nic) + return nil + } +} + +// WithMAC assigns a MAC address to the guest interface. +func WithMAC[DO DeviceOptioner](mac net.HardwareAddr) Opt[DO] { + return func(netdev string, id *qemu.IDAllocator, qops *qemu.Options, opts DO) error { + if mac != nil { + opts.setMAC(mac) + } + return nil + } +} + +// InterVM is a Device that can connect multiple QEMU VMs to each other. +// +// InterVM uses the QEMU socket mechanism to connect multiple VMs with a simple +// unix domain socket. +type InterVM struct { + socket string + err error + + // numVMs must be atomically accessed so VMs can be started in parallel + // in goroutines. + numVMs uint32 + + wg sync.WaitGroup +} + +// NewInterVM creates a new QEMU network between QEMU VMs. +// +// The network is closed from the world and only between the QEMU VMs. +func NewInterVM() *InterVM { + // Avoid returning an error here if unnecessary. + dir, err := os.MkdirTemp("", "intervm-") + return &InterVM{ + err: err, + socket: filepath.Join(dir, "intervm.socket"), + } +} + +// NewVM returns a Device that can be used with a new QEMU VM. +func (n *InterVM) NewVM(nopts ...Opt[*DeviceOptions]) qemu.Fn { + if n == nil { + return nil + } + + newNum := atomic.AddUint32(&n.numVMs, 1) + num := newNum - 1 + + n.wg.Add(1) + return func(alloc *qemu.IDAllocator, qopts *qemu.Options) error { + if n.err != nil { + return n.err + } + devID := alloc.ID("vm") + + opts := DeviceOptions{ + // Default NIC. + NIC: NICE1000, + + // MAC for the virtualized NIC. + // + // This is from the range of locally administered address ranges. + MAC: net.HardwareAddr{0xe, 0, 0, 0, 0, byte(num)}, + } + for _, opt := range nopts { + if err := opt(devID, alloc, qopts, &opts); err != nil { + return err + } + } + args := []string{"-device", fmt.Sprintf("%s,netdev=%s,mac=%s", opts.NIC, devID, opts.MAC)} + + if num != 0 { + args = append(args, "-netdev", fmt.Sprintf("stream,id=%s,server=false,addr.type=unix,addr.path=%s", devID, n.socket)) + } else { + args = append(args, "-netdev", fmt.Sprintf("stream,id=%s,server=true,addr.type=unix,addr.path=%s", devID, n.socket)) + + // When the server VM exits, wait until all clients + // close, then delete the socket file and directory. + qopts.Tasks = append(qopts.Tasks, func(ctx context.Context, notif *qemu.Notifications) error { + n.wg.Wait() + return os.RemoveAll(filepath.Dir(n.socket)) + }) + } + + // When each VM exits, call Done. + qopts.Tasks = append(qopts.Tasks, qemu.Cleanup(func() error { + n.wg.Done() + return nil + })) + qopts.AppendQEMU(args...) + return nil + } +} + +// UserOptions are options for a QEMU "user" network. +type UserOptions struct { + DeviceOptions + + Args []string +} + +// WithUserArg adds more comma-separated args to a `-netdev user,arg0,arg1,...` +// invocation. +func WithUserArg(arg ...string) Opt[*UserOptions] { + return func(netdev string, id *qemu.IDAllocator, qopts *qemu.Options, opts *UserOptions) error { + opts.Args = append(opts.Args, arg...) + return nil + } +} + +// IPv4HostNetwork provides QEMU user-mode networking to the host. +// +// Net must be an IPv4 network. +// +// Default NIC is e1000, with a MAC address of 0e:00:00:00:00:01. +func IPv4HostNetwork(cidr string, nopts ...Opt[*UserOptions]) qemu.Fn { + return func(alloc *qemu.IDAllocator, qopts *qemu.Options) error { + // TODO: use IP + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + if ipnet.IP.To4() == nil { + return fmt.Errorf("HostNetwork must be configured with an IPv4 address") + } + + netdevID := alloc.ID("netdev") + opts := UserOptions{ + DeviceOptions: DeviceOptions{ + // Default NIC. + NIC: NICE1000, + + // MAC for the virtualized NIC. + // + // This is from the range of locally administered address ranges. + MAC: net.HardwareAddr{0xe, 0, 0, 0, 0, 1}, + }, + } + + for _, opt := range nopts { + if err := opt(netdevID, alloc, qopts, &opts); err != nil { + return err + } + } + + var extraUserArgs string + if len(opts.Args) > 0 { + extraUserArgs = "," + strings.Join(opts.Args, ",") + } + qopts.AppendQEMU( + "-device", fmt.Sprintf("%s,netdev=%s,mac=%s", opts.NIC, netdevID, opts.MAC), + "-netdev", fmt.Sprintf("user,id=%s,net=%s,dhcpstart=%s,ipv6=off%s", netdevID, ipnet, nthIP(ipnet, 8), extraUserArgs), + ) + return nil + } +} + +func inc(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func nthIP(nt *net.IPNet, n int) net.IP { + ip := make(net.IP, net.IPv4len) + copy(ip, nt.IP.To4()) + for i := 0; i < n; i++ { + inc(ip) + } + if !nt.Contains(ip) { + return nil + } + return ip +} diff --git a/vendor/github.com/hugelgupf/vmtest/shelltest.go b/vendor/github.com/hugelgupf/vmtest/shelltest.go index 34b890024a..2bbbc1f468 100644 --- a/vendor/github.com/hugelgupf/vmtest/shelltest.go +++ b/vendor/github.com/hugelgupf/vmtest/shelltest.go @@ -15,22 +15,19 @@ import ( "github.com/u-root/u-root/pkg/uroot" ) -// RunCmdsInVM starts a VM and runs each command provided in testCmds in a -// shell in the VM. If any command fails, the test fails. +// RunCmdsInVM starts a VM and runs the given script using gosh in the guest. +// If any command fails, the test fails. // // The VM can be configured with o. The kernel can be provided via o or // VMTEST_KERNEL env var. Guest architecture can be set with VMTEST_ARCH. // -// Underneath, this generates an Elvish script with these commands. The script -// is shared with the VM and run from a special init. -// // - TODO: timeouts for individual individual commands. // - TODO: It should check their exit status. Hahaha. -func RunCmdsInVM(t *testing.T, testCmds []string, o ...Opt) { - vm := StartVMAndRunCmds(t, testCmds, o...) +func RunCmdsInVM(t testing.TB, script string, o ...Opt) { + vm := StartVMAndRunCmds(t, script, o...) if _, err := vm.Console.ExpectString("TESTS PASSED MARKER"); err != nil { - t.Errorf("Waiting for 'TESTS PASSED MARKER' signal: %v", err) + t.Errorf("Waiting for 'TESTS PASSED MARKER' failed -- script likely failed: %v", err) } if err := vm.Wait(); err != nil { @@ -38,22 +35,19 @@ func RunCmdsInVM(t *testing.T, testCmds []string, o ...Opt) { } } -// StartVMAndRunCmds starts a VM and runs each command provided in testCmds in -// a shell in the VM. If the commands return, the VM will be shutdown. +// StartVMAndRunCmds starts a VM and runs the script using gosh in the guest. +// If the commands return, the VM will be shutdown. // // The VM can be configured with o. -// -// Underneath, this generates an Elvish script with these commands. The script -// is shared with the VM and run from a special init. -func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { +func StartVMAndRunCmds(t testing.TB, script string, o ...Opt) *qemu.VM { SkipWithoutQEMU(t) sharedDir := testtmp.TempDir(t) - // Generate Elvish shell script of test commands in o.SharedDir. - if len(testCmds) > 0 { - testFile := filepath.Join(sharedDir, "test.elv") - if err := os.WriteFile(testFile, []byte(strings.Join(testCmds, "\n")), 0o777); err != nil { + // Generate gosh shell script of test commands in o.SharedDir. + if len(script) > 0 { + testFile := filepath.Join(sharedDir, "test.sh") + if err := os.WriteFile(testFile, []byte(strings.Join([]string{"set -ex", script}, "\n")), 0o777); err != nil { t.Fatal(err) } } @@ -61,7 +55,7 @@ func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { initramfs := uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/init", - "github.com/u-root/u-root/cmds/core/elvish", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/hugelgupf/vmtest/vminit/shelluinit", ), InitCmd: "init", @@ -72,5 +66,6 @@ func StartVMAndRunCmds(t *testing.T, testCmds []string, o ...Opt) *qemu.VM { WithQEMUFn(qemu.P9Directory(sharedDir, "shelltest")), WithMergedInitramfs(initramfs), CollectKernelCoverage(), + ShareGOCOVERDIR(), }, o...)...) } diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go index 8057fb55f2..b86164235f 100644 --- a/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go +++ b/vendor/github.com/hugelgupf/vmtest/vminit/gouinit/main_linux.go @@ -106,16 +106,8 @@ func run(testEvents *guest.Emitter[testevent.ErrorEvent]) error { defer cleanup() defer guest.CollectKernelCoverage() - var envv []string - if tag := os.Getenv("VMTEST_GOCOVERDIR"); tag != "" { - mp, err := guest.Mount9PDir("/gocov", tag) - if err != nil { - return err - } - defer func() { _ = mp.Unmount(0) }() - - envv = append(envv, "GOCOVERDIR=/gocov") - } + covCleanup := guest.GOCOVERDIR() + defer covCleanup() goTestEvents, err := guest.EventChannel[json2test.TestEvent]("/gotestdata/results.json") if err != nil { @@ -142,7 +134,6 @@ func run(testEvents *guest.Emitter[testevent.ErrorEvent]) error { cmd := exec.CommandContext(ctx, path, args...) cmd.Stdin, cmd.Stderr = os.Stdin, os.Stderr - cmd.Env = append(os.Environ(), envv...) // Write to stdout for humans, write to w for the JSON converter. // diff --git a/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go b/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go index d6be651c3b..9709080aa0 100644 --- a/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go +++ b/vendor/github.com/hugelgupf/vmtest/vminit/shelluinit/main_linux.go @@ -24,22 +24,25 @@ func runTest() error { defer cleanup() defer guest.CollectKernelCoverage() + covCleanup := guest.GOCOVERDIR() + defer covCleanup() + mp, err := guest.Mount9PDir("/shelltestdata", "shelltest") if err != nil { return err } defer func() { _ = mp.Unmount(0) }() - // Run the test script test.elv - test := "/shelltestdata/test.elv" + // Run the test script test.sh + test := "/shelltestdata/test.sh" if _, err := os.Stat(test); os.IsNotExist(err) { return errors.New("could not find any test script to run") } - cmd := exec.Command("elvish", test) + cmd := exec.Command("gosh", test) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("test.elv ran unsuccessfully: %v", err) + return fmt.Errorf("test.sh ran unsuccessfully: %v", err) } return nil } diff --git a/vendor/github.com/hugelgupf/vmtest/vmtest.go b/vendor/github.com/hugelgupf/vmtest/vmtest.go index 4460c3a07a..45a486919c 100644 --- a/vendor/github.com/hugelgupf/vmtest/vmtest.go +++ b/vendor/github.com/hugelgupf/vmtest/vmtest.go @@ -17,6 +17,7 @@ import ( "github.com/hugelgupf/vmtest/qemu" "github.com/hugelgupf/vmtest/testtmp" "github.com/hugelgupf/vmtest/uqemu" + "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/ulog/ulogtest" "github.com/u-root/u-root/pkg/uroot" "golang.org/x/exp/maps" @@ -118,6 +119,9 @@ func (v *VMOptions) MergeInitramfs(buildOpts uroot.Opts) error { if buildOpts.DefaultShell != "" { v.Initramfs.DefaultShell = buildOpts.DefaultShell } + if buildOpts.BuildOpts != nil { + v.Initramfs.BuildOpts = buildOpts.BuildOpts + } return nil } @@ -160,6 +164,9 @@ func WithMergedInitramfs(o uroot.Opts) Opt { } // WithBusyboxCommands merges more busybox commands into the initramfs build options. +// +// Note that busybox rewrites commands, so if attempting to get integration +// test coverage of commands, use WithBinaryCommands. func WithBusyboxCommands(cmds ...string) Opt { return func(_ testing.TB, v *VMOptions) error { return v.MergeInitramfs(uroot.Opts{ @@ -168,6 +175,15 @@ func WithBusyboxCommands(cmds ...string) Opt { } } +// WithBinaryCommands merges more binary commands into the initramfs build options. +func WithBinaryCommands(cmds ...string) Opt { + return func(_ testing.TB, v *VMOptions) error { + return v.MergeInitramfs(uroot.Opts{ + Commands: uroot.BinaryCmds(cmds...), + }) + } +} + // WithInitramfsFiles merges more extra files into the initramfs build options. // Syntax is like u-root's ExtraFiles. func WithInitramfsFiles(files ...string) Opt { @@ -178,6 +194,15 @@ func WithInitramfsFiles(files ...string) Opt { } } +// WithGoBuildOpts replaces Go build options for the initramfs. +func WithGoBuildOpts(g *golang.BuildOpts) Opt { + return func(_ testing.TB, v *VMOptions) error { + return v.MergeInitramfs(uroot.Opts{ + BuildOpts: g, + }) + } +} + // WithSharedDir shares a directory with the QEMU VM using 9P using the // tag "tmpdir". // diff --git a/vendor/modules.txt b/vendor/modules.txt index 86097a45b4..fd2bd48d8d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -127,7 +127,7 @@ github.com/hashicorp/errwrap # github.com/hashicorp/go-multierror v1.1.1 ## explicit; go 1.13 github.com/hashicorp/go-multierror -# github.com/hugelgupf/vmtest v0.0.0-20240124044647-150d99bfa80e +# github.com/hugelgupf/vmtest v0.0.0-20240127073930-89f92b39a1fa ## explicit; go 1.21 github.com/hugelgupf/vmtest github.com/hugelgupf/vmtest/guest @@ -135,7 +135,7 @@ github.com/hugelgupf/vmtest/internal/eventchannel github.com/hugelgupf/vmtest/internal/json2test github.com/hugelgupf/vmtest/internal/testevent github.com/hugelgupf/vmtest/qemu -github.com/hugelgupf/vmtest/qemu/network +github.com/hugelgupf/vmtest/qemu/qnetwork github.com/hugelgupf/vmtest/testtmp github.com/hugelgupf/vmtest/uqemu github.com/hugelgupf/vmtest/vminit/gouinit From 4000a65e684b69a72da1f6aa02a90fbcec82515d Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 06:30:44 +0000 Subject: [PATCH 045/109] Convert shell tests to new script format Signed-off-by: Chris Koch --- integration/generic-tests/basic_test.go | 14 +---- integration/generic-tests/dhclient_test.go | 68 +++++++++----------- integration/generic-tests/esxi_boot_test.go | 12 ++-- integration/generic-tests/io_test.go | 22 +++---- integration/generic-tests/kexec_test.go | 70 +++++++++++---------- integration/generic-tests/multiboot_test.go | 6 +- integration/generic-tests/pxeboot_test.go | 38 ++++------- integration/generic-tests/tcz_test.go | 48 +++++++------- integration/generic-tests/uefiboot_test.go | 9 +-- 9 files changed, 118 insertions(+), 169 deletions(-) diff --git a/integration/generic-tests/basic_test.go b/integration/generic-tests/basic_test.go index 26ca1b4fc5..27a348741e 100644 --- a/integration/generic-tests/basic_test.go +++ b/integration/generic-tests/basic_test.go @@ -13,21 +13,11 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/u-root/u-root/pkg/uroot" ) func TestScript(t *testing.T) { - testCmds := []string{ - "echo HELLO WORLD", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/shutdown", - )}), - vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), - ) - + script := "echo HELLO WORLD" + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second))) if _, err := vm.Console.ExpectString("HELLO WORLD"); err != nil { t.Errorf("Want HELLO WORLD: %v", err) } diff --git a/integration/generic-tests/dhclient_test.go b/integration/generic-tests/dhclient_test.go index 7130c36c57..592f1c5763 100644 --- a/integration/generic-tests/dhclient_test.go +++ b/integration/generic-tests/dhclient_test.go @@ -18,7 +18,7 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/testutil" "github.com/u-root/u-root/pkg/uroot" ) @@ -43,30 +43,24 @@ func TestDhclientQEMU4(t *testing.T) { s := &http.Server{} port := ln.Addr().(*net.TCPAddr).Port - testCmds := []string{ - "dhclient -ipv6=false -v", - "ip a", - // Download a file to make sure dhclient configures kernel networking correctly. - fmt.Sprintf("wget http://192.168.0.2:%d/foobar", port), - "cat ./foobar", - "sleep 5", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := fmt.Sprintf(` + dhclient -ipv6=false -v + ip a + wget http://192.168.0.2:%d/foobar + cat ./foobar + sleep 5 + `, port) + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/ip", "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/core/shutdown", "github.com/u-root/u-root/cmds/core/wget", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), - network.IPv4HostNetwork(&net.IPNet{ - IP: net.IP{192, 168, 0, 0}, - Mask: net.CIDRMask(24, 32), - }), + qnetwork.IPv4HostNetwork("192.168.0.0/24"), qemu.ServeHTTP(s, ln), ), ) @@ -84,19 +78,17 @@ func TestDhclientQEMU4(t *testing.T) { } func TestDhclientTimesOut(t *testing.T) { - testCmds := []string{ - "dhclient -v -retry 2 -timeout 10", - "echo \"DHCP timed out\"", - "sleep 5", - "shutdown -h", - } - - net := network.NewInterVM() - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + dhclient -v -retry 2 -timeout 10 + echo "DHCP timed out" + sleep 5 + ` + + net := qnetwork.NewInterVM() + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/core/shutdown", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), @@ -121,17 +113,17 @@ func TestDhclientTimesOut(t *testing.T) { } func TestDhclient6(t *testing.T) { - serverCmds := []string{ - "ip link set eth0 up", - "pxeserver -6 -your-ip6=fec0::3 -4=false", - } + serverScript := ` + ip link set eth0 up + pxeserver -6 -your-ip6=fec0::3 -4=false + ` // QEMU doesn't support DHCPv6 for getting IP configuration, so we have // to supply our own server. // // We don't currently have a radvd server we can use, so we also cannot // try to download a file using the DHCP configuration. - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestDhclient6_Server"), vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/ip", @@ -143,17 +135,15 @@ func TestDhclient6(t *testing.T) { ), ) - testCmds := []string{ - "dhclient -ipv4=false -vv", - "ip a", - "shutdown -h", - } - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientScript := ` + dhclient -ipv4=false -vv + ip a + ` + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestDhclient6_Client"), vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/ip", "github.com/u-root/u-root/cmds/core/dhclient", - "github.com/u-root/u-root/cmds/core/shutdown", )}), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), diff --git a/integration/generic-tests/esxi_boot_test.go b/integration/generic-tests/esxi_boot_test.go index e165c73336..5416b7ffb0 100644 --- a/integration/generic-tests/esxi_boot_test.go +++ b/integration/generic-tests/esxi_boot_test.go @@ -25,10 +25,8 @@ func TestESXi(t *testing.T) { t.Skip("ESXi disk image is not present. Please set UROOT_ESXI_IMAGE= to an installed ESXi disk image.") } - testCmds := []string{ - `esxiboot -d="/dev/sda" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"`, - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := `esxiboot -d="/dev/sda" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/exp/esxiboot", )}), @@ -79,10 +77,8 @@ func TestESXiNVMe(t *testing.T) { t.Skip("ESXi disk image is not present. Please set UROOT_ESXI_IMAGE= to an installed ESXi disk image.") } - testCmds := []string{ - `esxiboot -d="/dev/nvme0n1" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"`, - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := `esxiboot -d="/dev/nvme0n1" --append="vmkBootVerbose=TRUE vmbLog=TRUE debugLogToSerial=1 logPort=com1"` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/exp/esxiboot", )}), diff --git a/integration/generic-tests/io_test.go b/integration/generic-tests/io_test.go index 9b2517afae..f0a6a2fb79 100644 --- a/integration/generic-tests/io_test.go +++ b/integration/generic-tests/io_test.go @@ -9,27 +9,25 @@ package integration import ( "fmt" + "strings" "testing" "time" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/u-root/u-root/pkg/uroot" ) // TestIO tests the string "UART TEST" is written to the serial port on 0x3f8. func TestIO(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - testCmds := []string{} + var testCmds []string for _, b := range []byte("UART TEST\r\n") { testCmds = append(testCmds, fmt.Sprintf("io outb 0x3f8 %d", b)) } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/io", - )}), + vm := vmtest.StartVMAndRunCmds(t, strings.Join(testCmds, "\n"), + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/core/io"), vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), ) @@ -45,15 +43,9 @@ func TestIO(t *testing.T) { func TestCMOS(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - testCmds := []string{ - "io cw 14 1 cr 14 cw 14 0 cr 14", - "shutdown -h", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/io", - "github.com/u-root/u-root/cmds/core/shutdown", - )}), + script := "io cw 14 1 cr 14 cw 14 0 cr 14" + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/core/io"), vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), ) diff --git a/integration/generic-tests/kexec_test.go b/integration/generic-tests/kexec_test.go index a0ee05fcdd..1e941767f3 100644 --- a/integration/generic-tests/kexec_test.go +++ b/integration/generic-tests/kexec_test.go @@ -27,14 +27,14 @@ import ( func TestMountKexec(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "kexec -i /testdata/initramfs.cpio -c $CMDLINE' KEXEC=Y' /kernel", - } - - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + kexec -i /testdata/initramfs.cpio -c "${CMDLINE} KEXEC=Y" /kernel + ` + + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", @@ -73,13 +73,13 @@ func TestMountKexecLoad(t *testing.T) { t.Skipf("no gzip found, skip it as it won't be able to de-compress kernel") } - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "kexec -d -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE' KEXEC=Y' /kernel", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + kexec -d -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + ` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", @@ -118,11 +118,12 @@ func TestMountKexecLoadOnly(t *testing.T) { t.Skipf("no gzip found, skip it as it won't be able to de-compress kernel") } - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "echo kexecloadresult ?(kexec -d -l -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE /kernel)", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + CMDLINE=$(cat /proc/cmdline) + kexec -d -l -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE}" /kernel + echo kexecloadresult $? + ` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", @@ -142,7 +143,7 @@ func TestMountKexecLoadOnly(t *testing.T) { vmtest.WithSharedDir(testtmp.TempDir(t)), ) - if _, err := vm.Console.ExpectString("kexecloadresult $ok"); err != nil { + if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { t.Error(err) } if err := vm.Wait(); err != nil { @@ -154,14 +155,14 @@ func TestMountKexecLoadOnly(t *testing.T) { func TestMountKexecLoadCustomDTB(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchArm64) - testCmds := []string{ - "var CMDLINE = (cat /proc/cmdline)", - "var SUFFIX = $CMDLINE[-7..]", - "echo SAW $SUFFIX", - "cp /sys/firmware/fdt /tmp/userfdt", - "kexec -d --dtb /tmp/userfdt -i /testdata/initramfs.cpio --loadsyscall -c $CMDLINE' KEXEC=Y' /kernel", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + CMDLINE=$(cat /proc/cmdline) + SUFFIX=${CMDLINE:(-7)} + echo SAW $SUFFIX + cp /sys/firmware/fdt /tmp/userfdt + kexec -d --dtb /tmp/userfdt -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + ` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", @@ -200,10 +201,11 @@ func TestKexecLinuxImageCfgFile(t *testing.T) { t.Fatalf("Failed to setup test cfg file: %v", err) } - testCmds := []string{ - "echo kexecloadresult ?(kexec -d -l -I /linux_image_cfg.json)", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, + script := ` + kexec -d -l -I /linux_image_cfg.json + echo kexecloadresult $? + ` + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/cat", @@ -224,7 +226,7 @@ func TestKexecLinuxImageCfgFile(t *testing.T) { vmtest.WithSharedDir(testtmp.TempDir(t)), ) - if _, err := vm.Console.ExpectString("kexecloadresult $ok"); err != nil { + if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { t.Error(err) } if err := vm.Wait(); err != nil { diff --git a/integration/generic-tests/multiboot_test.go b/integration/generic-tests/multiboot_test.go index b55d47b6a6..d33663831e 100644 --- a/integration/generic-tests/multiboot_test.go +++ b/integration/generic-tests/multiboot_test.go @@ -40,11 +40,9 @@ func testMultiboot(t *testing.T, kernel string) { } dir := t.TempDir() - testCmds := []string{ - `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb"`, - } + script := `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb"` var b bytes.Buffer - vm := vmtest.StartVMAndRunCmds(t, testCmds, + vm := vmtest.StartVMAndRunCmds(t, script, vmtest.WithSharedDir(dir), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( diff --git a/integration/generic-tests/pxeboot_test.go b/integration/generic-tests/pxeboot_test.go index 5cadc61206..871b6999b7 100644 --- a/integration/generic-tests/pxeboot_test.go +++ b/integration/generic-tests/pxeboot_test.go @@ -13,22 +13,22 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/testutil" "github.com/u-root/u-root/pkg/uroot" ) // TestPxeboot runs a server and client to test pxebooting a node. func TestPxeboot4(t *testing.T) { - serverCmds := []string{ - "ip addr add 192.168.0.1/24 dev eth0", - "ip link set eth0 up", - "ip route add 0.0.0.0/0 dev eth0", - "ls -l /pxeroot", - "pxeserver -tftp-dir=/pxeroot", - } - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + serverScript := ` + ip addr add 192.168.0.1/24 dev eth0 + ip link set eth0 up + ip route add 0.0.0.0/0 dev eth0 + ls -l /pxeroot + pxeserver -tftp-dir=/pxeroot + ` + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestPxeboot_Server"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( @@ -46,22 +46,10 @@ func TestPxeboot4(t *testing.T) { ), ) - testCmds := []string{ - "pxeboot --no-exec -v", - // Sleep so serial console output gets flushed. The expect library is racy. - "sleep 5", - "shutdown -h", - } - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientScript := "pxeboot --no-exec -v" + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestPxeboot_Client"), - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/ip", - "github.com/u-root/u-root/cmds/core/shutdown", - "github.com/u-root/u-root/cmds/core/sleep", - "github.com/u-root/u-root/cmds/boot/pxeboot", - ), - }), + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/boot/pxeboot"), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), net.NewVM(), diff --git a/integration/generic-tests/tcz_test.go b/integration/generic-tests/tcz_test.go index 14fea7bae2..2a6968c771 100644 --- a/integration/generic-tests/tcz_test.go +++ b/integration/generic-tests/tcz_test.go @@ -14,7 +14,7 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/hugelgupf/vmtest/qemu/network" + "github.com/hugelgupf/vmtest/qemu/qnetwork" "github.com/u-root/u-root/pkg/uroot" ) @@ -24,18 +24,18 @@ func TestTczclient(t *testing.T) { t.Skip("This test is flaky, and must be fixed") - serverCmds := []string{ - "ip addr add 192.168.0.1/24 dev eth0", - "ip link set eth0 up", - "ip route add 255.255.255.255/32 dev eth0", - "ip l", - "ip a", - "srvfiles -h 192.168.0.1 -d /", - "echo The Server Completes", - "shutdown -h", - } - net := network.NewInterVM() - serverVM := vmtest.StartVMAndRunCmds(t, serverCmds, + serverScript := ` + ip addr add 192.168.0.1/24 dev eth0 + ip link set eth0 up + ip route add 255.255.255.255/32 dev eth0 + ip l + ip a + srvfiles -h 192.168.0.1 -d / + echo The Server Completes + shutdown -h + ` + net := qnetwork.NewInterVM() + serverVM := vmtest.StartVMAndRunCmds(t, serverScript, vmtest.WithName("TestTczclient_Server"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( @@ -55,20 +55,18 @@ func TestTczclient(t *testing.T) { ), ) - testCmds := []string{ - "ip addr add 192.168.0.2/24 dev eth0", - "ip link set eth0 up", - //"ip route add 255.255.255.255/32 dev eth0", - "ip a", - "tcz -d -h 192.168.0.1 -p 8080 libXcomposite libXdamage libXinerama libxshmfence", - "tcz -d -h 192.168.0.1 -p 8080 libXdmcp", - "echo HI THERE", - "ls /TinyCorePackages/tcloop", - "shutdown -h", - } + clientScript := ` + ip addr add 192.168.0.2/24 dev eth0 + ip link set eth0 up + ip a + tcz -d -h 192.168.0.1 -p 8080 libXcomposite libXdamage libXinerama libxshmfence + tcz -d -h 192.168.0.1 -p 8080 libXdmcp + echo HI THERE + ls /TinyCorePackages/tcloop + ` var b wc - clientVM := vmtest.StartVMAndRunCmds(t, testCmds, + clientVM := vmtest.StartVMAndRunCmds(t, clientScript, vmtest.WithName("TestTczclient_Client"), vmtest.WithMergedInitramfs(uroot.Opts{ Commands: uroot.BusyBoxCmds( diff --git a/integration/generic-tests/uefiboot_test.go b/integration/generic-tests/uefiboot_test.go index 89b80fa8df..9557e10180 100644 --- a/integration/generic-tests/uefiboot_test.go +++ b/integration/generic-tests/uefiboot_test.go @@ -15,7 +15,6 @@ import ( "github.com/Netflix/go-expect" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" - "github.com/u-root/u-root/pkg/uroot" ) // TestUefiboot tests uefiboot commmands to boot to uefishell. @@ -33,13 +32,9 @@ func TestUefiBoot(t *testing.T) { t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", payload) } - testCmds := []string{ + vm := vmtest.StartVMAndRunCmds(t, "uefiboot /dev/sda", - } - vm := vmtest.StartVMAndRunCmds(t, testCmds, - vmtest.WithMergedInitramfs(uroot.Opts{Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/exp/uefiboot", - )}), + vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/exp/uefiboot"), vmtest.WithQEMUFn( qemu.WithVMTimeout(2*time.Minute), qemu.IDEBlockDevice(payload), From 8fcf249804eb6e161906e7530a892d4876d9b9db Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 07:04:11 +0000 Subject: [PATCH 046/109] Enable coverage collection on kexec(-like) commands Signed-off-by: Chris Koch --- cmds/exp/uefiboot/main.go | 7 +- integration/generic-tests/kexec_test.go | 142 ++++++++++++-------- integration/generic-tests/multiboot_test.go | 32 +++-- integration/generic-tests/uefiboot_test.go | 27 +++- 4 files changed, 134 insertions(+), 74 deletions(-) diff --git a/cmds/exp/uefiboot/main.go b/cmds/exp/uefiboot/main.go index 385bb31d5d..fbd38dc6fd 100644 --- a/cmds/exp/uefiboot/main.go +++ b/cmds/exp/uefiboot/main.go @@ -33,6 +33,7 @@ var ( serialWidth = flag.Uint("serial_width", 1, "Serial port reg width") serialHertz = flag.Uint("serial_hertz", 1843200, "Serial port input hertz") serialBaud = flag.Uint("serial_baud", 115200, "Serial port baud rate") + execute = flag.Bool("e", true, "If true, kexec. If not true, just load kernel.") ) var v = func(string, ...interface{}) {} @@ -61,7 +62,9 @@ func main() { log.Fatal(err) } - if err := boot.Execute(); err != nil { - log.Fatal(err) + if *execute { + if err := boot.Execute(); err != nil { + log.Fatal(err) + } } } diff --git a/integration/generic-tests/kexec_test.go b/integration/generic-tests/kexec_test.go index 1e941767f3..605e6efcf6 100644 --- a/integration/generic-tests/kexec_test.go +++ b/integration/generic-tests/kexec_test.go @@ -18,7 +18,7 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" "github.com/hugelgupf/vmtest/testtmp" - "github.com/u-root/u-root/pkg/uroot" + "github.com/u-root/gobusybox/src/pkg/golang" ) // TestMountKexec tests that kexec occurs correctly by checking the kernel cmdline. @@ -31,20 +31,24 @@ func TestMountKexec(t *testing.T) { CMDLINE=$(cat /proc/cmdline) SUFFIX=${CMDLINE:(-7)} echo SAW $SUFFIX - kexec -i /testdata/initramfs.cpio -c "${CMDLINE} KEXEC=Y" /kernel + kexec -l -i /testdata/initramfs.cpio -c "${CMDLINE} KEXEC=Y" /kernel + sync + kexec -e ` vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/shutdown", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - }, - }), + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -52,6 +56,10 @@ func TestMountKexec(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -77,20 +85,24 @@ func TestMountKexecLoad(t *testing.T) { CMDLINE=$(cat /proc/cmdline) SUFFIX=${CMDLINE:(-7)} echo SAW $SUFFIX - kexec -d -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + kexec -l -d -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel + sync + kexec -e ` vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/shutdown", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - gzipP, - }, - }), + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + gzipP, + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -98,6 +110,10 @@ func TestMountKexecLoad(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -124,16 +140,18 @@ func TestMountKexecLoadOnly(t *testing.T) { echo kexecloadresult $? ` vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - gzipP, - }, - }), + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + gzipP, + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -141,6 +159,10 @@ func TestMountKexecLoadOnly(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { @@ -163,16 +185,16 @@ func TestMountKexecLoadCustomDTB(t *testing.T) { kexec -d --dtb /tmp/userfdt -i /testdata/initramfs.cpio --loadsyscall -c "${CMDLINE} KEXEC=Y" /kernel ` vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/cp", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - }, - }), + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/cp", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles(fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL"))), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -180,6 +202,10 @@ func TestMountKexecLoadCustomDTB(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("SAW KEXEC=Y"); err != nil { @@ -206,17 +232,19 @@ func TestKexecLinuxImageCfgFile(t *testing.T) { echo kexecloadresult $? ` vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/kexec", - ), - ExtraFiles: []string{ - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - fmt.Sprintf("%s:linux_image_cfg.json", cfgFile), - }, - }), + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/cat", + "github.com/u-root/u-root/cmds/core/echo", + ), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + ), + vmtest.WithInitramfsFiles( + fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), + fmt.Sprintf("%s:linux_image_cfg.json", cfgFile), + ), vmtest.WithQEMUFn( qemu.WithVMTimeout(time.Minute), qemu.ArbitraryArgs("-m", "8192"), @@ -224,6 +252,10 @@ func TestKexecLinuxImageCfgFile(t *testing.T) { // The initramfs will be placed in shared dir, so in the VM // it's available at /testdata/initramfs.cpio. vmtest.WithSharedDir(testtmp.TempDir(t)), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { diff --git a/integration/generic-tests/multiboot_test.go b/integration/generic-tests/multiboot_test.go index d33663831e..384de750e1 100644 --- a/integration/generic-tests/multiboot_test.go +++ b/integration/generic-tests/multiboot_test.go @@ -19,8 +19,8 @@ import ( "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" + "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/boot/multiboot" - "github.com/u-root/u-root/pkg/uroot" ) type nopCloser struct { @@ -39,24 +39,30 @@ func testMultiboot(t *testing.T, kernel string) { t.Error(err) } - dir := t.TempDir() - script := `kexec -l kernel -e -d --module="/kernel foo=bar" --module="/bbin/bb"` + script := ` + kexec -l kernel -d --module="/kernel foo=bar" --module="/bbin/bb" + sync + kexec -e + ` var b bytes.Buffer vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithSharedDir(dir), - vmtest.WithMergedInitramfs(uroot.Opts{ - Commands: uroot.BusyBoxCmds( - "github.com/u-root/u-root/cmds/core/kexec", - "github.com/u-root/u-root/cmds/core/tee", - ), - ExtraFiles: []string{ - src + ":kernel", - }, - }), + // Build kexec as a binary command to get accurate GOCOVERDIR + // integration coverage data (busybox rewrites command code). + vmtest.WithBinaryCommands( + "github.com/u-root/u-root/cmds/core/kexec", + "github.com/u-root/u-root/cmds/core/sync", + ), + vmtest.WithInitramfsFiles( + src+":kernel", + ), vmtest.WithQEMUFn( qemu.WithSerialOutput(nopCloser{&b}), qemu.WithVMTimeout(time.Minute), ), + // Build kexec (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) if _, err := vm.Console.ExpectString(`"status": "ok"`); err != nil { diff --git a/integration/generic-tests/uefiboot_test.go b/integration/generic-tests/uefiboot_test.go index 9557e10180..657ff43c43 100644 --- a/integration/generic-tests/uefiboot_test.go +++ b/integration/generic-tests/uefiboot_test.go @@ -15,10 +15,12 @@ import ( "github.com/Netflix/go-expect" "github.com/hugelgupf/vmtest" "github.com/hugelgupf/vmtest/qemu" + "github.com/u-root/gobusybox/src/pkg/golang" + "github.com/u-root/u-root/pkg/uroot" ) // TestUefiboot tests uefiboot commmands to boot to uefishell. -func TestUefiBoot(t *testing.T) { +func TestUEFIBoot(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) var payload string @@ -32,15 +34,32 @@ func TestUefiBoot(t *testing.T) { t.Skipf("UEFI payload image is not found: %s\n Usage: uefiboot ", payload) } - vm := vmtest.StartVMAndRunCmds(t, - "uefiboot /dev/sda", - vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/exp/uefiboot"), + // Separate loading and executing new kernel so GOCOVERDIR data is collected for uefiboot command. + script := ` + uefiboot -e=false /dev/sda + sync + kexec -e + ` + vm := vmtest.StartVMAndRunCmds(t, script, + vmtest.WithBusyboxCommands( + "github.com/u-root/u-root/cmds/core/kexec", + "github.com/u-root/u-root/cmds/core/sync", + ), + // Since busybox mode rewrites commands, build uefiboot + // straight up as a binary to get integration test coverage. + vmtest.WithMergedInitramfs(uroot.Opts{ + Commands: uroot.BinaryCmds("github.com/u-root/u-root/cmds/exp/uefiboot"), + }), vmtest.WithQEMUFn( qemu.WithVMTimeout(2*time.Minute), qemu.IDEBlockDevice(payload), qemu.ArbitraryArgs("-machine", "q35"), qemu.ArbitraryArgs("-m", "4096"), ), + // Build uefiboot (and all other initramfs commands) with coverage enabled. + vmtest.WithGoBuildOpts(&golang.BuildOpts{ + ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, + }), ) // Edk2 debug mode will print PROGRESS CODE. We will want to make sure From 96b204f316c7213642ae321da0eea3176a7ae97e Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 07:05:58 +0000 Subject: [PATCH 047/109] Convert GOCOVERDIR data into a format codecov understands Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8fe70c79e0..4b20018730 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,6 +63,9 @@ jobs: go test -v -covermode=atomic ${{ matrix.extra-arg }} \ -coverprofile=coverage.txt ./${{ matrix.pattern }} + - name: Convert GOCOVERDIR coverage data + run: go tool covdata textfmt -i=gocov -o vmintcoverage.txt + - uses: codecov/codecov-action@v4-beta env: CODECOV_TOKEN: '0ecf29bb-15fb-46f2-8c7d-0fb610d79f71' From 2dba26430bbee62e350efdd1005d9f89f818a455 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 07:20:53 +0000 Subject: [PATCH 048/109] Amend IO test to work with `set -x`. Output would be interleaved with commands from shell otherwise. Signed-off-by: Chris Koch --- integration/generic-tests/io_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/generic-tests/io_test.go b/integration/generic-tests/io_test.go index f0a6a2fb79..57e45bdc87 100644 --- a/integration/generic-tests/io_test.go +++ b/integration/generic-tests/io_test.go @@ -21,12 +21,12 @@ import ( func TestIO(t *testing.T) { vmtest.SkipIfNotArch(t, qemu.ArchAMD64) - var testCmds []string + testCmd := []string{"io"} for _, b := range []byte("UART TEST\r\n") { - testCmds = append(testCmds, fmt.Sprintf("io outb 0x3f8 %d", b)) + testCmd = append(testCmd, fmt.Sprintf("outb 0x3f8 %d", b)) } - vm := vmtest.StartVMAndRunCmds(t, strings.Join(testCmds, "\n"), + vm := vmtest.StartVMAndRunCmds(t, strings.Join(testCmd, " "), vmtest.WithBusyboxCommands("github.com/u-root/u-root/cmds/core/io"), vmtest.WithQEMUFn(qemu.WithVMTimeout(30*time.Second)), ) From 9f6837962f8f14c29fd3357b00cd74ecfc0c0968 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 07:41:37 +0000 Subject: [PATCH 049/109] Use new VMTEST_GOCOVERDIR to collect integration coverage Signed-off-by: Chris Koch --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b20018730..7770f59623 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: - name: Test run: | mkdir gocov - GOCOVERDIR=$(pwd)/gocov \ + VMTEST_GOCOVERDIR=$(pwd)/gocov \ VMTEST_GO_PROFILE=vmcoverage.txt runvmtest -- \ go test -v -covermode=atomic ${{ matrix.extra-arg }} \ -coverprofile=coverage.txt ./${{ matrix.pattern }} From 34b2bd094c076b3325873b2ea4cce658827b5a86 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 23:29:28 +0000 Subject: [PATCH 050/109] Build edk2 univeral payload Signed-off-by: Chris Koch --- .circleci/images/uefipayload-amd64/Dockerfile | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.circleci/images/uefipayload-amd64/Dockerfile b/.circleci/images/uefipayload-amd64/Dockerfile index 60fbbf38d5..d6caed8321 100644 --- a/.circleci/images/uefipayload-amd64/Dockerfile +++ b/.circleci/images/uefipayload-amd64/Dockerfile @@ -15,20 +15,28 @@ RUN git clone --branch uefipayload-2024 --recursive https://github.com/linuxboot RUN apt-get install -y --no-install-recommends \ make \ python3 \ + python3-dev \ + python3-pip \ gcc \ g++ \ - uuid-dev \ - nasm \ - bash \ - iasl && \ - rm -rf /var/lib/apt/lists/* + uuid-dev \ + nasm \ + bash \ + libfdt-dev \ + swig \ + iasl; + +RUN pip3 install --break-system-packages pefile pylibfdt SHELL ["/bin/bash", "-c"] RUN cd uefipayload; \ source ./edksetup.sh; \ make -C BaseTools; \ build -a X64 -p UefiPayloadPkg/UefiPayloadPkg.dsc -b DEBUG \ - -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; + -t GCC5 -D BOOTLOADER=LINUXBOOT -D DISABLE_MMX_SSE=true; \ + cp /uefipayload/Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd /UEFIPAYLOAD.fd; \ + python3 UefiPayloadPkg/UniversalPayloadBuild.py -t GCC5 --Fit; FROM scratch -COPY --from=base /uefipayload/Build/UefiPayloadPkgX64/DEBUG_GCC5/FV/UEFIPAYLOAD.fd /UEFIPAYLOAD.fd +COPY --from=base /UEFIPAYLOAD.fd /UEFIPAYLOAD.fd +COPY --from=base /uefipayload/Build/UefiPayloadPkgX64/UniversalPayload.fit /UEFIPayload.fit From 3853fd7cc4bdc56e6b2758081488ea56634d5123 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Sun, 28 Jan 2024 09:04:50 +0200 Subject: [PATCH 051/109] pkg/strace: fix linux/arm64 test there is no unix.SYS_OPEN for arm64 Signed-off-by: Siarhiej Siemianczuk --- pkg/strace/syscalls_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/strace/syscalls_test.go b/pkg/strace/syscalls_test.go index b9381fe9c3..95cbbfae52 100644 --- a/pkg/strace/syscalls_test.go +++ b/pkg/strace/syscalls_test.go @@ -20,8 +20,8 @@ func TestByName(t *testing.T) { val uintptr ret error }{ - {name: "open", val: unix.SYS_OPEN, ret: nil}, - {name: "Xopen", val: unix.SYS_OPEN, ret: fmt.Errorf("Xopen:not found")}, + {name: "read", val: unix.SYS_READ, ret: nil}, + {name: "Xread", val: unix.SYS_READ, ret: fmt.Errorf("Xread:not found")}, } { n, err := ByName(tt.name) if err != nil && tt.ret == nil { @@ -42,8 +42,8 @@ func TestByNum(t *testing.T) { val uintptr ret error }{ - {name: "open", val: unix.SYS_OPEN, ret: nil}, - {name: "bogus", val: 10000000, ret: fmt.Errorf("Xopen:not found")}, + {name: "read", val: unix.SYS_READ, ret: nil}, + {name: "bogus", val: 10000000, ret: fmt.Errorf("Xread:not found")}, } { n, err := ByNumber(tt.val) if err != nil && tt.ret == nil { From d464320b0fcb14809c1441e184cbf58676006e5b Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Tue, 16 Jan 2024 15:53:05 -0800 Subject: [PATCH 052/109] flash command: update write code This write code worked last week, and a logic analyzer shows the write signals being sent, but it's not working today. Signed-off-by: Ronald G. Minnich --- cmds/fwtools/flash/flash_linux.go | 18 ++++++++---------- cmds/fwtools/flash/spi_linux.go | 3 ++- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cmds/fwtools/flash/flash_linux.go b/cmds/fwtools/flash/flash_linux.go index 1c23671f3b..437c027f12 100644 --- a/cmds/fwtools/flash/flash_linux.go +++ b/cmds/fwtools/flash/flash_linux.go @@ -135,9 +135,9 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr }() // Create a buffer to hold the contents of the image. - buf := make([]byte, programmer.Size()) if *r != "" { + buf := make([]byte, programmer.Size()) f, err := os.Create(*r) if err != nil { return err @@ -155,21 +155,19 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr return err } } else if *w != "" { - f, err := os.Open(*w) + buf, err := os.ReadFile(*w) if err != nil { return err } - defer f.Close() - if _, err := io.ReadFull(f, buf); err != nil { - return err + amt, err := programmer.WriteAt(buf, 0) + if err != nil { + return fmt.Errorf("Writing %d bytes to dev %v:%w", len(buf), programmer, err) } - if leftover, err := io.Copy(io.Discard, f); err != nil { - return err - } else if leftover != 0 { - return fmt.Errorf("flash size (%#x) unequal to file size (%#x)", len(buf), int64(len(buf))+leftover) + if amt != len(buf) { + return fmt.Errorf("Only flashed %d of %d bytes", amt, len(buf)) } - return errors.New("write not yet supported") + return nil } return nil diff --git a/cmds/fwtools/flash/spi_linux.go b/cmds/fwtools/flash/spi_linux.go index 50442f7785..3ab5935540 100644 --- a/cmds/fwtools/flash/spi_linux.go +++ b/cmds/fwtools/flash/spi_linux.go @@ -6,6 +6,7 @@ package main import ( "fmt" + "log" "math" "strconv" @@ -30,7 +31,7 @@ func init() { } delete(params, "dev") - spi, err := spidev.Open(dev) + spi, err := spidev.Open(dev, spidev.WithLogger(log.Printf)) if err != nil { return nil, err } From 87227585e8bd9d955e2a86bad398fca6bb65b16f Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Tue, 16 Jan 2024 15:56:08 -0800 Subject: [PATCH 053/109] pkg/flash/flash_linux: ensure CS# goes high CSChange behaves in ways that are not obvious. Or it's a bug in spidev, not sure. But the only way to CE# to go high is to NOT assert CSChange on the last WriteDisable command. Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index af11e7c8f4..334139d493 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -152,7 +152,13 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { {Tx: append(op.PageProgram.Bytes(), f.prepareAddress(off)...)}, // Send the data. {Tx: p, CSChange: true}, - {Tx: op.WriteDisable.Bytes(), CSChange: true}, + // The meaning of CSChange is ... odd. + // IF CSChange is set true here, then CE# never goes + // high. If CSChange is left unchanged, + // CE# is properly deasserted from the data write above, + // asserted for this command, and deasserted + // at the end. + {Tx: op.WriteDisable.Bytes()}, }); err != nil { return 0, err } From d59a753415717814d226e7d49afdb703ba7030a3 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Tue, 16 Jan 2024 22:21:54 -0800 Subject: [PATCH 054/109] flash_linux: add 10 microsecond delay before writedis All the signals at the part look fine, but: writes no longer work. Hard to see what we could have damaged in rework such that spi ID, and read work correctly, but that's what's going on. Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 334139d493..172bed2aa3 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -10,6 +10,7 @@ import ( "encoding/binary" "fmt" "io" + "time" "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" @@ -151,7 +152,12 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { // Send the address. {Tx: append(op.PageProgram.Bytes(), f.prepareAddress(off)...)}, // Send the data. - {Tx: p, CSChange: true}, + {Tx: p}, + }); err != nil { + return 0, err + } + time.Sleep(10 * time.Microsecond) + if err := f.spi.Transfer([]spidev.Transfer{ // The meaning of CSChange is ... odd. // IF CSChange is set true here, then CE# never goes // high. If CSChange is left unchanged, From ac1a8a7229778f4236f1fe147f8ce5e912ee1cfe Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Wed, 17 Jan 2024 15:47:18 -0800 Subject: [PATCH 055/109] flash_linux.go: send write as one message, not two Code was sending the PageProgram + address, then data in a separate write. In an attempt to fix writing problem, group the entire write in one message. Write still does not work. Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 172bed2aa3..279fcc9008 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -150,9 +150,7 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { {Tx: op.PRDRES.Bytes(), CSChange: true}, {Tx: op.WriteEnable.Bytes(), CSChange: true}, // Send the address. - {Tx: append(op.PageProgram.Bytes(), f.prepareAddress(off)...)}, - // Send the data. - {Tx: p}, + {Tx: append(append(op.PageProgram.Bytes(), f.prepareAddress(off)...), p...)}, }); err != nil { return 0, err } From 7504eb79c7eba0e9200b43378a21bf23503a5c9b Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sun, 28 Jan 2024 15:17:54 -0800 Subject: [PATCH 056/109] pkg/flash: use DelayUSecs to delay WriteDisable Once the byte is written, there is a delay needed. Use DelayUsecs in the Transfer struct, not time.Sleep(). This is far more efficient. Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 279fcc9008..83def522bc 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -10,7 +10,6 @@ import ( "encoding/binary" "fmt" "io" - "time" "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" @@ -151,18 +150,13 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { {Tx: op.WriteEnable.Bytes(), CSChange: true}, // Send the address. {Tx: append(append(op.PageProgram.Bytes(), f.prepareAddress(off)...), p...)}, - }); err != nil { - return 0, err - } - time.Sleep(10 * time.Microsecond) - if err := f.spi.Transfer([]spidev.Transfer{ // The meaning of CSChange is ... odd. // IF CSChange is set true here, then CE# never goes // high. If CSChange is left unchanged, // CE# is properly deasserted from the data write above, // asserted for this command, and deasserted // at the end. - {Tx: op.WriteDisable.Bytes()}, + {Tx: op.WriteDisable.Bytes(), DelayUSecs: 10}, }); err != nil { return 0, err } From c27e9c3a39042e6e8d8ab70af237ee415a8a767e Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Sun, 28 Jan 2024 22:09:18 -0800 Subject: [PATCH 057/109] flash_linux.go: remove cap in error string Signed-off-by: Ronald G. Minnich --- cmds/fwtools/flash/flash_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/fwtools/flash/flash_linux.go b/cmds/fwtools/flash/flash_linux.go index 437c027f12..e51321a91e 100644 --- a/cmds/fwtools/flash/flash_linux.go +++ b/cmds/fwtools/flash/flash_linux.go @@ -161,10 +161,10 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr } amt, err := programmer.WriteAt(buf, 0) if err != nil { - return fmt.Errorf("Writing %d bytes to dev %v:%w", len(buf), programmer, err) + return fmt.Errorf("writing %d bytes to dev %v:%w", len(buf), programmer, err) } if amt != len(buf) { - return fmt.Errorf("Only flashed %d of %d bytes", amt, len(buf)) + return fmt.Errorf("only flashed %d of %d bytes", amt, len(buf)) } return nil From a89938c6aa43a2f92fa965217117c9dc0eb15e52 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 18:45:00 +0000 Subject: [PATCH 058/109] Split memory map routines into their own file Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 311 ---------------------- pkg/boot/kexec/memory_linux_test.go | 328 ----------------------- pkg/boot/kexec/memory_map_linux.go | 309 +++++++++++++++++++++ pkg/boot/kexec/memory_map_linux_test.go | 339 ++++++++++++++++++++++++ pkg/boot/linux/load_linux_amd64.go | 7 +- pkg/boot/linux/load_linux_arm64.go | 7 +- pkg/boot/multiboot/multiboot.go | 4 +- 7 files changed, 661 insertions(+), 644 deletions(-) create mode 100644 pkg/boot/kexec/memory_map_linux.go create mode 100644 pkg/boot/kexec/memory_map_linux_test.go diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 25a2b57e26..40035a885b 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -9,18 +9,12 @@ import ( "debug/elf" "fmt" "io" - "log" "os" - "path" - "path/filepath" "reflect" "sort" - "strconv" - "strings" "unsafe" "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/dt" ) var pageMask = uint(os.Getpagesize() - 1) @@ -516,179 +510,6 @@ func (m *Memory) LoadElfSegments(r io.ReaderAt) (Object, error) { return f, nil } -// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. -func (m *Memory) ParseMemoryMap() error { - p, err := ParseMemoryMap() - if err != nil { - return err - } - m.Phys = p - return nil -} - -// ParseMemoryMapFromFDT reads firmware provided memory map from an FDT. -func (m *Memory) ParseMemoryMapFromFDT(fdt *dt.FDT) error { - var phys MemoryMap - addMemory := func(n *dt.Node) error { - p, found := n.LookProperty("device_type") - if !found { - return nil - } - t, err := p.AsString() - if err != nil || t != "memory" { - return nil - } - p, found = n.LookProperty("reg") - if found { - r, err := p.AsRegion() - if err != nil { - return err - } - phys = append(phys, TypedRange{ - Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, - Type: RangeRAM, - }) - } - return nil - } - err := fdt.RootNode.Walk(addMemory) - if err != nil { - return err - } - - reserveMemory := func(n *dt.Node) error { - p, found := n.LookProperty("reg") - if found { - r, err := p.AsRegion() - if err != nil { - return err - } - - phys.Insert(TypedRange{ - Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, - Type: RangeReserved, - }) - } - return nil - } - resv, found := fdt.NodeByName("reserved-memory") - if found { - err := resv.Walk(reserveMemory) - if err != nil { - return err - } - } - - for _, r := range fdt.ReserveEntries { - phys.Insert(TypedRange{ - Range: Range{Start: uintptr(r.Address), Size: uint(r.Size)}, - Type: RangeReserved, - }) - } - - for _, r := range phys { - log.Printf("memmap: 0x%016x 0x%016x %s", r.Start, r.Size, r.Type) - } - m.Phys = phys - return nil -} - -var memoryMapRoot = "/sys/firmware/memmap/" - -// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. -func ParseMemoryMap() (MemoryMap, error) { - return internalParseMemoryMap(memoryMapRoot) -} - -func internalParseMemoryMap(memoryMapDir string) (MemoryMap, error) { - type memRange struct { - // start and end addresses are inclusive - start, end uintptr - typ RangeType - } - - ranges := make(map[string]memRange) - walker := func(name string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - - const ( - // file names - start = "start" - end = "end" - typ = "type" - ) - - base := path.Base(name) - if base != start && base != end && base != typ { - return fmt.Errorf("unexpected file %q", name) - } - dir := path.Dir(name) - - b, err := os.ReadFile(name) - if err != nil { - return fmt.Errorf("error reading file %q: %v", name, err) - } - - data := strings.TrimSpace(string(b)) - r := ranges[dir] - if base == typ { - typ, ok := sysfsToRangeType[data] - if !ok { - log.Printf("Sysfs file %q contains unrecognized memory map type %q, defaulting to Reserved", name, data) - r.typ = RangeReserved - } else { - r.typ = typ - } - ranges[dir] = r - return nil - } - - v, err := strconv.ParseUint(data, 0, strconv.IntSize) - if err != nil { - return err - } - switch base { - case start: - r.start = uintptr(v) - case end: - r.end = uintptr(v) - } - ranges[dir] = r - return nil - } - - if err := filepath.Walk(memoryMapDir, walker); err != nil { - return nil, err - } - - var phys []TypedRange - for _, r := range ranges { - // Range's end address is exclusive, while Linux's sysfs prints - // the end address inclusive. - // - // E.g. sysfs will contain - // - // start: 0x100, end: 0x1ff - // - // while we represent - // - // start: 0x100, size: 0x100. - phys = append(phys, TypedRange{ - Range: RangeFromInterval(r.start, r.end+1), - Type: r.typ, - }) - } - sort.Slice(phys, func(i, j int) bool { - return phys[i].Start < phys[j].Start - }) - return phys, nil -} - // M1 is 1 Megabyte in bits. const M1 = 1 << 20 @@ -799,135 +620,3 @@ func (m Memory) AvailableRAM() Ranges { } return alignedRanges } - -// RangeType defines type of a TypedRange based on the Linux -// kernel string provided by firmware memory map. -type RangeType string - -// These are the range types we know Linux uses. -const ( - RangeRAM RangeType = "System RAM" - RangeDefault RangeType = "Default" - RangeACPI RangeType = "ACPI Tables" - RangeNVS RangeType = "ACPI Non-volatile Storage" - RangeReserved RangeType = "Reserved" -) - -// String implements fmt.Stringer. -func (r RangeType) String() string { - return string(r) -} - -var sysfsToRangeType = map[string]RangeType{ - "System RAM": RangeRAM, - "Default": RangeDefault, - "ACPI Tables": RangeACPI, - "ACPI Non-volatile Storage": RangeNVS, - "Reserved": RangeReserved, - "reserved": RangeReserved, -} - -// TypedRange represents range of physical memory. -type TypedRange struct { - Range - Type RangeType -} - -func (tr TypedRange) String() string { - return fmt.Sprintf("{addr: %s, type: %s}", tr.Range, tr.Type) -} - -// MemoryMap defines the layout of physical memory. -// -// MemoryMap defines which ranges in memory are usable RAM and which are -// reserved for various reasons. -type MemoryMap []TypedRange - -// FilterByType only returns ranges of the given typ. -func (m MemoryMap) FilterByType(typ RangeType) Ranges { - var rs Ranges - for _, tr := range m { - if tr.Type == typ { - rs = append(rs, tr.Range) - } - } - return rs -} - -func (m MemoryMap) sort() { - sort.Slice(m, func(i, j int) bool { - return m[i].Start < m[j].Start - }) -} - -// Insert a new TypedRange into the memory map, removing chunks of other ranges -// as necessary. -// -// Assumes that TypedRange is a valid range -- no checking. -func (m *MemoryMap) Insert(r TypedRange) { - var newMap MemoryMap - - // Remove points in r from all existing physical ranges. - for _, q := range *m { - split := q.Range.Minus(r.Range) - for _, r2 := range split { - newMap = append(newMap, TypedRange{Range: r2, Type: q.Type}) - } - } - - newMap = append(newMap, r) - newMap.sort() - *m = newMap -} - -// PayloadMemType defines type of a memory map entry -type PayloadMemType uint32 - -// Payload memory type (PayloadMemType) in UefiPayload -const ( - PayloadTypeRAM = 1 - PayloadTypeDefault = 2 - PayloadTypeACPI = 3 - PayloadTypeNVS = 4 - PayloadTypeReserved = 5 -) - -// payloadMemoryMapEntry represent a memory map entry in payload param -type payloadMemoryMapEntry struct { - Start uint64 - End uint64 - Type PayloadMemType -} - -// PayloadMemoryMapParam is payload's MemoryMap parameter -type PayloadMemoryMapParam []payloadMemoryMapEntry - -var rangeTypeToPayloadMemType = map[RangeType]PayloadMemType{ - RangeRAM: PayloadTypeRAM, - RangeDefault: PayloadTypeDefault, - RangeACPI: PayloadTypeACPI, - RangeNVS: PayloadTypeNVS, - RangeReserved: PayloadTypeReserved, -} - -func convertToPayloadMemType(rt RangeType) PayloadMemType { - mt, ok := rangeTypeToPayloadMemType[rt] - if !ok { - // return reserved if range type is not recognized - return PayloadTypeReserved - } - return mt -} - -// AsPayloadParam converts MemoryMap to a PayloadMemoryMapParam -func (m *MemoryMap) AsPayloadParam() PayloadMemoryMapParam { - var p PayloadMemoryMapParam - for _, entry := range *m { - p = append(p, payloadMemoryMapEntry{ - Start: uint64(entry.Start), - End: uint64(entry.Start) + uint64(entry.Size) - 1, - Type: convertToPayloadMemType(entry.Type), - }) - } - return p -} diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index ce3ed57502..729c0e48c1 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -6,277 +6,12 @@ package kexec import ( "fmt" - "os" - "path" "reflect" "testing" "github.com/google/go-cmp/cmp" - "github.com/u-root/u-root/pkg/dt" ) -func checkMemoryMap(t *testing.T, got, want MemoryMap) { - t.Helper() - if len(got) != len(want) { - t.Errorf("got memory map length %d, want memory map length %d", len(got), len(want)) - } - for idx, r := range got { - if r.Type != want[idx].Type { - t.Errorf("got memory at index %d type %v, want type %v", idx, r.Type, want[idx].Type) - } - if r.Range.Start != want[idx].Start || r.Size != want[idx].Size { - t.Errorf("got memory at index %d range %v, want range %v", idx, r.Range, want[idx].Range) - } - } -} - -func TestParseMemoryMapFromFDT(t *testing.T) { - for _, tc := range []struct { - name string - fdt *dt.FDT - wantMap MemoryMap - wantErr error - }{ - { - "empty", - &dt.FDT{RootNode: &dt.Node{Name: "/"}}, - MemoryMap{}, - nil, - }, - { - "add system memory ok", - &dt.FDT{ - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0x2ffffffffffff}, "System RAM"}, - }, - nil, - }, - { - "add system memory, and reserved memory ok", - &dt.FDT{ - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "reserved-memory", - Properties: []dt.Property{ - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, - }, - Children: []*dt.Node{ - { - Name: "reserved mem child node", - Properties: []dt.Property{ - { - "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - }, - }, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". - }, - nil, - }, - { - "add system memory, reserved memory, and reserved entries ok", - &dt.FDT{ - ReserveEntries: []dt.ReserveEntry{ - { - Address: uint64(0x1000000000000), - Size: uint64(0xffff), - }, - }, - RootNode: &dt.Node{ - Name: "/", - Children: []*dt.Node{ - { - Name: "test memory", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 2", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "test memory 3", - Properties: []dt.Property{ - {"device_type", append([]byte("memory"), 0)}, - {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, - }, - }, - { - Name: "reserved-memory", - Properties: []dt.Property{ - {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, - }, - Children: []*dt.Node{ - { - Name: "reserved mem child node", - Properties: []dt.Property{ - { - "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, - }, - }, - }, - }, - }, - }, - }, - }, - MemoryMap{ - TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". - TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0xffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x100000000ffff), Size: 0x1ffffffff0000}, "System RAM"}, // carve out reserve entry. - TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, - TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". - }, - nil, - }, - } { - t.Run(tc.name, func(t *testing.T) { - km := &Memory{} - err := km.ParseMemoryMapFromFDT(tc.fdt) - if err != tc.wantErr { - t.Errorf("km.ParseMemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) - } - checkMemoryMap(t, km.Phys, tc.wantMap) - }) - } -} - -func TestParseMemoryMap(t *testing.T) { - root := t.TempDir() - - create := func(dir string, start, end uintptr, typ RangeType) error { - p := path.Join(root, dir) - if err := os.Mkdir(p, 0o755); err != nil { - return err - } - if err := os.WriteFile(path.Join(p, "start"), []byte(fmt.Sprintf("%#x\n", start)), 0o655); err != nil { - return err - } - if err := os.WriteFile(path.Join(p, "end"), []byte(fmt.Sprintf("%#x\n", end)), 0o655); err != nil { - return err - } - return os.WriteFile(path.Join(p, "type"), append([]byte(typ), '\n'), 0o655) - } - - if err := create("0", 0, 49, RangeRAM); err != nil { - t.Fatal(err) - } - if err := create("1", 100, 149, RangeACPI); err != nil { - t.Fatal(err) - } - if err := create("2", 200, 249, RangeNVS); err != nil { - t.Fatal(err) - } - if err := create("3", 300, 349, RangeReserved); err != nil { - t.Fatal(err) - } - - want := MemoryMap{ - {Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, - {Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, - {Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, - {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, - } - - phys, err := internalParseMemoryMap(root) - if err != nil { - t.Fatalf("ParseMemoryMap() error: %v", err) - } - if !reflect.DeepEqual(phys, want) { - t.Errorf("ParseMemoryMap() got %v, want %v", phys, want) - } -} - -func TestAsPayloadParam(t *testing.T) { - var mem Memory - mem.Phys = MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, - TypedRange{Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, - TypedRange{Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeRAM}, - } - want := PayloadMemoryMapParam{ - {Start: 0, End: 49, Type: PayloadTypeRAM}, - {Start: 100, End: 149, Type: PayloadTypeACPI}, - {Start: 200, End: 249, Type: PayloadTypeNVS}, - {Start: 300, End: 349, Type: PayloadTypeReserved}, - {Start: 400, End: 449, Type: PayloadTypeRAM}, - } - mm := mem.Phys.AsPayloadParam() - if !reflect.DeepEqual(mm, want) { - t.Errorf("MemoryMap.AsPayloadParam() got %v, want %v", mm, want) - } -} - func TestAvailableRAM(t *testing.T) { old := pageMask defer func() { @@ -922,69 +657,6 @@ func TestAdjacent(t *testing.T) { } } -func TestMemoryMapInsert(t *testing.T) { - for i, tt := range []struct { - m MemoryMap - r TypedRange - want MemoryMap - }{ - { - // r is entirely within m's one range. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x2000}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 0x200, Size: 0x2000 - 0x200}, Type: RangeRAM}, - }, - }, - { - // r sits across three RAM ranges. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x150}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x150, Size: 0x50}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x1a0, Size: 0x100}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - TypedRange{Range: Range{Start: 0x200, Size: 0xa0}, Type: RangeRAM}, - }, - }, - { - // r is a superset of the ranges in m. - m: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x50}, Type: RangeRAM}, - }, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - }, - }, - { - // r is the first range in the map. - m: MemoryMap{}, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - want: MemoryMap{ - TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, - }, - }, - } { - t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { - // Make a copy for the Errorf print. - m := tt.m - tt.m.Insert(tt.r) - - if !reflect.DeepEqual(tt.m, tt.want) { - t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", m, tt.r, tt.m, tt.want) - } - }) - } -} - func TestSegmentsInsert(t *testing.T) { for i, tt := range []struct { segs Segments diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go new file mode 100644 index 0000000000..3beceb02aa --- /dev/null +++ b/pkg/boot/kexec/memory_map_linux.go @@ -0,0 +1,309 @@ +// Copyright 2015-2019 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kexec + +import ( + "fmt" + "log" + "os" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/u-root/u-root/pkg/dt" +) + +// RangeType defines type of a TypedRange based on the Linux +// kernel string provided by firmware memory map. +type RangeType string + +// These are the range types we know Linux uses. +const ( + RangeRAM RangeType = "System RAM" + RangeDefault RangeType = "Default" + RangeACPI RangeType = "ACPI Tables" + RangeNVS RangeType = "ACPI Non-volatile Storage" + RangeReserved RangeType = "Reserved" +) + +// String implements fmt.Stringer. +func (r RangeType) String() string { + return string(r) +} + +var sysfsToRangeType = map[string]RangeType{ + "System RAM": RangeRAM, + "Default": RangeDefault, + "ACPI Tables": RangeACPI, + "ACPI Non-volatile Storage": RangeNVS, + "Reserved": RangeReserved, + "reserved": RangeReserved, +} + +// TypedRange represents range of physical memory. +type TypedRange struct { + Range + Type RangeType +} + +func (tr TypedRange) String() string { + return fmt.Sprintf("{addr: %s, type: %s}", tr.Range, tr.Type) +} + +// MemoryMap defines the layout of physical memory. +// +// MemoryMap defines which ranges in memory are usable RAM and which are +// reserved for various reasons. +type MemoryMap []TypedRange + +// FilterByType only returns ranges of the given typ. +func (m MemoryMap) FilterByType(typ RangeType) Ranges { + var rs Ranges + for _, tr := range m { + if tr.Type == typ { + rs = append(rs, tr.Range) + } + } + return rs +} + +func (m MemoryMap) sort() { + sort.Slice(m, func(i, j int) bool { + return m[i].Start < m[j].Start + }) +} + +// Insert a new TypedRange into the memory map, removing chunks of other ranges +// as necessary. +// +// Assumes that TypedRange is a valid range -- no checking. +func (m *MemoryMap) Insert(r TypedRange) { + var newMap MemoryMap + + // Remove points in r from all existing physical ranges. + for _, q := range *m { + split := q.Range.Minus(r.Range) + for _, r2 := range split { + newMap = append(newMap, TypedRange{Range: r2, Type: q.Type}) + } + } + + newMap = append(newMap, r) + newMap.sort() + *m = newMap +} + +// ParseMemoryMapFromFDT reads firmware provided memory map from an FDT. +func ParseMemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { + var phys MemoryMap + addMemory := func(n *dt.Node) error { + p, found := n.LookProperty("device_type") + if !found { + return nil + } + t, err := p.AsString() + if err != nil || t != "memory" { + return nil + } + p, found = n.LookProperty("reg") + if found { + r, err := p.AsRegion() + if err != nil { + return err + } + phys = append(phys, TypedRange{ + Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, + Type: RangeRAM, + }) + } + return nil + } + err := fdt.RootNode.Walk(addMemory) + if err != nil { + return nil, err + } + + reserveMemory := func(n *dt.Node) error { + p, found := n.LookProperty("reg") + if found { + r, err := p.AsRegion() + if err != nil { + return err + } + + phys.Insert(TypedRange{ + Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, + Type: RangeReserved, + }) + } + return nil + } + resv, found := fdt.NodeByName("reserved-memory") + if found { + err := resv.Walk(reserveMemory) + if err != nil { + return nil, err + } + } + + for _, r := range fdt.ReserveEntries { + phys.Insert(TypedRange{ + Range: Range{Start: uintptr(r.Address), Size: uint(r.Size)}, + Type: RangeReserved, + }) + } + + return phys, nil +} + +var memoryMapRoot = "/sys/firmware/memmap/" + +// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. +func ParseMemoryMap() (MemoryMap, error) { + return internalParseMemoryMap(memoryMapRoot) +} + +func internalParseMemoryMap(memoryMapDir string) (MemoryMap, error) { + type memRange struct { + // start and end addresses are inclusive + start, end uintptr + typ RangeType + } + + ranges := make(map[string]memRange) + walker := func(name string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + const ( + // file names + start = "start" + end = "end" + typ = "type" + ) + + base := path.Base(name) + if base != start && base != end && base != typ { + return fmt.Errorf("unexpected file %q", name) + } + dir := path.Dir(name) + + b, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading file %q: %v", name, err) + } + + data := strings.TrimSpace(string(b)) + r := ranges[dir] + if base == typ { + typ, ok := sysfsToRangeType[data] + if !ok { + log.Printf("Sysfs file %q contains unrecognized memory map type %q, defaulting to Reserved", name, data) + r.typ = RangeReserved + } else { + r.typ = typ + } + ranges[dir] = r + return nil + } + + v, err := strconv.ParseUint(data, 0, strconv.IntSize) + if err != nil { + return err + } + switch base { + case start: + r.start = uintptr(v) + case end: + r.end = uintptr(v) + } + ranges[dir] = r + return nil + } + + if err := filepath.Walk(memoryMapDir, walker); err != nil { + return nil, err + } + + var phys []TypedRange + for _, r := range ranges { + // Range's end address is exclusive, while Linux's sysfs prints + // the end address inclusive. + // + // E.g. sysfs will contain + // + // start: 0x100, end: 0x1ff + // + // while we represent + // + // start: 0x100, size: 0x100. + phys = append(phys, TypedRange{ + Range: RangeFromInterval(r.start, r.end+1), + Type: r.typ, + }) + } + sort.Slice(phys, func(i, j int) bool { + return phys[i].Start < phys[j].Start + }) + return phys, nil +} + +// PayloadMemType defines type of a memory map entry +type PayloadMemType uint32 + +// Payload memory type (PayloadMemType) in UefiPayload +const ( + PayloadTypeRAM = 1 + PayloadTypeDefault = 2 + PayloadTypeACPI = 3 + PayloadTypeNVS = 4 + PayloadTypeReserved = 5 +) + +// payloadMemoryMapEntry represent a memory map entry in payload param +type payloadMemoryMapEntry struct { + Start uint64 + End uint64 + Type PayloadMemType +} + +// PayloadMemoryMapParam is payload's MemoryMap parameter +type PayloadMemoryMapParam []payloadMemoryMapEntry + +var rangeTypeToPayloadMemType = map[RangeType]PayloadMemType{ + RangeRAM: PayloadTypeRAM, + RangeDefault: PayloadTypeDefault, + RangeACPI: PayloadTypeACPI, + RangeNVS: PayloadTypeNVS, + RangeReserved: PayloadTypeReserved, +} + +func convertToPayloadMemType(rt RangeType) PayloadMemType { + mt, ok := rangeTypeToPayloadMemType[rt] + if !ok { + // return reserved if range type is not recognized + return PayloadTypeReserved + } + return mt +} + +// AsPayloadParam converts MemoryMap to a PayloadMemoryMapParam +func (m *MemoryMap) AsPayloadParam() PayloadMemoryMapParam { + var p PayloadMemoryMapParam + for _, entry := range *m { + p = append(p, payloadMemoryMapEntry{ + Start: uint64(entry.Start), + End: uint64(entry.Start) + uint64(entry.Size) - 1, + Type: convertToPayloadMemType(entry.Type), + }) + } + return p +} diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go new file mode 100644 index 0000000000..ab258f3041 --- /dev/null +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -0,0 +1,339 @@ +// Copyright 2018-2019 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kexec + +import ( + "fmt" + "os" + "path" + "reflect" + "testing" + + "github.com/u-root/u-root/pkg/dt" +) + +func checkMemoryMap(t *testing.T, got, want MemoryMap) { + t.Helper() + if len(got) != len(want) { + t.Errorf("got memory map length %d, want memory map length %d", len(got), len(want)) + } + for idx, r := range got { + if r.Type != want[idx].Type { + t.Errorf("got memory at index %d type %v, want type %v", idx, r.Type, want[idx].Type) + } + if r.Range.Start != want[idx].Start || r.Size != want[idx].Size { + t.Errorf("got memory at index %d range %v, want range %v", idx, r.Range, want[idx].Range) + } + } +} + +func TestParseMemoryMapFromFDT(t *testing.T) { + for _, tc := range []struct { + name string + fdt *dt.FDT + wantMap MemoryMap + wantErr error + }{ + { + "empty", + &dt.FDT{RootNode: &dt.Node{Name: "/"}}, + MemoryMap{}, + nil, + }, + { + "add system memory ok", + &dt.FDT{ + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0x2ffffffffffff}, "System RAM"}, + }, + nil, + }, + { + "add system memory, and reserved memory ok", + &dt.FDT{ + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "reserved-memory", + Properties: []dt.Property{ + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, + }, + Children: []*dt.Node{ + { + Name: "reserved mem child node", + Properties: []dt.Property{ + { + "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + }, + }, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0x1ffffffffffff}, "System RAM"}, + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". + }, + nil, + }, + { + "add system memory, reserved memory, and reserved entries ok", + &dt.FDT{ + ReserveEntries: []dt.ReserveEntry{ + { + Address: uint64(0x1000000000000), + Size: uint64(0xffff), + }, + }, + RootNode: &dt.Node{ + Name: "/", + Children: []*dt.Node{ + { + Name: "test memory", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 2", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x01, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "test memory 3", + Properties: []dt.Property{ + {"device_type", append([]byte("memory"), 0)}, + {"reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + }, + }, + { + Name: "reserved-memory", + Properties: []dt.Property{ + {"reg", []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff}}, + }, + Children: []*dt.Node{ + { + Name: "reserved mem child node", + Properties: []dt.Property{ + { + "reg", []byte{0x0, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + }, + }, + }, + }, + }, + }, + }, + MemoryMap{ + TypedRange{Range{Start: uintptr(0x0), Size: 0xffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0xffffffff), Size: 0xffff00000000}, "System RAM"}, // carve out reserved portion from "reserved-memory". + TypedRange{Range{Start: uintptr(0x1000000000000), Size: 0xffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x100000000ffff), Size: 0x1ffffffff0000}, "System RAM"}, // carve out reserve entry. + TypedRange{Range{Start: uintptr(0x3000000000000), Size: 0xffffffffff}, "Reserved"}, + TypedRange{Range{Start: uintptr(0x300ffffffffff), Size: 0x2ff0000000000}, "System RAM"}, // Carve out reserved portion from "reserved mem child node". + }, + nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + mm, err := ParseMemoryMapFromFDT(tc.fdt) + if err != tc.wantErr { + t.Errorf("ParseMemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) + } + checkMemoryMap(t, mm, tc.wantMap) + }) + } +} + +func TestParseMemoryMap(t *testing.T) { + root := t.TempDir() + + create := func(dir string, start, end uintptr, typ RangeType) error { + p := path.Join(root, dir) + if err := os.Mkdir(p, 0o755); err != nil { + return err + } + if err := os.WriteFile(path.Join(p, "start"), []byte(fmt.Sprintf("%#x\n", start)), 0o655); err != nil { + return err + } + if err := os.WriteFile(path.Join(p, "end"), []byte(fmt.Sprintf("%#x\n", end)), 0o655); err != nil { + return err + } + return os.WriteFile(path.Join(p, "type"), append([]byte(typ), '\n'), 0o655) + } + + if err := create("0", 0, 49, RangeRAM); err != nil { + t.Fatal(err) + } + if err := create("1", 100, 149, RangeACPI); err != nil { + t.Fatal(err) + } + if err := create("2", 200, 249, RangeNVS); err != nil { + t.Fatal(err) + } + if err := create("3", 300, 349, RangeReserved); err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + {Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + {Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, + {Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, + {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, + } + + phys, err := internalParseMemoryMap(root) + if err != nil { + t.Fatalf("ParseMemoryMap() error: %v", err) + } + if !reflect.DeepEqual(phys, want) { + t.Errorf("ParseMemoryMap() got %v, want %v", phys, want) + } +} + +func TestAsPayloadParam(t *testing.T) { + var mem Memory + mem.Phys = MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, + TypedRange{Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, + TypedRange{Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeRAM}, + } + want := PayloadMemoryMapParam{ + {Start: 0, End: 49, Type: PayloadTypeRAM}, + {Start: 100, End: 149, Type: PayloadTypeACPI}, + {Start: 200, End: 249, Type: PayloadTypeNVS}, + {Start: 300, End: 349, Type: PayloadTypeReserved}, + {Start: 400, End: 449, Type: PayloadTypeRAM}, + } + mm := mem.Phys.AsPayloadParam() + if !reflect.DeepEqual(mm, want) { + t.Errorf("MemoryMap.AsPayloadParam() got %v, want %v", mm, want) + } +} + +func TestMemoryMapInsert(t *testing.T) { + for i, tt := range []struct { + m MemoryMap + r TypedRange + want MemoryMap + }{ + { + // r is entirely within m's one range. + m: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x2000}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 0x200, Size: 0x2000 - 0x200}, Type: RangeRAM}, + }, + }, + { + // r sits across three RAM ranges. + m: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x150}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x150, Size: 0x50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x1a0, Size: 0x100}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 0x100}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 0x200, Size: 0xa0}, Type: RangeRAM}, + }, + }, + { + // r is a superset of the ranges in m. + m: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x50}, Type: RangeRAM}, + }, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + }, + }, + { + // r is the first range in the map. + m: MemoryMap{}, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + want: MemoryMap{ + TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + }, + }, + } { + t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { + // Make a copy for the Errorf print. + m := tt.m + tt.m.Insert(tt.r) + + if !reflect.DeepEqual(tt.m, tt.want) { + t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", m, tt.r, tt.m, tt.want) + } + }) + } +} diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index 7cbc58783e..430454e87c 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -72,13 +72,16 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error Debug("kernelEntry: %v", kernelEntry) // Prepare segments. - kmem = &kexec.Memory{} Debug("Try parsing memory map...") // TODO(10000TB): refactor this call into initialization of // kexec.Memory, as it does not depend on specific boot. - if err := kmem.ParseMemoryMap(); err != nil { + mm, err := kexec.ParseMemoryMap() + if err != nil { return fmt.Errorf("parse memory map: %v", err) } + kmem = &kexec.Memory{ + Phys: mm, + } var relocatableKernel bool if bzimg.Header.Protocolversion < 0x0205 { diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index 1dd20a0932..673fc4d87f 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -80,11 +80,14 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error Debug("FDT after sanitization: %s", fdt) // Prepare segments. - kmem = &kexec.Memory{} Debug("Try parsing memory map...") - if err := kmem.ParseMemoryMapFromFDT(fdt); err != nil { + mm, err := kexec.ParseMemoryMapFromFDT(fdt) + if err != nil { return fmt.Errorf("ParseMemoryMapFromFDT(%v): %v", fdt, err) } + kmem = &kexec.Memory{ + Phys: mm, + } Debug("Mem map: \n%+v", kmem.Phys) // Load kernel. diff --git a/pkg/boot/multiboot/multiboot.go b/pkg/boot/multiboot/multiboot.go index 9d3e131101..c1c420b3a7 100644 --- a/pkg/boot/multiboot/multiboot.go +++ b/pkg/boot/multiboot/multiboot.go @@ -290,9 +290,11 @@ func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error { } log.Printf("Parsing memory map") - if err := m.mem.ParseMemoryMap(); err != nil { + memmap, err := kexec.ParseMemoryMap() + if err != nil { return fmt.Errorf("error parsing memory map: %v", err) } + m.mem.Phys = memmap // Insert the iBFT now, since nothing else has been allocated and this // is the most restricted allocation we're gonna have to make. From ef19c07de0c79e1454ca9c4645ff3005715e4632 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 18:49:17 +0000 Subject: [PATCH 059/109] Rename ParseMemoryMap routines Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 12 ++++++------ pkg/boot/kexec/memory_map_linux_test.go | 14 +++++++------- pkg/boot/linux/load_linux_amd64.go | 2 +- pkg/boot/linux/load_linux_arm64.go | 4 ++-- pkg/boot/multiboot/multiboot.go | 2 +- pkg/boot/uefi/uefi.go | 10 +++++----- pkg/boot/uefi/uefi_test.go | 24 ++++++++++++------------ 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 3beceb02aa..b90e892370 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -97,8 +97,8 @@ func (m *MemoryMap) Insert(r TypedRange) { *m = newMap } -// ParseMemoryMapFromFDT reads firmware provided memory map from an FDT. -func ParseMemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { +// MemoryMapFromFDT reads firmware provided memory map from an FDT. +func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { var phys MemoryMap addMemory := func(n *dt.Node) error { p, found := n.LookProperty("device_type") @@ -162,12 +162,12 @@ func ParseMemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { var memoryMapRoot = "/sys/firmware/memmap/" -// ParseMemoryMap reads firmware provided memory map from /sys/firmware/memmap. -func ParseMemoryMap() (MemoryMap, error) { - return internalParseMemoryMap(memoryMapRoot) +// MemoryMapFromEFI reads a firmware-provided memory map from /sys/firmware/memmap. +func MemoryMapFromEFI() (MemoryMap, error) { + return memoryMapFromEFI(memoryMapRoot) } -func internalParseMemoryMap(memoryMapDir string) (MemoryMap, error) { +func memoryMapFromEFI(memoryMapDir string) (MemoryMap, error) { type memRange struct { // start and end addresses are inclusive start, end uintptr diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index ab258f3041..5316fdbda5 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -29,7 +29,7 @@ func checkMemoryMap(t *testing.T, got, want MemoryMap) { } } -func TestParseMemoryMapFromFDT(t *testing.T) { +func TestMemoryMapFromFDT(t *testing.T) { for _, tc := range []struct { name string fdt *dt.FDT @@ -198,16 +198,16 @@ func TestParseMemoryMapFromFDT(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - mm, err := ParseMemoryMapFromFDT(tc.fdt) + mm, err := MemoryMapFromFDT(tc.fdt) if err != tc.wantErr { - t.Errorf("ParseMemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) + t.Errorf("MemoryMapFromFDT returned error %v, want error %v", err, tc.wantErr) } checkMemoryMap(t, mm, tc.wantMap) }) } } -func TestParseMemoryMap(t *testing.T) { +func TestMemoryMapFromEFI(t *testing.T) { root := t.TempDir() create := func(dir string, start, end uintptr, typ RangeType) error { @@ -244,12 +244,12 @@ func TestParseMemoryMap(t *testing.T) { {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, } - phys, err := internalParseMemoryMap(root) + phys, err := memoryMapFromEFI(root) if err != nil { - t.Fatalf("ParseMemoryMap() error: %v", err) + t.Fatalf("MemoryMapFromEFI() error: %v", err) } if !reflect.DeepEqual(phys, want) { - t.Errorf("ParseMemoryMap() got %v, want %v", phys, want) + t.Errorf("MemoryMapFromEFI() got %v, want %v", phys, want) } } diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index 430454e87c..94e96f153e 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -75,7 +75,7 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error Debug("Try parsing memory map...") // TODO(10000TB): refactor this call into initialization of // kexec.Memory, as it does not depend on specific boot. - mm, err := kexec.ParseMemoryMap() + mm, err := kexec.MemoryMapFromEFI() if err != nil { return fmt.Errorf("parse memory map: %v", err) } diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index 673fc4d87f..dc2ec31ae9 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -81,9 +81,9 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error // Prepare segments. Debug("Try parsing memory map...") - mm, err := kexec.ParseMemoryMapFromFDT(fdt) + mm, err := kexec.MemoryMapFromFDT(fdt) if err != nil { - return fmt.Errorf("ParseMemoryMapFromFDT(%v): %v", fdt, err) + return fmt.Errorf("MemoryMapFromFDT(%v): %v", fdt, err) } kmem = &kexec.Memory{ Phys: mm, diff --git a/pkg/boot/multiboot/multiboot.go b/pkg/boot/multiboot/multiboot.go index c1c420b3a7..8069f5a031 100644 --- a/pkg/boot/multiboot/multiboot.go +++ b/pkg/boot/multiboot/multiboot.go @@ -290,7 +290,7 @@ func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error { } log.Printf("Parsing memory map") - memmap, err := kexec.ParseMemoryMap() + memmap, err := kexec.MemoryMapFromEFI() if err != nil { return fmt.Errorf("error parsing memory map: %v", err) } diff --git a/pkg/boot/uefi/uefi.go b/pkg/boot/uefi/uefi.go index 29f9f06fe5..67a3c3b0c0 100644 --- a/pkg/boot/uefi/uefi.go +++ b/pkg/boot/uefi/uefi.go @@ -20,10 +20,10 @@ import ( ) var ( - kexecLoad = kexec.Load - kexecParseMemoryMap = kexec.ParseMemoryMap - getRSDP = acpi.GetRSDP - getSMBIOSBase = smbios.SMBIOSBase + kexecLoad = kexec.Load + kexecMemoryMapFromEFI = kexec.MemoryMapFromEFI + getRSDP = acpi.GetRSDP + getSMBIOSBase = smbios.SMBIOSBase ) // SerialPortConfig defines debug port configuration @@ -123,7 +123,7 @@ func (fv *FVImage) Load(verbose bool) error { configAddr := fv.ImageBase - uintptr(uefiPayloadConfigSize) // Get MemoryMap - mm, err := kexecParseMemoryMap() + mm, err := kexecMemoryMapFromEFI() if err != nil { return err } diff --git a/pkg/boot/uefi/uefi_test.go b/pkg/boot/uefi/uefi_test.go index f51b779e55..ea237f411d 100644 --- a/pkg/boot/uefi/uefi_test.go +++ b/pkg/boot/uefi/uefi_test.go @@ -32,7 +32,7 @@ func mockKexecLoad(entry uintptr, segments kexec.Segments, flags uint64) error { return nil } -func mockKexecParseMemoryMap() (kexec.MemoryMap, error) { +func mockKexecMemoryMapFromEFI() (kexec.MemoryMap, error) { return kexec.MemoryMap{}, nil } @@ -50,8 +50,8 @@ func TestLoadFvImage(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) + kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -97,14 +97,14 @@ func TestLoadFvImageNotFound(t *testing.T) { } } -func TestLoadFvImageFailAtParseMemoryMap(t *testing.T) { +func TestLoadFvImageFailAtMemoryMapFromEFI(t *testing.T) { fv, err := New("testdata/fv_with_sec.fd") if err != nil { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = func() (kexec.MemoryMap, error) { + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) + kexecMemoryMapFromEFI = func() (kexec.MemoryMap, error) { return nil, fmt.Errorf("PARSE_MEMORY_MAP_FAILED") } @@ -122,8 +122,8 @@ func TestLoadFvImageFailAtGetRSDP(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) + kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = func() (*acpi.RSDP, error) { @@ -144,8 +144,8 @@ func TestLoadFvImageFailAtGetSMBIOS(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) + kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -170,8 +170,8 @@ func TestLoadFvImageFailAtKexec(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecParseMemoryMap = old }(kexecParseMemoryMap) - kexecParseMemoryMap = mockKexecParseMemoryMap + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) + kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP From 83c567de7f51a2feccc6d16933eb900cda0487d9 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 18:56:04 +0000 Subject: [PATCH 060/109] Add parsing memory map from /proc/iomem Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 65 +++++++++++++++++++++++++ pkg/boot/kexec/memory_map_linux_test.go | 50 +++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index b90e892370..4cfc5e2367 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -5,7 +5,9 @@ package kexec import ( + "bufio" "fmt" + "io" "log" "os" "path" @@ -307,3 +309,66 @@ func (m *MemoryMap) AsPayloadParam() PayloadMemoryMapParam { } return p } + +// MemoryMapFromIOMem reads the kernel-maintained memory map from /proc/iomem. +func MemoryMapFromIOMem() (MemoryMap, error) { + return memoryMapFromIOMemFile("/proc/iomem") +} + +func rangeType(s string) RangeType { + if s == "reserved" { + return RangeReserved + } + return RangeType(s) +} + +func memoryMapFromIOMem(r io.Reader) (MemoryMap, error) { + var mm MemoryMap + b := bufio.NewScanner(r) + for b.Scan() { + // Format: + // 740100000000-7401001fffff : PCI Bus 0001:01 + els := strings.Split(b.Text(), ":") + if len(els) != 2 { + continue + } + typ := strings.TrimSpace(els[1]) + addrs := strings.Split(strings.TrimSpace(els[0]), "-") + if len(addrs) != 2 { + continue + } + start, err := strconv.ParseUint(addrs[0], 16, 64) + if err != nil { + continue + } + end, err := strconv.ParseUint(addrs[1], 16, 64) + if err != nil { + continue + } + // Special case -- empty ranges are represented as "000-000" + // even though the non-inclusive end would make that a 1-sized + // region. + if start == end { + continue + } + mm.Insert(TypedRange{ + // end is inclusive. + Range: RangeFromInterval(uintptr(start), uintptr(end+1)), + Type: rangeType(typ), + }) + } + if err := b.Err(); err != nil { + return nil, err + } + return mm, nil +} + +func memoryMapFromIOMemFile(path string) (MemoryMap, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return memoryMapFromIOMem(f) +} diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index 5316fdbda5..4e8282c853 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -9,6 +9,7 @@ import ( "os" "path" "reflect" + "strings" "testing" "github.com/u-root/u-root/pkg/dt" @@ -337,3 +338,52 @@ func TestMemoryMapInsert(t *testing.T) { }) } } + +func TestMemoryMapFromIOMem(t *testing.T) { + f := `10000000-101fffff : reserved +10201000-10202fff : reserved +14000000-1effffff : System RAM + 14154000-14154fff : reserved + 141c0000-14bcffff : reserved + 14c10000-1636ffff : Kernel code + 16370000-1686ffff : reserved + 16870000-1734ffff : Kernel data + 17350000-17377fff : reserved` + mm, err := memoryMapFromIOMem(strings.NewReader(f)) + if err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + TypedRange{Range: RangeFromInterval(0x10000000, 0x101fffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x10201000, 0x10202fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14000000, 0x14154000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x14154000, 0x14154fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14155000, 0x141c0000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x141c0000, 0x14bcffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x14bd0000, 0x14c10000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x14c10000, 0x1636ffff+1), Type: RangeType("Kernel code")}, + TypedRange{Range: RangeFromInterval(0x16370000, 0x1686ffff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x16870000, 0x1734ffff+1), Type: RangeType("Kernel data")}, + TypedRange{Range: RangeFromInterval(0x17350000, 0x17377fff+1), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x17378000, 0x1effffff+1), Type: RangeRAM}, + } + if !reflect.DeepEqual(mm, want) { + t.Errorf("Not equal, got %v", mm) + } + + ignored := `00000000-00000000 : reserved +10000000-101fffff +10201000 : reserved +: System RAM + 141GGGGG-14154fff : reserved + 141c0000-14GGGGGG : reserved` + mm2, err := memoryMapFromIOMem(strings.NewReader(ignored)) + if err != nil { + t.Fatal(err) + } + + if want := MemoryMap(nil); !reflect.DeepEqual(mm2, want) { + t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) + } +} From 72db3434434c89382fc40f258e002a87c7f08e7e Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:04:14 +0000 Subject: [PATCH 061/109] Add reading memory map from /sys/kernel/debug/memblock Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 89 +++++++++++++++++++++++++ pkg/boot/kexec/memory_map_linux_test.go | 47 +++++++++++++ 2 files changed, 136 insertions(+) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 4cfc5e2367..b7e91ca8ac 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -372,3 +372,92 @@ func memoryMapFromIOMemFile(path string) (MemoryMap, error) { return memoryMapFromIOMem(f) } + +func rangeFromMemblockLine(s string) *Range { + // Format: + // 0: 0x0000004000000000..0x00000040113fffff + els := strings.Split(s, ":") + if len(els) != 2 { + return nil + } + addrs := strings.Split(strings.TrimSpace(els[1]), "..") + if len(addrs) != 2 { + return nil + } + startS, _ := strings.CutPrefix(addrs[0], "0x") + start, err := strconv.ParseUint(startS, 16, 64) + if err != nil { + return nil + } + endS, _ := strings.CutPrefix(addrs[1], "0x") + end, err := strconv.ParseUint(endS, 16, 64) + if err != nil { + return nil + } + + // Special case -- empty ranges are represented as "000-000" + // even though the non-inclusive end would make that a 1-sized + // region. + if start == end { + return nil + } + + // end is inclusive. + r := RangeFromInterval(uintptr(start), uintptr(end+1)) + return &r +} + +// MemoryMapFromMemblock reads a kernel-maintained memory map from /sys/kernel/debug/memblock. +// +// memblock is only available on kernels with CONFIG_ARCH_KEEP_MEMBLOCK (and +// debugfs). Without it, the kernel only maintains memblock early during init +// as its memory allocation mechanism. +func MemoryMapFromMemblock() (MemoryMap, error) { + m, err := os.Open("/sys/kernel/debug/memblock/memory") + if err != nil { + return nil, err + } + defer m.Close() + + r, err := os.Open("/sys/kernel/debug/memblock/reserved") + if err != nil { + return nil, err + } + defer r.Close() + + return memoryMapFromMemblock(m, r) +} + +func memoryMapFromMemblock(memory io.Reader, reserved io.Reader) (MemoryMap, error) { + var mm MemoryMap + b := bufio.NewScanner(memory) + for b.Scan() { + r := rangeFromMemblockLine(b.Text()) + if r == nil { + continue + } + mm.Insert(TypedRange{ + Range: *r, + Type: RangeRAM, + }) + } + if err := b.Err(); err != nil { + return nil, err + } + + b = bufio.NewScanner(reserved) + for b.Scan() { + r := rangeFromMemblockLine(b.Text()) + if r == nil { + continue + } + mm.Insert(TypedRange{ + Range: *r, + Type: RangeReserved, + }) + } + if err := b.Err(); err != nil { + return nil, err + } + return mm, nil +} diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index 4e8282c853..75fb071f88 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -387,3 +387,50 @@ func TestMemoryMapFromIOMem(t *testing.T) { t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) } } + +func TestMemoryMapFromMemblock(t *testing.T) { + memory := ` 0: 0x0000004000000000..0x00000040113fffff + 1: 0x0000004011400000..0x00000040123fffff + 2: 0x0000004012400000..0x00000040dfffffff + 3: 0x0000004400000000..0x00000044dfffffff` + reserved := ` 0: 0x0000004000000000..0x00000040113fffff + 1: 0x0000004012400000..0x00000040125fffff + 2: 0x0000004012800000..0x00000040137fffff` + mm, err := memoryMapFromMemblock(strings.NewReader(memory), strings.NewReader(reserved)) + if err != nil { + t.Fatal(err) + } + + want := MemoryMap{ + TypedRange{Range: RangeFromInterval(0x4000000000, 0x4011400000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4011400000, 0x4012400000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4012400000, 0x4012600000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4012600000, 0x4012800000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4012800000, 0x4013800000), Type: RangeReserved}, + TypedRange{Range: RangeFromInterval(0x4013800000, 0x40e0000000), Type: RangeRAM}, + TypedRange{Range: RangeFromInterval(0x4400000000, 0x44e0000000), Type: RangeRAM}, + } + if !reflect.DeepEqual(mm, want) { + t.Errorf("Not equal, got %v", mm) + } + + memIgnored := ` 0: 0x0000000000000000..0x0000000000000000 + 0: 0x0000004000000000.. + 1: 0x0000004011400000..0x00000040GGGGGGGG + 0x0000004012400000..0x00000040dfffffff + 2: 0x000000401GGGGGGG..0x00000040dfffffff + 3: 0x00000044000000000x00000044dfffffff` + reservedIgnored := ` 0: 0x0000004000000000.. + 1: 0x0000004011400000..0x00000040GGGGGGGG + 0x0000004012400000..0x00000040dfffffff + 2: 0x000000401GGGGGGG..0x00000040dfffffff + 3: 0x00000044000000000x00000044dfffffff` + mm2, err := memoryMapFromMemblock(strings.NewReader(memIgnored), strings.NewReader(reservedIgnored)) + if err != nil { + t.Fatal(err) + } + + if want := MemoryMap(nil); !reflect.DeepEqual(mm2, want) { + t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) + } +} From ad67dd23f0a583f6c85753fe7603e00d932cd914 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:07:07 +0000 Subject: [PATCH 062/109] Add dumpmemmap command Signed-off-by: Chris Koch --- cmds/exp/dumpmemmap/main_linux.go | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cmds/exp/dumpmemmap/main_linux.go diff --git a/cmds/exp/dumpmemmap/main_linux.go b/cmds/exp/dumpmemmap/main_linux.go new file mode 100644 index 0000000000..23e33c9d61 --- /dev/null +++ b/cmds/exp/dumpmemmap/main_linux.go @@ -0,0 +1,71 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Command dumpmemmap prints different kernel interpretations of physical +// memory address space. +// +// Support for: +// +// - /proc/iomem (exists on all systems) +// - /sys/firmware/memmap (exists on EFI systems) +// - /sys/kernel/debug/memblock (exists on systems with CONFIG_ARCH_KEEP_MEMBLOCK, in particular arm64) +// - /sys/firmware/fdt (exists on systems with device trees) +package main + +import ( + "fmt" + "log" + + "github.com/dustin/go-humanize" + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" +) + +func printMM(mm kexec.MemoryMap) { + for _, r := range mm { + fmt.Println(" ", r, " ", humanize.Bytes(uint64(r.Range.Size))) + } +} + +func main() { + memmap, err := kexec.MemoryMapFromEFI() + if err != nil { + log.Printf("/sys/firmware/memmap: %v", err) + } else { + fmt.Println("/sys/firmware/memmap:") + printMM(memmap) + } + + memblock, err := kexec.MemoryMapFromMemblock() + if err != nil { + log.Printf("/sys/kernel/debug/memblock: %v", err) + } else { + fmt.Println("/sys/kernel/debug/memblock:") + printMM(memblock) + } + + iomem, err := kexec.MemoryMapFromIOMem() + if err != nil { + log.Printf("/proc/iomem: %v", err) + } else { + fmt.Println("/proc/iomem:") + printMM(iomem) + } + + fdt, err := dt.LoadFDT(nil) + if err != nil { + log.Printf("loadFDT: %v", err) + return + } + + // Prepare segments. + fdtMap, err := kexec.MemoryMapFromFDT(fdt) + if err != nil { + log.Printf("MemoryMapFromFDT(%v): %v", fdt, err) + return + } + + fmt.Println("/sys/firmware/fdt:") + printMM(fdtMap) +} From f6e7359f3fbc609f03e9e94ed08645d7d1d00ce9 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:27:15 +0000 Subject: [PATCH 063/109] Rename UEFI payload memory map types Clarifies what this type is used for. Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 54 ++++++++++++------------- pkg/boot/kexec/memory_map_linux_test.go | 23 +++++------ pkg/boot/uefi/uefi.go | 2 +- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index b7e91ca8ac..1ae039d6b5 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -258,53 +258,53 @@ func memoryMapFromEFI(memoryMapDir string) (MemoryMap, error) { return phys, nil } -// PayloadMemType defines type of a memory map entry -type PayloadMemType uint32 +// UEFIPayloadMemType are types used with LinuxBoot UEFI payload memory maps. +type UEFIPayloadMemType uint32 -// Payload memory type (PayloadMemType) in UefiPayload +// Payload memory type (PayloadMemType) in UEFI payload. const ( - PayloadTypeRAM = 1 - PayloadTypeDefault = 2 - PayloadTypeACPI = 3 - PayloadTypeNVS = 4 - PayloadTypeReserved = 5 + UEFIPayloadTypeRAM UEFIPayloadMemType = 1 + UEFIPayloadTypeDefault UEFIPayloadMemType = 2 + UEFIPayloadTypeACPI UEFIPayloadMemType = 3 + UEFIPayloadTypeNVS UEFIPayloadMemType = 4 + UEFIPayloadTypeReserved UEFIPayloadMemType = 5 ) -// payloadMemoryMapEntry represent a memory map entry in payload param -type payloadMemoryMapEntry struct { +// UEFIPayloadMemoryMapEntry represent a memory map entry for a LinuxBoot UEFI payload. +type UEFIPayloadMemoryMapEntry struct { Start uint64 End uint64 - Type PayloadMemType + Type UEFIPayloadMemType } -// PayloadMemoryMapParam is payload's MemoryMap parameter -type PayloadMemoryMapParam []payloadMemoryMapEntry +// UEFIPayloadMemoryMap is a memory map used with LinuxBoot's UEFI payload. +type UEFIPayloadMemoryMap []UEFIPayloadMemoryMapEntry -var rangeTypeToPayloadMemType = map[RangeType]PayloadMemType{ - RangeRAM: PayloadTypeRAM, - RangeDefault: PayloadTypeDefault, - RangeACPI: PayloadTypeACPI, - RangeNVS: PayloadTypeNVS, - RangeReserved: PayloadTypeReserved, +var rangeTypeToUEFIPayloadMemType = map[RangeType]UEFIPayloadMemType{ + RangeRAM: UEFIPayloadTypeRAM, + RangeDefault: UEFIPayloadTypeDefault, + RangeACPI: UEFIPayloadTypeACPI, + RangeNVS: UEFIPayloadTypeNVS, + RangeReserved: UEFIPayloadTypeReserved, } -func convertToPayloadMemType(rt RangeType) PayloadMemType { - mt, ok := rangeTypeToPayloadMemType[rt] +func convertToUEFIPayloadMemType(rt RangeType) UEFIPayloadMemType { + mt, ok := rangeTypeToUEFIPayloadMemType[rt] if !ok { // return reserved if range type is not recognized - return PayloadTypeReserved + return UEFIPayloadTypeReserved } return mt } -// AsPayloadParam converts MemoryMap to a PayloadMemoryMapParam -func (m *MemoryMap) AsPayloadParam() PayloadMemoryMapParam { - var p PayloadMemoryMapParam +// ToUEFIPayloadMemoryMap converts MemoryMap to a UEFI payload memory map. +func (m *MemoryMap) ToUEFIPayloadMemoryMap() UEFIPayloadMemoryMap { + var p UEFIPayloadMemoryMap for _, entry := range *m { - p = append(p, payloadMemoryMapEntry{ + p = append(p, UEFIPayloadMemoryMapEntry{ Start: uint64(entry.Start), End: uint64(entry.Start) + uint64(entry.Size) - 1, - Type: convertToPayloadMemType(entry.Type), + Type: convertToUEFIPayloadMemType(entry.Type), }) } return p diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index 75fb071f88..9af836616c 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -254,25 +254,24 @@ func TestMemoryMapFromEFI(t *testing.T) { } } -func TestAsPayloadParam(t *testing.T) { - var mem Memory - mem.Phys = MemoryMap{ +func TestToUEFIPayloadMemoryMap(t *testing.T) { + mm := MemoryMap{ TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, TypedRange{Range: Range{Start: 100, Size: 50}, Type: RangeACPI}, TypedRange{Range: Range{Start: 200, Size: 50}, Type: RangeNVS}, TypedRange{Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeRAM}, } - want := PayloadMemoryMapParam{ - {Start: 0, End: 49, Type: PayloadTypeRAM}, - {Start: 100, End: 149, Type: PayloadTypeACPI}, - {Start: 200, End: 249, Type: PayloadTypeNVS}, - {Start: 300, End: 349, Type: PayloadTypeReserved}, - {Start: 400, End: 449, Type: PayloadTypeRAM}, + want := UEFIPayloadMemoryMap{ + {Start: 0, End: 49, Type: UEFIPayloadTypeRAM}, + {Start: 100, End: 149, Type: UEFIPayloadTypeACPI}, + {Start: 200, End: 249, Type: UEFIPayloadTypeNVS}, + {Start: 300, End: 349, Type: UEFIPayloadTypeReserved}, + {Start: 400, End: 449, Type: UEFIPayloadTypeRAM}, } - mm := mem.Phys.AsPayloadParam() - if !reflect.DeepEqual(mm, want) { - t.Errorf("MemoryMap.AsPayloadParam() got %v, want %v", mm, want) + uefiMM := mm.ToUEFIPayloadMemoryMap() + if !reflect.DeepEqual(uefiMM, want) { + t.Errorf("ToUEFIPayloadMemoryMap() got %v, want %v", uefiMM, want) } } diff --git a/pkg/boot/uefi/uefi.go b/pkg/boot/uefi/uefi.go index 67a3c3b0c0..97092246db 100644 --- a/pkg/boot/uefi/uefi.go +++ b/pkg/boot/uefi/uefi.go @@ -154,7 +154,7 @@ func (fv *FVImage) Load(verbose bool) error { return err } - if err := binary.Write(pcbuf, binary.LittleEndian, mm.AsPayloadParam()); err != nil { + if err := binary.Write(pcbuf, binary.LittleEndian, mm.ToUEFIPayloadMemoryMap()); err != nil { return err } From 1c0eb6beaefe37f3523f0135ecddceceff9c1f04 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:37:20 +0000 Subject: [PATCH 064/109] Add MemoryMap.RAM function Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 4 ++-- pkg/boot/kexec/memory_map_linux.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 40035a885b..6516f15b83 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -600,9 +600,9 @@ func (m *Memory) AddKexecSegmentExplicit(d []byte, sz, offset, alignSizeBytes ui // // [{start:0 size:40} {start:4096 end:8000 - 4096}] func (m Memory) AvailableRAM() Ranges { - ram := m.Phys.FilterByType(RangeRAM) + ram := m.Phys.RAM() - // Remove all points in Segments from available RAM. + // Remove all points we've already reserved from available RAM. for _, s := range m.Segments { ram = ram.Minus(s.Phys) } diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 1ae039d6b5..2ba844135a 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -73,6 +73,12 @@ func (m MemoryMap) FilterByType(typ RangeType) Ranges { return rs } +// RAM is an alias for FilterByType(RangeRAM) and returns unreserved physical +// memory in the memory map. +func (m MemoryMap) RAM() Ranges { + return m.FilterByType(RangeRAM) +} + func (m MemoryMap) sort() { sort.Slice(m, func(i, j int) bool { return m[i].Start < m[j].Start From 30c5660b835f6eb4d196157fa18963e93dc86846 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:52:38 +0000 Subject: [PATCH 065/109] Merge adjacent MemoryMap entries of the same type Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 43 +++++++++++++++++++++---- pkg/boot/kexec/memory_map_linux_test.go | 28 ++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 2ba844135a..84951578d6 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -85,6 +85,32 @@ func (m MemoryMap) sort() { }) } +func (m *MemoryMap) mergeAdjacent() { + if len(*m) == 0 { + return + } + + newMap := MemoryMap{(*m)[0]} + for i := 1; i < len(*m); i++ { + seg := (*m)[i] + + prev := newMap[len(newMap)-1] + mergable := seg.Range.Overlaps(prev.Range) || seg.Range.Adjacent(prev.Range) + // Does the range overlap with the previous range? Merge them. + if mergable && seg.Type == prev.Type { + // Assuming the map is sorted by start, as it always + // should be, extend the size. + if seg.End() > prev.End() { + diffSize := seg.End() - prev.End() + newMap[len(newMap)-1].Range.Size += uint(diffSize) + } + } else { + newMap = append(newMap, seg) + } + } + *m = newMap +} + // Insert a new TypedRange into the memory map, removing chunks of other ranges // as necessary. // @@ -165,6 +191,8 @@ func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { }) } + phys.sort() + phys.mergeAdjacent() return phys, nil } @@ -241,7 +269,7 @@ func memoryMapFromEFI(memoryMapDir string) (MemoryMap, error) { return nil, err } - var phys []TypedRange + var mm MemoryMap for _, r := range ranges { // Range's end address is exclusive, while Linux's sysfs prints // the end address inclusive. @@ -253,15 +281,14 @@ func memoryMapFromEFI(memoryMapDir string) (MemoryMap, error) { // while we represent // // start: 0x100, size: 0x100. - phys = append(phys, TypedRange{ + mm = append(mm, TypedRange{ Range: RangeFromInterval(r.start, r.end+1), Type: r.typ, }) } - sort.Slice(phys, func(i, j int) bool { - return phys[i].Start < phys[j].Start - }) - return phys, nil + mm.sort() + mm.mergeAdjacent() + return mm, nil } // UEFIPayloadMemType are types used with LinuxBoot UEFI payload memory maps. @@ -366,6 +393,8 @@ func memoryMapFromIOMem(r io.Reader) (MemoryMap, error) { if err := b.Err(); err != nil { return nil, err } + mm.sort() + mm.mergeAdjacent() return mm, nil } @@ -465,5 +494,7 @@ func memoryMapFromMemblock(memory io.Reader, reserved io.Reader) (MemoryMap, err if err := b.Err(); err != nil { return nil, err } + mm.sort() + mm.mergeAdjacent() return mm, nil } diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index 9af836616c..fde2e27f27 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -433,3 +433,31 @@ func TestMemoryMapFromMemblock(t *testing.T) { t.Errorf("Memory maps not equal, got %v, want %v", mm2, want) } } + +func TestMemoryMapMerge(t *testing.T) { + mm := MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 50, Size: 20}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 70, Size: 40}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 111, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 121, Size: 50}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 20}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 20}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 50}, Type: RangeReserved}, + } + + want := MemoryMap{ + TypedRange{Range: Range{Start: 0, Size: 110}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 111, Size: 60}, Type: RangeRAM}, + TypedRange{Range: Range{Start: 400, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 500, Size: 50}, Type: RangeReserved}, + TypedRange{Range: Range{Start: 600, Size: 50}, Type: RangeReserved}, + } + + mm.mergeAdjacent() + if !reflect.DeepEqual(mm, want) { + t.Errorf("Merge() got %v, want %v", mm, want) + } +} From ac75283df6037d265d7d505e5d04cc008e8b7b23 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 19:57:58 +0000 Subject: [PATCH 066/109] Consistently refer to MemoryMap as mm Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 50 ++++++++++++------------- pkg/boot/kexec/memory_map_linux_test.go | 20 +++++----- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 84951578d6..1532f7a7a9 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -63,9 +63,9 @@ func (tr TypedRange) String() string { type MemoryMap []TypedRange // FilterByType only returns ranges of the given typ. -func (m MemoryMap) FilterByType(typ RangeType) Ranges { +func (mm MemoryMap) FilterByType(typ RangeType) Ranges { var rs Ranges - for _, tr := range m { + for _, tr := range mm { if tr.Type == typ { rs = append(rs, tr.Range) } @@ -75,24 +75,24 @@ func (m MemoryMap) FilterByType(typ RangeType) Ranges { // RAM is an alias for FilterByType(RangeRAM) and returns unreserved physical // memory in the memory map. -func (m MemoryMap) RAM() Ranges { - return m.FilterByType(RangeRAM) +func (mm MemoryMap) RAM() Ranges { + return mm.FilterByType(RangeRAM) } -func (m MemoryMap) sort() { - sort.Slice(m, func(i, j int) bool { - return m[i].Start < m[j].Start +func (mm MemoryMap) sort() { + sort.Slice(mm, func(i, j int) bool { + return mm[i].Start < mm[j].Start }) } -func (m *MemoryMap) mergeAdjacent() { - if len(*m) == 0 { +func (mm *MemoryMap) mergeAdjacent() { + if len(*mm) == 0 { return } - newMap := MemoryMap{(*m)[0]} - for i := 1; i < len(*m); i++ { - seg := (*m)[i] + newMap := MemoryMap{(*mm)[0]} + for i := 1; i < len(*mm); i++ { + seg := (*mm)[i] prev := newMap[len(newMap)-1] mergable := seg.Range.Overlaps(prev.Range) || seg.Range.Adjacent(prev.Range) @@ -108,18 +108,18 @@ func (m *MemoryMap) mergeAdjacent() { newMap = append(newMap, seg) } } - *m = newMap + *mm = newMap } // Insert a new TypedRange into the memory map, removing chunks of other ranges // as necessary. // // Assumes that TypedRange is a valid range -- no checking. -func (m *MemoryMap) Insert(r TypedRange) { +func (mm *MemoryMap) Insert(r TypedRange) { var newMap MemoryMap // Remove points in r from all existing physical ranges. - for _, q := range *m { + for _, q := range *mm { split := q.Range.Minus(r.Range) for _, r2 := range split { newMap = append(newMap, TypedRange{Range: r2, Type: q.Type}) @@ -128,12 +128,12 @@ func (m *MemoryMap) Insert(r TypedRange) { newMap = append(newMap, r) newMap.sort() - *m = newMap + *mm = newMap } // MemoryMapFromFDT reads firmware provided memory map from an FDT. func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { - var phys MemoryMap + var mm MemoryMap addMemory := func(n *dt.Node) error { p, found := n.LookProperty("device_type") if !found { @@ -149,7 +149,7 @@ func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { if err != nil { return err } - phys = append(phys, TypedRange{ + mm = append(mm, TypedRange{ Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, Type: RangeRAM, }) @@ -169,7 +169,7 @@ func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { return err } - phys.Insert(TypedRange{ + mm.Insert(TypedRange{ Range: Range{Start: uintptr(r.Start), Size: uint(r.Size)}, Type: RangeReserved, }) @@ -185,15 +185,15 @@ func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { } for _, r := range fdt.ReserveEntries { - phys.Insert(TypedRange{ + mm.Insert(TypedRange{ Range: Range{Start: uintptr(r.Address), Size: uint(r.Size)}, Type: RangeReserved, }) } - phys.sort() - phys.mergeAdjacent() - return phys, nil + mm.sort() + mm.mergeAdjacent() + return mm, nil } var memoryMapRoot = "/sys/firmware/memmap/" @@ -331,9 +331,9 @@ func convertToUEFIPayloadMemType(rt RangeType) UEFIPayloadMemType { } // ToUEFIPayloadMemoryMap converts MemoryMap to a UEFI payload memory map. -func (m *MemoryMap) ToUEFIPayloadMemoryMap() UEFIPayloadMemoryMap { +func (mm MemoryMap) ToUEFIPayloadMemoryMap() UEFIPayloadMemoryMap { var p UEFIPayloadMemoryMap - for _, entry := range *m { + for _, entry := range mm { p = append(p, UEFIPayloadMemoryMapEntry{ Start: uint64(entry.Start), End: uint64(entry.Start) + uint64(entry.Size) - 1, diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index fde2e27f27..485c11cab0 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -277,13 +277,13 @@ func TestToUEFIPayloadMemoryMap(t *testing.T) { func TestMemoryMapInsert(t *testing.T) { for i, tt := range []struct { - m MemoryMap + mm MemoryMap r TypedRange want MemoryMap }{ { // r is entirely within m's one range. - m: MemoryMap{ + mm: MemoryMap{ TypedRange{Range: Range{Start: 0, Size: 0x2000}, Type: RangeRAM}, }, r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, @@ -295,7 +295,7 @@ func TestMemoryMapInsert(t *testing.T) { }, { // r sits across three RAM ranges. - m: MemoryMap{ + mm: MemoryMap{ TypedRange{Range: Range{Start: 0, Size: 0x150}, Type: RangeRAM}, TypedRange{Range: Range{Start: 0x150, Size: 0x50}, Type: RangeRAM}, TypedRange{Range: Range{Start: 0x1a0, Size: 0x100}, Type: RangeRAM}, @@ -309,7 +309,7 @@ func TestMemoryMapInsert(t *testing.T) { }, { // r is a superset of the ranges in m. - m: MemoryMap{ + mm: MemoryMap{ TypedRange{Range: Range{Start: 0x100, Size: 0x50}, Type: RangeRAM}, }, r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, @@ -319,8 +319,8 @@ func TestMemoryMapInsert(t *testing.T) { }, { // r is the first range in the map. - m: MemoryMap{}, - r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, + mm: MemoryMap{}, + r: TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, want: MemoryMap{ TypedRange{Range: Range{Start: 0x100, Size: 0x100}, Type: RangeReserved}, }, @@ -328,11 +328,11 @@ func TestMemoryMapInsert(t *testing.T) { } { t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { // Make a copy for the Errorf print. - m := tt.m - tt.m.Insert(tt.r) + mm := tt.mm + tt.mm.Insert(tt.r) - if !reflect.DeepEqual(tt.m, tt.want) { - t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", m, tt.r, tt.m, tt.want) + if !reflect.DeepEqual(tt.mm, tt.want) { + t.Errorf("\n%v.Insert(%s) =\n%v, want\n%v", mm, tt.r, tt.mm, tt.want) } }) } From f47995a822c2f0a9190eb50a7ed7287a099f2338 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 20:27:00 +0000 Subject: [PATCH 067/109] Use built-in min/max functions Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 6516f15b83..e9bbe505d2 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -69,27 +69,6 @@ func (r Range) Contains(p uintptr) bool { return r.Start <= p && p < r.End() } -func min(a, b uintptr) uintptr { - if a < b { - return a - } - return b -} - -func minuint(a, b uint) uint { - if a < b { - return a - } - return b -} - -func max(a, b uintptr) uintptr { - if a > b { - return a - } - return b -} - // Intersect returns the continuous range of points common to r and r2 if there // is one. func (r Range) Intersect(r2 Range) *Range { @@ -419,7 +398,7 @@ func (segs Segments) Phys() Ranges { // the same buffer content. func (segs Segments) IsSupersetOf(o Segments) error { for _, seg := range o { - size := minuint(seg.Phys.Size, seg.Buf.Size) + size := min(seg.Phys.Size, seg.Buf.Size) if size == 0 { continue } From 5c5df9ad6861b541bb54df75bea8a26f79502dec Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 27 Jan 2024 20:33:19 +0000 Subject: [PATCH 068/109] MemoryMap String function Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_map_linux.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 1532f7a7a9..7fef2b024b 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -62,6 +62,15 @@ func (tr TypedRange) String() string { // reserved for various reasons. type MemoryMap []TypedRange +func (mm MemoryMap) String() string { + var s strings.Builder + for _, tr := range mm { + s.WriteString(tr.String()) + s.WriteString("\n") + } + return s.String() +} + // FilterByType only returns ranges of the given typ. func (mm MemoryMap) FilterByType(typ RangeType) Ranges { var rs Ranges From c5c71161c756677c7ed461a640f26799cb2585d0 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Thu, 18 Jan 2024 19:00:23 +0200 Subject: [PATCH 069/109] cmds/core/hohup: nohup command Signed-off-by: Siarhiej Siemianczuk --- cmds/core/nohup/nohup.go | 85 +++++++++++++++++++++++++++++++++++ cmds/core/nohup/nohup_test.go | 71 +++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 cmds/core/nohup/nohup.go create mode 100644 cmds/core/nohup/nohup_test.go diff --git a/cmds/core/nohup/nohup.go b/cmds/core/nohup/nohup.go new file mode 100644 index 0000000000..286ee1c8e3 --- /dev/null +++ b/cmds/core/nohup/nohup.go @@ -0,0 +1,85 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// nohup – invoke a utility immune to hangups. +// +// Synopsis: +// +// nohup [args...] +package main + +import ( + "errors" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "syscall" + + "golang.org/x/term" +) + +var ( + errUsage = fmt.Errorf("nohup [args...]") + errStart = fmt.Errorf("failed to start") + errFinish = fmt.Errorf("finished with error") +) + +func main() { + if err := run(os.Args); err != nil { + if errors.Is(err, errUsage) { + fmt.Fprintf(os.Stderr, "Usage: %v\n", errUsage) + os.Exit(127) + } + log.Fatalf("nohup: %v", err) + } +} + +func run(args []string) error { + if len(args) < 2 { + return errUsage + } + + signal.Ignore(syscall.SIGHUP) + + cmdName := args[1] + cmdArgs := args[2:] + + cmd := exec.Command(cmdName, cmdArgs...) + + stdoutIsTerminal := term.IsTerminal(int(os.Stdout.Fd())) + stderrIsTerminal := term.IsTerminal(int(os.Stderr.Fd())) + + if stdoutIsTerminal { + outputFile, err := os.OpenFile("nohup.out", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("error opening file: %w", err) + } + defer outputFile.Close() + + cmd.Stdout = outputFile + + if stderrIsTerminal { + cmd.Stderr = outputFile + } else { + cmd.Stderr = os.Stderr + } + } else { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + err := cmd.Start() + if err != nil { + return fmt.Errorf("%s: %w: %v", cmdName, errStart, err) + } + + err = cmd.Wait() + if err != nil { + return fmt.Errorf("%s: %w: %v", cmdName, errFinish, err) + } + + return nil +} diff --git a/cmds/core/nohup/nohup_test.go b/cmds/core/nohup/nohup_test.go new file mode 100644 index 0000000000..1e7c6c07cd --- /dev/null +++ b/cmds/core/nohup/nohup_test.go @@ -0,0 +1,71 @@ +// Copyright 2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "golang.org/x/term" +) + +func TestRun(t *testing.T) { + tests := []struct { + err error + name string + output string + args []string + }{ + { + name: "no arguments", + args: []string{"nohup"}, + err: errUsage, + }, + { + name: "invalid command", + args: []string{"nohup", "invalidcommand"}, + err: errStart, + }, + { + name: "false command", + args: []string{"nohup", "false"}, + err: errFinish, + }, + { + name: "valid command", + args: []string{"nohup", "echo", "hello"}, + output: "hello\n", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dir := t.TempDir() + err := os.Chdir(dir) + if err != nil { + t.Fatalf("can't chdir into %q", dir) + } + + err = run(test.args) + + if !errors.Is(err, test.err) { + t.Errorf("expected %v, got %v", test.err, err) + } + + if test.output != "" && term.IsTerminal(int(os.Stdout.Fd())) { + b, err := os.ReadFile(filepath.Join(dir, "nohup.out")) + if err != nil { + t.Fatalf("can't open nohup.out: %v", err) + } + + if string(b) != test.output { + t.Errorf("expected %q, got %q", test.output, string(b)) + } + } + }) + } +} From cd3d81d38d9951d86137465f96377acdd93d3a29 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 31 Jan 2024 03:16:12 +0000 Subject: [PATCH 070/109] vfile: save memory by removing TOCTTOU protections On low memory systems, vfile contributes one of many copies of an OS to be kexec'd in u-root. To enable only-one-copy kexec, drop TOCTTOU protections. Signed-off-by: Chris Koch --- pkg/boot/linux.go | 2 +- pkg/boot/linux_test.go | 34 ++++++--------------------- pkg/vfile/vfile.go | 53 ++++++++++++++++-------------------------- 3 files changed, 28 insertions(+), 61 deletions(-) diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index 1292e272b8..fa678cb8d4 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -122,7 +122,7 @@ func (lli *LoadedLinuxImage) UnmarshalJSON(b []byte) error { return nil } -// named is satisifed by both *os.File and *vfile.File. Hack hack hack. +// named is satisifed by *os.File. type named interface { Name() string } diff --git a/pkg/boot/linux_test.go b/pkg/boot/linux_test.go index 83a8e70c90..0b4c6b1672 100644 --- a/pkg/boot/linux_test.go +++ b/pkg/boot/linux_test.go @@ -19,7 +19,6 @@ import ( "github.com/u-root/u-root/pkg/curl" "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/uio" - "github.com/u-root/u-root/pkg/vfile" "github.com/u-root/uio/ulog/ulogtest" "golang.org/x/sys/unix" ) @@ -87,45 +86,26 @@ func TestLinuxLabel(t *testing.T) { }, want: "Linux(kernel=http://127.0.0.1/kernel initrd=http://127.0.0.1/initrd1,http://127.0.0.1/initrd2)", }, - { - desc: "verified file", - img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: CatInitrds( - &vfile.File{Reader: nil, FileName: "/boot/initrd1"}, - &vfile.File{Reader: nil, FileName: "/boot/initrd2"}, - ), - }, - want: "Linux(kernel=/boot/foobar initrd=/boot/initrd1,/boot/initrd2)", - }, - { - desc: "no initrd", - img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: nil, - }, - want: "Linux(kernel=/boot/foobar)", - }, { desc: "dtb file", img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, - Initrd: &vfile.File{Reader: nil, FileName: "/boot/initrd"}, + Kernel: osKernel, + Initrd: osInitrd, KexecOpts: linux.KexecOptions{ - DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, + DTB: osInitrd, }, }, - want: "Linux(kernel=/boot/foobar initrd=/boot/initrd dtb=/boot/board.dtb)", + want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd dtb=%s/initrd)", dir, dir, dir), }, { desc: "dtb file, no initrd", img: &LinuxImage{ - Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, + Kernel: osKernel, KexecOpts: linux.KexecOptions{ - DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, + DTB: osInitrd, }, }, - want: "Linux(kernel=/boot/foobar dtb=/boot/board.dtb)", + want: fmt.Sprintf("Linux(kernel=%s/kernel dtb=%s/initrd)", dir, dir), }, } { t.Run(tt.desc, func(t *testing.T) { diff --git a/pkg/vfile/vfile.go b/pkg/vfile/vfile.go index d54849e6e7..a7af7f33df 100644 --- a/pkg/vfile/vfile.go +++ b/pkg/vfile/vfile.go @@ -4,7 +4,7 @@ // Package vfile verifies files against a hash or signature. // -// vfile aims to be TOCTTOU-safe by reading files into memory before verifying. +// vfile is not TOCTTOU-safe against the contents of the file changing. package vfile import ( @@ -110,22 +110,10 @@ func GetRSAKeysFromRing(ring openpgp.KeyRing) ([]*rsa.PublicKey, error) { // OpenSignedSigFile calls OpenSignedFile expecting the signature to be in path.sig. // // E.g. if path is /foo/bar, the signature is expected to be in /foo/bar.sig. -func OpenSignedSigFile(keyring openpgp.KeyRing, path string, opts ...OpenSignedFileOption) (*File, error) { +func OpenSignedSigFile(keyring openpgp.KeyRing, path string, opts ...OpenSignedFileOption) (*os.File, error) { return OpenSignedFile(keyring, path, fmt.Sprintf("%s.sig", path), opts...) } -// File encapsulates a bytes.Reader with the file contents and its name. -type File struct { - *bytes.Reader - - FileName string -} - -// Name returns the file name. -func (f *File) Name() string { - return f.FileName -} - // OpenSignedFileOption is an optional argument to OpenSignedFile. type OpenSignedFileOption func(*openSignedFileOptions) @@ -151,19 +139,15 @@ func getEndOfTime() time.Time { // WARNING! Unlike many Go functions, this may return both the file and an // error. // -// It expects path.sig to be available. +// It expects pathSig to be available. // // If the signature does not exist or does not match the keyring, both the file // and a signature error will be returned. -func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenSignedFileOption) (*File, error) { - content, err := os.ReadFile(path) +func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenSignedFileOption) (*os.File, error) { + f, err := os.Open(path) if err != nil { return nil, err } - f := &File{ - Reader: bytes.NewReader(content), - FileName: path, - } var o openSignedFileOptions // Apply options if given. for _, opt := range opts { @@ -180,9 +164,13 @@ func OpenSignedFile(keyring openpgp.KeyRing, path, pathSig string, opts ...OpenS if o.ignoreTimeConflict { config.Time = getEndOfTime } + + // After CheckDetachedSignature reads the whole file, seek back to the beginning. + defer f.Seek(0, os.SEEK_SET) + if keyring == nil { return f, ErrUnsigned{Path: path, Err: ErrNoKeyRing} - } else if signer, err := openpgp.CheckDetachedSignature(keyring, bytes.NewReader(content), signaturef, &config); err != nil { + } else if signer, err := openpgp.CheckDetachedSignature(keyring, f, signaturef, &config); err != nil { return f, ErrUnsigned{Path: path, Err: err} } else if signer == nil { return f, ErrUnsigned{Path: path, Err: ErrWrongSigner{keyring}} @@ -226,8 +214,8 @@ var ErrNoExpectedHash = errors.New("OpenHashedFile: no expected hash given") // WARNING! Unlike many Go functions, this may return both the file and an // error in case the expected hash does not match the contents. // -// If the contents match, the contents are returned with no error. -func OpenHashedFile256(path string, wantSHA256Hash []byte) (*File, error) { +// If the contents match, the opened file is returned with no error. +func OpenHashedFile256(path string, wantSHA256Hash []byte) (*os.File, error) { return openHashedFile(path, wantSHA256Hash, sha256.New()) } @@ -237,20 +225,16 @@ func OpenHashedFile256(path string, wantSHA256Hash []byte) (*File, error) { // WARNING! Unlike many Go functions, this may return both the file and an // error in case the expected hash does not match the contents. // -// If the contents match, the contents are returned with no error. -func OpenHashedFile512(path string, wantSHA512Hash []byte) (*File, error) { +// If the contents match, the opened file is returned with no error. +func OpenHashedFile512(path string, wantSHA512Hash []byte) (*os.File, error) { return openHashedFile(path, wantSHA512Hash, sha512.New()) } -func openHashedFile(path string, wantHash []byte, h hash.Hash) (*File, error) { - content, err := os.ReadFile(path) +func openHashedFile(path string, wantHash []byte, h hash.Hash) (*os.File, error) { + f, err := os.Open(path) if err != nil { return nil, err } - f := &File{ - Reader: bytes.NewReader(content), - FileName: path, - } if len(wantHash) == 0 { return f, ErrInvalidHash{ @@ -259,8 +243,11 @@ func openHashedFile(path string, wantHash []byte, h hash.Hash) (*File, error) { } } + // After io.Copy reads the whole file, Seek back to beginning. + defer f.Seek(0, os.SEEK_SET) + // Hash the file. - if _, err := io.Copy(h, bytes.NewReader(content)); err != nil { + if _, err := io.Copy(h, f); err != nil { return f, ErrInvalidHash{ Path: path, Err: err, From 656656c05e159447e9fb72f5a212ba3fd286ee22 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 31 Jan 2024 04:00:13 +0000 Subject: [PATCH 071/109] memmap: rename /sys/firmware/memmap parser I was wrong. It only exists on x86 systems, and works with e820 and EFI. Signed-off-by: Chris Koch --- cmds/exp/dumpmemmap/main_linux.go | 4 ++-- pkg/boot/kexec/memory_map_linux.go | 10 ++++++---- pkg/boot/kexec/memory_map_linux_test.go | 8 ++++---- pkg/boot/linux/load_linux_amd64.go | 2 +- pkg/boot/multiboot/multiboot.go | 2 +- pkg/boot/uefi/uefi.go | 10 +++++----- pkg/boot/uefi/uefi_test.go | 24 ++++++++++++------------ 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/cmds/exp/dumpmemmap/main_linux.go b/cmds/exp/dumpmemmap/main_linux.go index 23e33c9d61..525d02c05a 100644 --- a/cmds/exp/dumpmemmap/main_linux.go +++ b/cmds/exp/dumpmemmap/main_linux.go @@ -8,7 +8,7 @@ // Support for: // // - /proc/iomem (exists on all systems) -// - /sys/firmware/memmap (exists on EFI systems) +// - /sys/firmware/memmap (exists on x86 systems) // - /sys/kernel/debug/memblock (exists on systems with CONFIG_ARCH_KEEP_MEMBLOCK, in particular arm64) // - /sys/firmware/fdt (exists on systems with device trees) package main @@ -29,7 +29,7 @@ func printMM(mm kexec.MemoryMap) { } func main() { - memmap, err := kexec.MemoryMapFromEFI() + memmap, err := kexec.MemoryMapFromSysfsMemmap() if err != nil { log.Printf("/sys/firmware/memmap: %v", err) } else { diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index 7fef2b024b..bcd34e118c 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -207,12 +207,14 @@ func MemoryMapFromFDT(fdt *dt.FDT) (MemoryMap, error) { var memoryMapRoot = "/sys/firmware/memmap/" -// MemoryMapFromEFI reads a firmware-provided memory map from /sys/firmware/memmap. -func MemoryMapFromEFI() (MemoryMap, error) { - return memoryMapFromEFI(memoryMapRoot) +// MemoryMapFromSysfsMemmap reads a firmware-provided memory map from /sys/firmware/memmap. +// +// Linux support for this exists only on X86 at the time of this commit. +func MemoryMapFromSysfsMemmap() (MemoryMap, error) { + return memoryMapFromSysfsMemmap(memoryMapRoot) } -func memoryMapFromEFI(memoryMapDir string) (MemoryMap, error) { +func memoryMapFromSysfsMemmap(memoryMapDir string) (MemoryMap, error) { type memRange struct { // start and end addresses are inclusive start, end uintptr diff --git a/pkg/boot/kexec/memory_map_linux_test.go b/pkg/boot/kexec/memory_map_linux_test.go index 485c11cab0..0da6b79c2e 100644 --- a/pkg/boot/kexec/memory_map_linux_test.go +++ b/pkg/boot/kexec/memory_map_linux_test.go @@ -208,7 +208,7 @@ func TestMemoryMapFromFDT(t *testing.T) { } } -func TestMemoryMapFromEFI(t *testing.T) { +func TestMemoryMapFromSysfsMemmap(t *testing.T) { root := t.TempDir() create := func(dir string, start, end uintptr, typ RangeType) error { @@ -245,12 +245,12 @@ func TestMemoryMapFromEFI(t *testing.T) { {Range: Range{Start: 300, Size: 50}, Type: RangeReserved}, } - phys, err := memoryMapFromEFI(root) + phys, err := memoryMapFromSysfsMemmap(root) if err != nil { - t.Fatalf("MemoryMapFromEFI() error: %v", err) + t.Fatalf("MemoryMapFromSysfsMemmap() error: %v", err) } if !reflect.DeepEqual(phys, want) { - t.Errorf("MemoryMapFromEFI() got %v, want %v", phys, want) + t.Errorf("MemoryMapFromSysfsMemmap() got %v, want %v", phys, want) } } diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index 94e96f153e..af4f8d747b 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -75,7 +75,7 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error Debug("Try parsing memory map...") // TODO(10000TB): refactor this call into initialization of // kexec.Memory, as it does not depend on specific boot. - mm, err := kexec.MemoryMapFromEFI() + mm, err := kexec.MemoryMapFromSysfsMemmap() if err != nil { return fmt.Errorf("parse memory map: %v", err) } diff --git a/pkg/boot/multiboot/multiboot.go b/pkg/boot/multiboot/multiboot.go index 8069f5a031..67ab911f54 100644 --- a/pkg/boot/multiboot/multiboot.go +++ b/pkg/boot/multiboot/multiboot.go @@ -290,7 +290,7 @@ func (m *multiboot) load(debug bool, ibft *ibft.IBFT) error { } log.Printf("Parsing memory map") - memmap, err := kexec.MemoryMapFromEFI() + memmap, err := kexec.MemoryMapFromSysfsMemmap() if err != nil { return fmt.Errorf("error parsing memory map: %v", err) } diff --git a/pkg/boot/uefi/uefi.go b/pkg/boot/uefi/uefi.go index 97092246db..aeb98db6d6 100644 --- a/pkg/boot/uefi/uefi.go +++ b/pkg/boot/uefi/uefi.go @@ -20,10 +20,10 @@ import ( ) var ( - kexecLoad = kexec.Load - kexecMemoryMapFromEFI = kexec.MemoryMapFromEFI - getRSDP = acpi.GetRSDP - getSMBIOSBase = smbios.SMBIOSBase + kexecLoad = kexec.Load + kexecMemoryMapFromSysfsMemmap = kexec.MemoryMapFromSysfsMemmap + getRSDP = acpi.GetRSDP + getSMBIOSBase = smbios.SMBIOSBase ) // SerialPortConfig defines debug port configuration @@ -123,7 +123,7 @@ func (fv *FVImage) Load(verbose bool) error { configAddr := fv.ImageBase - uintptr(uefiPayloadConfigSize) // Get MemoryMap - mm, err := kexecMemoryMapFromEFI() + mm, err := kexecMemoryMapFromSysfsMemmap() if err != nil { return err } diff --git a/pkg/boot/uefi/uefi_test.go b/pkg/boot/uefi/uefi_test.go index ea237f411d..0d74a23634 100644 --- a/pkg/boot/uefi/uefi_test.go +++ b/pkg/boot/uefi/uefi_test.go @@ -32,7 +32,7 @@ func mockKexecLoad(entry uintptr, segments kexec.Segments, flags uint64) error { return nil } -func mockKexecMemoryMapFromEFI() (kexec.MemoryMap, error) { +func mockKexecMemoryMapFromSysfsMemmap() (kexec.MemoryMap, error) { return kexec.MemoryMap{}, nil } @@ -50,8 +50,8 @@ func TestLoadFvImage(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) - kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -97,14 +97,14 @@ func TestLoadFvImageNotFound(t *testing.T) { } } -func TestLoadFvImageFailAtMemoryMapFromEFI(t *testing.T) { +func TestLoadFvImageFailAtMemoryMapFromSysfsMemmap(t *testing.T) { fv, err := New("testdata/fv_with_sec.fd") if err != nil { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) - kexecMemoryMapFromEFI = func() (kexec.MemoryMap, error) { + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = func() (kexec.MemoryMap, error) { return nil, fmt.Errorf("PARSE_MEMORY_MAP_FAILED") } @@ -122,8 +122,8 @@ func TestLoadFvImageFailAtGetRSDP(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) - kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = func() (*acpi.RSDP, error) { @@ -144,8 +144,8 @@ func TestLoadFvImageFailAtGetSMBIOS(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) - kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP @@ -170,8 +170,8 @@ func TestLoadFvImageFailAtKexec(t *testing.T) { t.Fatal(err) } - defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromEFI = old }(kexecMemoryMapFromEFI) - kexecMemoryMapFromEFI = mockKexecMemoryMapFromEFI + defer func(old func() (kexec.MemoryMap, error)) { kexecMemoryMapFromSysfsMemmap = old }(kexecMemoryMapFromSysfsMemmap) + kexecMemoryMapFromSysfsMemmap = mockKexecMemoryMapFromSysfsMemmap defer func(old func() (*acpi.RSDP, error)) { getRSDP = old }(getRSDP) getRSDP = mockGetRSDP From 265367435521ff9429df60af4a2bf8faaaff49b3 Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 30 Jan 2024 18:37:39 +0200 Subject: [PATCH 072/109] pkg/boot/boottest: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/boot/boottest/json.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/boot/boottest/json.go b/pkg/boot/boottest/json.go index 24b49991da..9957f16f77 100644 --- a/pkg/boot/boottest/json.go +++ b/pkg/boot/boottest/json.go @@ -46,11 +46,11 @@ func makeFileRel(u *url.URL) (*url.URL, error) { } relU, err := url.Parse(u.String()) if err != nil { - return nil, fmt.Errorf("error parsing %v: %v", u.String(), err) + return nil, fmt.Errorf("error parsing %v: %w", u.String(), err) } wd, err := os.Getwd() if err != nil { - return nil, fmt.Errorf("error from os.GetWd(): %v", err) + return nil, fmt.Errorf("error from os.GetWd(): %w", err) } relU.Path = strings.TrimPrefix(relU.Path, wd) return relU, nil @@ -81,7 +81,7 @@ func module(r io.ReaderAt) map[string]interface{} { func CompareImagesToJSON(imgs []boot.OSImage, jsonEncoded []byte) error { var want interface{} if err := json.Unmarshal(jsonEncoded, &want); err != nil { - return fmt.Errorf("failed to unmarshall test json %q: %v", jsonEncoded, err) + return fmt.Errorf("failed to unmarshall test json %q: %w", jsonEncoded, err) } got := ImagesToJSONLike(imgs) From f1f7f83ec8eae9ddf0be55af9f2f3aa390eb065b Mon Sep 17 00:00:00 2001 From: Siarhiej Siemianczuk Date: Tue, 30 Jan 2024 18:44:01 +0200 Subject: [PATCH 073/109] pkg/boot/bzimage: wrap errors Signed-off-by: Siarhiej Siemianczuk --- pkg/boot/bzimage/bzimage.go | 20 ++++++++++---------- pkg/boot/bzimage/bzimage_decompress.go | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/boot/bzimage/bzimage.go b/pkg/boot/bzimage/bzimage.go index d1eed75099..fb9e04104c 100644 --- a/pkg/boot/bzimage/bzimage.go +++ b/pkg/boot/bzimage/bzimage.go @@ -117,7 +117,7 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { stripped, err := stripSignature(d) if err != nil { - return fmt.Errorf("error stripping kernel signature: %v", err) + return fmt.Errorf("error stripping kernel signature: %w", err) } d = stripped @@ -161,11 +161,11 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { b.HeadCode = make([]byte, b.Header.PayloadOffset) if _, err := r.Read(b.HeadCode); err != nil { - return fmt.Errorf("can't read HeadCode: %v", err) + return fmt.Errorf("can't read HeadCode: %w", err) } b.compressed = make([]byte, b.Header.PayloadSize) if _, err := r.Read(b.compressed); err != nil { - return fmt.Errorf("can't read KernelCode: %v", err) + return fmt.Errorf("can't read KernelCode: %w", err) } decompressor, err := findDecompressor(b.compressed) if err != nil { @@ -190,14 +190,14 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { var uncompressedLength uint32 last4Bytes := b.compressed[(len(b.compressed) - 4):] if err := binary.Read(bytes.NewBuffer(last4Bytes), binary.LittleEndian, &uncompressedLength); err != nil { - return fmt.Errorf("error reading uncompressed kernel size: %v", err) + return fmt.Errorf("error reading uncompressed kernel size: %w", err) } Debug("Original length of uncompressed kernel is: %d", uncompressedLength) // Use the decompressor and write the decompressed payload into b.KernelCode. var buf bytes.Buffer if err := decompressor(&buf, bytes.NewBuffer(b.compressed)); err != nil { - return fmt.Errorf("error decompressing payload: %v", err) + return fmt.Errorf("error decompressing payload: %w", err) } b.KernelCode = buf.Bytes() @@ -217,13 +217,13 @@ func (b *BzImage) UnmarshalBinary(d []byte) error { } if err := binary.Read(r, binary.LittleEndian, &b.CRC32); err != nil { - return fmt.Errorf("error reading CRC: %v", err) + return fmt.Errorf("error reading CRC: %w", err) } Debug("CRC read from image is: 0x%08x", b.CRC32) b.TailCode = make([]byte, r.Len()) // Read all remaining bytes. if _, err := r.Read(b.TailCode); err != nil { - return fmt.Errorf("can't read TailCode: %v", err) + return fmt.Errorf("can't read TailCode: %w", err) } // Generate the CRC checksum of the entire image until the end of sys_size. @@ -303,7 +303,7 @@ func stripSignature(image []byte) ([]byte, error) { return d, nil } if err := binary.Read(bytes.NewReader(d[peMagicOffset:]), binary.LittleEndian, peImage); err != nil { - return nil, fmt.Errorf("failed to read PE header: %v", err) + return nil, fmt.Errorf("failed to read PE header: %w", err) } // Verify that the image has the PE magic number. if !bytes.Equal(peImage.PEMagic[:], peMagic) { @@ -467,7 +467,7 @@ func compress(b []byte, dictOps string) ([]byte, error) { // "_with_size"). buf := bytes.NewBuffer(dat) if binary.Write(buf, binary.LittleEndian, uint32(len(b))); err != nil { - return nil, fmt.Errorf("failed to append the uncompressed size: %v", err) + return nil, fmt.Errorf("failed to append the uncompressed size: %w", err) } return buf.Bytes(), nil } @@ -695,7 +695,7 @@ func (b *BzImage) InitRAMFS() (int, int, error) { archiver, err := cpio.Format("newc") if err != nil { - return -1, -1, fmt.Errorf("format newc not supported: %v", err) + return -1, -1, fmt.Errorf("format newc not supported: %w", err) } var cur int for cur < len(dat) { diff --git a/pkg/boot/bzimage/bzimage_decompress.go b/pkg/boot/bzimage/bzimage_decompress.go index 5a8dd308c4..7ad64503e6 100644 --- a/pkg/boot/bzimage/bzimage_decompress.go +++ b/pkg/boot/bzimage/bzimage_decompress.go @@ -24,7 +24,7 @@ func stripSize(d decompressor) decompressor { // Read all of the bytes so that we can determine the size. allBytes, err := io.ReadAll(r) if err != nil { - return fmt.Errorf("error reading all bytes: %v", err) + return fmt.Errorf("error reading all bytes: %w", err) } strippedLen := int64(len(allBytes) - 4) Debug("Stripped reader is of length %d bytes", strippedLen) @@ -44,20 +44,20 @@ func execer(command string, args ...string) decompressor { stderrPipe, err := cmd.StderrPipe() if err != nil { - return fmt.Errorf("error creating Stderr pipe: %v", err) + return fmt.Errorf("error creating Stderr pipe: %w", err) } if err := cmd.Start(); err != nil { - return fmt.Errorf("error starting decompressor: %v", err) + return fmt.Errorf("error starting decompressor: %w", err) } stderr, err := io.ReadAll(stderrPipe) if err != nil { - return fmt.Errorf("error reading stderr: %v", err) + return fmt.Errorf("error reading stderr: %w", err) } if err := cmd.Wait(); err != nil || len(stderr) > 0 { - return fmt.Errorf("decompressor failed: err=%v, stderr=%q", err, stderr) + return fmt.Errorf("decompressor failed: err=%w, stderr=%q", err, stderr) } return nil } @@ -68,11 +68,11 @@ func execer(command string, args ...string) decompressor { func gunzip(w io.Writer, r io.Reader) error { gzipReader, err := gzip.NewReader(r) if err != nil { - return fmt.Errorf("error creating gzip reader: %v", err) + return fmt.Errorf("error creating gzip reader: %w", err) } if _, err := io.Copy(w, gzipReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -82,11 +82,11 @@ func gunzip(w io.Writer, r io.Reader) error { func unlzma(w io.Writer, r io.Reader) error { lzmaReader, err := lzma.NewReader(r) if err != nil { - return fmt.Errorf("error creating lzma reader: %v", err) + return fmt.Errorf("error creating lzma reader: %w", err) } if _, err := io.Copy(w, lzmaReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -97,7 +97,7 @@ func unlz4(w io.Writer, r io.Reader) error { lz4Reader := lz4.NewReader(r) if _, err := io.Copy(w, lz4Reader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -108,7 +108,7 @@ func unbzip2(w io.Writer, r io.Reader) error { bzip2Reader := bzip2.NewReader(r) if _, err := io.Copy(w, bzip2Reader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } @@ -118,12 +118,12 @@ func unbzip2(w io.Writer, r io.Reader) error { func unzstd(w io.Writer, r io.Reader) error { zstdReader, err := zstd.NewReader(r) if err != nil { - return fmt.Errorf("failed to create new reader: %v", err) + return fmt.Errorf("failed to create new reader: %w", err) } defer zstdReader.Close() if _, err := io.Copy(w, zstdReader); err != nil { - return fmt.Errorf("failed writing decompressed bytes to writer: %v", err) + return fmt.Errorf("failed writing decompressed bytes to writer: %w", err) } return nil } From 258f0c7071b2428f47e3ce3361625bed592f448f Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 05:08:22 +0000 Subject: [PATCH 074/109] kexec: switch segments to use []byte This makes segment management much easier. It removes the "pool" hack we were using to keep pieces of memory alive. Convert them to kexec format only when actual kexec syscall is called. Signed-off-by: Chris Koch --- pkg/boot/kexec/kexec_load_linux.go | 51 +++++++++++++++++++-- pkg/boot/kexec/memory_linux.go | 71 ++++++++--------------------- pkg/boot/kexec/memory_linux_test.go | 5 +- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/pkg/boot/kexec/kexec_load_linux.go b/pkg/boot/kexec/kexec_load_linux.go index 0604ee4462..0e3c53f92a 100644 --- a/pkg/boot/kexec/kexec_load_linux.go +++ b/pkg/boot/kexec/kexec_load_linux.go @@ -6,6 +6,7 @@ package kexec import ( "fmt" + "runtime" "syscall" "unsafe" @@ -44,18 +45,60 @@ func (e ErrKexec) Error() string { return fmt.Sprintf("kexec_load(entry=%#x, segments=%s, flags %#x) = errno %s", e.Entry, e.Segments, e.Flags, e.Errno) } +// kexecSegment defines kernel memory layout. +type kexecSegment struct { + // Buf points to a buffer in user space. + Buf Range + + // Phys is a physical address of kernel. + Phys Range +} + +func (s Segment) toKexecSegment() kexecSegment { + if s.Buf == nil { + return kexecSegment{ + Buf: Range{Start: 0, Size: 0}, + Phys: s.Phys, + } + } + return kexecSegment{ + Buf: Range{ + Start: uintptr((unsafe.Pointer(&s.Buf[0]))), + Size: uint(len(s.Buf)), + }, + Phys: s.Phys, + } +} + +func (segs Segments) toKexecSegments() []kexecSegment { + var ks []kexecSegment + for _, seg := range segs { + ks = append(ks, seg.toKexecSegment()) + } + return ks +} + // rawLoad is a wrapper around kexec_load(2) syscall. // Preconditions: // - segments must not overlap // - segments must be full pages -func rawLoad(entry uintptr, segments []Segment, flags uint64) error { - if _, _, errno := unix.Syscall6( +func rawLoad(entry uintptr, segments Segments, flags uint64) error { + ks := segments.toKexecSegments() + _, _, errno := unix.Syscall6( unix.SYS_KEXEC_LOAD, entry, uintptr(len(segments)), - uintptr(unsafe.Pointer(&segments[0])), + uintptr(unsafe.Pointer(&ks[0])), uintptr(flags), - 0, 0); errno != 0 { + 0, 0) + // segments (and all the buffers therein) may have gotten freed after + // evaluating Syscall6 arguments, but before the syscall actually + // happens. + for _, seg := range segments { + runtime.KeepAlive(seg.Buf) + } + runtime.KeepAlive(segments) + if errno != 0 { return ErrKexec{ Entry: entry, Segments: segments, diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index e9bbe505d2..144ebc9cdc 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -182,14 +182,10 @@ func (rs Ranges) Sort() { }) } -// pool stores byte slices pointed by the pointers Segments.Buf to -// prevent underlying arrays to be collected by garbage collector. -var pool [][]byte - // Segment defines kernel memory layout. type Segment struct { - // Buf is a buffer in user space. - Buf Range + // Buf is a buffer to map to Phys in kexec. + Buf []byte // Phys is a physical address of kernel. Phys Range @@ -199,27 +195,14 @@ type Segment struct { // Segments should be created using NewSegment method to prevent // data pointed by Segment.Buf to be collected by garbage collector. func NewSegment(buf []byte, phys Range) Segment { - if buf == nil { - return Segment{ - Buf: Range{ - Start: 0, - Size: 0, - }, - Phys: phys, - } - } - pool = append(pool, buf) return Segment{ - Buf: Range{ - Start: uintptr((unsafe.Pointer(&buf[0]))), - Size: uint(len(buf)), - }, + Buf: buf, Phys: phys, } } func (s Segment) String() string { - return fmt.Sprintf("(userspace: %s, phys: %s)", s.Buf, s.Phys) + return fmt.Sprintf("(phys: %s, buffer: size %#x)", s.Phys, len(s.Buf)) } // AlignAndMerge adjusts segs to the preconditions of kexec_load. @@ -288,9 +271,7 @@ func AlignAndMerge(segs Segments) (Segments, error) { // We don't need to deal with the inverse, because kexec_load // will fill the remainder of the segment with zeros anyway // when buf.Size < phys.Size. - if newSegs[i].Buf.Size > newSegs[i].Phys.Size { - newSegs[i].Buf.Size = newSegs[i].Phys.Size - } + newSegs[i].Buf = newSegs[i].realBufTruncate() newSegs[i].Phys.Size = align.UpPage(newSegs[i].Phys.Size) } return newSegs, nil @@ -300,14 +281,16 @@ func AlignAndMerge(segs Segments) (Segments, error) { // or be truncated. func (s Segment) realBufPad() []byte { switch { - case s.Buf.Size == s.Phys.Size: - return s.Buf.toSlice() + case uint(len(s.Buf)) == s.Phys.Size: + return s.Buf - case s.Buf.Size < s.Phys.Size: - return append(s.Buf.toSlice(), make([]byte, int(s.Phys.Size-s.Buf.Size))...) + case uint(len(s.Buf)) < s.Phys.Size: + // Pad Buf. + return append(s.Buf, make([]byte, int(s.Phys.Size-uint(len(s.Buf))))...) - case s.Buf.Size > s.Phys.Size: - return s.Buf.toSlice()[:s.Phys.Size] + case uint(len(s.Buf)) > s.Phys.Size: + // Truncate Buf. + return s.Buf[:s.Phys.Size] } return nil } @@ -315,17 +298,10 @@ func (s Segment) realBufPad() []byte { // realBufTruncate adjusts s.Buf.Size = s.Phys.Size, except when Buf is smaller // than Phys. Buf will either remain the same or be truncated. func (s Segment) realBufTruncate() []byte { - switch { - case s.Buf.Size == s.Phys.Size: - return s.Buf.toSlice() - - case s.Buf.Size < s.Phys.Size: - return s.Buf.toSlice() - - case s.Buf.Size > s.Phys.Size: - return s.Buf.toSlice()[:s.Phys.Size] + if uint(len(s.Buf)) > s.Phys.Size { + return s.Buf[:s.Phys.Size] } - return nil + return s.Buf } func (s *Segment) mergeDisjoint(s2 Segment) bool { @@ -360,14 +336,7 @@ func AlignPhysStart(s Segment) Segment { diff := orig - s.Phys.Start s.Phys.Size = s.Phys.Size + uint(diff) - if s.Buf.Start < diff && diff > 0 { - panic("cannot have virtual memory address within first page") - } - s.Buf.Start -= diff - - if s.Buf.Size > 0 { - s.Buf.Size += uint(diff) - } + s.Buf = append(make([]byte, diff), s.Buf...) return s } @@ -398,7 +367,7 @@ func (segs Segments) Phys() Ranges { // the same buffer content. func (segs Segments) IsSupersetOf(o Segments) error { for _, seg := range o { - size := min(seg.Phys.Size, seg.Buf.Size) + size := min(seg.Phys.Size, uint(len(seg.Buf))) if size == 0 { continue } @@ -407,7 +376,7 @@ func (segs Segments) IsSupersetOf(o Segments) error { if buf == nil { return fmt.Errorf("phys %s not found", r) } - if !bytes.Equal(buf, seg.Buf.toSlice()[:size]) { + if !bytes.Equal(buf, seg.Buf[:size]) { return fmt.Errorf("phys %s contains different bytes", r) } } @@ -420,7 +389,7 @@ func (segs Segments) GetPhys(r Range) []byte { if seg.Phys.IsSupersetOf(r) { offset := r.Start - seg.Phys.Start // TODO: This could be out of range. - buf := seg.Buf.toSlice()[int(offset) : int(offset)+int(r.Size)] + buf := seg.Buf[int(offset) : int(offset)+int(r.Size)] return buf } } diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index 729c0e48c1..8f1309d7f5 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -79,7 +79,7 @@ func TestAlignAndMerge(t *testing.T) { NewSegment(nil, Range{Start: 0, Size: 0x1000}), }, want: Segments{ - NewSegment(nil, Range{Start: 0, Size: 0x1000}), + NewSegment([]byte{}, Range{Start: 0, Size: 0x1000}), }, }, { @@ -181,8 +181,7 @@ func TestAlignAndMerge(t *testing.T) { t.Errorf("AlignAndMerge physical ranges = (-want, +got):\n%s", diff) } for i, s := range got { - b := s.Buf.toSlice() - if diff := cmp.Diff(tt.want[i].Buf.toSlice(), b); diff != "" { + if diff := cmp.Diff(tt.want[i].Buf, s.Buf); diff != "" { t.Errorf("segment %s bytes differ (-want, +got):\n%s", got[i].Phys, diff) } } From 60b3cb4ab5f5339e0fb9285f69b5df3411bcd3e1 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 06:05:10 +0000 Subject: [PATCH 075/109] kexec: simplify error handling Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 10 ++----- pkg/boot/kexec/memory_linux_test.go | 44 ++++++++++++++++++----------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 144ebc9cdc..67fde9b25a 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -21,13 +21,7 @@ var pageMask = uint(os.Getpagesize() - 1) // ErrNotEnoughSpace is returned by the FindSpace family of functions if no // range is large enough to accommodate the request. -type ErrNotEnoughSpace struct { - Size uint -} - -func (e ErrNotEnoughSpace) Error() string { - return fmt.Sprintf("not enough space to allocate %#x bytes", e.Size) -} +var ErrNotEnoughSpace = fmt.Errorf("not enough space to allocate bytes") // Range represents a contiguous uintptr interval [Start, Start+Size). type Range struct { @@ -167,7 +161,7 @@ func (rs Ranges) FindSpaceIn(sz uint, limit Range) (space Range, err error) { return Range{Start: overlap.Start, Size: sz}, nil } } - return Range{}, ErrNotEnoughSpace{Size: sz} + return Range{}, fmt.Errorf("%w: %#x bytes", ErrNotEnoughSpace, sz) } // Sort sorts ranges by their start point. diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index 8f1309d7f5..dbf752a6a4 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -5,6 +5,7 @@ package kexec import ( + "errors" "fmt" "reflect" "testing" @@ -205,7 +206,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, MaxAddr), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no space under 0x1000", @@ -214,7 +215,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0, 0x1000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "disjunct space above 0x1000", @@ -248,7 +249,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, 0x2000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "space is split across 0x1000, with enough space above", @@ -276,7 +277,7 @@ func TestFindSpaceIn(t *testing.T) { }, size: 0x10, limit: RangeFromInterval(0x1000, 0x2000), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "space is split across 0x1000, with enough space in the next one", @@ -293,20 +294,23 @@ func TestFindSpaceIn(t *testing.T) { rs: Ranges{}, size: 0x10, limit: RangeFromInterval(0, MaxAddr), - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, limit: RangeFromInterval(0, MaxAddr), - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { got, err := tt.rs.FindSpaceIn(tt.size, tt.limit) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, tt.limit, got, err, tt.want, tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = %#x, want %#x", tt.rs, tt.size, tt.limit, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpaceIn(%#x, limit = %s) = %v, want %v", tt.rs, tt.size, tt.limit, err, tt.err) } }) } @@ -334,19 +338,22 @@ func TestFindSpace(t *testing.T) { name: "no ranges", rs: Ranges{}, size: 0x10, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { got, err := tt.rs.FindSpace(tt.size) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpace(%#x) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, got, err, tt.want, tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpace(%#x) = %#x, want %#x", tt.rs, tt.size, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpace(%#x) = %v, want %v", tt.rs, tt.size, err, tt.err) } }) } @@ -368,7 +375,7 @@ func TestFindSpaceAbove(t *testing.T) { }, size: 0x10, min: 0x1000, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "disjunct space above 0x1000", @@ -414,19 +421,22 @@ func TestFindSpaceAbove(t *testing.T) { name: "no ranges", rs: Ranges{}, size: 0x10, - err: ErrNotEnoughSpace{Size: 0x10}, + err: ErrNotEnoughSpace, }, { name: "no ranges, zero size", rs: Ranges{}, size: 0, - err: ErrNotEnoughSpace{Size: 0}, + err: ErrNotEnoughSpace, }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { got, err := tt.rs.FindSpaceAbove(tt.size, tt.min) - if !reflect.DeepEqual(got, tt.want) || err != tt.err { - t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = (%#x, %v), want (%#x, %v)", tt.rs, tt.size, tt.min, got, err, tt.want, tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = %#x, want %#x", tt.rs, tt.size, tt.min, got, tt.want) + } + if !errors.Is(err, tt.err) { + t.Errorf("%s.FindSpaceAbove(%#x, min=%#x) = %v, want %v", tt.rs, tt.size, tt.min, err, tt.err) } }) } From 122a5784b42fcdb4be16b1801556d3dd0431c8f4 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 02:02:32 +0000 Subject: [PATCH 076/109] align: generic aligning Signed-off-by: Chris Koch --- go.mod | 2 +- pkg/align/align.go | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 126e188bb4..53e2c80500 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 golang.org/x/net v0.19.0 golang.org/x/sys v0.15.0 golang.org/x/term v0.15.0 @@ -81,7 +82,6 @@ require ( github.com/sahilm/fuzzy v0.1.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect golang.org/x/arch v0.2.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.5.0 // indirect ) diff --git a/pkg/align/align.go b/pkg/align/align.go index d401eb6dd1..3b31d63d1d 100644 --- a/pkg/align/align.go +++ b/pkg/align/align.go @@ -8,10 +8,12 @@ // size need be a power of 2. package align +import "golang.org/x/exp/constraints" + // Up aligns v up to next multiple of alignSize. // // alignSize need be a power of 2. -func Up(v uint, alignSize uint) uint { +func Up[T constraints.Unsigned](v T, alignSize T) T { mask := alignSize - 1 return (v + mask) &^ mask } @@ -19,16 +21,23 @@ func Up(v uint, alignSize uint) uint { // Down aligns v down to a previous multiple of alignSize. // // alignSize need be a power of 2. -func Down(v uint, alignSize uint) uint { +func Down[T constraints.Unsigned](v T, alignSize T) T { return Up(v-(alignSize-1), alignSize) } // UpPage aligns v up by system page size. -func UpPage(v uint) uint { - return Up(v, pageSize) +func UpPage[T constraints.Unsigned](v T) T { + return Up(v, T(pageSize)) } // DownPage aligns v down by system page size. -func DownPage(v uint) uint { - return Down(v, pageSize) +func DownPage[T constraints.Unsigned](v T) T { + return Down(v, T(pageSize)) +} + +// IsAligned checks whether v is aligned to alignSize. +// +// alignSize need be a power of 2. +func IsAligned[T constraints.Unsigned](v T, alignSize T) bool { + return v%alignSize == 0 } From 50099a82951b780974ba9203388ea9ee57997189 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 02:10:14 +0000 Subject: [PATCH 077/109] FindSpace: add ability to ask for aligned hole Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 85 ++++++++++++-- pkg/boot/kexec/memory_linux_test.go | 165 +++++++++++++++++++++++++++- 2 files changed, 239 insertions(+), 11 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 67fde9b25a..6975652862 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -63,6 +63,18 @@ func (r Range) Contains(p uintptr) bool { return r.Start <= p && p < r.End() } +// WithStart returns a range that begins at start and ends at r.End(). +func (r Range) WithStart(start uintptr) Range { + switch { + case r.Start > start: + return Range{Start: start, Size: r.Size + uint(r.Start-start)} + case r.Start == start: + return Range{Start: start, Size: 0} + default: + return Range{Start: start, Size: r.Size - uint(start-r.Start)} + } +} + // Intersect returns the continuous range of points common to r and r2 if there // is one. func (r Range) Intersect(r2 Range) *Range { @@ -136,14 +148,6 @@ func (rs Ranges) Minus(r Range) Ranges { return ram } -// FindSpace finds a continuous piece of sz points within Ranges and returns -// the Range pointing to it. -// -// If alignSizeBytes is zero, align up by page size. -func (rs Ranges) FindSpace(sz uint) (space Range, err error) { - return rs.FindSpaceAbove(sz, 0) -} - // MaxAddr is the highest address in a 64bit address space. const MaxAddr = ^uintptr(0) @@ -156,9 +160,70 @@ func (rs Ranges) FindSpaceAbove(sz uint, minAddr uintptr) (space Range, err erro // FindSpaceIn finds a continuous piece of sz points within Ranges and returns // a Range where space.Start >= limit.Start, with space.End() < limit.End(). func (rs Ranges) FindSpaceIn(sz uint, limit Range) (space Range, err error) { + return rs.FindSpace(sz, WithinRange(limit)) +} + +type findSpaceOptions struct { + limit Range + size uint + startAlign uint +} + +// FindOptioner is a config option for FindSpace. +type FindOptioner func(o *findSpaceOptions) + +// WithMinimumAddr requires FindSpace to return a range with an address above +// minAddr. +func WithMinimumAddr(minAddr uintptr) FindOptioner { + return func(o *findSpaceOptions) { + o.limit.Start = minAddr + o.limit.Size -= uint(minAddr) + } +} + +// WithinRange requires FindSpace to return a range within the limit. +func WithinRange(limit Range) FindOptioner { + return func(o *findSpaceOptions) { + o.limit = limit + } +} + +// WithAlignment requires FindSpace to return a range with an address and size +// aligned to alignSize. +func WithAlignment(alignSize uint) FindOptioner { + return func(o *findSpaceOptions) { + o.size = align.Up(o.size, alignSize) + o.startAlign = alignSize + } +} + +// WithStartAlignment requires FindSpace to return a range with an address +// aligned to alignSize. +func WithStartAlignment(alignSize uint) FindOptioner { + return func(o *findSpaceOptions) { + o.startAlign = alignSize + } +} + +// FindSpace finds a continuous piece of sz points within Ranges and the given +// options and returns the Range pointing to it. +func (rs Ranges) FindSpace(sz uint, opts ...FindOptioner) (Range, error) { + o := &findSpaceOptions{ + limit: RangeFromInterval(0, MaxAddr), + size: sz, + } + for _, opt := range opts { + opt(o) + } + if o.startAlign != 0 && !align.IsAligned(o.limit.Start, uintptr(o.startAlign)) { + o.limit = o.limit.WithStart(align.Up(o.limit.Start, uintptr(o.startAlign))) + } for _, r := range rs { - if overlap := r.Intersect(limit); overlap != nil && overlap.Size >= sz { - return Range{Start: overlap.Start, Size: sz}, nil + if o.startAlign != 0 && !align.IsAligned(r.Start, uintptr(o.startAlign)) { + r = r.WithStart(align.Up(r.Start, uintptr(o.startAlign))) + } + if overlap := r.Intersect(o.limit); overlap != nil && overlap.Size >= o.size { + return Range{Start: overlap.Start, Size: o.size}, nil } } return Range{}, fmt.Errorf("%w: %#x bytes", ErrNotEnoughSpace, sz) diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index dbf752a6a4..c20bee2a85 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -320,6 +320,7 @@ func TestFindSpace(t *testing.T) { for i, tt := range []struct { name string rs Ranges + opts []FindOptioner size uint want Range err error @@ -346,9 +347,171 @@ func TestFindSpace(t *testing.T) { size: 0, err: ErrNotEnoughSpace, }, + { + name: "no space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + err: ErrNotEnoughSpace, + }, + { + name: "disjunct space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space above", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1010}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space in the next one", + rs: Ranges{ + Range{Start: 0x0, Size: 0x100f}, + Range{Start: 0x1010, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithMinimumAddr(0x1000)}, + want: Range{Start: 0x1010, Size: 0x10}, + }, + { + name: "just enough space under 0x1000", + rs: Ranges{ + Range{Start: 0xFF, Size: 0xf}, + Range{Start: 0xFF0, Size: 0x10}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "no space under 0x1000", + rs: Ranges{ + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + err: ErrNotEnoughSpace, + }, + { + name: "disjunct space above 0x1000", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "just enough space under 0x1000", + rs: Ranges{ + Range{Start: 0xFF, Size: 0xf}, + Range{Start: 0xFF0, Size: 0x10}, + Range{Start: 0x1000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "all spaces abvoe 0x1000 and under 0x2000 are too small", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1000, Size: 0xf}, + Range{Start: 0x1010, Size: 0xf}, + Range{Start: 0x1f00, Size: 0xf}, + Range{Start: 0x2000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, 0x2000))}, + err: ErrNotEnoughSpace, + }, + { + name: "space is split across 0x1000, with enough space above", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1010}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1000, Size: 0x10}, + }, + { + name: "space is split across 0x1000, with enough space under", + rs: Ranges{ + Range{Start: 0xFF0, Size: 0x20}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0, 0x1000))}, + want: Range{Start: 0xFF0, Size: 0x10}, + }, + { + name: "space is split across 0x1000 and 0x2000, but not enough space above or below", + rs: Ranges{ + Range{Start: 0xFF1, Size: 0xf + 0xf}, + Range{Start: 0x1FF1, Size: 0xf + 0xf}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, 0x2000))}, + err: ErrNotEnoughSpace, + }, + { + name: "space is split across 0x1000, with enough space in the next one", + rs: Ranges{ + Range{Start: 0x0, Size: 0x100f}, + Range{Start: 0x1010, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x1000, MaxAddr))}, + want: Range{Start: 0x1010, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x10}, + Range{Start: 0x2000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithStartAlignment(0x1000)}, + want: Range{Start: 0x2000, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x1010}, + Range{Start: 0x3000, Size: 0x10}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithStartAlignment(0x1000)}, + want: Range{Start: 0x2000, Size: 0x10}, + }, + { + name: "alignment with limit", + rs: Ranges{ + Range{Start: 0x0, Size: 0x1000}, + Range{Start: 0x1010, Size: 0x1010}, + Range{Start: 0x3000, Size: 0x1000}, + }, + size: 0x10, + opts: []FindOptioner{WithinRange(RangeFromInterval(0x500, MaxAddr)), WithAlignment(0x1000)}, + want: Range{Start: 0x3000, Size: 0x1000}, + }, } { t.Run(fmt.Sprintf("test_%d_%s", i, tt.name), func(t *testing.T) { - got, err := tt.rs.FindSpace(tt.size) + got, err := tt.rs.FindSpace(tt.size, tt.opts...) if !reflect.DeepEqual(got, tt.want) { t.Errorf("%s.FindSpace(%#x) = %#x, want %#x", tt.rs, tt.size, got, tt.want) } From f301554d6862e1a1c500ab1bc4d09494145d80d9 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 31 Jan 2024 16:28:46 -0300 Subject: [PATCH 078/109] termios: support netbsd I don't have a netbsd box to test, but per my research, this seems like it should work... If it doesn't, happy to fix whatever I did wrong. Thanks! Signed-off-by: Carlos Alexandro Becker --- pkg/termios/sgtty_netbsd.go | 18 ++++++++++++++++++ pkg/termios/sgtty_unix.go | 4 ++-- pkg/termios/termios_bsd.go | 4 ++-- pkg/termios/var_netbsd.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 pkg/termios/sgtty_netbsd.go create mode 100644 pkg/termios/var_netbsd.go diff --git a/pkg/termios/sgtty_netbsd.go b/pkg/termios/sgtty_netbsd.go new file mode 100644 index 0000000000..784a3d4ce9 --- /dev/null +++ b/pkg/termios/sgtty_netbsd.go @@ -0,0 +1,18 @@ +// Copyright 2021 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package termios + +import "golang.org/x/sys/unix" + +const ( + gets = unix.TIOCGETA + sets = unix.TIOCSETA + getWinSize = unix.TIOCGWINSZ + setWinSize = unix.TIOCSWINSZ +) + +func speed(speed int) int32 { + return int32(speed) +} diff --git a/pkg/termios/sgtty_unix.go b/pkg/termios/sgtty_unix.go index 4d306e3945..f561d16437 100644 --- a/pkg/termios/sgtty_unix.go +++ b/pkg/termios/sgtty_unix.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !plan9 && !windows && !darwin && !freebsd && !openbsd -// +build !plan9,!windows,!darwin,!freebsd,!openbsd +//go:build !plan9 && !windows && !darwin && !freebsd && !openbsd && !netbsd +// +build !plan9,!windows,!darwin,!freebsd,!openbsd,!netbsd package termios diff --git a/pkg/termios/termios_bsd.go b/pkg/termios/termios_bsd.go index 2ff943cc8e..30fb0ec751 100644 --- a/pkg/termios/termios_bsd.go +++ b/pkg/termios/termios_bsd.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || freebsd || openbsd -// +build darwin freebsd openbsd +//go:build darwin || freebsd || openbsd || netbsd +// +build darwin freebsd openbsd netbsd package termios diff --git a/pkg/termios/var_netbsd.go b/pkg/termios/var_netbsd.go new file mode 100644 index 0000000000..b29418b30a --- /dev/null +++ b/pkg/termios/var_netbsd.go @@ -0,0 +1,33 @@ +// Copyright 2021 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package termios + +import ( + "golang.org/x/sys/unix" +) + +// baud2unixB convert a baudrate to the corresponding unix const. +var baud2unixB = map[int]int32{ + 50: unix.B50, + 75: unix.B75, + 110: unix.B110, + 134: unix.B134, + 150: unix.B150, + 200: unix.B200, + 300: unix.B300, + 600: unix.B600, + 1200: unix.B1200, + 1800: unix.B1800, + 2400: unix.B2400, + 4800: unix.B4800, + 9600: unix.B9600, + 19200: unix.B19200, + 38400: unix.B38400, + 57600: unix.B57600, + 115200: unix.B115200, + 230400: unix.B230400, +} + +func toTermiosCflag(r int32) uint32 { return uint32(r) } From 809a676c7dc1b39b57c9f8207c368b06b664108a Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 06:53:24 +0000 Subject: [PATCH 079/109] dt: API to create device trees compactly Signed-off-by: Chris Koch --- pkg/dt/node.go | 58 ++++++++++++++++++++++++ pkg/dt/node_test.go | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/pkg/dt/node.go b/pkg/dt/node.go index e7bd10d380..1166e5d38e 100644 --- a/pkg/dt/node.go +++ b/pkg/dt/node.go @@ -60,6 +60,34 @@ type Node struct { Children []*Node `json:",omitempty"` } +// NodeOptioner is used to modify a Node for NewNode. +type NodeOptioner func(n *Node) + +// WithProperty adds the given properties to the node. +func WithProperty(p ...Property) NodeOptioner { + return func(n *Node) { + n.Properties = append(n.Properties, p...) + } +} + +// WithChildren adds childen to the node. +func WithChildren(c ...*Node) NodeOptioner { + return func(n *Node) { + n.Children = append(n.Children, c...) + } +} + +// NewNode creates a node. +func NewNode(name string, opts ...NodeOptioner) *Node { + n := &Node{ + Name: name, + } + for _, opt := range opts { + opt(n) + } + return n +} + // FindFirstMatchingChildIndex returns the index of the first immediate child node satisfying the // given predicate. func (n *Node) FindFirstMatchingChildIndex(predicate func(*Node) bool) (int, bool) { @@ -184,6 +212,36 @@ func (n *Node) UpdateProperty(name string, value []byte) bool { return false } +// Update updates an existing property with the given one, if they match in +// name, or adds a new property. +func (n *Node) Update(prop Property) bool { + p, found := n.LookProperty(prop.Name) + if found { + p.Value = prop.Value + return true + } + n.Properties = append(n.Properties, prop) + return false +} + +// PropertyU64 creates a uint64 property. +func PropertyU64(name string, value uint64) Property { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, value) + return Property{ + Name: name, + Value: b, + } +} + +// PropertyString creates a string property. +func PropertyString(name string, value string) Property { + return Property{ + Name: name, + Value: append([]byte(value), 0), + } +} + // Property is a name-value pair. Note the PropertyType of Value is not // encoded. type Property struct { diff --git a/pkg/dt/node_test.go b/pkg/dt/node_test.go index 29e303a2f3..0bd289d771 100644 --- a/pkg/dt/node_test.go +++ b/pkg/dt/node_test.go @@ -69,6 +69,58 @@ func TestLookupImmediateChild(t *testing.T) { } } +func TestNewNode(t *testing.T) { + for _, tt := range []struct { + name string + opts []NodeOptioner + node *Node + }{ + { + name: "new", + opts: nil, + node: &Node{Name: "new"}, + }, + { + name: "new-with-property", + opts: []NodeOptioner{WithProperty(PropertyU64("foo", 1))}, + node: &Node{ + Name: "new-with-property", + Properties: []Property{ + { + Name: "foo", + Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}, + }, + }, + }, + }, + { + name: "new-with-children", + opts: []NodeOptioner{WithChildren(NewNode("child", WithProperty(PropertyString("foo", "abc"))))}, + node: &Node{ + Name: "new-with-children", + Children: []*Node{ + { + Name: "child", + Properties: []Property{ + { + Name: "foo", + Value: []byte{'a', 'b', 'c', 0}, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + n := NewNode(tt.name, tt.opts...) + if !reflect.DeepEqual(n, tt.node) { + t.Errorf("NewNode = %v, want %v", n, tt.node) + } + }) + } +} + func TestPruneSubtree(t *testing.T) { subtree1 := &Node{ Name: "child1", @@ -270,6 +322,59 @@ func TestRemoveProperty(t *testing.T) { } } +func TestUpdate(t *testing.T) { + for _, tt := range []struct { + node *Node + prop Property + want *Node + found bool + }{ + { + node: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{1, 2, 3}}, + }, + }, + prop: PropertyU64("kaslr-seed", 1), + want: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}}, + }, + }, + found: true, + }, + { + node: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + }, + }, + prop: PropertyU64("kaslr-seed", 1), + want: &Node{ + Name: "test node", + Properties: []Property{ + {Name: "linux,usable-memory-range", Value: []byte{1, 2, 3}}, + {Name: "kaslr-seed", Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}}, + }, + }, + found: false, + }, + } { + got := tt.node.Update(tt.prop) + if got != tt.found { + t.Errorf("Node update, want %v, got %v", got, tt.found) + } + if !reflect.DeepEqual(tt.node, tt.want) { + t.Errorf("Node update, want %v, got %v", tt.node, tt.want) + } + } +} + func TestUpdateProperty(t *testing.T) { node := &Node{ Name: "test node", From d39ddc8077c9cb178e84d8ce78cab6e2a7052315 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 02:24:02 +0000 Subject: [PATCH 080/109] arm64 kexec: separate testable code Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_arm64.go | 215 +------------------------- pkg/boot/linux/load_linux_image.go | 234 +++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 210 deletions(-) create mode 100644 pkg/boot/linux/load_linux_image.go diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index dc2ec31ae9..2b575b291e 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -5,226 +5,21 @@ package linux import ( - "bytes" - "encoding/binary" "fmt" "os" - "syscall" - "github.com/u-root/u-root/pkg/boot/image" "github.com/u-root/u-root/pkg/boot/kexec" - "github.com/u-root/u-root/pkg/dt" - "github.com/u-root/u-root/pkg/uio" - "golang.org/x/sys/unix" ) -const ( - kernelAlignSize = 1 << 21 // 2 MB. -) - -func mmap(f *os.File) (data []byte, ummap func() error, err error) { - s, err := f.Stat() - if err != nil { - return nil, nil, fmt.Errorf("stat error: %w", err) - } - if s.Size() == 0 { - return nil, nil, fmt.Errorf("cannot mmap zero-len file") - } - d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) - if err != nil { - return nil, nil, fmt.Errorf("mmap failed: %w", err) - } - - ummap = func() error { - return unix.Munmap(d) - } - - return d, ummap, nil -} - -// sanitizeFDT cleanups boot param properties from chosen node of the given FDT. -func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { - // Clear old entries in case we've already been through kexec to get - // to this instance of runtime. - chosen, _ := fdt.NodeByName("chosen") - if chosen == nil { - return nil, fmt.Errorf("no /chosen node in device tree") - } - for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} { - chosen.RemoveProperty(property) - } - - return chosen, nil -} - // KexecLoad loads arm64 Image, with the given ramfs and kernel cmdline. func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { - var err error - // kmem is a struct holding kexec segments. - // - // It has routines to work with physical memory - // ranges. - var kmem *kexec.Memory - var kernelRange, ramfsRange, dtbRange, trampolineRange kexec.Range - - fdt, err := dt.LoadFDT(opts.DTB) - if err != nil { - return fmt.Errorf("loadFDT(%s) = %v", opts.DTB, err) - } - Debug("Loaded FDT: %s", fdt) - - chosen, err := sanitizeFDT(fdt) - if err != nil { - return fmt.Errorf("sanitizeFDT(%v) = %v", fdt, err) - } - Debug("FDT after sanitization: %s", fdt) - - // Prepare segments. - Debug("Try parsing memory map...") - mm, err := kexec.MemoryMapFromFDT(fdt) - if err != nil { - return fmt.Errorf("MemoryMapFromFDT(%v): %v", fdt, err) - } - kmem = &kexec.Memory{ - Phys: mm, - } - Debug("Mem map: \n%+v", kmem.Phys) - - // Load kernel. - var kernelBuf []byte - if opts.MmapKernel { - Debug("Mmapping kernel to virtual buffer...") - var cleanup func() error - kernelBuf, cleanup, err = mmap(kernel) - if err != nil { - return fmt.Errorf("mmap kernel: %v", err) - } - defer func() { - if err = cleanup(); err != nil { - Debug("Ummap kernel failed: %v", err) - } - }() - } else { - Debug("Read kernel from file ...") - kernelBuf, err = uio.ReadAll(kernel) - if err != nil { - return fmt.Errorf("read kernel from file: %v", err) - } - } - - kImage, err := image.ParseFromBytes(kernelBuf) + img, err := kexecLoadImage(kernel, ramfs, cmdline, opts) if err != nil { - return fmt.Errorf("parse arm64 Image from bytes: %v", err) - } - - if kernelRange, err = kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize+kImage.Header.TextOffset), uint(kImage.Header.TextOffset), kernelAlignSize); err != nil { - return fmt.Errorf("add kernel segment: %v", err) - } - - Debug("Added %d byte (size %d) kernel at %s", len(kernelBuf), kImage.Header.ImageSize, kernelRange) - - var ramfsBuf []byte - if ramfs != nil { - if opts.MmapRamfs { - Debug("Mmap ramfs file to virtual buffer...") - var cleanup func() error - ramfsBuf, cleanup, err = mmap(ramfs) - if err != nil { - return fmt.Errorf("mmap ramfs: %v", err) - } - defer func() { - if err = cleanup(); err != nil { - Debug("Ummap ramfs failed: %v", err) - } - }() - } else { - Debug("Read ramfs from file...") - ramfsBuf, err = uio.ReadAll(ramfs) - if err != nil { - return fmt.Errorf("read ramfs from file: %v", err) - } - } + return err } - - // NOTE(10000TB): This need be placed after kernel by convention. - if ramfsRange, err = kmem.AddKexecSegment(ramfsBuf); err != nil { - return fmt.Errorf("add initramfs segment: %v", err) + defer img.clean() + if err = kexec.Load(img.entry, img.segments, 0); err != nil { + return fmt.Errorf("kexec Load(%v, %v, %d) = %v", img.entry, img.segments, 0, err) } - Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) - - ramfsStart := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) - chosen.UpdateProperty("linux,initrd-start", ramfsStart) - ramfsEnd := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) - chosen.UpdateProperty("linux,initrd-end", ramfsEnd) - - Debug("Kernel cmdline to append: %s", cmdline) - if len(cmdline) > 0 { - cmdlineBuf := append([]byte(cmdline), byte(0)) - chosen.UpdateProperty("bootargs", cmdlineBuf) - } else { - chosen.RemoveProperty("bootargs") - } - - dtbBuffer := &bytes.Buffer{} - _, err = fdt.Write(dtbBuffer) - if err != nil { - return fmt.Errorf("flattening device tree: %v", err) - } - dtbBuf := dtbBuffer.Bytes() - if dtbRange, err = kmem.AddKexecSegment(dtbBuf); err != nil { - return fmt.Errorf("add device tree segment: %w", err) - } - Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange) - - // Trampoline. - // - // We need a trampoline to pass the DTB to the kernel; because - // we'll use this code as our entry point, it also needs to know - // the real entry point to kernel. - // - // TODO(10000TB): this assumes a little endian kernel, support - // big endian if needed per flag. - kernelEntry := kernelRange.Start - dtbBase := dtbRange.Start - - var trampoline [10]uint32 - // Instruction encoding per - // "Arm Architecture Reference Manual Armv8, for Armv8-A architecture - // profile" [ ARM DDI 0487E.a (ID070919) ] - trampoline[0] = 0x580000c4 // ldr x4, #0x18 (PC relative: trampoline[6 and 7]) - trampoline[1] = 0x580000e0 // ldr x0, #0x1c (PC relative: trampoline[8 and 9]) - // Zero out x1, x2, x3 - trampoline[2] = 0xaa1f03e1 // mov x1, xzr - trampoline[3] = 0xaa1f03e2 // mov x2, xzr - trampoline[4] = 0xaa1f03e3 // mov x3, xzr - // Branch register / Jump to instruction from x4. - trampoline[5] = 0xd61f0080 // br x4 - - trampoline[6] = uint32(uint64(kernelEntry) & 0xffffffff) - trampoline[7] = uint32(uint64(kernelEntry) >> 32) - trampoline[8] = uint32(uint64(dtbBase) & 0xffffffff) - trampoline[9] = uint32(uint64(dtbBase) >> 32) - - trampolineBuffer := new(bytes.Buffer) - err = binary.Write(trampolineBuffer, binary.LittleEndian, trampoline) - if err != nil { - return fmt.Errorf("make trampoline: %v", err) - } - Debug("trampoline bytes %x", trampolineBuffer.Bytes()) - trampolineRange, err = kmem.AddKexecSegment(trampolineBuffer.Bytes()) - if err != nil { - return fmt.Errorf("add trampoline segment: %v", err) - } - Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange) - - /* Load it */ - entry := trampolineRange.Start - Debug("Entry: %#x", entry) - if err = kexec.Load(entry, kmem.Segments, 0); err != nil { - return fmt.Errorf("kexec Load(%v, %v, %d) = %v", entry, kmem.Segments, 0, err) - } - return nil } diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go new file mode 100644 index 0000000000..17ec442eda --- /dev/null +++ b/pkg/boot/linux/load_linux_image.go @@ -0,0 +1,234 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "bytes" + "encoding/binary" + "fmt" + "os" + "syscall" + + "github.com/u-root/u-root/pkg/boot/image" + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" + "github.com/u-root/u-root/pkg/uio" + "golang.org/x/sys/unix" +) + +type kimage struct { + segments kexec.Segments + entry uintptr + cleanup []func() error +} + +func (k kimage) clean() { + for _, c := range k.cleanup { + if err := c(); err != nil { + Debug("Failure: %v", err) + } + } +} + +const ( + kernelAlignSize = 1 << 21 // 2 MB. +) + +func mmap(f *os.File) (data []byte, ummap func() error, err error) { + s, err := f.Stat() + if err != nil { + return nil, nil, fmt.Errorf("stat error: %w", err) + } + if s.Size() == 0 { + return nil, nil, fmt.Errorf("cannot mmap zero-len file") + } + d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) + if err != nil { + return nil, nil, fmt.Errorf("mmap failed: %w", err) + } + + ummap = func() error { + if err := unix.Munmap(d); err != nil { + return fmt.Errorf("failed to unmap %s: %v", f.Name(), err) + } + return nil + } + + return d, ummap, nil +} + +// sanitizeFDT cleanups boot param properties from chosen node of the given FDT. +func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { + // Clear old entries in case we've already been through kexec to get + // to this instance of runtime. + chosen, _ := fdt.NodeByName("chosen") + if chosen == nil { + return nil, fmt.Errorf("no /chosen node in device tree") + } + for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} { + chosen.RemoveProperty(property) + } + + return chosen, nil +} + +func kexecLoadImage(kernel, ramfs *os.File, cmdline string, opts KexecOptions) (*kimage, error) { + fdt, err := dt.LoadFDT(opts.DTB) + if err != nil { + return nil, fmt.Errorf("loadFDT(%s) = %v", opts.DTB, err) + } + Debug("Loaded FDT: %s", fdt) + + // Prepare segments. + Debug("Try parsing memory map...") + mm, err := kexec.MemoryMapFromFDT(fdt) + if err != nil { + return nil, fmt.Errorf("MemoryMapFromFDT(%v): %v", fdt, err) + } + Debug("Mem map: \n%+v", mm) + return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline, opts) +} + +func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, cmdline string, opts KexecOptions) (*kimage, error) { + kmem := &kexec.Memory{ + Phys: mm, + } + + img := &kimage{} + // Load kernel. + var kernelBuf []byte + var err error + if opts.MmapKernel { + Debug("Mmapping kernel to virtual buffer...") + var cleanup func() error + kernelBuf, cleanup, err = mmap(kernel) + if err != nil { + return nil, fmt.Errorf("mmap kernel: %v", err) + } + img.cleanup = append(img.cleanup, cleanup) + } else { + Debug("Read kernel from file ...") + kernelBuf, err = uio.ReadAll(kernel) + if err != nil { + return nil, fmt.Errorf("read kernel from file: %v", err) + } + } + + kImage, err := image.ParseFromBytes(kernelBuf) + if err != nil { + return nil, fmt.Errorf("parse arm64 Image from bytes: %v", err) + } + + kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize+kImage.Header.TextOffset), uint(kImage.Header.TextOffset), kernelAlignSize) + if err != nil { + return nil, fmt.Errorf("add kernel segment: %v", err) + } + + Debug("Added %d byte (size %d) kernel at %s", len(kernelBuf), kImage.Header.ImageSize, kernelRange) + + chosen, err := sanitizeFDT(fdt) + if err != nil { + return nil, fmt.Errorf("sanitizeFDT(%v) = %v", fdt, err) + } + Debug("FDT after sanitization: %s", fdt) + + var ramfsBuf []byte + if ramfs != nil { + if opts.MmapRamfs { + Debug("Mmap ramfs file to virtual buffer...") + var cleanup func() error + ramfsBuf, cleanup, err = mmap(ramfs) + if err != nil { + return nil, fmt.Errorf("mmap ramfs: %v", err) + } + img.cleanup = append(img.cleanup, cleanup) + } else { + Debug("Read ramfs from file...") + ramfsBuf, err = uio.ReadAll(ramfs) + if err != nil { + return nil, fmt.Errorf("read ramfs from file: %v", err) + } + } + } + + // NOTE(10000TB): This need be placed after kernel by convention. + ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) + if err != nil { + return nil, fmt.Errorf("add initramfs segment: %v", err) + } + Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) + + ramfsStart := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) + chosen.UpdateProperty("linux,initrd-start", ramfsStart) + ramfsEnd := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) + chosen.UpdateProperty("linux,initrd-end", ramfsEnd) + + Debug("Kernel cmdline to append: %s", cmdline) + if len(cmdline) > 0 { + cmdlineBuf := append([]byte(cmdline), byte(0)) + chosen.UpdateProperty("bootargs", cmdlineBuf) + } else { + chosen.RemoveProperty("bootargs") + } + + var dtbBuffer bytes.Buffer + if _, err := fdt.Write(&dtbBuffer); err != nil { + return nil, fmt.Errorf("flattening device tree: %v", err) + } + dtbBuf := dtbBuffer.Bytes() + dtbRange, err := kmem.AddKexecSegment(dtbBuf) + if err != nil { + return nil, fmt.Errorf("add device tree segment: %w", err) + } + Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange) + + // Trampoline. + // + // We need a trampoline to pass the DTB to the kernel; because + // we'll use this code as our entry point, it also needs to know + // the real entry point to kernel. + // + // TODO(10000TB): this assumes a little endian kernel, support + // big endian if needed per flag. + kernelEntry := kernelRange.Start + dtbBase := dtbRange.Start + + var trampoline [10]uint32 + // Instruction encoding per + // "Arm Architecture Reference Manual Armv8, for Armv8-A architecture + // profile" [ ARM DDI 0487E.a (ID070919) ] + trampoline[0] = 0x580000c4 // ldr x4, #0x18 (PC relative: trampoline[6 and 7]) + trampoline[1] = 0x580000e0 // ldr x0, #0x1c (PC relative: trampoline[8 and 9]) + // Zero out x1, x2, x3 + trampoline[2] = 0xaa1f03e1 // mov x1, xzr + trampoline[3] = 0xaa1f03e2 // mov x2, xzr + trampoline[4] = 0xaa1f03e3 // mov x3, xzr + // Branch register / Jump to instruction from x4. + trampoline[5] = 0xd61f0080 // br x4 + + trampoline[6] = uint32(uint64(kernelEntry) & 0xffffffff) + trampoline[7] = uint32(uint64(kernelEntry) >> 32) + trampoline[8] = uint32(uint64(dtbBase) & 0xffffffff) + trampoline[9] = uint32(uint64(dtbBase) >> 32) + + var trampolineBuffer bytes.Buffer + if err := binary.Write(&trampolineBuffer, binary.LittleEndian, trampoline); err != nil { + return nil, fmt.Errorf("make trampoline: %v", err) + } + Debug("trampoline bytes %x", trampolineBuffer.Bytes()) + trampolineRange, err := kmem.AddKexecSegment(trampolineBuffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("add trampoline segment: %v", err) + } + Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange) + + /* Load it */ + img.entry = trampolineRange.Start + img.segments = kmem.Segments + Debug("Entry: %#x", img.entry) + return img, nil +} From 7250a4a83b6c98b540240b49d3f1be7349532026 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 03:56:41 +0000 Subject: [PATCH 081/109] kexec,linux,arm64: fix adding kexec alignment Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 8 +------- pkg/boot/linux/load_linux_image.go | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 6975652862..622cd0bc0d 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -575,13 +575,7 @@ func (m *Memory) AddKexecSegment(d []byte) (Range, error) { // AddKexecSegmentExplicit adds d to a new kexec segment, but allows asking // for extra space, secifying alignment size, and setting text_offset. func (m *Memory) AddKexecSegmentExplicit(d []byte, sz, offset, alignSizeBytes uint) (Range, error) { - if sz < uint(len(d)) { - return Range{}, fmt.Errorf("length of d is more than size requested") - } - if offset > sz { - return Range{}, fmt.Errorf("offset is larger than size requested") - } - r, err := m.FindSpace(sz, alignSizeBytes) + r, err := m.AvailableRAM().FindSpace(offset+sz, WithAlignment(alignSizeBytes)) if err != nil { return Range{}, err } diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 17ec442eda..0189ea1d6f 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -121,12 +121,12 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c return nil, fmt.Errorf("parse arm64 Image from bytes: %v", err) } - kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize+kImage.Header.TextOffset), uint(kImage.Header.TextOffset), kernelAlignSize) + kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize), uint(kImage.Header.TextOffset), kernelAlignSize) if err != nil { return nil, fmt.Errorf("add kernel segment: %v", err) } - Debug("Added %d byte (size %d) kernel at %s", len(kernelBuf), kImage.Header.ImageSize, kernelRange) + Debug("Added %#x byte (size %#x) kernel at %s with offset %#x with alignment %#x", len(kernelBuf), kImage.Header.ImageSize, kernelRange, kImage.Header.TextOffset, kernelAlignSize) chosen, err := sanitizeFDT(fdt) if err != nil { From 921ff4d7a5a24091ef32c1d97c3ca988e0e51fd6 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 03:57:36 +0000 Subject: [PATCH 082/109] kexec,linux,arm64: ensure that no initramfs works Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 0189ea1d6f..5031a3f122 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -151,21 +151,21 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c return nil, fmt.Errorf("read ramfs from file: %v", err) } } - } - // NOTE(10000TB): This need be placed after kernel by convention. - ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) - if err != nil { - return nil, fmt.Errorf("add initramfs segment: %v", err) - } - Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) + // NOTE(10000TB): This need be placed after kernel by convention. + ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) + if err != nil { + return nil, fmt.Errorf("add initramfs segment: %v", err) + } + Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) - ramfsStart := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) - chosen.UpdateProperty("linux,initrd-start", ramfsStart) - ramfsEnd := make([]byte, 8) - binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) - chosen.UpdateProperty("linux,initrd-end", ramfsEnd) + ramfsStart := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsStart, uint64(ramfsRange.Start)) + chosen.UpdateProperty("linux,initrd-start", ramfsStart) + ramfsEnd := make([]byte, 8) + binary.BigEndian.PutUint64(ramfsEnd, uint64(ramfsRange.Start)+uint64(ramfsRange.Size)) + chosen.UpdateProperty("linux,initrd-end", ramfsEnd) + } Debug("Kernel cmdline to append: %s", cmdline) if len(cmdline) > 0 { From 138c687e3257d8fb0fd5247e8975596acdad837e Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 03:59:06 +0000 Subject: [PATCH 083/109] kexec,linux,arm64: add test Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 30 ++++++ pkg/boot/linux/load_linux_image.go | 1 - pkg/boot/linux/load_linux_image_test.go | 131 ++++++++++++++++++++++++ pkg/boot/linux/pkg_test.go | 14 --- 4 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 pkg/boot/linux/load_linux_image_test.go delete mode 100644 pkg/boot/linux/pkg_test.go diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index 622cd0bc0d..bb54bdee98 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -12,6 +12,7 @@ import ( "os" "reflect" "sort" + "strings" "unsafe" "github.com/u-root/u-root/pkg/align" @@ -260,6 +261,12 @@ func NewSegment(buf []byte, phys Range) Segment { } } +// SegmentEqual returns whether s and t point at the same physical region and +// contain the same data. +func SegmentEqual(s, t Segment) bool { + return s.Phys == t.Phys && bytes.Equal(s.Buf, t.Buf) +} + func (s Segment) String() string { return fmt.Sprintf("(phys: %s, buffer: size %#x)", s.Phys, len(s.Buf)) } @@ -402,6 +409,15 @@ func AlignPhysStart(s Segment) Segment { // Segments is a collection of segments. type Segments []Segment +func (segs Segments) String() string { + var s strings.Builder + for _, seg := range segs { + s.WriteString(seg.String()) + s.WriteString("\n") + } + return s.String() +} + // PhysContains returns whether p exists in any of segs' physical memory // ranges. func (segs Segments) PhysContains(p uintptr) bool { @@ -422,6 +438,20 @@ func (segs Segments) Phys() Ranges { return r } +// SegmentsEqual returns whether the contents of all segments are the same, +// while pointing to the same physical memory region. +func SegmentsEqual(s, t Segments) bool { + if len(s) != len(t) { + return false + } + for i := range s { + if !SegmentEqual(s[i], t[i]) { + return false + } + } + return true +} + // IsSupersetOf checks whether all segments in o are present in s and contain // the same buffer content. func (segs Segments) IsSupersetOf(o Segments) error { diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 5031a3f122..8f31f3b151 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -55,7 +55,6 @@ func mmap(f *os.File) (data []byte, ummap func() error, err error) { } return nil } - return d, ummap, nil } diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go new file mode 100644 index 0000000000..c8727ce89b --- /dev/null +++ b/pkg/boot/linux/load_linux_image_test.go @@ -0,0 +1,131 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "bytes" + "encoding/binary" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/u-root/u-root/pkg/boot/kexec" + "github.com/u-root/u-root/pkg/dt" +) + +func readFile(t *testing.T, path string) []byte { + t.Helper() + b, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + return b +} + +func createFile(t *testing.T, content []byte) *os.File { + t.Helper() + p := filepath.Join(t.TempDir(), "file") + if err := os.WriteFile(p, content, 0o777); err != nil { + t.Fatal(err) + } + f, err := os.Open(p) + if err != nil { + t.Fatal(err) + } + return f +} + +func openFile(t *testing.T, path string) *os.File { + t.Helper() + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + return f +} + +func fdtBytes(t *testing.T, fdt *dt.FDT) []byte { + t.Helper() + var b bytes.Buffer + if _, err := fdt.Write(&b); err != nil { + t.Fatal(err) + } + return b.Bytes() +} + +func trampoline(kernelEntry, dtbBase uint64) []byte { + t := []byte{ + 0xc4, 0x00, 0x00, 0x58, + 0xe0, 0x00, 0x00, 0x58, + 0xe1, 0x03, 0x1f, 0xaa, + 0xe2, 0x03, 0x1f, 0xaa, + 0xe3, 0x03, 0x1f, 0xaa, + 0x80, 0x00, 0x1f, 0xd6, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + } + binary.LittleEndian.PutUint64(t[24:], kernelEntry) + binary.LittleEndian.PutUint64(t[32:], dtbBase) + return t +} + +func TestKexecLoadImage(t *testing.T) { + chosen := dt.NewNode("chosen", + dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + ), + ) + tree := &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren(chosen)), + } + + Debug = t.Logf + + for _, tt := range []struct { + name string + mm kexec.MemoryMap + kernel *os.File + ramfs *os.File + cmdline string + opts KexecOptions + segments kexec.Segments + entry uintptr + err error + }{ + { + name: "load", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + entry: 0x101000, /* trampoline entry */ + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, chosen, tree, tt.cmdline, tt.opts) + if !errors.Is(err, tt.err) { + t.Errorf("kexecLoad Arm Image = %v, want %v", err, tt.err) + } + if got.entry != tt.entry { + t.Errorf("kexecLoad Arm Image = %#x, want %#x", got.entry, tt.entry) + } + if !kexec.SegmentsEqual(got.segments, tt.segments) { + t.Errorf("kexecLoad Arm Image =\n%v, want\n%v", got.segments, tt.segments) + } + for i := range got.segments { + if !kexec.SegmentEqual(got.segments[i], tt.segments[i]) { + t.Errorf("Segment %d wrong", i) + } + } + }) + } +} diff --git a/pkg/boot/linux/pkg_test.go b/pkg/boot/linux/pkg_test.go deleted file mode 100644 index 54803001d1..0000000000 --- a/pkg/boot/linux/pkg_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2022 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package linux - -import ( - "testing" -) - -// Empty test so that Go coverage detects 0% for this package. -func TestEmpty(t *testing.T) { - t.Logf("TODO") -} From aae370c6fd7b726ba759a1619c059da4c192500a Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 05:40:09 +0000 Subject: [PATCH 084/109] kexec,load: always mmap file if mmap is supported Signed-off-by: Chris Koch --- cmds/core/kexec/kexec_linux.go | 10 +--- pkg/boot/linux/load_linux.go | 55 ++++++++++++++++++++ pkg/boot/linux/load_linux_image.go | 67 ++++--------------------- pkg/boot/linux/load_linux_image_test.go | 59 ++++++++++++++++++---- pkg/boot/linux/opts.go | 29 +---------- 5 files changed, 115 insertions(+), 105 deletions(-) create mode 100644 pkg/boot/linux/load_linux.go diff --git a/cmds/core/kexec/kexec_linux.go b/cmds/core/kexec/kexec_linux.go index fe316196da..8c90d1ed33 100644 --- a/cmds/core/kexec/kexec_linux.go +++ b/cmds/core/kexec/kexec_linux.go @@ -54,8 +54,6 @@ type options struct { load bool loadSyscall bool linuxImageCfgFile string - mmapInitrd bool - mmapKernel bool modules []string purgatory string reuseCmdline bool @@ -74,8 +72,6 @@ func registerFlags() *options { flag.BoolVarP(&o.load, "load", "l", false, "Load the new kernel into the current kernel") flag.BoolVarP(&o.loadSyscall, "loadsyscall", "L", false, "Use the kexec_load syscall (not kexec_file_load)") flag.StringVarP(&o.linuxImageCfgFile, "linux-image-cfg-file", "I", "", "Load Linux image from JSON info file given") - flag.BoolVar(&o.mmapInitrd, "mmap-initrd", true, "Mmap initrd file into virtual buffer, other than directly reading it (Only supported in Arm64 classic load mode for now)") - flag.BoolVar(&o.mmapKernel, "mmap-kernel", true, "Mmap kernel file into virtual buffer, other than directly reading it (Only supported in Arm64 classi load mode for now)") flag.StringArrayVar(&o.modules, "module", nil, `Load multiboot module with command line args (e.g --module="mod arg1")`) // This is broken out as it is almost never to be used. But it is valueable, nonetheless. @@ -117,8 +113,6 @@ func main() { opts.initramfs = lli.Initrd.Name() } opts.loadSyscall = lli.LoadSyscall - opts.mmapKernel = lli.KexecOpts.MmapKernel - opts.mmapInitrd = lli.KexecOpts.MmapRamfs } if (!opts.exec && len(kernelpath) == 0) || flag.NArg() > 1 { @@ -194,9 +188,7 @@ func main() { Cmdline: newCmdline, LoadSyscall: opts.loadSyscall, KexecOpts: linux.KexecOptions{ - DTB: dtb, - MmapKernel: opts.mmapKernel, - MmapRamfs: opts.mmapInitrd, + DTB: dtb, }, } } diff --git a/pkg/boot/linux/load_linux.go b/pkg/boot/linux/load_linux.go new file mode 100644 index 0000000000..7eb66b4b4e --- /dev/null +++ b/pkg/boot/linux/load_linux.go @@ -0,0 +1,55 @@ +// Copyright 2022 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package linux + +import ( + "fmt" + "io" + "os" + "syscall" + + "github.com/u-root/u-root/pkg/uio" + "golang.org/x/sys/unix" +) + +func mmap(f *os.File) ([]byte, func() error, error) { + s, err := f.Stat() + if err != nil { + return nil, nil, fmt.Errorf("stat error: %w", err) + } + if s.Size() == 0 { + return nil, nil, fmt.Errorf("cannot mmap zero-len file") + } + d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) + if err != nil { + return nil, nil, fmt.Errorf("mmap failed: %w", err) + } + + ummap := func() error { + if err := unix.Munmap(d); err != nil { + return fmt.Errorf("failed to unmap %s: %v", f.Name(), err) + } + return nil + } + return d, ummap, nil +} + +func getFile(f *os.File) ([]byte, func() error, error) { + if d, unmap, err := mmap(f); err == nil { + return d, unmap, nil + } + var d []byte + var err error + // Pipes and other files like that will fail to seek. + if _, serr := f.Seek(0, 0); serr != nil { + d, err = io.ReadAll(f) + } else { + d, err = uio.ReadAll(f) + } + if err != nil { + return nil, nil, fmt.Errorf("failed to read kernel file: %w", err) + } + return d, func() error { return nil }, nil +} diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 8f31f3b151..d269589fcd 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -9,13 +9,10 @@ import ( "encoding/binary" "fmt" "os" - "syscall" "github.com/u-root/u-root/pkg/boot/image" "github.com/u-root/u-root/pkg/boot/kexec" "github.com/u-root/u-root/pkg/dt" - "github.com/u-root/u-root/pkg/uio" - "golang.org/x/sys/unix" ) type kimage struct { @@ -36,28 +33,6 @@ const ( kernelAlignSize = 1 << 21 // 2 MB. ) -func mmap(f *os.File) (data []byte, ummap func() error, err error) { - s, err := f.Stat() - if err != nil { - return nil, nil, fmt.Errorf("stat error: %w", err) - } - if s.Size() == 0 { - return nil, nil, fmt.Errorf("cannot mmap zero-len file") - } - d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) - if err != nil { - return nil, nil, fmt.Errorf("mmap failed: %w", err) - } - - ummap = func() error { - if err := unix.Munmap(d); err != nil { - return fmt.Errorf("failed to unmap %s: %v", f.Name(), err) - } - return nil - } - return d, ummap, nil -} - // sanitizeFDT cleanups boot param properties from chosen node of the given FDT. func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { // Clear old entries in case we've already been through kexec to get @@ -96,28 +71,17 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c } img := &kimage{} + // Load kernel. - var kernelBuf []byte - var err error - if opts.MmapKernel { - Debug("Mmapping kernel to virtual buffer...") - var cleanup func() error - kernelBuf, cleanup, err = mmap(kernel) - if err != nil { - return nil, fmt.Errorf("mmap kernel: %v", err) - } - img.cleanup = append(img.cleanup, cleanup) - } else { - Debug("Read kernel from file ...") - kernelBuf, err = uio.ReadAll(kernel) - if err != nil { - return nil, fmt.Errorf("read kernel from file: %v", err) - } + kernelBuf, cleanup, err := getFile(kernel) + if err != nil { + return nil, fmt.Errorf("failed to get kernel contents: %w", err) } + img.cleanup = append(img.cleanup, cleanup) kImage, err := image.ParseFromBytes(kernelBuf) if err != nil { - return nil, fmt.Errorf("parse arm64 Image from bytes: %v", err) + return nil, fmt.Errorf("parse arm64 Image from bytes: %w", err) } kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize), uint(kImage.Header.TextOffset), kernelAlignSize) @@ -133,23 +97,12 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c } Debug("FDT after sanitization: %s", fdt) - var ramfsBuf []byte if ramfs != nil { - if opts.MmapRamfs { - Debug("Mmap ramfs file to virtual buffer...") - var cleanup func() error - ramfsBuf, cleanup, err = mmap(ramfs) - if err != nil { - return nil, fmt.Errorf("mmap ramfs: %v", err) - } - img.cleanup = append(img.cleanup, cleanup) - } else { - Debug("Read ramfs from file...") - ramfsBuf, err = uio.ReadAll(ramfs) - if err != nil { - return nil, fmt.Errorf("read ramfs from file: %v", err) - } + ramfsBuf, cleanup, err := getFile(ramfs) + if err != nil { + return nil, fmt.Errorf("failed to get initramfs contents: %w", err) } + img.cleanup = append(img.cleanup, cleanup) // NOTE(10000TB): This need be placed after kernel by convention. ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index c8727ce89b..51a27ae526 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/binary" "errors" + "io" "os" "path/filepath" "testing" @@ -56,6 +57,19 @@ func fdtBytes(t *testing.T, fdt *dt.FDT) []byte { return b.Bytes() } +func pipe(t *testing.T, content []byte) *os.File { + t.Helper() + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + go func() { + _, _ = io.Copy(w, bytes.NewReader(content)) + w.Close() + }() + return r +} + func trampoline(kernelEntry, dtbBase uint64) []byte { t := []byte{ 0xc4, 0x00, 0x00, 0x58, @@ -73,16 +87,6 @@ func trampoline(kernelEntry, dtbBase uint64) []byte { } func TestKexecLoadImage(t *testing.T) { - chosen := dt.NewNode("chosen", - dt.WithProperty( - dt.PropertyU64("linux,initrd-start", 500), - dt.PropertyU64("linux,initrd-end", 500), - ), - ) - tree := &dt.FDT{ - RootNode: dt.NewNode("/", dt.WithChildren(chosen)), - } - Debug = t.Logf for _, tt := range []struct { @@ -90,6 +94,7 @@ func TestKexecLoadImage(t *testing.T) { mm kexec.MemoryMap kernel *os.File ramfs *os.File + fdt *dt.FDT cmdline string opts KexecOptions segments kexec.Segments @@ -103,6 +108,35 @@ func TestKexecLoadImage(t *testing.T) { }, kernel: openFile(t, "../image/testdata/Image"), entry: 0x101000, /* trampoline entry */ + fdt: &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen", + dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + ), + ))), + }, + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, + { + name: "pipefile", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: pipe(t, readFile(t, "../image/testdata/Image")), + entry: 0x101000, /* trampoline entry */ + fdt: &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen", + dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + ), + ))), + }, segments: kexec.Segments{ kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}), kexec.Range{Start: 0x100000, Size: 0x1000}), kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), @@ -111,10 +145,13 @@ func TestKexecLoadImage(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, chosen, tree, tt.cmdline, tt.opts) + got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline, tt.opts) if !errors.Is(err, tt.err) { t.Errorf("kexecLoad Arm Image = %v, want %v", err, tt.err) } + if got == nil { + return + } if got.entry != tt.entry { t.Errorf("kexecLoad Arm Image = %#x, want %#x", got.entry, tt.entry) } diff --git a/pkg/boot/linux/opts.go b/pkg/boot/linux/opts.go index d2ddcde279..89f9bc26ac 100644 --- a/pkg/boot/linux/opts.go +++ b/pkg/boot/linux/opts.go @@ -18,40 +18,15 @@ import ( type KexecOptions struct { // DTB is used as the device tree blob, if specified. DTB io.ReaderAt - - // Mmap kernel and initramfs, so virtual pages are directly mapped - // to page cache. Here it is agnostic to whether original kernel and - // initramfs file is in tmpfs, or other devices. - // - // *) If in tmpfs, file objects are backed by page cache. - // *) If on disk, kernel cache it during first disk I/O. In case when - // we are mmapping a file on disk, pages frames backing the page cache - // are only allocated when bytes are accessed, as kernel is lazy, so disk - // I/O happens at kexec load time. - // - // MmapKernel indicates if mmap kernel kernel into virtual memory. - MmapKernel bool - // MmapRamfs indicates if mmap initramfs into virtual memory. - MmapRamfs bool } // kexecOptionsJSON is same as KexecOptions, but with transformed fields to help with serialization of KexecOptions. type kexecOptionsJSON struct { - dtb string - mmapKernel bool - mmapRamfs bool + dtb string } func (ko *KexecOptions) MarshalJSON() ([]byte, error) { koJSON := kexecOptionsJSON{} - // TODO(100000TB): consider support serializing dtb. - // - // We can either change default type to path name, or - // read it and save it to a file under tmpfs during - // marshaling. - - koJSON.mmapKernel = ko.MmapKernel - koJSON.mmapRamfs = ko.MmapRamfs return json.Marshal(&koJSON) } @@ -60,7 +35,5 @@ func (ko *KexecOptions) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &koJSON); err != nil { return err } - ko.MmapKernel = koJSON.mmapKernel - ko.MmapRamfs = koJSON.mmapRamfs return nil } From dacbc01f8e0d39a80e49ed2a302ffe3d2d734722 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 05:58:49 +0000 Subject: [PATCH 085/109] kexec,linux,arm64: test for no chosen node in device tree Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 6 ++++-- pkg/boot/linux/load_linux_image_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index d269589fcd..f7b6de6d07 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -33,13 +33,15 @@ const ( kernelAlignSize = 1 << 21 // 2 MB. ) +var errNoChosenNode = fmt.Errorf("no /chosen node in device tree") + // sanitizeFDT cleanups boot param properties from chosen node of the given FDT. func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { // Clear old entries in case we've already been through kexec to get // to this instance of runtime. chosen, _ := fdt.NodeByName("chosen") if chosen == nil { - return nil, fmt.Errorf("no /chosen node in device tree") + return nil, errNoChosenNode } for _, property := range []string{"linux,elfcorehdr", "linux,usable-memory-range", "kaslr-seed", "rng-seed", "linux,initrd-start", "linux,initrd-end"} { chosen.RemoveProperty(property) @@ -93,7 +95,7 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c chosen, err := sanitizeFDT(fdt) if err != nil { - return nil, fmt.Errorf("sanitizeFDT(%v) = %v", fdt, err) + return nil, fmt.Errorf("sanitizeFDT(%v) = %w", fdt, err) } Debug("FDT after sanitization: %s", fdt) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 51a27ae526..2402df7339 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -143,6 +143,15 @@ func TestKexecLoadImage(t *testing.T) { kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), }, }, + { + name: "no chosen node in fdt", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: pipe(t, readFile(t, "../image/testdata/Image")), + fdt: &dt.FDT{RootNode: dt.NewNode("/")}, + err: errNoChosenNode, + }, } { t.Run(tt.name, func(t *testing.T) { got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline, tt.opts) From a82f83dd994cb45da63085f8a9a067b37790d195 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 06:08:14 +0000 Subject: [PATCH 086/109] kexec,linux,arm64: not enough space test Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 16 +++- pkg/boot/linux/load_linux_image_test.go | 122 +++++++++++++++++++++--- 2 files changed, 121 insertions(+), 17 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index f7b6de6d07..6af9f7b352 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -7,6 +7,7 @@ package linux import ( "bytes" "encoding/binary" + "errors" "fmt" "os" @@ -67,6 +68,13 @@ func kexecLoadImage(kernel, ramfs *os.File, cmdline string, opts KexecOptions) ( return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline, opts) } +var ( + errKernelSegmentFailed = errors.New("failed to add kernel segment") + errInitramfsSegmentFailed = errors.New("failed to add initramfs segment") + errDTBSegmentFailed = errors.New("failed to add DTB segment") + errTrampolineSegmentFailed = errors.New("failed to add trampolineSegment") +) + func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, cmdline string, opts KexecOptions) (*kimage, error) { kmem := &kexec.Memory{ Phys: mm, @@ -88,7 +96,7 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize), uint(kImage.Header.TextOffset), kernelAlignSize) if err != nil { - return nil, fmt.Errorf("add kernel segment: %v", err) + return nil, fmt.Errorf("%w: %w", errKernelSegmentFailed, err) } Debug("Added %#x byte (size %#x) kernel at %s with offset %#x with alignment %#x", len(kernelBuf), kImage.Header.ImageSize, kernelRange, kImage.Header.TextOffset, kernelAlignSize) @@ -109,7 +117,7 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c // NOTE(10000TB): This need be placed after kernel by convention. ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) if err != nil { - return nil, fmt.Errorf("add initramfs segment: %v", err) + return nil, fmt.Errorf("%w: %w", errInitramfsSegmentFailed, err) } Debug("Added %d byte initramfs at %s", len(ramfsBuf), ramfsRange) @@ -136,7 +144,7 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c dtbBuf := dtbBuffer.Bytes() dtbRange, err := kmem.AddKexecSegment(dtbBuf) if err != nil { - return nil, fmt.Errorf("add device tree segment: %w", err) + return nil, fmt.Errorf("%w: %w", errDTBSegmentFailed, err) } Debug("Added %d byte device tree at %s", len(dtbBuf), dtbRange) @@ -176,7 +184,7 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c Debug("trampoline bytes %x", trampolineBuffer.Bytes()) trampolineRange, err := kmem.AddKexecSegment(trampolineBuffer.Bytes()) if err != nil { - return nil, fmt.Errorf("add trampoline segment: %v", err) + return nil, fmt.Errorf("%w: %w", errTrampolineSegmentFailed, err) } Debug("Added %d byte trampoline at %s", len(trampolineBuffer.Bytes()), trampolineRange) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 2402df7339..237f06ef19 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -39,6 +39,16 @@ func createFile(t *testing.T, content []byte) *os.File { return f } +func closedFile(t *testing.T) *os.File { + t.Helper() + f, err := os.Create(filepath.Join(t.TempDir(), "file")) + if err != nil { + t.Fatal(err) + } + f.Close() + return f +} + func openFile(t *testing.T, path string) *os.File { t.Helper() f, err := os.Open(path) @@ -90,19 +100,23 @@ func TestKexecLoadImage(t *testing.T) { Debug = t.Logf for _, tt := range []struct { - name string - mm kexec.MemoryMap - kernel *os.File - ramfs *os.File - fdt *dt.FDT - cmdline string - opts KexecOptions + name string + + // Inputs + mm kexec.MemoryMap + kernel *os.File + ramfs *os.File + fdt *dt.FDT + cmdline string + opts KexecOptions + + // Results segments kexec.Segments entry uintptr - err error + errs []error }{ { - name: "load", + name: "load-no-initramfs", mm: kexec.MemoryMap{ kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, }, @@ -113,6 +127,7 @@ func TestKexecLoadImage(t *testing.T) { dt.WithProperty( dt.PropertyU64("linux,initrd-start", 500), dt.PropertyU64("linux,initrd-end", 500), + dt.PropertyString("bootargs", "ohno"), ), ))), }, @@ -122,6 +137,34 @@ func TestKexecLoadImage(t *testing.T) { kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), }, }, + { + name: "load-initramfs-and-cmdline", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + cmdline: "foobar", + fdt: &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen"))), + }, + entry: 0x102000, /* trampoline entry */ + segments: kexec.Segments{ + kexec.NewSegment([]byte("ramfs"), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", + dt.WithChildren(dt.NewNode("chosen", + dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 0x100000), + // TODO: should this actually be 0x100005? + dt.PropertyU64("linux,initrd-end", 0x101000), + dt.PropertyString("bootargs", "foobar"), + ), + )), + )}), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x200000, 0x101000), kexec.Range{Start: 0x102000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), + }, + }, { name: "pipefile", mm: kexec.MemoryMap{ @@ -148,15 +191,68 @@ func TestKexecLoadImage(t *testing.T) { mm: kexec.MemoryMap{ kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, }, - kernel: pipe(t, readFile(t, "../image/testdata/Image")), + kernel: openFile(t, "../image/testdata/Image"), fdt: &dt.FDT{RootNode: dt.NewNode("/")}, - err: errNoChosenNode, + errs: []error{errNoChosenNode}, + }, + { + name: "not enough space for kernel image", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0, 0x100000), Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, + errs: []error{errKernelSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "kernel-error", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: closedFile(t), + fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, + errs: []error{os.ErrClosed}, + }, + { + name: "initramfs-error", + mm: kexec.MemoryMap{ + kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + ramfs: closedFile(t), + fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, + errs: []error{os.ErrClosed}, + }, + { + name: "not-enough-space-for-kernel-and-initramfs", + mm: kexec.MemoryMap{ + // kernel is 0x940000, which rounds up to 0xa00000 + kexec.TypedRange{Range: kexec.Range{Start: 0x200000, Size: 0xa00000}, Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, + errs: []error{errInitramfsSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "not-enough-space-for-dtb", + mm: kexec.MemoryMap{ + // kernel is 0x940000, which rounds up to 0xa00000 + // Initramfs takes another 0x1000 + kexec.TypedRange{Range: kexec.Range{Start: 0x200000, Size: 0xa01000}, Type: kexec.RangeRAM}, + }, + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, + errs: []error{errDTBSegmentFailed, kexec.ErrNotEnoughSpace}, }, } { t.Run(tt.name, func(t *testing.T) { got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline, tt.opts) - if !errors.Is(err, tt.err) { - t.Errorf("kexecLoad Arm Image = %v, want %v", err, tt.err) + for _, wantErr := range tt.errs { + if !errors.Is(err, wantErr) { + t.Errorf("kexecLoad Arm Image = %v, want %v", err, wantErr) + } } if got == nil { return From 7242ef77a738685ddf0c3d9bed3a37c9635e74b5 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 06:27:33 +0000 Subject: [PATCH 087/109] Fix kexec/linux godoc Signed-off-by: Chris Koch --- pkg/boot/linux/doc.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/boot/linux/doc.go b/pkg/boot/linux/doc.go index 546d34ef8c..fb5c8820d2 100644 --- a/pkg/boot/linux/doc.go +++ b/pkg/boot/linux/doc.go @@ -99,13 +99,14 @@ // those seven quadwords can be used for parameter passing. // // These changes should let us: -// o build the purgatory as a non-relative ELF, i.e. a statically linked program with one ELF program (segment) -// o and link it at 0x3000; the code was putting the current relative ELF in a fixed place anyway -// o use the ELF program header to tell us where to put the purgatory -// o communicate arguments in the seven quadwords mentioned above -// o rather than one does-it-all purgatory as we have today, we can provide several variants -// -// so we get one suited to the job at hand. +// - build the purgatory as a non-relative ELF, i.e. a statically linked +// program with one ELF program (segment) +// - and link it at 0x3000; the code was putting the current relative ELF in +// a fixed place anyway - use the ELF program header to tell us where to +// put the purgatory +// - communicate arguments in the seven quadwords mentioned above +// - rather than one does-it-all purgatory as we have today, we can provide +// several variants so we get one suited to the job at hand. // // This should result in a dramatically simpler purgatory implementation. Also, // being much simpler, it can be entirely Go assembly, obviating the need for a From c945942185da087fe27017b705aaa1576f859779 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 20:25:03 +0000 Subject: [PATCH 088/109] kexec,linux,arm64: leave some TODO comments Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 6af9f7b352..32e16c16af 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -94,6 +94,19 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c return nil, fmt.Errorf("parse arm64 Image from bytes: %w", err) } + // "The Image must be placed text_offset bytes from a 2MB aligned base + // address anywhere in usable system RAM and called there." + // (arm64/booting.rst) + // + // "At least image_size bytes from the start of the image must be free + // for use by the kernel." (arm64/booting.rst) + // + // TODO: support versions below v4.6? + // + // "NOTE: versions prior to v4.6 cannot make use of memory below the + // physical offset of the Image so it is recommended that the Image be + // placed as close as possible to the start of system RAM." + // (arm64/booting.rst) kernelRange, err := kmem.AddKexecSegmentExplicit(kernelBuf, uint(kImage.Header.ImageSize), uint(kImage.Header.TextOffset), kernelAlignSize) if err != nil { return nil, fmt.Errorf("%w: %w", errKernelSegmentFailed, err) @@ -115,6 +128,11 @@ func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, c img.cleanup = append(img.cleanup, cleanup) // NOTE(10000TB): This need be placed after kernel by convention. + // + // "If an initrd/initramfs is passed to the kernel at boot, it + // must reside entirely within a 1 GB aligned physical memory + // window of up to 32 GB in size that fully covers the kernel + // Image as well." (arm64/booting.rst) ramfsRange, err := kmem.AddKexecSegment(ramfsBuf) if err != nil { return nil, fmt.Errorf("%w: %w", errInitramfsSegmentFailed, err) From 6002e37a627da220dfe793e86eb90227ad174a89 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 20:40:42 +0000 Subject: [PATCH 089/109] kexec,linux: wrap mmap errors Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/boot/linux/load_linux.go b/pkg/boot/linux/load_linux.go index 7eb66b4b4e..184f1c452c 100644 --- a/pkg/boot/linux/load_linux.go +++ b/pkg/boot/linux/load_linux.go @@ -20,7 +20,7 @@ func mmap(f *os.File) ([]byte, func() error, error) { return nil, nil, fmt.Errorf("stat error: %w", err) } if s.Size() == 0 { - return nil, nil, fmt.Errorf("cannot mmap zero-len file") + return nil, nil, fmt.Errorf("%w: cannot mmap zero-len file", os.ErrInvalid) } d, err := unix.Mmap(int(f.Fd()), 0, int(s.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE) if err != nil { @@ -29,7 +29,7 @@ func mmap(f *os.File) ([]byte, func() error, error) { ummap := func() error { if err := unix.Munmap(d); err != nil { - return fmt.Errorf("failed to unmap %s: %v", f.Name(), err) + return fmt.Errorf("failed to unmap %s: %w", f.Name(), err) } return nil } From 65fabd6d7948499d09cb3a7e496e4c7617630110 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 21:20:55 +0000 Subject: [PATCH 090/109] kexec: remove JSON confg file Signed-off-by: Chris Koch --- cmds/core/kexec/kexec_linux.go | 48 +++------ pkg/boot/boot.go | 21 +--- pkg/boot/linux.go | 187 +++++---------------------------- pkg/boot/linux_test.go | 108 ++++++------------- pkg/boot/menu/menu.go | 7 +- 5 files changed, 76 insertions(+), 295 deletions(-) diff --git a/cmds/core/kexec/kexec_linux.go b/cmds/core/kexec/kexec_linux.go index 8c90d1ed33..b606617542 100644 --- a/cmds/core/kexec/kexec_linux.go +++ b/cmds/core/kexec/kexec_linux.go @@ -27,7 +27,6 @@ package main import ( - "encoding/json" "io" "log" "os" @@ -45,18 +44,17 @@ import ( ) type options struct { - cmdline string - debug bool - dtb string - exec bool - extra string - initramfs string - load bool - loadSyscall bool - linuxImageCfgFile string - modules []string - purgatory string - reuseCmdline bool + cmdline string + debug bool + dtb string + exec bool + extra string + initramfs string + load bool + loadSyscall bool + modules []string + purgatory string + reuseCmdline bool } func registerFlags() *options { @@ -71,7 +69,6 @@ func registerFlags() *options { flag.StringVar(&o.initramfs, "initramfs", "", "Use file as the kernel's initial ramdisk") flag.BoolVarP(&o.load, "load", "l", false, "Load the new kernel into the current kernel") flag.BoolVarP(&o.loadSyscall, "loadsyscall", "L", false, "Use the kexec_load syscall (not kexec_file_load)") - flag.StringVarP(&o.linuxImageCfgFile, "linux-image-cfg-file", "I", "", "Load Linux image from JSON info file given") flag.StringArrayVar(&o.modules, "module", nil, `Load multiboot module with command line args (e.g --module="mod arg1")`) // This is broken out as it is almost never to be used. But it is valueable, nonetheless. @@ -93,31 +90,10 @@ func main() { if flag.NArg() > 0 { kernelpath = flag.Arg(0) } - // Option values from linux image config takes precedence - // over from cmdline. - if len(opts.linuxImageCfgFile) > 0 { - log.Printf("Load image info from %s", opts.linuxImageCfgFile) - d, err := os.ReadFile(opts.linuxImageCfgFile) - if err != nil { - log.Fatalf("Failed to load image info: %v", err) - } - lli := boot.LoadedLinuxImage{} - if err := json.Unmarshal(d, &lli); err != nil { - log.Fatalf("Unmarshal image info: %v", err) - } - if lli.Kernel != nil { - kernelpath = lli.Kernel.Name() - } - opts.cmdline = lli.Cmdline - if lli.Initrd != nil { - opts.initramfs = lli.Initrd.Name() - } - opts.loadSyscall = lli.LoadSyscall - } if (!opts.exec && len(kernelpath) == 0) || flag.NArg() > 1 { flag.PrintDefaults() - log.Fatalf("usage: kexec [flags] kernelname OR kexec [flags] -linux-image-cfg-file [file] OR kexec -e") + log.Fatalf("usage: kexec [flags] kernelname OR kexec -e") } if opts.cmdline != "" && opts.reuseCmdline { diff --git a/pkg/boot/boot.go b/pkg/boot/boot.go index 93d0ddf9ee..b86ab107aa 100644 --- a/pkg/boot/boot.go +++ b/pkg/boot/boot.go @@ -8,16 +8,11 @@ package boot import ( "fmt" - "os" - "path/filepath" "github.com/u-root/u-root/pkg/boot/kexec" "github.com/u-root/uio/ulog" ) -// DefaultLinuxImageCfgFile is the default file name in tmp directory to write loaded LinuxImage info to. -const DefaultLinuxImageCfgFile = "linux_image_cfg.json" - // LoadOption is an optional parameter to Load. type LoadOption func(*loadOptions) @@ -25,16 +20,13 @@ type loadOptions struct { logger ulog.Logger verbose bool callKexecLoad bool - // linuxImageCfgFile specifies where to writes loaded linuximage info to. - linuxImageCfgFile string } func defaultLoadOptions() *loadOptions { return &loadOptions{ - logger: ulog.Null, - verbose: false, - callKexecLoad: true, - linuxImageCfgFile: filepath.Join(os.TempDir(), DefaultLinuxImageCfgFile), + logger: ulog.Null, + verbose: false, + callKexecLoad: true, } } @@ -71,13 +63,6 @@ func WithDryRun(dryRun bool) LoadOption { } } -// WithLinuxImageCfgFile allows user to specify a different linux image config file path. -func WithLinuxImageCfgFile(f string) LoadOption { - return func(o *loadOptions) { - o.linuxImageCfgFile = f - } -} - // OSImage represents a bootable OS package. type OSImage interface { fmt.Stringer diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index fa678cb8d4..337c55f75e 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -5,7 +5,6 @@ package boot import ( - "encoding/json" "errors" "fmt" "io" @@ -17,7 +16,6 @@ import ( "github.com/u-root/u-root/pkg/boot/util" "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/uio" - "github.com/u-root/uio/ulog" "golang.org/x/sys/unix" ) @@ -34,94 +32,10 @@ type LinuxImage struct { KexecOpts linux.KexecOptions } -// LoadedLinuxImage is a processed version of LinuxImage. -// -// Main difference being that kernel and initrd is made as -// a read-only *os.File. There is also additional processing -// such as DTB, if available under KexecOpts, will be appended -// to Initrd. -type LoadedLinuxImage struct { - Name string - Kernel *os.File - Initrd *os.File - Cmdline string - LoadSyscall bool - KexecOpts linux.KexecOptions -} - -// loadedLinuxImageJSON is same as LoadedLinuxImage, but with transformed fields to help with serialization of LoadedLinuxImage. -type loadedLinuxImageJSON struct { - Name string - KernelPath string - InitrdPath string - Cmdline string - LoadSyscall bool - KexecOpts linux.KexecOptions -} - var _ OSImage = &LinuxImage{} var errNilKernel = errors.New("kernel image is empty, nothing to execute") -// MarshalJSON customizes marshaling for LoadedLinuxImage. It handles serializations -// for *os.File, so that kernel and initrd can be unmarshalled properly. -func (lli *LoadedLinuxImage) MarshalJSON() ([]byte, error) { - lliJSON := loadedLinuxImageJSON{} - // Sync and close kernel and initrd File object, and marshal paths to the files. - if lli.Kernel != nil { - if err := lli.Kernel.Sync(); err != nil { - return nil, err - } - lliJSON.KernelPath = lli.Kernel.Name() - if err := lli.Kernel.Close(); err != nil { - return nil, err - } - } - if lli.Initrd != nil { - if err := lli.Initrd.Sync(); err != nil { - return nil, err - } - lliJSON.InitrdPath = lli.Initrd.Name() - if err := lli.Initrd.Close(); err != nil { - return nil, err - } - } - lliJSON.Name = lli.Name - lliJSON.Cmdline = lli.Cmdline - lliJSON.LoadSyscall = lli.LoadSyscall - lliJSON.KexecOpts = lli.KexecOpts - - return json.Marshal(lliJSON) -} - -// UnmarshalJSON customizes unmarshaling for LoadedLinuxImage. It processes kernel -// and initrd file by name, and opens a read-only copies for further execution. -func (lli *LoadedLinuxImage) UnmarshalJSON(b []byte) error { - lliJSON := loadedLinuxImageJSON{} - if err := json.Unmarshal(b, &lliJSON); err != nil { - return err - } - if len(strings.TrimSpace(lliJSON.KernelPath)) > 0 { - readOnlyK, err := os.Open(lliJSON.KernelPath) - if err != nil { - return err - } - lli.Kernel = readOnlyK - } - if len(strings.TrimSpace(lliJSON.InitrdPath)) > 0 { - readOnlyI, err := os.Open(lliJSON.InitrdPath) - if err != nil { - return err - } - lli.Initrd = readOnlyI - } - lli.Name = lliJSON.Name - lli.Cmdline = lliJSON.Cmdline - lli.LoadSyscall = lliJSON.LoadSyscall - lli.KexecOpts = lliJSON.KexecOpts - return nil -} - // named is satisifed by *os.File. type named interface { Name() string @@ -268,20 +182,17 @@ func CopyToFileIfNotRegular(r io.ReaderAt, verbose bool) (*os.File, error) { return readOnlyF, nil } -// loadLinuxImage processes given LinuxImage, and make it ready for kexec. -// -// For example: -// -// - Acquiring a read-only copy of kernel and initrd as kernel -// don't like them being opened for writting by anyone while -// executing. -// - Append DTB, if present to end of initrd. -func loadLinuxImage(li *LinuxImage, logger ulog.Logger, verbose bool) (*LoadedLinuxImage, func(), error) { +// Edit the kernel command line. +func (li *LinuxImage) Edit(f func(cmdline string) string) { + li.Cmdline = f(li.Cmdline) +} + +func (li *LinuxImage) loadImage(loadOpts *loadOptions) (*os.File, *os.File, error) { if li.Kernel == nil { return nil, nil, errNilKernel } - k, err := CopyToFileIfNotRegular(util.TryGzipFilter(li.Kernel), verbose) + k, err := CopyToFileIfNotRegular(util.TryGzipFilter(li.Kernel), loadOpts.verbose) if err != nil { return nil, nil, err } @@ -297,68 +208,13 @@ func loadLinuxImage(li *LinuxImage, logger ulog.Logger, verbose bool) (*LoadedLi var i *os.File if li.Initrd != nil { - i, err = CopyToFileIfNotRegular(li.Initrd, verbose) + i, err = CopyToFileIfNotRegular(li.Initrd, loadOpts.verbose) if err != nil { + k.Close() return nil, nil, err } } - - logger.Printf("Kernel: %s", k.Name()) - if i != nil { - logger.Printf("Initrd: %s", i.Name()) - } - logger.Printf("Command line: %s", li.Cmdline) - logger.Printf("KexecOpts: %#v", li.KexecOpts) - - cleanup := func() { - k.Close() - i.Close() - } - - return &LoadedLinuxImage{ - Name: li.Name, - Kernel: k, - Initrd: i, - Cmdline: li.Cmdline, - LoadSyscall: li.LoadSyscall, - KexecOpts: li.KexecOpts, - }, cleanup, nil -} - -// saveLoadedLinuxImage marshals a LoadedLinuxImage to a json file. -// -// Marshalling it to a json versus a binary format, makes it readable -// and easier to tamper in case when we need change a field or two -// for experiments and debug. -// -// With that said, it is obvious that this saved info can then be -// loaded by a later kexec for further execution from an already loaded -// linux image and original load options. -func saveLoadedLinuxImage(lli *LoadedLinuxImage, p string) error { - f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) - if err != nil { - return err - } - out, err := json.Marshal(lli) - if err != nil { - return err - } - nw, err := f.Write(out) - if err != nil { - return err - } - if nw != len(out) { - return fmt.Errorf("written %d bytes, want %d bytes", nw, len(out)) - } - if err := f.Sync(); err != nil { - return err - } - return f.Close() -} - -// Edit the kernel command line. -func (li *LinuxImage) Edit(f func(cmdline string) string) { - li.Cmdline = f(li.Cmdline) + return k, i, nil } // Load implements OSImage.Load and kexec_load's the kernel with its initramfs. @@ -368,20 +224,27 @@ func (li *LinuxImage) Load(opts ...LoadOption) error { opt(loadOpts) } - loadedImage, cleanup, err := loadLinuxImage(li, loadOpts.logger, loadOpts.verbose) + k, i, err := li.loadImage(loadOpts) if err != nil { return err } - defer cleanup() + defer k.Close() + if i != nil { + defer i.Close() + } + + loadOpts.logger.Printf("Kernel: %s", k.Name()) + if i != nil { + loadOpts.logger.Printf("Initrd: %s", i.Name()) + } + loadOpts.logger.Printf("Command line: %s", li.Cmdline) + loadOpts.logger.Printf("KexecOpts: %#v", li.KexecOpts) if !loadOpts.callKexecLoad { - // If dryRun, serializes previously loaded linuxImage info to a file in tmpfs. - // The info can be re-loaded for later kexec execution. It works b/c kernel and - // initrd are already downloaded and saved into tmpfs. - return saveLoadedLinuxImage(loadedImage, loadOpts.linuxImageCfgFile) + return nil } if li.LoadSyscall { - return linux.KexecLoad(loadedImage.Kernel, loadedImage.Initrd, loadedImage.Cmdline, loadedImage.KexecOpts) + return linux.KexecLoad(k, i, li.Cmdline, li.KexecOpts) } - return kexec.FileLoad(loadedImage.Kernel, loadedImage.Initrd, loadedImage.Cmdline) + return kexec.FileLoad(k, i, li.Cmdline) } diff --git a/pkg/boot/linux_test.go b/pkg/boot/linux_test.go index 0b4c6b1672..7c22160e07 100644 --- a/pkg/boot/linux_test.go +++ b/pkg/boot/linux_test.go @@ -6,6 +6,7 @@ package boot import ( "bytes" + "errors" "fmt" "io" "net/url" @@ -167,20 +168,10 @@ func checkFilePath(t *testing.T, fsrc io.ReaderAt, fdst *os.File) { func setupTestFile(t *testing.T, path, content string) *os.File { t.Helper() - f, err := os.Create(path) - if err != nil { - t.Fatal(err) - } - n, err := f.Write([]byte(content)) - if err != nil { + if err := os.WriteFile(path, []byte(content), 0o777); err != nil { t.Fatal(err) } - if n != len([]byte(content)) { - t.Fatalf("want %d bytes written, but got %d", len([]byte(content)), n) - } - if err := f.Close(); err != nil { - t.Fatalf("could not close test file: %v", err) - } + nf, err := os.Open(path) if err != nil { t.Fatalf("could not open test file: %v", err) @@ -202,41 +193,28 @@ func GenerateCatDummyInitrd(t *testing.T, initrds ...string) string { return string(d) } -type wantData struct { - loadedImage *LoadedLinuxImage - cleanup func() - err error -} - func TestLoadLinuxImage(t *testing.T) { testDir := t.TempDir() for _, tt := range []struct { - name string - li *LinuxImage - want wantData + name string + li *LinuxImage + wantKernel *os.File + wantInitrd *os.File + err error }{ { - name: "kernel is nil", - li: &LinuxImage{Kernel: nil}, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: nil, - }, - err: errNilKernel, - }, + name: "kernel is nil", + li: &LinuxImage{Kernel: nil}, + wantKernel: nil, + err: errNilKernel, }, { name: "basic happy case w/o initrd", li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_wo_initrd_bzimage"), "testkernel"), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_wo_initrd_bzimage"), "testkernel"), }, { name: "basic happy case w/ initrd", @@ -244,13 +222,8 @@ func TestLoadLinuxImage(t *testing.T) { Kernel: strings.NewReader("testkernel"), Initrd: strings.NewReader("testinitrd"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_initramfs"), "testinitrd"), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_initramfs"), "testinitrd"), }, { name: "empty initrd, with DTB present", // Expect DTB appended to loaded initrd. @@ -261,13 +234,8 @@ func TestLoadLinuxImage(t *testing.T) { DTB: strings.NewReader("testdtb"), }, }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), }, { name: "non-empty initrd, with DTB present", // Expect DTB appended to loaded initrd. @@ -278,13 +246,8 @@ func TestLoadLinuxImage(t *testing.T) { DTB: strings.NewReader("testdtb"), }, }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), - }, - err: nil, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), }, { name: "oringnal kernel and initrd are files, skip copying", @@ -292,31 +255,26 @@ func TestLoadLinuxImage(t *testing.T) { Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_bzImage"), "testkernel"), Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_initramfs"), "testinitrd"), }, - want: wantData{ - loadedImage: &LoadedLinuxImage{ - Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_bzImage"), "testkernel"), - Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_initramfs"), "testinitrd"), - }, - }, + wantKernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_bzImage"), "testkernel"), + wantInitrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_initramfs"), "testinitrd"), }, } { t.Run(tt.name, func(t *testing.T) { - gotImage, _, gotErr := loadLinuxImage(tt.li, ulogtest.Logger{t}, true) - if gotErr != nil { - if gotErr != tt.want.err { - t.Errorf("got error %v, want %v", gotErr, tt.want.err) - } + gotKernel, gotInitrd, err := tt.li.loadImage(&loadOptions{logger: ulogtest.Logger{t}, verbose: true}) + if !errors.Is(err, tt.err) { + t.Errorf("got error %v, want %v", err, tt.err) + } else if err != nil { return } // Kernel is opened as read only, and contents match that from original LinuxImage. - checkReadOnly(t, gotImage.Kernel) + checkReadOnly(t, gotKernel) // If src is a read-only *os.File on tmpfs, shoukd skip copying. - checkFilePath(t, tt.li.Kernel, gotImage.Kernel) - kernelBytes, err := io.ReadAll(gotImage.Kernel) + checkFilePath(t, tt.li.Kernel, gotKernel) + kernelBytes, err := io.ReadAll(gotKernel) if err != nil { t.Fatalf("could not read kernel from loaded image: %v", err) } - wantBytes, err := io.ReadAll(tt.want.loadedImage.Kernel) + wantBytes, err := io.ReadAll(tt.wantKernel) if err != nil { t.Fatalf("could not read expected kernel: %v", err) } @@ -326,14 +284,14 @@ func TestLoadLinuxImage(t *testing.T) { // Initrd, if present, is opened as read only, and contents match that from original LinuxImage. // OR original initrd, with DTB appended. if tt.li.Initrd != nil { - checkReadOnly(t, gotImage.Initrd) + checkReadOnly(t, gotInitrd) // If src is a read-only *os.File on tmpfs, should skip copying. - checkFilePath(t, tt.li.Initrd, gotImage.Initrd) - initrdBytes, err := io.ReadAll(gotImage.Initrd) + checkFilePath(t, tt.li.Initrd, gotInitrd) + initrdBytes, err := io.ReadAll(gotInitrd) if err != nil { t.Fatalf("could not read initrd from loaded image: %v", err) } - wantInitrdBytes, err := io.ReadAll(tt.want.loadedImage.Initrd) + wantInitrdBytes, err := io.ReadAll(tt.wantInitrd) if err != nil { t.Fatalf("could not read expected initrd: %v", err) } diff --git a/pkg/boot/menu/menu.go b/pkg/boot/menu/menu.go index 32d12bca70..f897b55e12 100644 --- a/pkg/boot/menu/menu.go +++ b/pkg/boot/menu/menu.go @@ -267,14 +267,13 @@ func OSImages(verbose bool, imgs ...boot.OSImage) []Entry { // OSImageAction is a menu.Entry that boots an OSImage. type OSImageAction struct { boot.OSImage - Verbose bool - NoKexecLoad bool - LinuxImageCfgFile string + Verbose bool + NoKexecLoad bool } // Load implements Entry.Load by loading the OS image into memory. func (oia OSImageAction) Load() error { - if err := oia.OSImage.Load(boot.WithVerbose(oia.Verbose), boot.WithDryRun(oia.NoKexecLoad), boot.WithLinuxImageCfgFile(oia.LinuxImageCfgFile)); err != nil { + if err := oia.OSImage.Load(boot.WithVerbose(oia.Verbose), boot.WithDryRun(oia.NoKexecLoad)); err != nil { return fmt.Errorf("could not load image %s: %v", oia.OSImage, err) } return nil From 52cb054751464afc2bdec09309e0b10f70d78d7a Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 22:18:53 +0000 Subject: [PATCH 091/109] kexec,linux,arm64: remove unused arg Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 4 ++-- pkg/boot/linux/load_linux_image_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 32e16c16af..a3ce7e1b1f 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -65,7 +65,7 @@ func kexecLoadImage(kernel, ramfs *os.File, cmdline string, opts KexecOptions) ( return nil, fmt.Errorf("MemoryMapFromFDT(%v): %v", fdt, err) } Debug("Mem map: \n%+v", mm) - return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline, opts) + return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline) } var ( @@ -75,7 +75,7 @@ var ( errTrampolineSegmentFailed = errors.New("failed to add trampolineSegment") ) -func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, cmdline string, opts KexecOptions) (*kimage, error) { +func kexecLoadImageMM(mm kexec.MemoryMap, kernel, ramfs *os.File, fdt *dt.FDT, cmdline string) (*kimage, error) { kmem := &kexec.Memory{ Phys: mm, } diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 237f06ef19..58b7097062 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -108,7 +108,6 @@ func TestKexecLoadImage(t *testing.T) { ramfs *os.File fdt *dt.FDT cmdline string - opts KexecOptions // Results segments kexec.Segments @@ -248,7 +247,7 @@ func TestKexecLoadImage(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { - got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline, tt.opts) + got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline) for _, wantErr := range tt.errs { if !errors.Is(err, wantErr) { t.Errorf("kexecLoad Arm Image = %v, want %v", err, wantErr) From 3e32eb2bb6e991f24f8c1053be14eb3b724ba587 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 22:19:51 +0000 Subject: [PATCH 092/109] kexec: remove cfg-file test Signed-off-by: Chris Koch --- integration/generic-tests/kexec_test.go | 50 ------------------------- 1 file changed, 50 deletions(-) diff --git a/integration/generic-tests/kexec_test.go b/integration/generic-tests/kexec_test.go index 605e6efcf6..4f331035de 100644 --- a/integration/generic-tests/kexec_test.go +++ b/integration/generic-tests/kexec_test.go @@ -11,7 +11,6 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "testing" "time" @@ -216,52 +215,3 @@ func TestMountKexecLoadCustomDTB(t *testing.T) { } _ = vm.Wait() } - -func TestKexecLinuxImageCfgFile(t *testing.T) { - vmtest.SkipIfNotArch(t, qemu.ArchAMD64, qemu.ArchArm64) - - dir := t.TempDir() - cfg := []byte("{ \"InitrdPath\": \"/testdata/initramfs.cpio\", \"KernelPath\": \"/kernel\", \"Cmdline\": \"/proc/cmdline\", \"Name\": \"testloadconfig\" }") - cfgFile := filepath.Join(dir, "linux_image_cfg.json") - if err := os.WriteFile(cfgFile, cfg, 0777); err != nil { - t.Fatalf("Failed to setup test cfg file: %v", err) - } - - script := ` - kexec -d -l -I /linux_image_cfg.json - echo kexecloadresult $? - ` - vm := vmtest.StartVMAndRunCmds(t, script, - vmtest.WithBusyboxCommands( - "github.com/u-root/u-root/cmds/core/cat", - "github.com/u-root/u-root/cmds/core/echo", - ), - // Build kexec as a binary command to get accurate GOCOVERDIR - // integration coverage data (busybox rewrites command code). - vmtest.WithBinaryCommands( - "github.com/u-root/u-root/cmds/core/kexec", - ), - vmtest.WithInitramfsFiles( - fmt.Sprintf("%s:kernel", os.Getenv("VMTEST_KERNEL")), - fmt.Sprintf("%s:linux_image_cfg.json", cfgFile), - ), - vmtest.WithQEMUFn( - qemu.WithVMTimeout(time.Minute), - qemu.ArbitraryArgs("-m", "8192"), - ), - // The initramfs will be placed in shared dir, so in the VM - // it's available at /testdata/initramfs.cpio. - vmtest.WithSharedDir(testtmp.TempDir(t)), - // Build kexec (and all other initramfs commands) with coverage enabled. - vmtest.WithGoBuildOpts(&golang.BuildOpts{ - ExtraArgs: []string{"-cover", "-coverpkg=github.com/u-root/u-root/...", "-covermode=atomic"}, - }), - ) - - if _, err := vm.Console.ExpectString("kexecloadresult 0"); err != nil { - t.Error(err) - } - if err := vm.Wait(); err != nil { - t.Errorf("Wait: %v", err) - } -} From 4e8a13836c9e5f56f5618934ee22c1824a6fa592 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 22:30:06 +0000 Subject: [PATCH 093/109] kexec,linux: remove linux.KexecOpts Signed-off-by: Chris Koch --- cmds/core/kexec/kexec_linux.go | 4 +-- pkg/boot/linux.go | 21 ++++++++-------- pkg/boot/linux/load_linux_amd64.go | 2 +- pkg/boot/linux/load_linux_arm64.go | 5 ++-- pkg/boot/linux/load_linux_image.go | 7 +++--- pkg/boot/linux/load_other_linux.go | 3 ++- pkg/boot/linux/opts.go | 39 ------------------------------ pkg/boot/linux_test.go | 17 +++---------- pkg/boot/syslinux/syslinux.go | 2 +- pkg/boot/syslinux/syslinux_test.go | 10 +++----- 10 files changed, 30 insertions(+), 80 deletions(-) delete mode 100644 pkg/boot/linux/opts.go diff --git a/cmds/core/kexec/kexec_linux.go b/cmds/core/kexec/kexec_linux.go index b606617542..0112ab41c2 100644 --- a/cmds/core/kexec/kexec_linux.go +++ b/cmds/core/kexec/kexec_linux.go @@ -163,9 +163,7 @@ func main() { Initrd: i, Cmdline: newCmdline, LoadSyscall: opts.loadSyscall, - KexecOpts: linux.KexecOptions{ - DTB: dtb, - }, + DTB: dtb, } } if err := image.Load(boot.WithVerbose(opts.debug)); err != nil { diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index 337c55f75e..c64df21556 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -28,8 +28,7 @@ type LinuxImage struct { Cmdline string BootRank int LoadSyscall bool - - KexecOpts linux.KexecOptions + DTB io.ReaderAt } var _ OSImage = &LinuxImage{} @@ -65,10 +64,10 @@ func (li *LinuxImage) Label() string { fmt.Sprintf("initrd=%s", stringer(li.Initrd)), ) } - if li.KexecOpts.DTB != nil { + if li.DTB != nil { labelInfo = append( labelInfo, - fmt.Sprintf("dtb=%s", stringer(li.KexecOpts.DTB)), + fmt.Sprintf("dtb=%s", stringer(li.DTB)), ) } @@ -83,8 +82,8 @@ func (li *LinuxImage) Rank() int { // String prints a human-readable version of this linux image. func (li *LinuxImage) String() string { return fmt.Sprintf( - "LinuxImage(\n Name: %s\n Kernel: %s\n Initrd: %s\n Cmdline: %s\n KexecOpts: %v\n)\n", - li.Name, stringer(li.Kernel), stringer(li.Initrd), li.Cmdline, li.KexecOpts, + "LinuxImage(\n Name: %s\n Kernel: %s\n Initrd: %s\n Cmdline: %s\n DTB: %v\n)\n", + li.Name, stringer(li.Kernel), stringer(li.Initrd), li.Cmdline, stringer(li.DTB), ) } @@ -198,11 +197,11 @@ func (li *LinuxImage) loadImage(loadOpts *loadOptions) (*os.File, *os.File, erro } // Append device-tree file to the end of initrd. - if li.KexecOpts.DTB != nil { + if li.DTB != nil { if li.Initrd != nil { - li.Initrd = CatInitrds(li.Initrd, li.KexecOpts.DTB) + li.Initrd = CatInitrds(li.Initrd, li.DTB) } else { - li.Initrd = li.KexecOpts.DTB + li.Initrd = li.DTB } } @@ -238,13 +237,13 @@ func (li *LinuxImage) Load(opts ...LoadOption) error { loadOpts.logger.Printf("Initrd: %s", i.Name()) } loadOpts.logger.Printf("Command line: %s", li.Cmdline) - loadOpts.logger.Printf("KexecOpts: %#v", li.KexecOpts) + loadOpts.logger.Printf("DTB: %#v", li.DTB) if !loadOpts.callKexecLoad { return nil } if li.LoadSyscall { - return linux.KexecLoad(k, i, li.Cmdline, li.KexecOpts) + return linux.KexecLoad(k, i, li.Cmdline, li.DTB) } return kexec.FileLoad(k, i, li.Cmdline) } diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index af4f8d747b..47fe3a13ed 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -25,7 +25,7 @@ const ( // kernel with the given ramfs file and cmdline string. // // It uses the kexec_load system call. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { bzimage.Debug = Debug // A collection of vars used for processing the kernel for kexec diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index 2b575b291e..b2de1c2ece 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -6,14 +6,15 @@ package linux import ( "fmt" + "io" "os" "github.com/u-root/u-root/pkg/boot/kexec" ) // KexecLoad loads arm64 Image, with the given ramfs and kernel cmdline. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { - img, err := kexecLoadImage(kernel, ramfs, cmdline, opts) +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { + img, err := kexecLoadImage(kernel, ramfs, cmdline, dtb) if err != nil { return err } diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index a3ce7e1b1f..c0431ab527 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "errors" "fmt" + "io" "os" "github.com/u-root/u-root/pkg/boot/image" @@ -51,10 +52,10 @@ func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { return chosen, nil } -func kexecLoadImage(kernel, ramfs *os.File, cmdline string, opts KexecOptions) (*kimage, error) { - fdt, err := dt.LoadFDT(opts.DTB) +func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*kimage, error) { + fdt, err := dt.LoadFDT(dtb) if err != nil { - return nil, fmt.Errorf("loadFDT(%s) = %v", opts.DTB, err) + return nil, fmt.Errorf("loadFDT(%s) = %v", dtb, err) } Debug("Loaded FDT: %s", fdt) diff --git a/pkg/boot/linux/load_other_linux.go b/pkg/boot/linux/load_other_linux.go index de24661567..8c0399f667 100644 --- a/pkg/boot/linux/load_other_linux.go +++ b/pkg/boot/linux/load_other_linux.go @@ -8,12 +8,13 @@ package linux import ( + "io" "os" "golang.org/x/sys/unix" ) // KexecLoad is not implemented for platforms other than amd64 and arm64. -func KexecLoad(kernel, ramfs *os.File, cmdline string, opts KexecOptions) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { return unix.ENOSYS } diff --git a/pkg/boot/linux/opts.go b/pkg/boot/linux/opts.go deleted file mode 100644 index 89f9bc26ac..0000000000 --- a/pkg/boot/linux/opts.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package linux - -import ( - "encoding/json" - "io" -) - -// KexecOptions abstract a collection of options to be passed in KexecLoad. -// -// Arch agnostic. Each arch knows to just look for options they care about. -// Alternatively, we could introduce arch specific options, so irrelevant options -// won't be compiled. But for simplification, have one shared struct to begin -// with, we can split when time comes. -type KexecOptions struct { - // DTB is used as the device tree blob, if specified. - DTB io.ReaderAt -} - -// kexecOptionsJSON is same as KexecOptions, but with transformed fields to help with serialization of KexecOptions. -type kexecOptionsJSON struct { - dtb string -} - -func (ko *KexecOptions) MarshalJSON() ([]byte, error) { - koJSON := kexecOptionsJSON{} - return json.Marshal(&koJSON) -} - -func (ko *KexecOptions) UnmarshalJSON(b []byte) error { - koJSON := kexecOptionsJSON{} - if err := json.Unmarshal(b, &koJSON); err != nil { - return err - } - return nil -} diff --git a/pkg/boot/linux_test.go b/pkg/boot/linux_test.go index 7c22160e07..6df6ef52b9 100644 --- a/pkg/boot/linux_test.go +++ b/pkg/boot/linux_test.go @@ -16,7 +16,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/curl" "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/uio" @@ -92,9 +91,7 @@ func TestLinuxLabel(t *testing.T) { img: &LinuxImage{ Kernel: osKernel, Initrd: osInitrd, - KexecOpts: linux.KexecOptions{ - DTB: osInitrd, - }, + DTB: osInitrd, }, want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd dtb=%s/initrd)", dir, dir, dir), }, @@ -102,9 +99,7 @@ func TestLinuxLabel(t *testing.T) { desc: "dtb file, no initrd", img: &LinuxImage{ Kernel: osKernel, - KexecOpts: linux.KexecOptions{ - DTB: osInitrd, - }, + DTB: osInitrd, }, want: fmt.Sprintf("Linux(kernel=%s/kernel dtb=%s/initrd)", dir, dir), }, @@ -230,9 +225,7 @@ func TestLoadLinuxImage(t *testing.T) { li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), Initrd: nil, - KexecOpts: linux.KexecOptions{ - DTB: strings.NewReader("testdtb"), - }, + DTB: strings.NewReader("testdtb"), }, wantKernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), wantInitrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), @@ -242,9 +235,7 @@ func TestLoadLinuxImage(t *testing.T) { li: &LinuxImage{ Kernel: strings.NewReader("testkernel"), Initrd: strings.NewReader("testinitrd"), - KexecOpts: linux.KexecOptions{ - DTB: strings.NewReader("testdtb"), - }, + DTB: strings.NewReader("testdtb"), }, wantKernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), wantInitrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), diff --git a/pkg/boot/syslinux/syslinux.go b/pkg/boot/syslinux/syslinux.go index 4a7205060a..bc03ca885e 100644 --- a/pkg/boot/syslinux/syslinux.go +++ b/pkg/boot/syslinux/syslinux.go @@ -393,7 +393,7 @@ func (c *parser) append(ctx context.Context, config string) error { if err != nil { return err } - e.KexecOpts.DTB = dtb + e.DTB = dtb } case "append": diff --git a/pkg/boot/syslinux/syslinux_test.go b/pkg/boot/syslinux/syslinux_test.go index b43651b3b8..46cd14fc14 100644 --- a/pkg/boot/syslinux/syslinux_test.go +++ b/pkg/boot/syslinux/syslinux_test.go @@ -19,7 +19,6 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/boottest" - "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/curl" ) @@ -557,11 +556,10 @@ func TestParseGeneral(t *testing.T) { }, want: []boot.OSImage{ &boot.LinuxImage{ - Name: "foo", - Kernel: strings.NewReader(kernel1), - Initrd: strings.NewReader(globalInitrd), - Cmdline: "foo=bar", - KexecOpts: linux.KexecOptions{}, + Name: "foo", + Kernel: strings.NewReader(kernel1), + Initrd: strings.NewReader(globalInitrd), + Cmdline: "foo=bar", }, }, }, From ce5d5f50090a6320968b7dce873b41b7ae6f8da1 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 2 Feb 2024 09:17:50 -0800 Subject: [PATCH 094/109] fwtools/flash: allow user to set size and offset Add -size and -offset switches, for partial reading and writing. Signed-off-by: Ronald G. Minnich --- cmds/fwtools/flash/flash_linux.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cmds/fwtools/flash/flash_linux.go b/cmds/fwtools/flash/flash_linux.go index e51321a91e..02e81aaaad 100644 --- a/cmds/fwtools/flash/flash_linux.go +++ b/cmds/fwtools/flash/flash_linux.go @@ -10,6 +10,8 @@ // // Options: // +// -o offset: Offset at which to start. +// -s size: Number of bytes to read or write. // -p PROGRAMMER: Specify the programmer with zero or more parameters (see // below). // -r FILE: Read flash data into the file. @@ -43,6 +45,7 @@ import ( "fmt" "io" "log" + "math" "os" "sort" "strings" @@ -94,9 +97,11 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr // Parse args. fs := flag.NewFlagSet("flash", flag.ContinueOnError) var ( - p = fs.StringP("programmer", "p", "", fmt.Sprintf("programmer (%s)", strings.Join(programmerList, ","))) - r = fs.StringP("read", "r", "", "read flash data into the file") - w = fs.StringP("write", "w", "", "write the file to flash") + p = fs.StringP("programmer", "p", "", fmt.Sprintf("programmer (%s)", strings.Join(programmerList, ","))) + r = fs.StringP("read", "r", "", "read flash data into the file") + w = fs.StringP("write", "w", "", "write the file to flash") + off = fs.Int64P("offset", "o", 0, "off at which to write") + size = fs.Int64P("size", "s", math.MaxInt64, "number of bytes") ) if err := fs.Parse(args); err != nil { return err @@ -137,7 +142,7 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr // Create a buffer to hold the contents of the image. if *r != "" { - buf := make([]byte, programmer.Size()) + buf := make([]byte, min(*size, programmer.Size())) f, err := os.Create(*r) if err != nil { return err @@ -148,7 +153,7 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr reterr = err } }() - if _, err := programmer.ReadAt(buf, 0); err != nil { + if _, err := programmer.ReadAt(buf, *off); err != nil { return err } if _, err := f.Write(buf); err != nil { @@ -159,7 +164,8 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr if err != nil { return err } - amt, err := programmer.WriteAt(buf, 0) + buf = buf[:min(int64(len(buf)), *size)] + amt, err := programmer.WriteAt(buf, *off) if err != nil { return fmt.Errorf("writing %d bytes to dev %v:%w", len(buf), programmer, err) } From 231ddad49a6cbed585abd3b1394bc45d3ff43509 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 22:39:39 +0000 Subject: [PATCH 095/109] kexec,linux,amd64: (optionally) mmap initramfs to save memory If the initramfs is already in memory at this point (e.g. in tmpfs), use mmap to forego a second copy. Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_amd64.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index 47fe3a13ed..af4e5b83d0 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -100,10 +100,16 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { var ramfsRange kexec.Range if ramfs != nil { - ramfsContents, err := io.ReadAll(ramfs) + ramfsContents, cleanup, err := getFile(ramfs) if err != nil { return fmt.Errorf("unable to read initramfs: %w", err) } + defer func() { + if err := cleanup(); err != nil { + Debug("Failed to clean up initramfs: %v", err) + } + }() + if ramfsRange, err = kmem.AddKexecSegment(ramfsContents); err != nil { return fmt.Errorf("add initramfs segment: %w", err) } From d257b7213a3618295f94df42e2cb976095d35392 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Thu, 1 Feb 2024 22:48:05 +0000 Subject: [PATCH 096/109] kexec,linux,amd64: allow setup region to be placed somewhere other than 0x90000 Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_amd64.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index af4e5b83d0..a4cc0905cb 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -141,16 +141,13 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { return fmt.Errorf("re-marshaling header: %w", err) } - // TODO(10000TB): Free mem hole start end aligns by - // max(16, pagesize). - // - // Push alignment logic to kexec memory functions, e.g. a similar - // function to FindSpace. setupRange, err := kmem.AddPhysSegment( linuxParam, + // We use Linux's 32bit/64bit entry point, so we can place the + // setup range anywhere in the low 4G. kexec.RangeFromInterval( - uintptr(0x90000), - uintptr(len(linuxParam)), + uintptr(4096), + uintptr(1<<32-1), ), // TODO(10000TB): evaluate if we need to provide option to // reserve from end. From fdc720a901ec3f56edf6d9c214d095939f92d4e5 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 2 Feb 2024 10:31:50 -0800 Subject: [PATCH 097/109] flash: use parameters from SFDP part, fix bug in WriteAt WriteAt had a bug where if the middle was too short, it would reference past the slice. looks like a fix, try one more thing. Signed-off-by: Ronald G. Minnich --- pkg/flash/chips/chips.go | 14 ++++++----- pkg/flash/flash_linux.go | 51 ++++++++++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/pkg/flash/chips/chips.go b/pkg/flash/chips/chips.go index d70ee2f03f..a8402d8f38 100644 --- a/pkg/flash/chips/chips.go +++ b/pkg/flash/chips/chips.go @@ -38,9 +38,10 @@ type Chip struct { Is4BA bool EraseBlocks []EraseBlock - Unlock op.OpCode - Write op.OpCode - Read op.OpCode + WriteEnableInstructionRequired bool + WriteEnableOpcodeSelect op.OpCode + Write op.OpCode + Read op.OpCode } // String implements string for Chip. @@ -97,8 +98,9 @@ var Chips = []Chip{ }, }, - Unlock: op.WriteEnable, - Write: op.AAI, - Read: op.Read, + WriteEnableInstructionRequired: true, + WriteEnableOpcodeSelect: op.WriteEnable, + Write: op.AAI, + Read: op.Read, }, } diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 83def522bc..93a16805b8 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -7,6 +7,7 @@ package flash import ( + "bytes" "encoding/binary" "fmt" "io" @@ -74,6 +75,11 @@ func New(spi SPI) (*Flash, error) { return f, nil } +// FillFromSFDP fills the Flash struct with parameters from +// the SFDP. Querying the SFDP is a bit messy, for each type of +// parameter, so this code pulls the SFDP parameters into +// struct members. It also makes the creation of chips a bit easier, +// when we do not have an SFDP. func (f *Flash) FillFromSFDP() error { density, err := f.SFDP().Param(sfdp.ParamFlashMemoryDensity) if err != nil { @@ -89,6 +95,15 @@ func (f *Flash) FillFromSFDP() error { f.Is4BA = true } + if wer, err := f.SFDP().Param(sfdp.ParamWriteEnableInstructionRequired); err == nil && wer != 0 { + we, err := f.SFDP().Param(sfdp.ParamWriteEnableOpcodeSelect) + if err != nil { + return fmt.Errorf("write enable is required but WriteEnableOpcodeSelect is not in SFDP:%w", err) + } + f.WriteEnableInstructionRequired = true + f.WriteEnableOpcodeSelect = op.OpCode(we) + } + // TODO f.PageSize = 256 f.SectorSize = 4096 @@ -144,20 +159,32 @@ func (f *Flash) ReadAt(p []byte, off int64) (int, error) { // writeAt performs a write operation without any care for page sizes or // alignment. func (f *Flash) writeAt(p []byte, off int64) (int, error) { - if err := f.spi.Transfer([]spidev.Transfer{ - // Enable writing. + t := []spidev.Transfer{ {Tx: op.PRDRES.Bytes(), CSChange: true}, - {Tx: op.WriteEnable.Bytes(), CSChange: true}, - // Send the address. - {Tx: append(append(op.PageProgram.Bytes(), f.prepareAddress(off)...), p...)}, + } + if f.Chip.WriteEnableInstructionRequired { + t = append(t, spidev.Transfer{Tx: f.Chip.WriteEnableOpcodeSelect.Bytes(), CSChange: true}) + } + // AAAAAAARRRRGHHHHH! + // If this WriteEnable is not here, then the page program request + // NEVER MAKES IT TO THE SPI BUS. + // So, ... put it here, even if not requested, until we figure this out. + // Further, on the macronix part, we can't leave the write disable in, or + // the write enable command ends up being written to the part? + // This is a mess. + t = append(t, spidev.Transfer{Tx: op.WriteEnable.Bytes(), CSChange: true}) + t = append(t, spidev.Transfer{Tx: append(append(op.PageProgram.Bytes(), f.prepareAddress(off)...), p...)}) + if f.Chip.WriteEnableInstructionRequired { // The meaning of CSChange is ... odd. // IF CSChange is set true here, then CE# never goes // high. If CSChange is left unchanged, // CE# is properly deasserted from the data write above, // asserted for this command, and deasserted // at the end. - {Tx: op.WriteDisable.Bytes(), DelayUSecs: 10}, - }); err != nil { + + t = append(t, spidev.Transfer{Tx: op.WriteDisable.Bytes(), DelayUSecs: 10}) + } + if err := f.spi.Transfer(t); err != nil { return 0, err } return len(p), nil @@ -188,18 +215,22 @@ func (f *Flash) WriteAt(p []byte, off int64) (int, error) { firstAlignedOff := (off + f.PageSize - 1) / f.PageSize * f.PageSize lastAlignedOff := (off + int64(len(p))) / f.PageSize * f.PageSize + b := bytes.NewBuffer(p) if off != firstAlignedOff { - if n, err := f.writeAt(p[:firstAlignedOff-off], off); err != nil { + dat := b.Next(int(firstAlignedOff - off)) + if n, err := f.writeAt(dat, off); err != nil { return n, err } } for i := firstAlignedOff; i < lastAlignedOff; i += f.PageSize { - if _, err := f.writeAt(p[i:i+f.PageSize], off+i); err != nil { + dat := b.Next(int(f.PageSize)) + if _, err := f.writeAt(dat, i); err != nil { return int(i), err } } if off+int64(len(p)) != lastAlignedOff { - if _, err := f.writeAt(p[lastAlignedOff-off:], lastAlignedOff); err != nil { + dat := b.Bytes() + if _, err := f.writeAt(dat, lastAlignedOff); err != nil { return int(lastAlignedOff - off), err } } From c79bc7ecb09ec3ef1e34e70588a414f5b6f860a9 Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 2 Feb 2024 17:45:54 -0800 Subject: [PATCH 098/109] flash: add polling to write, fix other bugs Signed-off-by: Ronald G. Minnich --- cmds/fwtools/spidev/spidev_linux.go | 2 ++ pkg/flash/flash_linux.go | 34 +++++++++++++++------- pkg/flash/op/op.go | 45 ++++++++++++++++++++++++++++- pkg/flash/op/op_test.go | 35 ++++++++++++++++++++++ pkg/flash/spimock/spimock_linux.go | 7 +++++ pkg/spidev/spidev_linux.go | 30 ++++++++++++++++++- 6 files changed, 140 insertions(+), 13 deletions(-) diff --git a/cmds/fwtools/spidev/spidev_linux.go b/cmds/fwtools/spidev/spidev_linux.go index 3baa8d5c35..d55a93de9d 100644 --- a/cmds/fwtools/spidev/spidev_linux.go +++ b/cmds/fwtools/spidev/spidev_linux.go @@ -33,6 +33,7 @@ import ( flag "github.com/spf13/pflag" "github.com/u-root/u-root/pkg/flash" "github.com/u-root/u-root/pkg/flash/chips" + "github.com/u-root/u-root/pkg/flash/op" "github.com/u-root/u-root/pkg/flash/sfdp" "github.com/u-root/u-root/pkg/spidev" ) @@ -40,6 +41,7 @@ import ( type spi interface { Transfer([]spidev.Transfer) error ID() (chips.ID, error) + Status() (op.Status, error) SetSpeedHz(uint32) error Close() error } diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 93a16805b8..5f92686181 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -11,6 +11,7 @@ import ( "encoding/binary" "fmt" "io" + "time" "github.com/u-root/u-root/pkg/flash/chips" "github.com/u-root/u-root/pkg/flash/op" @@ -25,6 +26,7 @@ const sfdpMaxAddress = (1 << 24) - 1 type SPI interface { Transfer(transfers []spidev.Transfer) error ID() (chips.ID, error) + Status() (op.Status, error) } // Flash provides operations for SPI flash chips. @@ -181,12 +183,30 @@ func (f *Flash) writeAt(p []byte, off int64) (int, error) { // CE# is properly deasserted from the data write above, // asserted for this command, and deasserted // at the end. - t = append(t, spidev.Transfer{Tx: op.WriteDisable.Bytes(), DelayUSecs: 10}) } if err := f.spi.Transfer(t); err != nil { return 0, err } + // Hang out for a bit, let the part do its thing. + time.Sleep(time.Duration(len(p)) * time.Microsecond) + var i int + for i = 0; i < len(p); i++ { + stat, err := f.spi.Status() + if err != nil { + return len(p), fmt.Errorf("spi status read fails after writing %d bytes:%w", len(p), err) + } + if !stat.Busy() { + break + } + time.Sleep(10 * time.Microsecond) + } + + if i == len(p) { + return len(p), fmt.Errorf("spi busy after writing %d bytes", len(p)) + } + // Between each check for done, sleep about 10 microseconds, to give the part + // a chance to catch its breath. return len(p), nil } @@ -210,10 +230,8 @@ func (f *Flash) WriteAt(p []byte, off int64) (int, error) { // Otherwise, there are three regions: // 1. A partial page before the first aligned offset. (optional) - // 2. All the aligned pages in the middle. - // 3. A partial page after the last aligned offset. (optional) + // 2. All the aligned pages firstAlignedOff := (off + f.PageSize - 1) / f.PageSize * f.PageSize - lastAlignedOff := (off + int64(len(p))) / f.PageSize * f.PageSize b := bytes.NewBuffer(p) if off != firstAlignedOff { @@ -222,18 +240,12 @@ func (f *Flash) WriteAt(p []byte, off int64) (int, error) { return n, err } } - for i := firstAlignedOff; i < lastAlignedOff; i += f.PageSize { + for i := firstAlignedOff; b.Len() > 0; i += f.PageSize { dat := b.Next(int(f.PageSize)) if _, err := f.writeAt(dat, i); err != nil { return int(i), err } } - if off+int64(len(p)) != lastAlignedOff { - dat := b.Bytes() - if _, err := f.writeAt(dat, lastAlignedOff); err != nil { - return int(lastAlignedOff - off), err - } - } return len(p), nil } diff --git a/pkg/flash/op/op.go b/pkg/flash/op/op.go index e28b57c733..5f14ff601f 100644 --- a/pkg/flash/op/op.go +++ b/pkg/flash/op/op.go @@ -6,7 +6,10 @@ // the beginning of a SPI transaction. package op -import "fmt" +import ( + "fmt" + "strings" +) type OpCode byte @@ -75,3 +78,43 @@ func (o OpCode) String() string { func (o OpCode) Bytes() []byte { return []byte{byte(o)} } + +type Status byte + +// Status is not universally defined, but a few bits are common. +const ( + WriteBusy Status = 1 << iota + WriteEnabled + ByteProtect0 + ByteProtect1 + ByteProtect2 + ByteProtectP3 + AutoAddressIncrement + ByteProtectLocked +) + +var names = []string{ + "WriteBusy", + "WriteEnabled", + "ByteProtect0", + "ByteProtect1", + "ByteProtect2", + "ByteProtectP3", + "AutoAddressIncrement", + "ByteProtectLocked", +} + +func (status Status) String() string { + var s string + for i := 0; i < 8; i++ { + if byte(status)&(1< Date: Fri, 2 Feb 2024 22:24:29 -0800 Subject: [PATCH 099/109] flash: get erase working too, including -offset and -size You can now erase portions of flash. Signed-off-by: Ronald G. Minnich --- cmds/fwtools/flash/flash_linux.go | 14 ++++++++++++-- pkg/flash/flash_linux.go | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/cmds/fwtools/flash/flash_linux.go b/cmds/fwtools/flash/flash_linux.go index 02e81aaaad..50f04becc3 100644 --- a/cmds/fwtools/flash/flash_linux.go +++ b/cmds/fwtools/flash/flash_linux.go @@ -56,6 +56,7 @@ import ( type programmer interface { io.ReaderAt io.WriterAt + EraseAt(int64, int64) (int64, error) Size() int64 Close() error } @@ -97,6 +98,7 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr // Parse args. fs := flag.NewFlagSet("flash", flag.ContinueOnError) var ( + e = fs.BoolP("erase", "e", false, "erase the flash part") p = fs.StringP("programmer", "p", "", fmt.Sprintf("programmer (%s)", strings.Join(programmerList, ","))) r = fs.StringP("read", "r", "", "read flash data into the file") w = fs.StringP("write", "w", "", "write the file to flash") @@ -115,8 +117,8 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr return errors.New("-p needs to be set") } - if *r == "" && *w == "" { - return errors.New("either -r or -w need to be set") + if *r == "" && *w == "" && !*e { + return errors.New("at least one of -e, -r or -w need to be set") } if *r != "" && *w != "" { return errors.New("both -r and -w cannot be set") @@ -139,6 +141,14 @@ func run(args []string, supportedProgrammers map[string]programmerInit) (reterr } }() + if *e { + n, err := programmer.EraseAt(min(*size, programmer.Size()), *off) + if err != nil { + log.Fatal(err) + } + log.Printf("Erased %#x bytes @ %#x", n, *off) + } + // Create a buffer to hold the contents of the image. if *r != "" { diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 5f92686181..6320f4bae0 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -11,6 +11,7 @@ import ( "encoding/binary" "fmt" "io" + "os" "time" "github.com/u-root/u-root/pkg/flash/chips" @@ -258,11 +259,11 @@ func (f *Flash) ProgramAt(p []byte, off int64) (int, error) { // sectorSize. func (f *Flash) EraseAt(n int64, off int64) (int64, error) { if off < 0 || off > f.ArraySize || off+n > f.ArraySize { - return 0, io.EOF + return 0, fmt.Errorf("offset (%#x) is < 0, or > %#x, or off+size (%#x) is > f.ArraySize (%#x):%w", off, f.ArraySize, off+n, f.ArraySize, os.ErrInvalid) } if (off%f.SectorSize != 0) || (n%f.SectorSize != 0) { - return 0, fmt.Errorf("len(p) and off must be multiple of the sector size") + return 0, fmt.Errorf("offset (%#x) and size (%#x) must be multiple of the sector size(%#x):%w", off, n, f.SectorSize, os.ErrInvalid) } for i := int64(0); i < n; { @@ -287,6 +288,23 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { return i, err } + var spin int + // Give it 100 tries, which is 1000 ms + for spin = 0; spin <= 100; spin++ { + time.Sleep(10 * time.Millisecond) + stat, err := f.spi.Status() + if err != nil { + return n, fmt.Errorf("spi status read fails after erasing %#x:%w", off+i, err) + } + if !stat.Busy() { + break + } + } + + if spin > 101 { + return i, fmt.Errorf("spi busy after erasing %d bytes", i) + } + i += eraseSize } return n, nil From f3ae33f10e906cfc70ff57ea429383fdd8d89c0e Mon Sep 17 00:00:00 2001 From: "Ronald G. Minnich" Date: Fri, 2 Feb 2024 23:03:02 -0800 Subject: [PATCH 100/109] flash: fix a typo in erase spin loop Signed-off-by: Ronald G. Minnich --- pkg/flash/flash_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/flash/flash_linux.go b/pkg/flash/flash_linux.go index 6320f4bae0..e0b047fd4a 100644 --- a/pkg/flash/flash_linux.go +++ b/pkg/flash/flash_linux.go @@ -301,7 +301,7 @@ func (f *Flash) EraseAt(n int64, off int64) (int64, error) { } } - if spin > 101 { + if spin > 100 { return i, fmt.Errorf("spi busy after erasing %d bytes", i) } From a2d88bb3b7e1a2c687dd59bf955d0e412dcb50d5 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 02:53:26 +0000 Subject: [PATCH 101/109] kexec,linux,arm64: test reading memory map as well Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 2 +- pkg/boot/linux/load_linux_image_test.go | 212 +++++++++++++++--------- pkg/dt/node.go | 11 ++ 3 files changed, 142 insertions(+), 83 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index c0431ab527..d4d6a36d33 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -55,7 +55,7 @@ func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*kimage, error) { fdt, err := dt.LoadFDT(dtb) if err != nil { - return nil, fmt.Errorf("loadFDT(%s) = %v", dtb, err) + return nil, fmt.Errorf("loadFDT(%s) = %w", dtb, err) } Debug("Loaded FDT: %s", fdt) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 58b7097062..62906902f7 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -61,12 +61,25 @@ func openFile(t *testing.T, path string) *os.File { func fdtBytes(t *testing.T, fdt *dt.FDT) []byte { t.Helper() var b bytes.Buffer + fdt.Header.Magic = dt.Magic + fdt.Header.Version = 17 if _, err := fdt.Write(&b); err != nil { t.Fatal(err) } return b.Bytes() } +func fdtReader(t *testing.T, fdt *dt.FDT) io.ReaderAt { + t.Helper() + var b bytes.Buffer + fdt.Header.Magic = dt.Magic + fdt.Header.Version = 17 + if _, err := fdt.Write(&b); err != nil { + t.Fatal(err) + } + return bytes.NewReader(b.Bytes()) +} + func pipe(t *testing.T, content []byte) *os.File { t.Helper() r, w, err := os.Pipe() @@ -103,10 +116,9 @@ func TestKexecLoadImage(t *testing.T) { name string // Inputs - mm kexec.MemoryMap kernel *os.File ramfs *os.File - fdt *dt.FDT + fdt io.ReaderAt cmdline string // Results @@ -115,139 +127,175 @@ func TestKexecLoadImage(t *testing.T) { errs []error }{ { - name: "load-no-initramfs", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "load-no-initramfs", kernel: openFile(t, "../image/testdata/Image"), entry: 0x101000, /* trampoline entry */ - fdt: &dt.FDT{ - RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen", - dt.WithProperty( + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( dt.PropertyU64("linux,initrd-start", 500), dt.PropertyU64("linux,initrd-end", 500), dt.PropertyString("bootargs", "ohno"), - ), - ))), - }, + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), segments: kexec.Segments{ - kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), }, }, { - name: "load-initramfs-and-cmdline", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "load-initramfs-and-cmdline", kernel: openFile(t, "../image/testdata/Image"), ramfs: createFile(t, []byte("ramfs")), cmdline: "foobar", - fdt: &dt.FDT{ - RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen"))), - }, - entry: 0x102000, /* trampoline entry */ + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + entry: 0x102000, segments: kexec.Segments{ kexec.NewSegment([]byte("ramfs"), kexec.Range{Start: 0x100000, Size: 0x1000}), - kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", - dt.WithChildren(dt.NewNode("chosen", - dt.WithProperty( - dt.PropertyU64("linux,initrd-start", 0x100000), - // TODO: should this actually be 0x100005? - dt.PropertyU64("linux,initrd-end", 0x101000), - dt.PropertyString("bootargs", "foobar"), - ), + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 0x100000), + // TODO: should this actually be 0x100005? + dt.PropertyU64("linux,initrd-end", 0x101000), + dt.PropertyString("bootargs", "foobar"), + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), )), - )}), kexec.Range{Start: 0x101000, Size: 0x1000}), + ))}), kexec.Range{Start: 0x101000, Size: 0x1000}), kexec.NewSegment(trampoline(0x200000, 0x101000), kexec.Range{Start: 0x102000, Size: 0x1000}), kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), }, }, { - name: "pipefile", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "pipefile", kernel: pipe(t, readFile(t, "../image/testdata/Image")), - entry: 0x101000, /* trampoline entry */ - fdt: &dt.FDT{ - RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen", - dt.WithProperty( + entry: 0x101000, + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( dt.PropertyU64("linux,initrd-start", 500), dt.PropertyU64("linux,initrd-end", 500), - ), - ))), - }, + )), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), segments: kexec.Segments{ - kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), kexec.NewSegment(trampoline(0x200000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x200000, Size: 0xa00000}), }, }, { - name: "no chosen node in fdt", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "no chosen node in fdt", kernel: openFile(t, "../image/testdata/Image"), - fdt: &dt.FDT{RootNode: dt.NewNode("/")}, - errs: []error{errNoChosenNode}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), + errs: []error{errNoChosenNode}, }, { - name: "not enough space for kernel image", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0, 0x100000), Type: kexec.RangeRAM}, - }, + name: "not enough space for kernel image", kernel: openFile(t, "../image/testdata/Image"), - fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, - errs: []error{errKernelSegmentFailed, kexec.ErrNotEnoughSpace}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x100000), + )), + ))}), + errs: []error{errKernelSegmentFailed, kexec.ErrNotEnoughSpace}, }, { - name: "kernel-error", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "kernel-error", kernel: closedFile(t), - fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, - errs: []error{os.ErrClosed}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x1000000), + )), + ))}), + errs: []error{os.ErrClosed}, }, { - name: "initramfs-error", - mm: kexec.MemoryMap{ - kexec.TypedRange{Range: kexec.RangeFromInterval(0x100000, 0x10000000), Type: kexec.RangeRAM}, - }, + name: "initramfs-error", kernel: openFile(t, "../image/testdata/Image"), ramfs: closedFile(t), - fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, - errs: []error{os.ErrClosed}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0, 0x1000000), + )), + ))}), + errs: []error{os.ErrClosed}, }, { - name: "not-enough-space-for-kernel-and-initramfs", - mm: kexec.MemoryMap{ - // kernel is 0x940000, which rounds up to 0xa00000 - kexec.TypedRange{Range: kexec.Range{Start: 0x200000, Size: 0xa00000}, Type: kexec.RangeRAM}, - }, + name: "not-enough-space-for-kernel-and-initramfs", kernel: openFile(t, "../image/testdata/Image"), ramfs: createFile(t, []byte("ramfs")), - fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, - errs: []error{errInitramfsSegmentFailed, kexec.ErrNotEnoughSpace}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + dt.PropertyRegion("reg", 0x200000, 0xa00000), + )), + ))}), + errs: []error{errInitramfsSegmentFailed, kexec.ErrNotEnoughSpace}, }, { - name: "not-enough-space-for-dtb", - mm: kexec.MemoryMap{ - // kernel is 0x940000, which rounds up to 0xa00000 - // Initramfs takes another 0x1000 - kexec.TypedRange{Range: kexec.Range{Start: 0x200000, Size: 0xa01000}, Type: kexec.RangeRAM}, - }, + name: "not-enough-space-for-dtb", kernel: openFile(t, "../image/testdata/Image"), ramfs: createFile(t, []byte("ramfs")), - fdt: &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren(dt.NewNode("chosen")))}, - errs: []error{errDTBSegmentFailed, kexec.ErrNotEnoughSpace}, + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + // Initramfs takes another 0x1000 + dt.PropertyRegion("reg", 0x200000, 0xa01000), + )), + ))}), + errs: []error{errDTBSegmentFailed, kexec.ErrNotEnoughSpace}, }, } { t.Run(tt.name, func(t *testing.T) { - got, err := kexecLoadImageMM(tt.mm, tt.kernel, tt.ramfs, tt.fdt, tt.cmdline) + got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt) for _, wantErr := range tt.errs { if !errors.Is(err, wantErr) { t.Errorf("kexecLoad Arm Image = %v, want %v", err, wantErr) diff --git a/pkg/dt/node.go b/pkg/dt/node.go index 1166e5d38e..537daade9b 100644 --- a/pkg/dt/node.go +++ b/pkg/dt/node.go @@ -242,6 +242,17 @@ func PropertyString(name string, value string) Property { } } +// PropertyRegion creates a property encoding start and size of a region of memory. +func PropertyRegion(name string, start, size uint64) Property { + b := bytes.NewBuffer(nil) + _ = binary.Write(b, binary.BigEndian, start) + _ = binary.Write(b, binary.BigEndian, size) + return Property{ + Name: name, + Value: b.Bytes(), + } +} + // Property is a name-value pair. Note the PropertyType of Value is not // encoded. type Property struct { From 597d657dc9dee340a84266e35187a1402681410e Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 02:58:57 +0000 Subject: [PATCH 102/109] kexec,linux,arm64: more tests Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 62906902f7..a2e8327d3c 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -293,6 +293,27 @@ func TestKexecLoadImage(t *testing.T) { ))}), errs: []error{errDTBSegmentFailed, kexec.ErrNotEnoughSpace}, }, + { + name: "not-enough-space-for-trampoline", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // kernel size is 0x940000, which rounds up to 0xa00000 + // Initramfs takes another 0x1000 + // DTB takes another 0x1000 + dt.PropertyRegion("reg", 0x200000, 0xa02000), + )), + ))}), + errs: []error{errTrampolineSegmentFailed, kexec.ErrNotEnoughSpace}, + }, + { + name: "loadFDT fails", + fdt: closedFile(t), + errs: []error{dt.ErrNoValidReaders}, + }, } { t.Run(tt.name, func(t *testing.T) { got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt) From c2024e8087837e1eb17770535d36e76a6b51ae81 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 03:11:59 +0000 Subject: [PATCH 103/109] kexec,linux,arm64: test empty memmap Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 7 ++++++- pkg/boot/linux/load_linux_image_test.go | 27 +++++++++++++++++++++++++ pkg/dt/node.go | 4 ++-- pkg/dt/node_test.go | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index d4d6a36d33..28cdc56f98 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -52,6 +52,8 @@ func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { return chosen, nil } +var ErrMemmapEmpty = errors.New("memory map is empty or contains no information about system RAM") + func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*kimage, error) { fdt, err := dt.LoadFDT(dtb) if err != nil { @@ -63,9 +65,12 @@ func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*k Debug("Try parsing memory map...") mm, err := kexec.MemoryMapFromFDT(fdt) if err != nil { - return nil, fmt.Errorf("MemoryMapFromFDT(%v): %v", fdt, err) + return nil, fmt.Errorf("MemoryMapFromFDT(%v): %w", fdt, err) } Debug("Mem map: \n%+v", mm) + if len(mm.RAM()) == 0 { + return nil, ErrMemmapEmpty + } return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline) } diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index a2e8327d3c..f3f817ab65 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -314,6 +314,33 @@ func TestKexecLoadImage(t *testing.T) { fdt: closedFile(t), errs: []error{dt.ErrNoValidReaders}, }, + { + name: "invalid-memmap", + kernel: openFile(t, "../image/testdata/Image"), + ramfs: createFile(t, []byte("ramfs")), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + // Too short. + dt.Property{Name: "reg", Value: []byte{0x0}}, + )), + ))}), + errs: []error{dt.ErrPropertyRegionInvalid}, + }, + { + name: "no-memmap", + kernel: openFile(t, "../image/testdata/Image"), + fdt: fdtReader(t, &dt.FDT{RootNode: dt.NewNode("/", + dt.WithChildren( + dt.NewNode("chosen", dt.WithProperty( + dt.PropertyU64("linux,initrd-start", 500), + dt.PropertyU64("linux,initrd-end", 500), + )), + ), + )}), + errs: []error{ErrMemmapEmpty}, + }, } { t.Run(tt.name, func(t *testing.T) { got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt) diff --git a/pkg/dt/node.go b/pkg/dt/node.go index 537daade9b..bd342c1ec7 100644 --- a/pkg/dt/node.go +++ b/pkg/dt/node.go @@ -50,7 +50,7 @@ var StandardPropertyTypes = map[string]PropertyType{ var ( errInvalidChildIndex = errors.New("invalid child index") - errPropertyRegionInvalid = errors.New("property value is not ") + ErrPropertyRegionInvalid = errors.New("property value is not ") ) // Node is one Node in the Device Tree. @@ -357,7 +357,7 @@ type Region struct { // AsRegion converts the property to a Region. func (p *Property) AsRegion() (*Region, error) { if len(p.Value) != 16 { - return nil, errPropertyRegionInvalid + return nil, ErrPropertyRegionInvalid } var start, size uint64 b := bytes.NewBuffer(p.Value) diff --git a/pkg/dt/node_test.go b/pkg/dt/node_test.go index 0bd289d771..5726b3ea0f 100644 --- a/pkg/dt/node_test.go +++ b/pkg/dt/node_test.go @@ -433,7 +433,7 @@ func TestAsRegion(t *testing.T) { Value: []byte{}, }, want: &Region{}, - wantErr: errPropertyRegionInvalid, + wantErr: ErrPropertyRegionInvalid, }, { name: "read start and size, success", From 72f884228c9d039c52627a3be02fcb711e5e92a0 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 03:12:13 +0000 Subject: [PATCH 104/109] dt: better parse debugging Signed-off-by: Chris Koch --- pkg/dt/fdt.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/dt/fdt.go b/pkg/dt/fdt.go index 9947a81c2b..7a8bff3b80 100644 --- a/pkg/dt/fdt.go +++ b/pkg/dt/fdt.go @@ -78,20 +78,20 @@ type ReserveEntry struct { func ReadFDT(f io.ReadSeeker) (*FDT, error) { fdt := &FDT{} if err := fdt.readHeader(f); err != nil { - return nil, err + return nil, fmt.Errorf("reading FDT header: %w", err) } if err := fdt.readMemoryReservationBlock(f); err != nil { - return nil, err + return nil, fmt.Errorf("reading memory reservation block: %w", err) } if err := fdt.checkLayout(); err != nil { - return nil, err + return nil, fmt.Errorf("layout check: %w", err) } strs, err := fdt.readStringsBlock(f) if err != nil { - return nil, err + return nil, fmt.Errorf("reading strings block: %w", err) } if err := fdt.readStructBlock(f, strs); err != nil { - return nil, err + return nil, fmt.Errorf("reading struct block: %w", err) } return fdt, nil } From fa111def8637d187038cf7d144045dfac6a13a83 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 03:31:04 +0000 Subject: [PATCH 105/109] kexec,linux,arm64: avoid the dt.LoadFDT API Signed-off-by: Chris Koch --- pkg/boot/linux/load_linux_image.go | 13 +++++++++++-- pkg/boot/linux/load_linux_image_test.go | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 28cdc56f98..2696f017b8 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "math" "os" "github.com/u-root/u-root/pkg/boot/image" @@ -55,9 +56,17 @@ func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { var ErrMemmapEmpty = errors.New("memory map is empty or contains no information about system RAM") func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*kimage, error) { - fdt, err := dt.LoadFDT(dtb) + var fdt *dt.FDT + var err error + // We want to fail when a user-supplied FDT is not parseable, not + // implicitly fall back to some other FDT. Avoid the dt.LoadFDT API. + if dtb != nil { + fdt, err = dt.ReadFDT(io.NewSectionReader(dtb, 0, math.MaxInt64)) + } else { + fdt, err = dt.ReadFile("/sys/firmware/fdt") + } if err != nil { - return nil, fmt.Errorf("loadFDT(%s) = %w", dtb, err) + return nil, fmt.Errorf("Read FDT = %w", err) } Debug("Loaded FDT: %s", fdt) diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index f3f817ab65..88ceb71ed2 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -312,7 +312,7 @@ func TestKexecLoadImage(t *testing.T) { { name: "loadFDT fails", fdt: closedFile(t), - errs: []error{dt.ErrNoValidReaders}, + errs: []error{os.ErrClosed}, }, { name: "invalid-memmap", From 30ccd0afc2d9533f92d2b9083c4c8c88beee6f45 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 03:52:40 +0000 Subject: [PATCH 106/109] kexec,linux: add ability to avoid specific pieces of physical memory Signed-off-by: Chris Koch --- pkg/boot/linux.go | 10 +++++- pkg/boot/linux/load_linux_amd64.go | 5 ++- pkg/boot/linux/load_linux_arm64.go | 8 +++-- pkg/boot/linux/load_linux_image.go | 5 ++- pkg/boot/linux/load_linux_image_test.go | 41 ++++++++++++++++++++++--- pkg/boot/linux/load_other_linux.go | 3 +- 6 files changed, 61 insertions(+), 11 deletions(-) diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index c64df21556..83bc932f2d 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -29,6 +29,14 @@ type LinuxImage struct { BootRank int LoadSyscall bool DTB io.ReaderAt + + // ReservedRanges are additional physical memory pieces that will be + // avoided when allocating kexec segments. Only used for LoadSyscall. + // + // ReservedRanges will not be shared with the next kernel, which is + // free to use this memory unless some other mechanism (such as + // memmap=) reserves it. + ReservedRanges kexec.Ranges } var _ OSImage = &LinuxImage{} @@ -243,7 +251,7 @@ func (li *LinuxImage) Load(opts ...LoadOption) error { return nil } if li.LoadSyscall { - return linux.KexecLoad(k, i, li.Cmdline, li.DTB) + return linux.KexecLoad(k, i, li.Cmdline, li.DTB, li.ReservedRanges) } return kexec.FileLoad(k, i, li.Cmdline) } diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index a4cc0905cb..c7608b24a9 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -25,7 +25,7 @@ const ( // kernel with the given ramfs file and cmdline string. // // It uses the kexec_load system call. -func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservations kexec.Ranges) error { bzimage.Debug = Debug // A collection of vars used for processing the kernel for kexec @@ -79,6 +79,9 @@ func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { if err != nil { return fmt.Errorf("parse memory map: %v", err) } + for _, r := range reservations { + mm.Insert(kexec.TypedRange{Range: r, Type: kexec.RangeReserved}) + } kmem = &kexec.Memory{ Phys: mm, } diff --git a/pkg/boot/linux/load_linux_arm64.go b/pkg/boot/linux/load_linux_arm64.go index b2de1c2ece..55c762bbb4 100644 --- a/pkg/boot/linux/load_linux_arm64.go +++ b/pkg/boot/linux/load_linux_arm64.go @@ -13,8 +13,12 @@ import ( ) // KexecLoad loads arm64 Image, with the given ramfs and kernel cmdline. -func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { - img, err := kexecLoadImage(kernel, ramfs, cmdline, dtb) +// +// reservedRanges are additional pieces of physical memory that are not used +// for kexec segment allocation. They are not transmitted to the next kernel to +// be considered reserved. +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservedRanges kexec.Ranges) error { + img, err := kexecLoadImage(kernel, ramfs, cmdline, dtb, reservedRanges) if err != nil { return err } diff --git a/pkg/boot/linux/load_linux_image.go b/pkg/boot/linux/load_linux_image.go index 2696f017b8..3a1a20f36e 100644 --- a/pkg/boot/linux/load_linux_image.go +++ b/pkg/boot/linux/load_linux_image.go @@ -55,7 +55,7 @@ func sanitizeFDT(fdt *dt.FDT) (*dt.Node, error) { var ErrMemmapEmpty = errors.New("memory map is empty or contains no information about system RAM") -func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*kimage, error) { +func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservedRanges kexec.Ranges) (*kimage, error) { var fdt *dt.FDT var err error // We want to fail when a user-supplied FDT is not parseable, not @@ -80,6 +80,9 @@ func kexecLoadImage(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) (*k if len(mm.RAM()) == 0 { return nil, ErrMemmapEmpty } + for _, r := range reservedRanges { + mm.Insert(kexec.TypedRange{Range: r, Type: kexec.RangeReserved}) + } return kexecLoadImageMM(mm, kernel, ramfs, fdt, cmdline) } diff --git a/pkg/boot/linux/load_linux_image_test.go b/pkg/boot/linux/load_linux_image_test.go index 88ceb71ed2..c6671405c5 100644 --- a/pkg/boot/linux/load_linux_image_test.go +++ b/pkg/boot/linux/load_linux_image_test.go @@ -116,10 +116,11 @@ func TestKexecLoadImage(t *testing.T) { name string // Inputs - kernel *os.File - ramfs *os.File - fdt io.ReaderAt - cmdline string + kernel *os.File + ramfs *os.File + fdt io.ReaderAt + cmdline string + reservations kexec.Ranges // Results segments kexec.Segments @@ -341,9 +342,39 @@ func TestKexecLoadImage(t *testing.T) { )}), errs: []error{ErrMemmapEmpty}, }, + { + name: "load-with-reservation", + kernel: openFile(t, "../image/testdata/Image"), + entry: 0x101000, /* trampoline entry */ + fdt: fdtReader(t, &dt.FDT{ + RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + )), + }), + reservations: []kexec.Range{ + // Kernel would normally be allocated here. + // This forces kernel to go for the next 2M boundary, 0x400000. + {Start: 0x200000, Size: 0x1}, + }, + segments: kexec.Segments{ + kexec.NewSegment(fdtBytes(t, &dt.FDT{RootNode: dt.NewNode("/", dt.WithChildren( + dt.NewNode("chosen"), + dt.NewNode("test memory", dt.WithProperty( + dt.PropertyString("device_type", "memory"), + dt.PropertyRegion("reg", 0x100000, 0xf00000), + )), + ))}), kexec.Range{Start: 0x100000, Size: 0x1000}), + kexec.NewSegment(trampoline(0x400000, 0x100000), kexec.Range{Start: 0x101000, Size: 0x1000}), + kexec.NewSegment(readFile(t, "../image/testdata/Image"), kexec.Range{Start: 0x400000, Size: 0xa00000}), + }, + }, } { t.Run(tt.name, func(t *testing.T) { - got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt) + got, err := kexecLoadImage(tt.kernel, tt.ramfs, tt.cmdline, tt.fdt, tt.reservations) for _, wantErr := range tt.errs { if !errors.Is(err, wantErr) { t.Errorf("kexecLoad Arm Image = %v, want %v", err, wantErr) diff --git a/pkg/boot/linux/load_other_linux.go b/pkg/boot/linux/load_other_linux.go index 8c0399f667..ee71973999 100644 --- a/pkg/boot/linux/load_other_linux.go +++ b/pkg/boot/linux/load_other_linux.go @@ -11,10 +11,11 @@ import ( "io" "os" + "github.com/u-root/u-root/pkg/boot/kexec" "golang.org/x/sys/unix" ) // KexecLoad is not implemented for platforms other than amd64 and arm64. -func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt) error { +func KexecLoad(kernel, ramfs *os.File, cmdline string, dtb io.ReaderAt, reservations kexec.Ranges) error { return unix.ENOSYS } From 8ccfc73c1f3926e90fced092b813c052b9fe2aa2 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Sat, 3 Feb 2024 23:41:37 +0000 Subject: [PATCH 107/109] Replace pkg/uio with uio/uio Signed-off-by: Chris Koch --- cmds/core/cp/cp_test.go | 2 +- cmds/core/kexec/kexec_linux.go | 2 +- cmds/core/wget/wget.go | 2 +- cmds/exp/madeye/madeye.go | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/boot/boottest/same_image.go | 2 +- pkg/boot/esxi/esxi.go | 2 +- pkg/boot/esxi/esxi_test.go | 2 +- pkg/boot/grub/grub.go | 2 +- pkg/boot/ibft/ibft.go | 2 +- pkg/boot/initrd.go | 2 +- pkg/boot/initrd_test.go | 2 +- pkg/boot/linux.go | 2 +- pkg/boot/linux/load_linux.go | 2 +- pkg/boot/linux/load_linux_amd64.go | 2 +- pkg/boot/linux_test.go | 2 +- pkg/boot/multiboot/description.go | 2 +- pkg/boot/multiboot/module.go | 2 +- pkg/boot/multiboot/multiboot.go | 2 +- pkg/boot/multiboot/mutiboot_info.go | 2 +- pkg/boot/netboot/ipxe/ipxe.go | 2 +- pkg/boot/netboot/ipxe/ipxe_test.go | 2 +- pkg/boot/syslinux/syslinux.go | 2 +- pkg/boot/util/reader.go | 2 +- pkg/cpio/fs_plan9.go | 2 +- pkg/cpio/fs_unix.go | 2 +- pkg/cpio/fs_windows.go | 2 +- pkg/cpio/newc.go | 2 +- pkg/cpio/newc_test.go | 2 +- pkg/cpio/utils.go | 2 +- pkg/curl/schemes.go | 2 +- pkg/curl/schemes_test.go | 2 +- pkg/dt/fdt.go | 2 +- pkg/gzip/file.go | 2 +- pkg/securelaunch/launcher/launcher.go | 2 +- pkg/strace/tracer_test.go | 2 +- pkg/uio/alignreader.go | 41 -- pkg/uio/alignwriter.go | 34 -- pkg/uio/archivereader.go | 85 ---- pkg/uio/archivereader_test.go | 244 ------------ pkg/uio/buffer.go | 372 ------------------ pkg/uio/cached.go | 98 ----- pkg/uio/cached_test.go | 244 ------------ pkg/uio/lazy.go | 165 -------- pkg/uio/lazy_test.go | 171 -------- pkg/uio/linewriter.go | 57 --- pkg/uio/null.go | 70 ---- pkg/uio/progress.go | 42 -- pkg/uio/progress_test.go | 62 --- pkg/uio/reader.go | 67 ---- pkg/uio/reader_test.go | 64 --- pkg/uio/uio.go | 9 - pkg/uio/uiotest/pkg_test.go | 11 - pkg/uroot/initramfs/files_test.go | 2 +- pkg/uroot/initramfs/test/ramfs.go | 2 +- uroot_test.go | 2 +- vendor/github.com/u-root/uio/uio/lazy.go | 75 +++- .../u-root/uio}/uio/uiotest/uiotest.go | 16 +- vendor/modules.txt | 3 +- 60 files changed, 123 insertions(+), 1889 deletions(-) delete mode 100644 pkg/uio/alignreader.go delete mode 100644 pkg/uio/alignwriter.go delete mode 100644 pkg/uio/archivereader.go delete mode 100644 pkg/uio/archivereader_test.go delete mode 100644 pkg/uio/buffer.go delete mode 100644 pkg/uio/cached.go delete mode 100644 pkg/uio/cached_test.go delete mode 100644 pkg/uio/lazy.go delete mode 100644 pkg/uio/lazy_test.go delete mode 100644 pkg/uio/linewriter.go delete mode 100644 pkg/uio/null.go delete mode 100644 pkg/uio/progress.go delete mode 100644 pkg/uio/progress_test.go delete mode 100644 pkg/uio/reader.go delete mode 100644 pkg/uio/reader_test.go delete mode 100644 pkg/uio/uio.go delete mode 100644 pkg/uio/uiotest/pkg_test.go rename {pkg => vendor/github.com/u-root/uio}/uio/uiotest/uiotest.go (71%) diff --git a/cmds/core/cp/cp_test.go b/cmds/core/cp/cp_test.go index 4824b9003e..1ae0b18ee3 100644 --- a/cmds/core/cp/cp_test.go +++ b/cmds/core/cp/cp_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/u-root/u-root/pkg/cp" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/cmds/core/kexec/kexec_linux.go b/cmds/core/kexec/kexec_linux.go index 0112ab41c2..3caa9e106d 100644 --- a/cmds/core/kexec/kexec_linux.go +++ b/cmds/core/kexec/kexec_linux.go @@ -40,7 +40,7 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/boot/purgatory" "github.com/u-root/u-root/pkg/cmdline" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type options struct { diff --git a/cmds/core/wget/wget.go b/cmds/core/wget/wget.go index e59971d9a4..c9566e76f6 100644 --- a/cmds/core/wget/wget.go +++ b/cmds/core/wget/wget.go @@ -35,7 +35,7 @@ import ( "strings" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var errEmptyURL = errors.New("empty url") diff --git a/cmds/exp/madeye/madeye.go b/cmds/exp/madeye/madeye.go index 53d923b277..d72588f53b 100644 --- a/cmds/exp/madeye/madeye.go +++ b/cmds/exp/madeye/madeye.go @@ -39,7 +39,7 @@ import ( "path/filepath" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/go.mod b/go.mod index 53e2c80500..31f5a2a1b5 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a - github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 + github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e github.com/ulikunitz/xz v0.5.11 github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vtolstov/go-ioctl v0.0.0-20151206205506-6be9cced4810 diff --git a/go.sum b/go.sum index 244ccd806e..8987918c44 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa h1:unMPGGK/CR github.com/u-root/gobusybox/src v0.0.0-20231228173702-b69f654846aa/go.mod h1:Zj4Tt22fJVn/nz/y6Ergm1SahR9dio1Zm/D2/S0TmXM= github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a h1:A0sK7WEodak7eVd21MOEatnh2pfAAwZaEPSIEEsjctQ= github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a/go.mod h1:RWIgJWqm9/0gjBZ0Hl8iR6MVGzZ+yAda2uqqLmetE2I= -github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg= -github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e h1:BA9O3BmlTmpjbvajAwzWx4Wo2TRVdpPXZEeemGQcajw= +github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= diff --git a/pkg/boot/boottest/same_image.go b/pkg/boot/boottest/same_image.go index 9611ff52e0..f72139f71b 100644 --- a/pkg/boot/boottest/same_image.go +++ b/pkg/boot/boottest/same_image.go @@ -9,7 +9,7 @@ import ( "io" "github.com/u-root/u-root/pkg/boot" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func mustReadAll(r io.ReaderAt) string { diff --git a/pkg/boot/esxi/esxi.go b/pkg/boot/esxi/esxi.go index 083dadcab6..50494faa24 100644 --- a/pkg/boot/esxi/esxi.go +++ b/pkg/boot/esxi/esxi.go @@ -42,7 +42,7 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/mount/gpt" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func partNo(device string, number int) (string, error) { diff --git a/pkg/boot/esxi/esxi_test.go b/pkg/boot/esxi/esxi_test.go index 6ad8abf70b..1de8d86629 100644 --- a/pkg/boot/esxi/esxi_test.go +++ b/pkg/boot/esxi/esxi_test.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/multiboot" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func TestParse(t *testing.T) { diff --git a/pkg/boot/grub/grub.go b/pkg/boot/grub/grub.go index 5a9acde0c9..3b28a0be8d 100644 --- a/pkg/boot/grub/grub.go +++ b/pkg/boot/grub/grub.go @@ -34,8 +34,8 @@ import ( "github.com/u-root/u-root/pkg/mount" "github.com/u-root/u-root/pkg/mount/block" "github.com/u-root/u-root/pkg/shlex" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog" + "github.com/u-root/uio/uio" ) var probeGrubFiles = []string{ diff --git a/pkg/boot/ibft/ibft.go b/pkg/boot/ibft/ibft.go index b465d8a0ad..fe9312a3d9 100644 --- a/pkg/boot/ibft/ibft.go +++ b/pkg/boot/ibft/ibft.go @@ -22,7 +22,7 @@ import ( "fmt" "net" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var ( diff --git a/pkg/boot/initrd.go b/pkg/boot/initrd.go index 984beab264..ba7090eb9d 100644 --- a/pkg/boot/initrd.go +++ b/pkg/boot/initrd.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // CatInitrdsWithFileCache lazily reads up multiple initrds into single tmpfs file diff --git a/pkg/boot/initrd_test.go b/pkg/boot/initrd_test.go index 8d720b3c41..18fa7f6487 100644 --- a/pkg/boot/initrd_test.go +++ b/pkg/boot/initrd_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type file struct { diff --git a/pkg/boot/linux.go b/pkg/boot/linux.go index 83bc932f2d..cc0f64b8da 100644 --- a/pkg/boot/linux.go +++ b/pkg/boot/linux.go @@ -15,7 +15,7 @@ import ( "github.com/u-root/u-root/pkg/boot/linux" "github.com/u-root/u-root/pkg/boot/util" "github.com/u-root/u-root/pkg/mount" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/pkg/boot/linux/load_linux.go b/pkg/boot/linux/load_linux.go index 184f1c452c..06d6e27c61 100644 --- a/pkg/boot/linux/load_linux.go +++ b/pkg/boot/linux/load_linux.go @@ -10,7 +10,7 @@ import ( "os" "syscall" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/pkg/boot/linux/load_linux_amd64.go b/pkg/boot/linux/load_linux_amd64.go index c7608b24a9..44cd72007f 100644 --- a/pkg/boot/linux/load_linux_amd64.go +++ b/pkg/boot/linux/load_linux_amd64.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/boot/bzimage" "github.com/u-root/u-root/pkg/boot/kexec" "github.com/u-root/u-root/pkg/boot/purgatory" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( diff --git a/pkg/boot/linux_test.go b/pkg/boot/linux_test.go index 6df6ef52b9..ebb923ff0b 100644 --- a/pkg/boot/linux_test.go +++ b/pkg/boot/linux_test.go @@ -18,7 +18,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/u-root/u-root/pkg/curl" "github.com/u-root/u-root/pkg/mount" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "github.com/u-root/uio/ulog/ulogtest" "golang.org/x/sys/unix" ) diff --git a/pkg/boot/multiboot/description.go b/pkg/boot/multiboot/description.go index fbf3120b2b..a784b2699a 100644 --- a/pkg/boot/multiboot/description.go +++ b/pkg/boot/multiboot/description.go @@ -11,7 +11,7 @@ import ( "fmt" "strconv" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // DebugPrefix is a prefix that some messages are printed with for tests to parse. diff --git a/pkg/boot/multiboot/module.go b/pkg/boot/multiboot/module.go index 76bf9006ef..abc84ee5a9 100644 --- a/pkg/boot/multiboot/module.go +++ b/pkg/boot/multiboot/module.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/align" "github.com/u-root/u-root/pkg/ubinary" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // A module represents a module to be loaded along with the kernel. diff --git a/pkg/boot/multiboot/multiboot.go b/pkg/boot/multiboot/multiboot.go index 67ab911f54..d6d8e42c96 100644 --- a/pkg/boot/multiboot/multiboot.go +++ b/pkg/boot/multiboot/multiboot.go @@ -25,7 +25,7 @@ import ( "github.com/u-root/u-root/pkg/boot/multiboot/internal/trampoline" "github.com/u-root/u-root/pkg/boot/util" "github.com/u-root/u-root/pkg/ubinary" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const bootloader = "u-root kexec" diff --git a/pkg/boot/multiboot/mutiboot_info.go b/pkg/boot/multiboot/mutiboot_info.go index f00c784fcf..a6e0502f33 100644 --- a/pkg/boot/multiboot/mutiboot_info.go +++ b/pkg/boot/multiboot/mutiboot_info.go @@ -5,7 +5,7 @@ package multiboot import ( - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type esxBootInfoInfo struct { diff --git a/pkg/boot/netboot/ipxe/ipxe.go b/pkg/boot/netboot/ipxe/ipxe.go index c0945d71ec..cb6eb7e850 100644 --- a/pkg/boot/netboot/ipxe/ipxe.go +++ b/pkg/boot/netboot/ipxe/ipxe.go @@ -17,8 +17,8 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog" + "github.com/u-root/uio/uio" ) // ErrNotIpxeScript is returned when the config file is not an diff --git a/pkg/boot/netboot/ipxe/ipxe_test.go b/pkg/boot/netboot/ipxe/ipxe_test.go index 11d748a83d..82d85d6904 100644 --- a/pkg/boot/netboot/ipxe/ipxe_test.go +++ b/pkg/boot/netboot/ipxe/ipxe_test.go @@ -15,8 +15,8 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/ulog/ulogtest" + "github.com/u-root/uio/uio" ) func mustReadAll(r io.ReaderAt) string { diff --git a/pkg/boot/syslinux/syslinux.go b/pkg/boot/syslinux/syslinux.go index bc03ca885e..c86d634a4b 100644 --- a/pkg/boot/syslinux/syslinux.go +++ b/pkg/boot/syslinux/syslinux.go @@ -24,7 +24,7 @@ import ( "github.com/u-root/u-root/pkg/boot" "github.com/u-root/u-root/pkg/boot/multiboot" "github.com/u-root/u-root/pkg/curl" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func probeIsolinuxFiles() []string { diff --git a/pkg/boot/util/reader.go b/pkg/boot/util/reader.go index 28f608c2e8..21a751ec28 100644 --- a/pkg/boot/util/reader.go +++ b/pkg/boot/util/reader.go @@ -9,7 +9,7 @@ import ( "compress/gzip" "io" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func readGzip(r io.Reader) ([]byte, error) { diff --git a/pkg/cpio/fs_plan9.go b/pkg/cpio/fs_plan9.go index e976e2a40e..5abc07bf6e 100644 --- a/pkg/cpio/fs_plan9.go +++ b/pkg/cpio/fs_plan9.go @@ -14,8 +14,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" ) // A Recorder is a structure that contains variables used to calculate diff --git a/pkg/cpio/fs_unix.go b/pkg/cpio/fs_unix.go index eef60fdfc2..d048fd9b8e 100644 --- a/pkg/cpio/fs_unix.go +++ b/pkg/cpio/fs_unix.go @@ -17,8 +17,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" "golang.org/x/sys/unix" ) diff --git a/pkg/cpio/fs_windows.go b/pkg/cpio/fs_windows.go index 1c6d43c36b..cc2079166f 100644 --- a/pkg/cpio/fs_windows.go +++ b/pkg/cpio/fs_windows.go @@ -14,8 +14,8 @@ import ( "time" "github.com/u-root/u-root/pkg/ls" - "github.com/u-root/u-root/pkg/uio" "github.com/u-root/u-root/pkg/upath" + "github.com/u-root/uio/uio" ) // A Recorder is a structure that contains variables used to calculate diff --git a/pkg/cpio/newc.go b/pkg/cpio/newc.go index 9e767e83ed..ff72ce5a55 100644 --- a/pkg/cpio/newc.go +++ b/pkg/cpio/newc.go @@ -12,7 +12,7 @@ import ( "io" "os" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( diff --git a/pkg/cpio/newc_test.go b/pkg/cpio/newc_test.go index b8a600f9d8..88fbf9eee8 100644 --- a/pkg/cpio/newc_test.go +++ b/pkg/cpio/newc_test.go @@ -14,7 +14,7 @@ import ( "syscall" "testing" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) /* diff --git a/pkg/cpio/utils.go b/pkg/cpio/utils.go index 71dde815b7..b4c507395b 100644 --- a/pkg/cpio/utils.go +++ b/pkg/cpio/utils.go @@ -12,7 +12,7 @@ import ( "path" "strings" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // Trailer is the name of the trailer record. diff --git a/pkg/curl/schemes.go b/pkg/curl/schemes.go index 75cd531212..8bac3bc0da 100644 --- a/pkg/curl/schemes.go +++ b/pkg/curl/schemes.go @@ -22,7 +22,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" "pack.ag/tftp" ) diff --git a/pkg/curl/schemes_test.go b/pkg/curl/schemes_test.go index 42d61041f2..f9b075ba5b 100644 --- a/pkg/curl/schemes_test.go +++ b/pkg/curl/schemes_test.go @@ -15,7 +15,7 @@ import ( "testing" "github.com/cenkalti/backoff/v4" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) var ( diff --git a/pkg/dt/fdt.go b/pkg/dt/fdt.go index 7a8bff3b80..09fcb382b5 100644 --- a/pkg/dt/fdt.go +++ b/pkg/dt/fdt.go @@ -17,7 +17,7 @@ import ( "unsafe" "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) const ( diff --git a/pkg/gzip/file.go b/pkg/gzip/file.go index 28abc0032a..74a6061b68 100644 --- a/pkg/gzip/file.go +++ b/pkg/gzip/file.go @@ -9,7 +9,7 @@ import ( "os" "strings" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // File is a file path to be compressed or decompressed. diff --git a/pkg/securelaunch/launcher/launcher.go b/pkg/securelaunch/launcher/launcher.go index f3ceb890d3..ad5ce8a448 100644 --- a/pkg/securelaunch/launcher/launcher.go +++ b/pkg/securelaunch/launcher/launcher.go @@ -14,7 +14,7 @@ import ( "github.com/u-root/u-root/pkg/mount" slaunch "github.com/u-root/u-root/pkg/securelaunch" "github.com/u-root/u-root/pkg/securelaunch/measurement" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) // Launcher describes the "launcher" section of policy file. diff --git a/pkg/strace/tracer_test.go b/pkg/strace/tracer_test.go index cdd2002676..44965f7e80 100644 --- a/pkg/strace/tracer_test.go +++ b/pkg/strace/tracer_test.go @@ -16,7 +16,7 @@ import ( "time" "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio/uiotest" + "github.com/u-root/uio/uio/uiotest" ) func prepareTestCmd(t *testing.T, cmd string) { diff --git a/pkg/uio/alignreader.go b/pkg/uio/alignreader.go deleted file mode 100644 index 6800644148..0000000000 --- a/pkg/uio/alignreader.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "io" -) - -// AlignReader keeps track of how many bytes were read so the reader can be -// aligned at a future time. -type AlignReader struct { - R io.Reader - N int -} - -// Read reads from the underlying io.Reader. -func (r *AlignReader) Read(b []byte) (int, error) { - n, err := r.R.Read(b) - r.N += n - return n, err -} - -// ReadByte reads one byte from the underlying io.Reader. -func (r *AlignReader) ReadByte() (byte, error) { - b := make([]byte, 1) - _, err := io.ReadFull(r, b) - return b[0], err -} - -// Align aligns the reader to the given number of bytes and returns the -// bytes read to pad it. -func (r *AlignReader) Align(n int) ([]byte, error) { - if r.N%n == 0 { - return []byte{}, nil - } - pad := make([]byte, n-r.N%n) - m, err := io.ReadFull(r, pad) - return pad[:m], err -} diff --git a/pkg/uio/alignwriter.go b/pkg/uio/alignwriter.go deleted file mode 100644 index 95a52f61fa..0000000000 --- a/pkg/uio/alignwriter.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// AlignWriter keeps track of how many bytes were written so the writer can be -// aligned at a future time. -type AlignWriter struct { - W io.Writer - N int -} - -// Write writes to the underlying io.Writew. -func (w *AlignWriter) Write(b []byte) (int, error) { - n, err := w.W.Write(b) - w.N += n - return n, err -} - -// Align aligns the writer to the given number of bytes using the given pad -// value. -func (w *AlignWriter) Align(n int, pad byte) error { - if w.N%n == 0 { - return nil - } - _, err := w.Write(bytes.Repeat([]byte{pad}, n-w.N%n)) - return err -} diff --git a/pkg/uio/archivereader.go b/pkg/uio/archivereader.go deleted file mode 100644 index 4a3a9fc068..0000000000 --- a/pkg/uio/archivereader.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "errors" - "io" - - "github.com/pierrec/lz4/v4" -) - -const ( - // preReadSizeBytes is the num of bytes pre-read from a io.Reader that will - // be used to match against archive header. - defaultArchivePreReadSizeBytes = 1024 -) - -var ErrPreReadError = errors.New("pre-read nothing") - -// ArchiveReader reads from a io.Reader, decompresses source bytes -// when applicable. -// -// It allows probing for multiple archive format, while still able -// to read from beginning, by pre-reading a small number of bytes. -// -// Always use newArchiveReader to initialize. -type ArchiveReader struct { - // src is where we read source bytes. - src io.Reader - // buf stores pre-read bytes from original io.Reader. Archive format - // detection will be done against it. - buf []byte - - // preReadSizeBytes is how many bytes we pre-read for magic number - // matching for each archive type. This should be greater than or - // equal to the largest header frame size of each supported archive - // format. - preReadSizeBytes int -} - -func NewArchiveReader(r io.Reader) (ArchiveReader, error) { - ar := ArchiveReader{ - src: r, - // Randomly chosen, should be enough for most types: - // - // e.g. gzip with 10 byte header, lz4 with a header size - // between 7 and 19 bytes. - preReadSizeBytes: defaultArchivePreReadSizeBytes, - } - pbuf := make([]byte, ar.preReadSizeBytes) - - nr, err := io.ReadFull(r, pbuf) - // In case the image is smaller pre-read block size, 1kb for now. - // Ever possible ? probably not in case a compression is needed! - ar.buf = pbuf[:nr] - if err == io.EOF { - // If we could not pre-read anything, we can't determine if - // it is a compressed file. - ar.src = io.MultiReader(bytes.NewReader(pbuf[:nr]), r) - return ar, ErrPreReadError - } - - // Try each supported compression type, return upon first match. - - // Try lz4. - // magic number error will be thrown if source is not a lz4 archive. - // e.g. "lz4: bad magic number". - if ok, err := lz4.ValidFrameHeader(ar.buf); err == nil && ok { - ar.src = lz4.NewReader(io.MultiReader(bytes.NewReader(ar.buf), r)) - return ar, nil - } - - // Try other archive types here, gzip, xz, etc when needed. - - // Last resort, read as is. - ar.src = io.MultiReader(bytes.NewReader(ar.buf), r) - return ar, nil -} - -func (ar ArchiveReader) Read(p []byte) (n int, err error) { - return ar.src.Read(p) -} diff --git a/pkg/uio/archivereader_test.go b/pkg/uio/archivereader_test.go deleted file mode 100644 index 798b1d172a..0000000000 --- a/pkg/uio/archivereader_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "math/rand" - "strings" - "testing" - "time" - - "github.com/pierrec/lz4/v4" -) - -const choices = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -func TestArchiveReaderRegular(t *testing.T) { - dataStr := strings.Repeat("This is an important data!@#$%^^&&*&**(()())", 1000) - - ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != nil { - t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", []byte(dataStr), err) - } - - buf := new(strings.Builder) - if _, err := io.Copy(buf, ar); err != nil { - t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) - } - if buf.String() != dataStr { - t.Errorf("got %s, want %s", buf.String(), dataStr) - } -} - -func TestArchiveReaderPreReadShort(t *testing.T) { - dataStr := "short data" - ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != nil { - t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want nil", dataStr, err) - } - got, err := io.ReadAll(ar) - if err != nil { - t.Errorf("got error reading archive reader: %v, want nil", err) - } - if string(got) != dataStr { - t.Errorf("got %s, want %s", string(got), dataStr) - } - // Pre-read nothing. - dataStr = "" - ar, err = NewArchiveReader(bytes.NewReader([]byte(dataStr))) - if err != ErrPreReadError { - t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want %v", dataStr, err, ErrPreReadError) - } - got, err = io.ReadAll(ar) - if err != nil { - t.Errorf("got error reading archive reader: %v, want nil", err) - } - if string(got) != dataStr { - t.Errorf("got %s, want %s", string(got), dataStr) - } -} - -// randomString generates random string of fixed length in a fast and simple way. -func randomString(l int) string { - rand.Seed(time.Now().UnixNano()) - r := make([]byte, l) - for i := 0; i < l; i++ { - r[i] = byte(choices[rand.Intn(len(choices))]) - } - return string(r) -} - -func checkArchiveReaderLZ4(t *testing.T, tt archiveReaderLZ4Case) { - t.Helper() - - srcR := bytes.NewReader([]byte(tt.dataStr)) - - srcBuf := new(bytes.Buffer) - lz4w := tt.setup(srcBuf) - - n, err := io.Copy(lz4w, srcR) - if err != nil { - t.Fatalf("io.Copy(%v, %v) returned error: %v, want nil", lz4w, srcR, err) - } - if n != int64(len([]byte(tt.dataStr))) { - t.Fatalf("got %d bytes compressed, want %d", n, len([]byte(tt.dataStr))) - } - if err = lz4w.Close(); err != nil { - t.Fatalf("Failed to close lz4 writer: %v", err) - } - - // Test ArchiveReader reading it. - ar, err := NewArchiveReader(bytes.NewReader(srcBuf.Bytes())) - if err != nil { - t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", srcBuf.Bytes(), err) - } - buf := new(strings.Builder) - if _, err := io.Copy(buf, ar); err != nil { - t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) - } - if buf.String() != tt.dataStr { - t.Errorf("got %s, want %s", buf.String(), tt.dataStr) - } -} - -type archiveReaderLZ4Case struct { - name string - setup func(w io.Writer) *lz4.Writer - dataStr string -} - -func TestArchiveReaderLZ4(t *testing.T) { - for _, tt := range []archiveReaderLZ4Case{ - { - name: "non-legacy regular", - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(1024), - }, - { - name: "non-legacy larger data", - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(5 * 1024), - }, - { - name: "non-legacy short data", // Likley not realistic for most cases in the real world. - setup: func(w io.Writer) *lz4.Writer { - return lz4.NewWriter(w) - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes. - }, - { - name: "legacy regular", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(1024), - }, - { - name: "legacy larger data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy small data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. - }, - { - name: "legacy small data", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - return lz4w - }, - dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. - }, - { - name: "regular larger data with fast compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Fast)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with fast compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Fast)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - } { - t.Run(tt.name, func(t *testing.T) { - checkArchiveReaderLZ4(t, tt) - }) - } -} - -func TestArchiveReaderLZ4SlowCompressed(t *testing.T) { - for _, tt := range []archiveReaderLZ4Case{ - { - name: "regular larger data with medium compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level5)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "regular larger data with slow compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level9)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with medium compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level5)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - { - name: "legacy larger data with slow compression", - setup: func(w io.Writer) *lz4.Writer { - lz4w := lz4.NewWriter(w) - lz4w.Apply(lz4.LegacyOption(true)) - lz4w.Apply(lz4.CompressionLevelOption(lz4.Level9)) - return lz4w - }, - dataStr: randomString(5 * 1024), - }, - } { - t.Run(tt.name, func(t *testing.T) { - checkArchiveReaderLZ4(t, tt) - }) - } -} diff --git a/pkg/uio/buffer.go b/pkg/uio/buffer.go deleted file mode 100644 index e7b39d2f84..0000000000 --- a/pkg/uio/buffer.go +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "encoding/binary" - "fmt" - - "github.com/u-root/u-root/pkg/align" - "github.com/u-root/u-root/pkg/ubinary" -) - -// Marshaler is the interface implemented by an object that can marshal itself -// into binary form. -// -// Marshal appends data to the buffer b. -type Marshaler interface { - Marshal(l *Lexer) -} - -// Unmarshaler is the interface implemented by an object that can unmarshal a -// binary representation of itself. -// -// Unmarshal Consumes data from the buffer b. -type Unmarshaler interface { - Unmarshal(l *Lexer) error -} - -// ToBytes marshals m in the given byte order. -func ToBytes(m Marshaler, order binary.ByteOrder) []byte { - l := NewLexer(NewBuffer(nil), order) - m.Marshal(l) - return l.Data() -} - -// FromBytes unmarshals b into obj in the given byte order. -func FromBytes(obj Unmarshaler, b []byte, order binary.ByteOrder) error { - l := NewLexer(NewBuffer(b), order) - return obj.Unmarshal(l) -} - -// ToBigEndian marshals m to big endian byte order. -func ToBigEndian(m Marshaler) []byte { - l := NewBigEndianBuffer(nil) - m.Marshal(l) - return l.Data() -} - -// FromBigEndian unmarshals b into obj in big endian byte order. -func FromBigEndian(obj Unmarshaler, b []byte) error { - l := NewBigEndianBuffer(b) - return obj.Unmarshal(l) -} - -// ToLittleEndian marshals m to little endian byte order. -func ToLittleEndian(m Marshaler) []byte { - l := NewLittleEndianBuffer(nil) - m.Marshal(l) - return l.Data() -} - -// FromLittleEndian unmarshals b into obj in little endian byte order. -func FromLittleEndian(obj Unmarshaler, b []byte) error { - l := NewLittleEndianBuffer(b) - return obj.Unmarshal(l) -} - -// Buffer implements functions to manipulate byte slices in a zero-copy way. -type Buffer struct { - // data is the underlying data. - data []byte - - // byteCount keeps track of how many bytes have been consumed for - // debugging. - byteCount int -} - -// NewBuffer Consumes b for marshaling or unmarshaling in the given byte order. -func NewBuffer(b []byte) *Buffer { - return &Buffer{data: b} -} - -// Preallocate increases the capacity of the buffer by n bytes. -func (b *Buffer) Preallocate(n int) { - b.data = append(b.data, make([]byte, 0, n)...) -} - -// WriteN appends n bytes to the Buffer and returns a slice pointing to the -// newly appended bytes. -func (b *Buffer) WriteN(n int) []byte { - b.data = append(b.data, make([]byte, n)...) - return b.data[len(b.data)-n:] -} - -// ReadN consumes n bytes from the Buffer. It returns nil, false if there -// aren't enough bytes left. -func (b *Buffer) ReadN(n int) ([]byte, error) { - if !b.Has(n) { - return nil, fmt.Errorf("buffer too short at position %d: have %d bytes, want %d bytes", b.byteCount, b.Len(), n) - } - rval := b.data[:n] - b.data = b.data[n:] - b.byteCount += n - return rval, nil -} - -// Data is unConsumed data remaining in the Buffer. -func (b *Buffer) Data() []byte { - return b.data -} - -// Has returns true if n bytes are available. -func (b *Buffer) Has(n int) bool { - return len(b.data) >= n -} - -// Len returns the length of the remaining bytes. -func (b *Buffer) Len() int { - return len(b.data) -} - -// Cap returns the available capacity. -func (b *Buffer) Cap() int { - return cap(b.data) -} - -// Lexer is a convenient encoder/decoder for buffers. -// -// Use: -// -// func (s *something) Unmarshal(l *Lexer) { -// s.Foo = l.Read8() -// s.Bar = l.Read8() -// s.Baz = l.Read16() -// return l.Error() -// } -type Lexer struct { - *Buffer - - // order is the byte order to write in / read in. - order binary.ByteOrder - - // err - err error -} - -// NewLexer returns a new coder for buffers. -func NewLexer(b *Buffer, order binary.ByteOrder) *Lexer { - return &Lexer{ - Buffer: b, - order: order, - } -} - -// NewLittleEndianBuffer returns a new little endian coder for a new buffer. -func NewLittleEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: binary.LittleEndian, - } -} - -// NewBigEndianBuffer returns a new big endian coder for a new buffer. -func NewBigEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: binary.BigEndian, - } -} - -// NewNativeEndianBuffer returns a new native endian coder for a new buffer. -func NewNativeEndianBuffer(b []byte) *Lexer { - return &Lexer{ - Buffer: NewBuffer(b), - order: ubinary.NativeEndian, - } -} - -func (l *Lexer) setError(err error) { - if l.err == nil { - l.err = err - } -} - -// Consume returns a slice of the next n bytes from the buffer. -// -// Consume gives direct access to the underlying data. -func (l *Lexer) Consume(n int) []byte { - v, err := l.Buffer.ReadN(n) - if err != nil { - l.setError(err) - return nil - } - return v -} - -func (l *Lexer) append(n int) []byte { - return l.Buffer.WriteN(n) -} - -// Error returns an error if an error occurred reading from the buffer. -func (l *Lexer) Error() error { - return l.err -} - -// FinError returns an error if an error occurred or if there is more data left -// to read in the buffer. -func (l *Lexer) FinError() error { - if l.err != nil { - return l.err - } - if l.Buffer.Len() > 0 { - return fmt.Errorf("buffer contains more bytes than it should") - } - return nil -} - -// Read8 reads a byte from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read8() uint8 { - v := l.Consume(1) - if v == nil { - return 0 - } - return uint8(v[0]) -} - -// Read16 reads a 16-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read16() uint16 { - v := l.Consume(2) - if v == nil { - return 0 - } - return l.order.Uint16(v) -} - -// Read32 reads a 32-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read32() uint32 { - v := l.Consume(4) - if v == nil { - return 0 - } - return l.order.Uint32(v) -} - -// Read64 reads a 64-bit value from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Read64() uint64 { - v := l.Consume(8) - if v == nil { - return 0 - } - return l.order.Uint64(v) -} - -// CopyN returns a copy of the next n bytes. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) CopyN(n int) []byte { - v := l.Consume(n) - if v == nil { - return nil - } - - p := make([]byte, n) - m := copy(p, v) - return p[:m] -} - -// ReadAll Consumes and returns a copy of all remaining bytes in the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadAll() []byte { - return l.CopyN(l.Len()) -} - -// ReadBytes reads exactly len(p) values from the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadBytes(p []byte) { - copy(p, l.Consume(len(p))) -} - -// Read implements io.Reader.Read. -func (l *Lexer) Read(p []byte) (int, error) { - v := l.Consume(len(p)) - if v == nil { - return 0, l.Error() - } - return copy(p, v), nil -} - -// ReadData reads the binary representation of data from the buffer. -// -// See binary.Read. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) ReadData(data interface{}) { - l.setError(binary.Read(l, l.order, data)) -} - -// WriteData writes a binary representation of data to the buffer. -// -// See binary.Write. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) WriteData(data interface{}) { - l.setError(binary.Write(l, l.order, data)) -} - -// Write8 writes a byte to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write8(v uint8) { - l.append(1)[0] = byte(v) -} - -// Write16 writes a 16-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write16(v uint16) { - l.order.PutUint16(l.append(2), v) -} - -// Write32 writes a 32-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write32(v uint32) { - l.order.PutUint32(l.append(4), v) -} - -// Write64 writes a 64-bit value to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write64(v uint64) { - l.order.PutUint64(l.append(8), v) -} - -// Append returns a newly appended n-size Buffer to write to. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Append(n int) []byte { - return l.append(n) -} - -// WriteBytes writes p to the Buffer. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) WriteBytes(p []byte) { - copy(l.append(len(p)), p) -} - -// Write implements io.Writer.Write. -// -// If an error occurred, Error() will return a non-nil error. -func (l *Lexer) Write(p []byte) (int, error) { - return copy(l.append(len(p)), p), nil -} - -// Align appends bytes to align the length of the buffer to be divisible by n. -func (l *Lexer) Align(n int) { - pad := int(align.Up(uint(l.Len()), uint(n))) - l.Len() - l.Append(pad) -} diff --git a/pkg/uio/cached.go b/pkg/uio/cached.go deleted file mode 100644 index a39ff981ea..0000000000 --- a/pkg/uio/cached.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// CachingReader is a lazily caching wrapper of an io.Reader. -// -// The wrapped io.Reader is only read from on demand, not upfront. -type CachingReader struct { - buf bytes.Buffer - r io.Reader - pos int - eof bool -} - -// NewCachingReader buffers reads from r. -// -// r is only read from when Read() is called. -func NewCachingReader(r io.Reader) *CachingReader { - return &CachingReader{ - r: r, - } -} - -func (cr *CachingReader) read(p []byte) (int, error) { - n, err := cr.r.Read(p) - cr.buf.Write(p[:n]) - if err == io.EOF || (n == 0 && err == nil) { - cr.eof = true - return n, io.EOF - } - return n, err -} - -// NewReader returns a new io.Reader that reads cr from offset 0. -func (cr *CachingReader) NewReader() io.Reader { - return Reader(cr) -} - -// Read reads from cr; implementing io.Reader. -// -// TODO(chrisko): Decide whether to keep this or only keep NewReader(). -func (cr *CachingReader) Read(p []byte) (int, error) { - n, err := cr.ReadAt(p, int64(cr.pos)) - cr.pos += n - return n, err -} - -// ReadAt reads from cr; implementing io.ReaderAt. -func (cr *CachingReader) ReadAt(p []byte, off int64) (int, error) { - if len(p) == 0 { - return 0, nil - } - end := int(off) + len(p) - - // Is the caller asking for some uncached bytes? - unread := end - cr.buf.Len() - if unread > 0 { - // Avoiding allocations: use `p` to read more bytes. - for unread > 0 { - toRead := unread % len(p) - if toRead == 0 { - toRead = len(p) - } - - m, err := cr.read(p[:toRead]) - unread -= m - if err == io.EOF { - break - } - if err != nil { - return 0, err - } - } - } - - // If this is true, the entire file was read just to find out, but the - // offset is beyond the end of the file. - if off > int64(cr.buf.Len()) { - return 0, io.EOF - } - - var err error - // Did the caller ask for more than was available? - // - // Note that any io.ReaderAt implementation *must* return an error for - // short reads. - if cr.eof && unread > 0 { - err = io.EOF - } - return copy(p, cr.buf.Bytes()[off:]), err -} diff --git a/pkg/uio/cached_test.go b/pkg/uio/cached_test.go deleted file mode 100644 index fda93df80d..0000000000 --- a/pkg/uio/cached_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "fmt" - "io" - "testing" -) - -func TestCachingReaderRead(t *testing.T) { - type read struct { - // Buffer sizes to call Read with. - size int - - // Buffer value corresponding Read(size) we want. - want []byte - - // Error corresponding to Read(size) we want. - err error - } - - for i, tt := range []struct { - // Content of the underlying io.Reader. - content []byte - - // Read calls to make in order. - reads []read - }{ - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []read{ - { - size: 0, - }, - { - size: 1, - want: []byte{0x11}, - }, - { - size: 2, - want: []byte{0x22, 0x33}, - }, - { - size: 0, - }, - { - size: 3, - want: []byte{0x44, 0x55, 0x66}, - }, - { - size: 4, - want: []byte{0x77, 0x88, 0x99}, - err: io.EOF, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []read{ - { - size: 11, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - err: io.EOF, - }, - }, - }, - { - content: nil, - reads: []read{ - { - size: 2, - err: io.EOF, - }, - { - size: 0, - }, - }, - }, - { - content: []byte{0x33, 0x22, 0x11}, - reads: []read{ - { - size: 3, - want: []byte{0x33, 0x22, 0x11}, - err: nil, - }, - { - size: 0, - }, - { - size: 1, - err: io.EOF, - }, - }, - }, - } { - t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { - buf := NewCachingReader(bytes.NewBuffer(tt.content)) - for j, r := range tt.reads { - p := make([]byte, r.size) - m, err := buf.Read(p) - if err != r.err { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) - } - if m != len(r.want) { - t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) - } - if !bytes.Equal(r.want, p[:m]) { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) - } - } - }) - } -} - -func TestCachingReaderReadAt(t *testing.T) { - type readAt struct { - // Buffer sizes to call Read with. - size int - - // Offset to read from. - off int64 - - // Buffer value corresponding Read(size) we want. - want []byte - - // Error corresponding to Read(size) we want. - err error - } - - for i, tt := range []struct { - // Content of the underlying io.Reader. - content []byte - - // Read calls to make in order. - reads []readAt - }{ - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - off: 0, - size: 0, - }, - { - off: 0, - size: 1, - want: []byte{0x11}, - }, - { - off: 1, - size: 2, - want: []byte{0x22, 0x33}, - }, - { - off: 0, - size: 0, - }, - { - off: 3, - size: 3, - want: []byte{0x44, 0x55, 0x66}, - }, - { - off: 6, - size: 4, - want: []byte{0x77, 0x88, 0x99}, - err: io.EOF, - }, - { - off: 0, - size: 9, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - }, - { - off: 0, - size: 10, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - err: io.EOF, - }, - { - off: 0, - size: 8, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - off: 10, - size: 10, - err: io.EOF, - }, - { - off: 5, - size: 4, - want: []byte{0x66, 0x77, 0x88, 0x99}, - }, - }, - }, - { - content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - reads: []readAt{ - { - size: 9, - want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, - }, - { - off: 5, - size: 4, - want: []byte{0x66, 0x77, 0x88, 0x99}, - }, - { - off: 9, - size: 1, - err: io.EOF, - }, - }, - }, - } { - t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { - buf := NewCachingReader(bytes.NewBuffer(tt.content)) - for j, r := range tt.reads { - p := make([]byte, r.size) - m, err := buf.ReadAt(p, r.off) - if err != r.err { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) - } - if m != len(r.want) { - t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) - } - if !bytes.Equal(r.want, p[:m]) { - t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) - } - } - }) - } -} diff --git a/pkg/uio/lazy.go b/pkg/uio/lazy.go deleted file mode 100644 index 4cb06ac32b..0000000000 --- a/pkg/uio/lazy.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "fmt" - "io" - "os" -) - -// ReadOneByte reads one byte from given io.ReaderAt. -func ReadOneByte(r io.ReaderAt) error { - buf := make([]byte, 1) - n, err := r.ReadAt(buf, 0) - if err != nil { - return err - } - if n != 1 { - return fmt.Errorf("expected to read 1 byte, but got %d", n) - } - return nil -} - -// LazyOpener is a lazy io.Reader. -// -// LazyOpener will use a given open function to derive an io.Reader when Read -// is first called on the LazyOpener. -type LazyOpener struct { - r io.Reader - s string - err error - open func() (io.Reader, error) -} - -// NewLazyOpener returns a lazy io.Reader based on `open`. -func NewLazyOpener(filename string, open func() (io.Reader, error)) *LazyOpener { - if len(filename) == 0 { - return nil - } - return &LazyOpener{s: filename, open: open} -} - -// Read implements io.Reader.Read lazily. -// -// If called for the first time, the underlying reader will be obtained and -// then used for the first and subsequent calls to Read. -func (lr *LazyOpener) Read(p []byte) (int, error) { - if lr.r == nil && lr.err == nil { - lr.r, lr.err = lr.open() - } - if lr.err != nil { - return 0, lr.err - } - return lr.r.Read(p) -} - -// String implements fmt.Stringer. -func (lr *LazyOpener) String() string { - if len(lr.s) > 0 { - return lr.s - } - if lr.r != nil { - return fmt.Sprintf("%v", lr.r) - } - return "unopened mystery file" -} - -// Close implements io.Closer.Close. -func (lr *LazyOpener) Close() error { - if c, ok := lr.r.(io.Closer); ok { - return c.Close() - } - return nil -} - -// LazyOpenerAt is a lazy io.ReaderAt. -// -// LazyOpenerAt will use a given open function to derive an io.ReaderAt when -// ReadAt is first called. -type LazyOpenerAt struct { - r io.ReaderAt - s string - err error - limit int64 - open func() (io.ReaderAt, error) -} - -// NewLazyFile returns a lazy ReaderAt opened from path. -func NewLazyFile(path string) *LazyOpenerAt { - if len(path) == 0 { - return nil - } - return NewLazyOpenerAt(path, func() (io.ReaderAt, error) { - return os.Open(path) - }) -} - -// NewLazyLimitFile returns a lazy ReaderAt opened from path with a limit reader on it. -func NewLazyLimitFile(path string, limit int64) *LazyOpenerAt { - if len(path) == 0 { - return nil - } - return NewLazyLimitOpenerAt(path, limit, func() (io.ReaderAt, error) { - return os.Open(path) - }) -} - -// NewLazyOpenerAt returns a lazy io.ReaderAt based on `open`. -func NewLazyOpenerAt(filename string, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open, limit: -1} -} - -// NewLazyLimitOpenerAt returns a lazy io.ReaderAt based on `open`. -func NewLazyLimitOpenerAt(filename string, limit int64, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open, limit: limit} -} - -// String implements fmt.Stringer. -func (loa *LazyOpenerAt) String() string { - if len(loa.s) > 0 { - return loa.s - } - if loa.r != nil { - return fmt.Sprintf("%v", loa.r) - } - return "unopened mystery file" -} - -// File returns the backend file of the io.ReaderAt if it -// is backed by a os.File. -func (loa *LazyOpenerAt) File() *os.File { - if f, ok := loa.r.(*os.File); ok { - return f - } - return nil -} - -// ReadAt implements io.ReaderAt.ReadAt. -func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { - if loa.r == nil && loa.err == nil { - loa.r, loa.err = loa.open() - } - if loa.err != nil { - return 0, loa.err - } - if loa.limit > 0 { - if off >= loa.limit { - return 0, io.EOF - } - if int64(len(p)) > loa.limit-off { - p = p[0 : loa.limit-off] - } - } - return loa.r.ReadAt(p, off) -} - -// Close implements io.Closer.Close. -func (loa *LazyOpenerAt) Close() error { - if c, ok := loa.r.(io.Closer); ok { - return c.Close() - } - return nil -} diff --git a/pkg/uio/lazy_test.go b/pkg/uio/lazy_test.go deleted file mode 100644 index 5c83855cbe..0000000000 --- a/pkg/uio/lazy_test.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "errors" - "fmt" - "io" - "strings" - "testing" -) - -type mockReader struct { - // called is whether Read has been called. - called bool - - // err is the error to return on Read. - err error -} - -func (m *mockReader) Read([]byte) (int, error) { - m.called = true - return 0, m.err -} - -func (m *mockReader) ReadAt([]byte, int64) (int, error) { - m.called = true - return 0, m.err -} - -func TestLazyOpenerRead(t *testing.T) { - for i, tt := range []struct { - openErr error - reader *mockReader - wantCalled bool - }{ - { - openErr: nil, - reader: &mockReader{}, - wantCalled: true, - }, - { - openErr: io.EOF, - reader: nil, - wantCalled: false, - }, - { - openErr: nil, - reader: &mockReader{ - err: io.ErrUnexpectedEOF, - }, - wantCalled: true, - }, - } { - t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { - var opened bool - lr := NewLazyOpener("testname", func() (io.Reader, error) { - opened = true - return tt.reader, tt.openErr - }) - _, err := lr.Read([]byte{}) - if !opened { - t.Fatalf("Read(): Reader was not opened") - } - if tt.openErr != nil && err != tt.openErr { - t.Errorf("Read() = %v, want %v", err, tt.openErr) - } - if tt.reader != nil { - if got, want := tt.reader.called, tt.wantCalled; got != want { - t.Errorf("mockReader.Read() called is %v, want %v", got, want) - } - if tt.reader.err != nil && err != tt.reader.err { - t.Errorf("Read() = %v, want %v", err, tt.reader.err) - } - } - }) - } -} - -func TestLazyOpenerReadAt(t *testing.T) { - for i, tt := range []struct { - limit int64 - bufSize int - openErr error - reader io.ReaderAt - off int64 - want error - wantB []byte - }{ - { - limit: -1, - bufSize: 10, - openErr: nil, - reader: &mockReader{}, - }, - { - limit: -1, - bufSize: 10, - openErr: io.EOF, - reader: nil, - want: io.EOF, - }, - { - limit: -1, - bufSize: 10, - openErr: nil, - reader: &mockReader{ - err: io.ErrUnexpectedEOF, - }, - want: io.ErrUnexpectedEOF, - }, - { - limit: -1, - bufSize: 6, - reader: strings.NewReader("foobar"), - wantB: []byte("foobar"), - }, - { - limit: -1, - off: 3, - bufSize: 3, - reader: strings.NewReader("foobar"), - wantB: []byte("bar"), - }, - { - limit: 5, - off: 3, - bufSize: 3, - reader: strings.NewReader("foobar"), - wantB: []byte("ba"), - }, - { - limit: 2, - bufSize: 2, - reader: strings.NewReader("foobar"), - wantB: []byte("fo"), - }, - { - limit: 2, - off: 2, - reader: strings.NewReader("foobar"), - want: io.EOF, - }, - } { - t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { - var opened bool - lr := NewLazyLimitOpenerAt("", tt.limit, func() (io.ReaderAt, error) { - opened = true - return tt.reader, tt.openErr - }) - - b := make([]byte, tt.bufSize) - n, err := lr.ReadAt(b, tt.off) - if !opened { - t.Fatalf("Read(): Reader was not opened") - } - if !errors.Is(tt.want, err) { - t.Errorf("Read() = %v, want %v", err, tt.want) - } - - if err == nil { - if !bytes.Equal(b[:n], tt.wantB) { - t.Errorf("Read() = %s, want %s", b[:n], tt.wantB) - } - } - }) - } -} diff --git a/pkg/uio/linewriter.go b/pkg/uio/linewriter.go deleted file mode 100644 index a78835b91c..0000000000 --- a/pkg/uio/linewriter.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" -) - -// LineWriter processes one line of log output at a time. -type LineWriter interface { - // OneLine is always called with exactly one line of output. - OneLine(b []byte) -} - -// FullLineWriter returns an io.Writer that waits for a full line of prints -// before calling w.Write on one line each. -func FullLineWriter(w LineWriter) io.WriteCloser { - return &fullLineWriter{w: w} -} - -type fullLineWriter struct { - w LineWriter - buffer []byte -} - -func (fsw *fullLineWriter) printBuf() { - bufs := bytes.Split(fsw.buffer, []byte{'\n'}) - for _, buf := range bufs { - if len(buf) != 0 { - fsw.w.OneLine(buf) - } - } - fsw.buffer = nil -} - -// Write implements io.Writer and buffers p until at least one full line is -// received. -func (fsw *fullLineWriter) Write(p []byte) (int, error) { - i := bytes.LastIndexByte(p, '\n') - if i == -1 { - fsw.buffer = append(fsw.buffer, p...) - } else { - fsw.buffer = append(fsw.buffer, p[:i]...) - fsw.printBuf() - fsw.buffer = append([]byte{}, p[i:]...) - } - return len(p), nil -} - -// Close implements io.Closer and flushes the buffer. -func (fsw *fullLineWriter) Close() error { - fsw.printBuf() - return nil -} diff --git a/pkg/uio/null.go b/pkg/uio/null.go deleted file mode 100644 index 64156f4c0e..0000000000 --- a/pkg/uio/null.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2012-2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Discard implementation copied from the Go project: -// https://golang.org/src/io/ioutil/ioutil.go. -// Copyright 2009 The Go Authors. All rights reserved. - -package uio - -import ( - "io" - "sync" -) - -type devNull int - -// devNull implements ReaderFrom as an optimization so io.Copy to -// ioutil.Discard can avoid doing unnecessary work. -var _ io.ReaderFrom = devNull(0) - -func (devNull) Write(p []byte) (int, error) { - return len(p), nil -} - -func (devNull) Name() string { - return "null" -} - -func (devNull) WriteString(s string) (int, error) { - return len(s), nil -} - -var blackHolePool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 8192) - return &b - }, -} - -func (devNull) ReadFrom(r io.Reader) (n int64, err error) { - bufp := blackHolePool.Get().(*[]byte) - readSize := 0 - for { - readSize, err = r.Read(*bufp) - n += int64(readSize) - if err != nil { - blackHolePool.Put(bufp) - if err == io.EOF { - return n, nil - } - return - } - } -} - -func (devNull) Close() error { - return nil -} - -// WriteNameCloser is the interface that groups Write, Close, and Name methods. -type WriteNameCloser interface { - io.Writer - io.Closer - Name() string -} - -// Discard is a WriteNameCloser on which all Write and Close calls succeed -// without doing anything, and the Name call returns "null". -var Discard WriteNameCloser = devNull(0) diff --git a/pkg/uio/progress.go b/pkg/uio/progress.go deleted file mode 100644 index 606b1eabe9..0000000000 --- a/pkg/uio/progress.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "io" - "strings" -) - -// ProgressReadCloser implements io.ReadCloser and prints Symbol to W after every -// Interval bytes passes through RC. -type ProgressReadCloser struct { - RC io.ReadCloser - - Symbol string - Interval int - W io.Writer - - counter int - written bool -} - -// Read implements io.Reader for ProgressReadCloser. -func (rc *ProgressReadCloser) Read(p []byte) (n int, err error) { - defer func() { - numSymbols := (rc.counter%rc.Interval + n) / rc.Interval - rc.W.Write([]byte(strings.Repeat(rc.Symbol, numSymbols))) - rc.counter += n - rc.written = (rc.written || numSymbols > 0) - if err == io.EOF && rc.written { - rc.W.Write([]byte("\n")) - } - }() - return rc.RC.Read(p) -} - -// Read implements io.Closer for ProgressReader. -func (rc *ProgressReadCloser) Close() error { - return rc.RC.Close() -} diff --git a/pkg/uio/progress_test.go b/pkg/uio/progress_test.go deleted file mode 100644 index 6d8de14ecf..0000000000 --- a/pkg/uio/progress_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "testing" -) - -func TestProgressReadCloser(t *testing.T) { - input := io.NopCloser(bytes.NewBufferString("01234567890123456789")) - stdout := &bytes.Buffer{} - prc := ProgressReadCloser{ - RC: input, - Symbol: "#", - Interval: 4, - W: stdout, - } - - // Read one byte at a time. - output := make([]byte, 1) - prc.Read(output) - prc.Read(output) - prc.Read(output) - if len(stdout.Bytes()) != 0 { - t.Errorf("found %q, but expected no bytes to be written", stdout) - } - prc.Read(output) - if stdout.String() != "#" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "#") - } - - // Read 9 bytes all at once. - output = make([]byte, 9) - prc.Read(output) - if stdout.String() != "###" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "###") - } - if string(output) != "456789012" { - t.Errorf("found %q, expected %q to be written", string(output), "456789012") - } - - // Read until EOF - output, err := io.ReadAll(&prc) - if err != nil { - t.Errorf("got %v, expected nil error", err) - } - if stdout.String() != "#####\n" { - t.Errorf("found %q, expected %q to be written", stdout.String(), "#####\n") - } - if string(output) != "3456789" { - t.Errorf("found %q, expected %q to be written", string(output), "3456789") - } - - err = prc.Close() - if err != nil { - t.Errorf("got %v, expected nil error", err) - } -} diff --git a/pkg/uio/reader.go b/pkg/uio/reader.go deleted file mode 100644 index 0ca839a073..0000000000 --- a/pkg/uio/reader.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "bytes" - "io" - "math" - "os" - "reflect" -) - -type inMemReaderAt interface { - Bytes() []byte -} - -// ReadAll reads everything that r contains. -// -// Callers *must* not modify bytes in the returned byte slice. -// -// If r is an in-memory representation, ReadAll will attempt to return a -// pointer to those bytes directly. -func ReadAll(r io.ReaderAt) ([]byte, error) { - if imra, ok := r.(inMemReaderAt); ok { - return imra.Bytes(), nil - } - return io.ReadAll(Reader(r)) -} - -// Reader generates a Reader from a ReaderAt. -func Reader(r io.ReaderAt) io.Reader { - return io.NewSectionReader(r, 0, math.MaxInt64) -} - -// ReaderAtEqual compares the contents of r1 and r2. -func ReaderAtEqual(r1, r2 io.ReaderAt) bool { - var c, d []byte - var r1err, r2err error - if r1 != nil { - c, r1err = ReadAll(r1) - } - if r2 != nil { - d, r2err = ReadAll(r2) - } - return bytes.Equal(c, d) && reflect.DeepEqual(r1err, r2err) -} - -// ReadIntoFile reads all from io.Reader into the file at given path. -// -// If the file at given path does not exist, a new file will be created. -// If the file exists at the given path, but not empty, it will be truncated. -func ReadIntoFile(r io.Reader, p string) error { - f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644) - if err != nil { - return err - } - defer f.Close() - - _, err = io.Copy(f, r) - if err != nil { - return err - } - - return f.Close() -} diff --git a/pkg/uio/reader_test.go b/pkg/uio/reader_test.go deleted file mode 100644 index c6ebfc70bd..0000000000 --- a/pkg/uio/reader_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uio - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -func readAndCheck(t *testing.T, want, tmpfileP string) { - t.Helper() - r := strings.NewReader(want) - if err := ReadIntoFile(r, tmpfileP); err != nil { - t.Errorf("ReadIntoFile(%v, %s) = %v, want no error", r, tmpfileP, err) - } - - got, err := os.ReadFile(tmpfileP) - if err != nil { - t.Fatalf("os.ReadFile(%s) = %v, want no error", tmpfileP, err) - } - if want != string(got) { - t.Errorf("got: %v, want %s", string(got), want) - } -} - -func TestReadIntoFile(t *testing.T) { - want := "I am the wanted" - - dir := t.TempDir() - - // Write to a file already exist. - p := filepath.Join(dir, "uio-out") - // Expect net effect to be creating a new empty file: "uio-out". - f, err := os.OpenFile(p, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0o755) - if err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - readAndCheck(t, want, f.Name()) - - // Write to a file that does not exist. - p = filepath.Join(dir, "uio-out-not-existing") - readAndCheck(t, want, p) - - // Write to an existing file that has pre-existing content. - p = filepath.Join(dir, "uio-out-prexist-content") - f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) - if err != nil { - t.Fatal(err) - } - if _, err := f.Write([]byte("temporary file's content")); err != nil { - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - readAndCheck(t, want, p) -} diff --git a/pkg/uio/uio.go b/pkg/uio/uio.go deleted file mode 100644 index bdd507c8eb..0000000000 --- a/pkg/uio/uio.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package uio unifies commonly used io utilities for u-root. -// -// uio's most used feature is the Buffer/Lexer combination to parse binary data -// of arbitrary endianness into data structures. -package uio diff --git a/pkg/uio/uiotest/pkg_test.go b/pkg/uio/uiotest/pkg_test.go deleted file mode 100644 index cbe3021508..0000000000 --- a/pkg/uio/uiotest/pkg_test.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 the u-root Authors. All rights reserved -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package uiotest - -import "testing" - -func TestTODO(t *testing.T) { - // TODO: Write a unit test. -} diff --git a/pkg/uroot/initramfs/files_test.go b/pkg/uroot/initramfs/files_test.go index c25099e32b..66746f6b1e 100644 --- a/pkg/uroot/initramfs/files_test.go +++ b/pkg/uroot/initramfs/files_test.go @@ -14,7 +14,7 @@ import ( "testing" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) func TestFilesAddFileNoFollow(t *testing.T) { diff --git a/pkg/uroot/initramfs/test/ramfs.go b/pkg/uroot/initramfs/test/ramfs.go index 47fe904a88..ce0b8d5fea 100644 --- a/pkg/uroot/initramfs/test/ramfs.go +++ b/pkg/uroot/initramfs/test/ramfs.go @@ -9,7 +9,7 @@ import ( "os" "github.com/u-root/u-root/pkg/cpio" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) type ArchiveValidator interface { diff --git a/uroot_test.go b/uroot_test.go index 85674289d0..cadc8fc546 100644 --- a/uroot_test.go +++ b/uroot_test.go @@ -20,8 +20,8 @@ import ( gbbgolang "github.com/u-root/gobusybox/src/pkg/golang" "github.com/u-root/u-root/pkg/cpio" "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio" itest "github.com/u-root/u-root/pkg/uroot/initramfs/test" + "github.com/u-root/uio/uio" ) var twocmds = []string{ diff --git a/vendor/github.com/u-root/uio/uio/lazy.go b/vendor/github.com/u-root/uio/uio/lazy.go index a5cabca557..4cb06ac32b 100644 --- a/vendor/github.com/u-root/uio/uio/lazy.go +++ b/vendor/github.com/u-root/uio/uio/lazy.go @@ -10,19 +10,36 @@ import ( "os" ) +// ReadOneByte reads one byte from given io.ReaderAt. +func ReadOneByte(r io.ReaderAt) error { + buf := make([]byte, 1) + n, err := r.ReadAt(buf, 0) + if err != nil { + return err + } + if n != 1 { + return fmt.Errorf("expected to read 1 byte, but got %d", n) + } + return nil +} + // LazyOpener is a lazy io.Reader. // // LazyOpener will use a given open function to derive an io.Reader when Read // is first called on the LazyOpener. type LazyOpener struct { r io.Reader + s string err error open func() (io.Reader, error) } // NewLazyOpener returns a lazy io.Reader based on `open`. -func NewLazyOpener(open func() (io.Reader, error)) io.ReadCloser { - return &LazyOpener{open: open} +func NewLazyOpener(filename string, open func() (io.Reader, error)) *LazyOpener { + if len(filename) == 0 { + return nil + } + return &LazyOpener{s: filename, open: open} } // Read implements io.Reader.Read lazily. @@ -39,6 +56,17 @@ func (lr *LazyOpener) Read(p []byte) (int, error) { return lr.r.Read(p) } +// String implements fmt.Stringer. +func (lr *LazyOpener) String() string { + if len(lr.s) > 0 { + return lr.s + } + if lr.r != nil { + return fmt.Sprintf("%v", lr.r) + } + return "unopened mystery file" +} + // Close implements io.Closer.Close. func (lr *LazyOpener) Close() error { if c, ok := lr.r.(io.Closer); ok { @@ -52,10 +80,11 @@ func (lr *LazyOpener) Close() error { // LazyOpenerAt will use a given open function to derive an io.ReaderAt when // ReadAt is first called. type LazyOpenerAt struct { - r io.ReaderAt - s string - err error - open func() (io.ReaderAt, error) + r io.ReaderAt + s string + err error + limit int64 + open func() (io.ReaderAt, error) } // NewLazyFile returns a lazy ReaderAt opened from path. @@ -68,9 +97,24 @@ func NewLazyFile(path string) *LazyOpenerAt { }) } +// NewLazyLimitFile returns a lazy ReaderAt opened from path with a limit reader on it. +func NewLazyLimitFile(path string, limit int64) *LazyOpenerAt { + if len(path) == 0 { + return nil + } + return NewLazyLimitOpenerAt(path, limit, func() (io.ReaderAt, error) { + return os.Open(path) + }) +} + // NewLazyOpenerAt returns a lazy io.ReaderAt based on `open`. func NewLazyOpenerAt(filename string, open func() (io.ReaderAt, error)) *LazyOpenerAt { - return &LazyOpenerAt{s: filename, open: open} + return &LazyOpenerAt{s: filename, open: open, limit: -1} +} + +// NewLazyLimitOpenerAt returns a lazy io.ReaderAt based on `open`. +func NewLazyLimitOpenerAt(filename string, limit int64, open func() (io.ReaderAt, error)) *LazyOpenerAt { + return &LazyOpenerAt{s: filename, open: open, limit: limit} } // String implements fmt.Stringer. @@ -84,6 +128,15 @@ func (loa *LazyOpenerAt) String() string { return "unopened mystery file" } +// File returns the backend file of the io.ReaderAt if it +// is backed by a os.File. +func (loa *LazyOpenerAt) File() *os.File { + if f, ok := loa.r.(*os.File); ok { + return f + } + return nil +} + // ReadAt implements io.ReaderAt.ReadAt. func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { if loa.r == nil && loa.err == nil { @@ -92,6 +145,14 @@ func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { if loa.err != nil { return 0, loa.err } + if loa.limit > 0 { + if off >= loa.limit { + return 0, io.EOF + } + if int64(len(p)) > loa.limit-off { + p = p[0 : loa.limit-off] + } + } return loa.r.ReadAt(p, off) } diff --git a/pkg/uio/uiotest/uiotest.go b/vendor/github.com/u-root/uio/uio/uiotest/uiotest.go similarity index 71% rename from pkg/uio/uiotest/uiotest.go rename to vendor/github.com/u-root/uio/uio/uiotest/uiotest.go index 826bc51951..5e670beb91 100644 --- a/pkg/uio/uiotest/uiotest.go +++ b/vendor/github.com/u-root/uio/uio/uiotest/uiotest.go @@ -2,18 +2,26 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package uiotest contains tests for uio functions. package uiotest import ( "io" "testing" + "time" - "github.com/u-root/u-root/pkg/testutil" - "github.com/u-root/u-root/pkg/uio" + "github.com/u-root/uio/uio" ) +// NowLog returns the current time formatted like the standard log package's +// timestamp. +func NowLog() string { + return time.Now().Format("2006/01/02 15:04:05") +} + // TestLineWriter is an io.Writer that logs full lines of serial to tb. func TestLineWriter(tb testing.TB, prefix string) io.WriteCloser { + tb.Helper() if len(prefix) > 0 { return uio.FullLineWriter(&testLinePrefixWriter{tb: tb, prefix: prefix}) } @@ -27,7 +35,7 @@ type testLinePrefixWriter struct { } func (tsw *testLinePrefixWriter) OneLine(p []byte) { - tsw.tb.Logf("%s %s: %s", testutil.NowLog(), tsw.prefix, p) + tsw.tb.Logf("%s %s: %s", NowLog(), tsw.prefix, p) } // testLineWriter is an io.Writer that logs full lines of serial to tb. @@ -36,5 +44,5 @@ type testLineWriter struct { } func (tsw *testLineWriter) OneLine(p []byte) { - tsw.tb.Logf("%s: %s", testutil.NowLog(), p) + tsw.tb.Logf("%s: %s", NowLog(), p) } diff --git a/vendor/modules.txt b/vendor/modules.txt index fd2bd48d8d..cce2985375 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -279,11 +279,12 @@ github.com/u-root/gobusybox/src/pkg/uflag # github.com/u-root/iscsinl v0.1.1-0.20210528121423-84c32645822a ## explicit; go 1.13 github.com/u-root/iscsinl -# github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 +# github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e ## explicit; go 1.16 github.com/u-root/uio/cp github.com/u-root/uio/rand github.com/u-root/uio/uio +github.com/u-root/uio/uio/uiotest github.com/u-root/uio/ulog github.com/u-root/uio/ulog/ulogtest # github.com/ulikunitz/xz v0.5.11 From ec02d477eb7c191aeaeb12752d42d598adb0248b Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 6 Feb 2024 18:24:15 +0000 Subject: [PATCH 108/109] kexec,memory: some utilities Signed-off-by: Chris Koch --- pkg/boot/kexec/memory_linux.go | 46 ++++++++++++ pkg/boot/kexec/memory_linux_test.go | 112 ++++++++++++++++++++++++++++ pkg/boot/kexec/memory_map_linux.go | 7 +- 3 files changed, 161 insertions(+), 4 deletions(-) diff --git a/pkg/boot/kexec/memory_linux.go b/pkg/boot/kexec/memory_linux.go index bb54bdee98..c4c40e1ef1 100644 --- a/pkg/boot/kexec/memory_linux.go +++ b/pkg/boot/kexec/memory_linux.go @@ -43,6 +43,14 @@ func RangeFromInterval(start, end uintptr) Range { } } +// RangeFromInclusiveInterval returns a Range representing [start, last]. +func RangeFromInclusiveInterval(start, last uintptr) Range { + return Range{ + Start: start, + Size: uint(last - start + 1), + } +} + // String returns [Start, Start+Size) as a string. func (r Range) String() string { return fmt.Sprintf("[%#x, %#x)", r.Start, r.End()) @@ -53,6 +61,11 @@ func (r Range) End() uintptr { return r.Start + uintptr(r.Size) } +// Last returns last uintptr inside the interval. +func (r Range) Last() uintptr { + return r.Start + uintptr(r.Size) - 1 +} + // Adjacent returns true if r and r2 do not overlap, but are immediately next // to each other. func (r Range) Adjacent(r2 Range) bool { @@ -393,6 +406,39 @@ func (s *Segment) mergeDisjoint(s2 Segment) bool { return true } +// Align aligns start and size by the given alignSize. +// +// The resulting range is guaranteed to be superset of r. +func (r Range) Align(alignSize uint) Range { + if alignSize == 0 { + return r + } + s := align.Down(r.Start, uintptr(alignSize)) + // Empty range remains empty. + if r.Size == 0 { + return Range{Start: s} + } + return Range{ + Start: s, + Size: align.Up(r.Size+uint(r.Start-s), alignSize), + } +} + +// AlignPage aligns start and size by page size. +// +// The resulting range is guaranteed to be superset of r. +func (r Range) AlignPage() Range { + s := align.DownPage(r.Start) + // Empty range remains empty. + if r.Size == 0 { + return Range{Start: s} + } + return Range{ + Start: s, + Size: align.UpPage(r.Size + uint(r.Start-s)), + } +} + // AlignPhysStart aligns s.Phys.Start to the page size. AlignPhysStart does not // align the size of the segment. func AlignPhysStart(s Segment) Segment { diff --git a/pkg/boot/kexec/memory_linux_test.go b/pkg/boot/kexec/memory_linux_test.go index c20bee2a85..4248a7a6c7 100644 --- a/pkg/boot/kexec/memory_linux_test.go +++ b/pkg/boot/kexec/memory_linux_test.go @@ -907,3 +907,115 @@ func TestIsSupersetOf(t *testing.T) { } } } + +func TestRanges(t *testing.T) { + for _, tt := range []struct { + start uintptr + end uintptr + conv func(uintptr, uintptr) Range + want Range + }{ + { + start: 0, + end: 0x1000, + conv: RangeFromInterval, + want: Range{Start: 0, Size: 0x1000}, + }, + { + start: 0, + end: 0xfff, + conv: RangeFromInclusiveInterval, + want: Range{Start: 0, Size: 0x1000}, + }, + { + start: 0, + end: 0, + conv: RangeFromInterval, + want: Range{Start: 0, Size: 0}, + }, + { + start: 0, + end: 0, + conv: RangeFromInclusiveInterval, + want: Range{Start: 0, Size: 0x1}, + }, + } { + got := tt.conv(tt.start, tt.end) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Range(%#x, %#x) = %v, want %v", tt.start, tt.end, got, tt.want) + } + } +} + +func TestAlign(t *testing.T) { + for _, tt := range []struct { + r Range + alignSize uint + want Range + }{ + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0x10}, + alignSize: 0x1000, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 0, + want: Range{Start: 0x10, Size: 0x10}, + }, + { + r: Range{Start: 0x10, Size: 0x10}, + alignSize: 1, + want: Range{Start: 0x10, Size: 0x10}, + }, + } { + got := tt.r.Align(tt.alignSize) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%v.Align(%#x) = %v, want %v", tt.r, tt.alignSize, got, tt.want) + } + } +} + +func TestAlignPage(t *testing.T) { + for _, tt := range []struct { + r Range + want Range + }{ + { + r: Range{Start: 0x10, Size: 0x10}, + want: Range{Start: 0, Size: 0x1000}, + }, + { + r: Range{Start: 0x10, Size: 0}, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0}, + want: Range{Start: 0, Size: 0}, + }, + { + r: Range{Start: 0, Size: 0x10}, + want: Range{Start: 0, Size: 0x1000}, + }, + } { + got := tt.r.AlignPage() + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("%v.AlignPage() = %v, want %v", tt.r, got, tt.want) + } + } +} diff --git a/pkg/boot/kexec/memory_map_linux.go b/pkg/boot/kexec/memory_map_linux.go index bcd34e118c..34656c0c60 100644 --- a/pkg/boot/kexec/memory_map_linux.go +++ b/pkg/boot/kexec/memory_map_linux.go @@ -293,7 +293,7 @@ func memoryMapFromSysfsMemmap(memoryMapDir string) (MemoryMap, error) { // // start: 0x100, size: 0x100. mm = append(mm, TypedRange{ - Range: RangeFromInterval(r.start, r.end+1), + Range: RangeFromInclusiveInterval(r.start, r.end), Type: r.typ, }) } @@ -396,8 +396,7 @@ func memoryMapFromIOMem(r io.Reader) (MemoryMap, error) { continue } mm.Insert(TypedRange{ - // end is inclusive. - Range: RangeFromInterval(uintptr(start), uintptr(end+1)), + Range: RangeFromInclusiveInterval(uintptr(start), uintptr(end)), Type: rangeType(typ), }) } @@ -449,7 +448,7 @@ func rangeFromMemblockLine(s string) *Range { } // end is inclusive. - r := RangeFromInterval(uintptr(start), uintptr(end+1)) + r := RangeFromInclusiveInterval(uintptr(start), uintptr(end)) return &r } From 3ee8b392262d52b2df7692008c81786242def7a2 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Wed, 7 Feb 2024 01:54:04 +0000 Subject: [PATCH 109/109] Make gosh default shell Signed-off-by: Chris Koch --- README.md | 26 +++++++++++++------------- cmds/core/sshd/const_unix.go | 2 +- integration/gotests/gotest_test.go | 2 +- templates.go | 6 +++--- u-root.go | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index be6cce5014..deb58581a5 100644 --- a/README.md +++ b/README.md @@ -77,17 +77,17 @@ u-root u-root core boot # Generate an archive with only these given commands -u-root ./cmds/core/{init,ls,ip,dhclient,wget,cat,elvish} +u-root ./cmds/core/{init,ls,ip,dhclient,wget,cat,gosh} # Generate an archive with all of the core tools with some exceptions u-root core -cmds/core/{ls,losetup} # Generate an archive with a tool outside of u-root git clone https://github.com/u-root/cpu -u-root ./cmds/core/{init,ls,elvish} ./cpu/cmds/cpud +u-root ./cmds/core/{init,ls,gosh} ./cpu/cmds/cpud # Generate an archive with a tool outside of u-root, in any PWD -(cd /tmp && GBB_PATH=$UROOT_PATH:$CPU_PATH u-root ./cmds/core/{init,ls,elvish} ./cmds/cpud) +(cd /tmp && GBB_PATH=$UROOT_PATH:$CPU_PATH u-root ./cmds/core/{init,ls,gosh} ./cmds/cpud) ``` The default set of packages included is all packages in @@ -100,12 +100,12 @@ checked for existence. For example: ```shell GBB_PATH=$HOME/u-root:$HOME/u-bmc u-root \ cmds/core/init \ - cmds/core/elvish \ + cmds/core/gosh \ cmd/socreset # matches: # $HOME/u-root/cmds/core/init -# $HOME/u-root/cmds/core/elvish +# $HOME/u-root/cmds/core/gosh # $HOME/u-bmc/cmd/socreset ``` @@ -153,7 +153,7 @@ symlink path. **Only `-uinitcmd` accepts command-line arguments, however.** For example, ```bash -u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,echo,elvish} +u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,echo,gosh} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... @@ -178,7 +178,7 @@ Passing command line arguments like above is equivalent to passing the arguments Additionally, you can pass arguments to uinit via the `uroot.uinitargs` kernel parameters, for example: ```bash -u-root -uinitcmd="echo Gopher" ./cmds/core/{init,echo,elvish} +u-root -uinitcmd="echo Gopher" ./cmds/core/{init,echo,gosh} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... @@ -205,7 +205,7 @@ The command you name must be present in the command set. The following will *not work*: ```bash -u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,elvish} +u-root -uinitcmd="echo Go Gopher" ./cmds/core/{init,gosh} # 2020/04/30 21:05:57 could not create symlink from "bin/uinit" to "echo": command or path "echo" not included in u-root build: specify -uinitcmd="" to ignore this error and build without a uinit ``` @@ -215,30 +215,30 @@ don't presume to know whether your symlink target is correct or not. This will build, but not work unless you add a /bin/foobar to the initramfs. ```bash -u-root -uinitcmd="/bin/foobar Go Gopher" ./cmds/core/{init,elvish} +u-root -uinitcmd="/bin/foobar Go Gopher" ./cmds/core/{init,gosh} ``` This will boot the same as the above. ```bash -u-root -uinitcmd="/bin/foobar Go Gopher" -files /bin/echo:bin/foobar -files your-hosts-file:/etc/hosts ./cmds/core/{init,elvish} +u-root -uinitcmd="/bin/foobar Go Gopher" -files /bin/echo:bin/foobar -files your-hosts-file:/etc/hosts ./cmds/core/{init,gosh} ``` The effect of the above command: * Sets up the uinit command to be /bin/foobar, with 2 arguments: Go Gopher * Adds /bin/echo as bin/foobar * Adds your-hosts-file as etc/hosts -* builds in the cmds/core/init, and cmds/core/elvish commands. +* builds in the cmds/core/init, and cmds/core/gosh commands. The {} are expanded by the shell This will bypass the regular u-root init and just launch a shell: ```bash -u-root -initcmd=elvish ./cmds/core/{elvish,ls} +u-root -initcmd=gosh ./cmds/core/{gosh,ls} cpio -ivt < /tmp/initramfs.linux_amd64.cpio # ... -# lrwxrwxrwx 0 root root 9 Dec 31 1969 init -> bbin/elvish +# lrwxrwxrwx 0 root root 9 Dec 31 1969 init -> bbin/gosh qemu-system-x86_64 -kernel $KERNEL -initrd /tmp/initramfs.linux_amd64.cpio -nographic -append "console=ttyS0" # ... diff --git a/cmds/core/sshd/const_unix.go b/cmds/core/sshd/const_unix.go index ee50821725..23f8cf8acb 100644 --- a/cmds/core/sshd/const_unix.go +++ b/cmds/core/sshd/const_unix.go @@ -8,6 +8,6 @@ package main var ( - shells = [...]string{"bash", "zsh", "elvish"} + shells = [...]string{"bash", "zsh", "gosh", "elvish"} shell = "/bin/sh" ) diff --git a/integration/gotests/gotest_test.go b/integration/gotests/gotest_test.go index 2feefd9ede..abdc19b223 100644 --- a/integration/gotests/gotest_test.go +++ b/integration/gotests/gotest_test.go @@ -118,7 +118,7 @@ func TestGoTest(t *testing.T) { vmtest.RunGoTestsInVM(t, pkgs, vmtest.WithVMOpt( vmtest.WithMergedInitramfs(uroot.Opts{ - DefaultShell: "elvish", + DefaultShell: "gosh", Commands: uroot.BusyBoxCmds( "github.com/u-root/u-root/cmds/core/*", ), diff --git a/templates.go b/templates.go index c668ea1d2e..a517e67097 100644 --- a/templates.go +++ b/templates.go @@ -38,9 +38,9 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/find", "github.com/u-root/u-root/cmds/core/free", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/gpgv", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/gzip", @@ -91,8 +91,8 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", "github.com/u-root/u-root/cmds/core/echo", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/free", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/init", "github.com/u-root/u-root/cmds/core/insmod", @@ -121,8 +121,8 @@ var templates = map[string][]string{ "github.com/u-root/u-root/cmds/core/dd", "github.com/u-root/u-root/cmds/core/dhclient", "github.com/u-root/u-root/cmds/core/dmesg", - "github.com/u-root/u-root/cmds/core/elvish", "github.com/u-root/u-root/cmds/core/find", + "github.com/u-root/u-root/cmds/core/gosh", "github.com/u-root/u-root/cmds/core/grep", "github.com/u-root/u-root/cmds/core/id", "github.com/u-root/u-root/cmds/core/init", diff --git a/u-root.go b/u-root.go index 89d33de985..7b5d38e3c0 100644 --- a/u-root.go +++ b/u-root.go @@ -68,7 +68,7 @@ func init() { case "plan9": sh = "" default: - sh = "elvish" + sh = "gosh" } build = flag.String("build", "gbb", "u-root build format (e.g. bb/gbb or binary).")