diff --git a/Dockerfile b/Dockerfile index 0a279bb..5bc2e3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ FROM scratch AS generate # base toolchain image FROM ${TOOLCHAIN} AS toolchain -RUN apk --update --no-cache add bash curl build-base protoc protobuf-dev cdrkit cryptsetup dosfstools e2fsprogs parted util-linux xfsprogs +RUN apk --update --no-cache add bash curl build-base protoc protobuf-dev cdrkit cryptsetup dosfstools e2fsprogs parted util-linux xfsprogs lvm2 # build tools FROM --platform=${BUILDPLATFORM} toolchain AS tools diff --git a/blkid/blkid_linux_test.go b/blkid/blkid_linux_test.go index c4cd8d7..92988d0 100644 --- a/blkid/blkid_linux_test.go +++ b/blkid/blkid_linux_test.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "testing" @@ -104,19 +105,51 @@ func isoSetup(useJoilet bool) func(t *testing.T, path string) { } } -//nolint:gocognit +func swapSetup(t *testing.T, path string) { + t.Helper() + + cmd := exec.Command("mkswap", "--label", "swaplabel", "-p", "8192", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + require.NoError(t, cmd.Run()) +} + +func swapSetup2(t *testing.T, path string) { + t.Helper() + + cmd := exec.Command("mkswap", "--label", "swapswap", "-p", "4096", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + require.NoError(t, cmd.Run()) +} + +func lvm2Setup(t *testing.T, path string) { + t.Helper() + + cmd := exec.Command("pvcreate", "-v", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + require.NoError(t, cmd.Run()) +} + +//nolint:gocognit,maintidx func TestProbePathFilesystems(t *testing.T) { for _, test := range []struct { //nolint:govet name string - noLoop bool + noLoop bool + loopOnly bool size uint64 setup func(*testing.T, string) - expectedName string - expectedLabel string - expectUUID bool + expectedName string + expectedLabel string + expectedLabelRegex *regexp.Regexp + expectUUID bool expectedBlockSize []uint32 expectedFSBlockSize []uint32 @@ -210,6 +243,58 @@ func TestProbePathFilesystems(t *testing.T) { expectedFSBlockSize: []uint32{2048}, expectedFSSize: 0x15b000, }, + { + name: "swap 8k", + + size: 500 * MiB, + setup: swapSetup, + + expectedName: "swap", + expectedLabel: "swaplabel", + expectUUID: true, + + expectedBlockSize: []uint32{8192}, + expectedFSBlockSize: []uint32{8192}, + expectedFSSize: 524279808, + }, + { + name: "swap 4k", + + size: 500 * MiB, + setup: swapSetup2, + + expectedName: "swap", + expectedLabel: "swapswap", + expectUUID: true, + + expectedBlockSize: []uint32{4096}, + expectedFSBlockSize: []uint32{4096}, + expectedFSSize: 524283904, + }, + { + name: "swap 200 MiB", + + size: 200 * MiB, + setup: swapSetup, + + expectedName: "swap", + expectedLabel: "swaplabel", + expectUUID: true, + + expectedBlockSize: []uint32{8192}, + expectedFSBlockSize: []uint32{8192}, + expectedFSSize: 209707008, + }, + { + name: "lvm2-pv", + loopOnly: true, + + size: 500 * MiB, + setup: lvm2Setup, + + expectedName: "lvm2-pv", + expectedLabelRegex: regexp.MustCompile(`(?m)^[0-9a-zA-Z]{6}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{6}$`), + }, } { for _, useLoopDevice := range []bool{false, true} { t.Run(fmt.Sprintf("loop=%v", useLoopDevice), func(t *testing.T) { @@ -222,6 +307,10 @@ func TestProbePathFilesystems(t *testing.T) { t.Skip("test does not support loop devices") } + if !useLoopDevice && test.loopOnly { + t.Skip("test does not support running without loop devices") + } + tmpDir := t.TempDir() rawImage := filepath.Join(tmpDir, "image.raw") @@ -268,10 +357,13 @@ func TestProbePathFilesystems(t *testing.T) { assert.Equal(t, test.expectedName, info.Name) - if test.expectedLabel != "" { + switch { + case test.expectedLabel != "": require.NotNil(t, info.Label) assert.Equal(t, test.expectedLabel, *info.Label) - } else { + case test.expectedLabelRegex != nil: + assert.True(t, test.expectedLabelRegex.MatchString(*info.Label)) + default: assert.Nil(t, info.Label) } diff --git a/blkid/internal/chain/chain.go b/blkid/internal/chain/chain.go index 9973889..9ef62cb 100644 --- a/blkid/internal/chain/chain.go +++ b/blkid/internal/chain/chain.go @@ -10,6 +10,8 @@ import ( "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/ext" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/iso9660" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/luks" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/lvm2" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/swap" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/vfat" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/filesystems/xfs" "github.com/siderolabs/go-blockdevice/v2/blkid/internal/partitions/gpt" @@ -35,13 +37,13 @@ func (chain Chain) MaxMagicSize() int { } // MagicMatches returns the prober that matches the magic value in the buffer. -func (chain Chain) MagicMatches(buf []byte) []probe.Prober { - var matches []probe.Prober +func (chain Chain) MagicMatches(buf []byte) []probe.MagicMatch { + var matches []probe.MagicMatch for _, prober := range chain { for _, magic := range prober.Magic() { if magic.Matches(buf) { - matches = append(matches, prober) + matches = append(matches, probe.MagicMatch{Magic: *magic, Prober: prober}) continue } @@ -57,6 +59,8 @@ func Default() Chain { &xfs.Probe{}, &ext.Probe{}, &vfat.Probe{}, + &swap.Probe{}, + &lvm2.Probe{}, &gpt.Probe{}, &luks.Probe{}, &iso9660.Probe{}, diff --git a/blkid/internal/chain/chain_test.go b/blkid/internal/chain/chain_test.go index 5744a07..fd308c7 100644 --- a/blkid/internal/chain/chain_test.go +++ b/blkid/internal/chain/chain_test.go @@ -13,5 +13,5 @@ import ( ) func TestMaxMagicSize(t *testing.T) { - assert.Equal(t, 32774, chain.Default().MaxMagicSize()) + assert.Equal(t, 65536, chain.Default().MaxMagicSize()) } diff --git a/blkid/internal/filesystems/bluestore/bluestore.go b/blkid/internal/filesystems/bluestore/bluestore.go index 913c756..7c83c9e 100644 --- a/blkid/internal/filesystems/bluestore/bluestore.go +++ b/blkid/internal/filesystems/bluestore/bluestore.go @@ -29,6 +29,6 @@ func (p *Probe) Name() string { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(probe.Reader, magic.Magic) (*probe.Result, error) { return &probe.Result{}, nil } diff --git a/blkid/internal/filesystems/ext/ext.go b/blkid/internal/filesystems/ext/ext.go index 1e7fa0c..7b73d7f 100644 --- a/blkid/internal/filesystems/ext/ext.go +++ b/blkid/internal/filesystems/ext/ext.go @@ -46,7 +46,7 @@ func (p *Probe) Name() string { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { buf := make([]byte, SUPERBLOCK_SIZE) if _, err := r.ReadAt(buf, sbOffset); err != nil { diff --git a/blkid/internal/filesystems/iso9660/iso9660.go b/blkid/internal/filesystems/iso9660/iso9660.go index 46195f4..1dcc96a 100644 --- a/blkid/internal/filesystems/iso9660/iso9660.go +++ b/blkid/internal/filesystems/iso9660/iso9660.go @@ -54,7 +54,7 @@ func isonum16(b []byte) uint16 { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { var pvd, joilet VolumeDescriptor vdLoop: diff --git a/blkid/internal/filesystems/luks/luks.go b/blkid/internal/filesystems/luks/luks.go index f679d6d..26e96a8 100644 --- a/blkid/internal/filesystems/luks/luks.go +++ b/blkid/internal/filesystems/luks/luks.go @@ -36,7 +36,7 @@ func (p *Probe) Name() string { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { buf := make([]byte, LUKS2HEADER_SIZE) if _, err := r.ReadAt(buf, 0); err != nil { diff --git a/blkid/internal/filesystems/lvm2/lvm2.go b/blkid/internal/filesystems/lvm2/lvm2.go new file mode 100644 index 0000000..b016b09 --- /dev/null +++ b/blkid/internal/filesystems/lvm2/lvm2.go @@ -0,0 +1,87 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package lvm2 probes LVM2 PVs. +package lvm2 + +//go:generate go run ../../cstruct/cstruct.go -pkg lvm2 -struct LVM2Header -input lvm2_header.h -endianness LittleEndian + +import ( + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/magic" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/probe" +) + +var ( + lvmMagic1 = magic.Magic{ + Offset: 0x018, + Value: []byte("LVM2 001"), + } + + lvmMagic2 = magic.Magic{ + Offset: 0x218, + Value: []byte("LVM2 001"), + } +) + +// Probe for the filesystem. +type Probe struct{} + +// Magic returns the magic value for the filesystem. +func (p *Probe) Magic() []*magic.Magic { + return []*magic.Magic{ + &lvmMagic1, + &lvmMagic2, + } +} + +// Name returns the name of the filesystem. +func (p *Probe) Name() string { + return "lvm2-pv" +} + +func (p *Probe) probe(r probe.Reader, offset int64) (LVM2Header, error) { + buf := make([]byte, LVM2HEADER_SIZE) + + if _, err := r.ReadAt(buf, offset); err != nil { + return nil, err + } + + hdr := LVM2Header(buf) + + if string(hdr.Get_id()) != "LABELONE" || string(hdr.Get_type()) != "LVM2 001" { + return nil, nil + } + + return hdr, nil +} + +// Probe runs the further inspection and returns the result if successful. +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { + hdr, err := p.probe(r, 0) + if hdr == nil { + if err != nil { + return nil, err + } + + hdr, err = p.probe(r, 512) + if err != nil { + return nil, err + } + + if hdr == nil { + return nil, nil //nolint:nilnil + } + } + + res := &probe.Result{} + + // LVM2 UUIDs aren't 16 bytes thus are treated as labels + labelUUID := string(hdr.Get_pv_uuid()) + labelUUID = labelUUID[:6] + "-" + labelUUID[6:10] + "-" + labelUUID[10:14] + + "-" + labelUUID[14:18] + "-" + labelUUID[18:22] + + "-" + labelUUID[22:26] + "-" + labelUUID[26:] + res.Label = &labelUUID + + return res, nil +} diff --git a/blkid/internal/filesystems/lvm2/lvm2_header.h b/blkid/internal/filesystems/lvm2/lvm2_header.h new file mode 100644 index 0000000..6eddbbc --- /dev/null +++ b/blkid/internal/filesystems/lvm2/lvm2_header.h @@ -0,0 +1,13 @@ +#include + +/* https://github.com/util-linux/util-linux/blob/c0207d354ee47fb56acfa64b03b5b559bb301280/libblkid/src/superblocks/lvm.c#L23-L32 */ +struct lvm2_pv_header { + /* label_header */ + uint8_t id[8]; /* LABELONE */ + uint64_t sector_xl; /* Sector number of this label */ + uint32_t crc_xl; /* From next field to end of sector */ + uint32_t offset_xl; /* Offset from start of struct to contents */ + uint8_t type[8]; /* LVM2 001 */ + /* pv_header */ + uint8_t pv_uuid[32]; +} __attribute__ ((packed)); diff --git a/blkid/internal/filesystems/lvm2/lvm2header.go b/blkid/internal/filesystems/lvm2/lvm2header.go new file mode 100644 index 0000000..6196bbd --- /dev/null +++ b/blkid/internal/filesystems/lvm2/lvm2header.go @@ -0,0 +1,47 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Code generated by "cstruct -pkg lvm2 -struct LVM2Header -input lvm2_header.h -endianness LittleEndian"; DO NOT EDIT. + +package lvm2 + +import "encoding/binary" + +var _ = binary.LittleEndian + +// LVM2Header is a byte slice representing the lvm2_header.h C header. +type LVM2Header []byte + +// Get_id returns LABELONE. +func (s LVM2Header) Get_id() []byte { + return s[0:8] +} + +// Get_sector_xl returns Sector number of this label. +func (s LVM2Header) Get_sector_xl() uint64 { + return binary.LittleEndian.Uint64(s[8:16]) +} + +// Get_crc_xl returns From next field to end of sector. +func (s LVM2Header) Get_crc_xl() uint32 { + return binary.LittleEndian.Uint32(s[16:20]) +} + +// Get_offset_xl returns Offset from start of struct to contents. +func (s LVM2Header) Get_offset_xl() uint32 { + return binary.LittleEndian.Uint32(s[20:24]) +} + +// Get_type returns LVM2 001. +func (s LVM2Header) Get_type() []byte { + return s[24:32] +} + +// Get_pv_uuid returns pv_uuid. +func (s LVM2Header) Get_pv_uuid() []byte { + return s[32:64] +} + +// LVM2HEADER_SIZE is the size of the LVM2Header struct. +const LVM2HEADER_SIZE = 64 diff --git a/blkid/internal/filesystems/swap/swap.go b/blkid/internal/filesystems/swap/swap.go new file mode 100644 index 0000000..f8d3658 --- /dev/null +++ b/blkid/internal/filesystems/swap/swap.go @@ -0,0 +1,135 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package swap probes Linux swapspaces. +package swap + +// TODO: is it little or host endian? +//go:generate go run ../../cstruct/cstruct.go -pkg swap -struct SwapHeader -input swap_header.h -endianness LittleEndian + +import ( + "bytes" + + "github.com/google/uuid" + "github.com/siderolabs/go-pointer" + + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/magic" + "github.com/siderolabs/go-blockdevice/v2/blkid/internal/probe" +) + +var ( + swapMagic1 = magic.Magic{ + Offset: 0xff6, + Value: []byte("SWAP-SPACE"), + } + + swapMagic2 = magic.Magic{ + Offset: 0xff6, + Value: []byte("SWAPSPACE2"), + } + + swapMagic3 = magic.Magic{ + Offset: 0x1ff6, + Value: []byte("SWAP-SPACE"), + } + + swapMagic4 = magic.Magic{ + Offset: 0x1ff6, + Value: []byte("SWAPSPACE2"), + } + + swapMagic5 = magic.Magic{ + Offset: 0x3ff6, + Value: []byte("SWAP-SPACE"), + } + + swapMagic6 = magic.Magic{ + Offset: 0x3ff6, + Value: []byte("SWAPSPACE2"), + } + + swapMagic7 = magic.Magic{ + Offset: 0x7ff6, + Value: []byte("SWAP-SPACE"), + } + + swapMagic8 = magic.Magic{ + Offset: 0x7ff6, + Value: []byte("SWAPSPACE2"), + } + + swapMagic9 = magic.Magic{ + Offset: 0xfff6, + Value: []byte("SWAP-SPACE"), + } + + swapMagic10 = magic.Magic{ + Offset: 0xfff6, + Value: []byte("SWAPSPACE2"), + } +) + +// Probe for the filesystem. +type Probe struct{} + +// Magic returns the magic value for the filesystem. +func (p *Probe) Magic() []*magic.Magic { + return []*magic.Magic{ + &swapMagic1, + &swapMagic2, + &swapMagic3, + &swapMagic4, + &swapMagic5, + &swapMagic6, + &swapMagic7, + &swapMagic8, + &swapMagic9, + &swapMagic10, + } +} + +// Name returns the name of the filesystem. +func (p *Probe) Name() string { + return "swap" +} + +// Probe runs the further inspection and returns the result if successful. +func (p *Probe) Probe(r probe.Reader, m magic.Magic) (*probe.Result, error) { + buf := make([]byte, SWAPHEADER_SIZE) + + if _, err := r.ReadAt(buf, 1024); err != nil { + return nil, err + } + + hdr := SwapHeader(buf) + + if hdr.Get_version() != 1 || hdr.Get_lastpage() == 0 { + return nil, nil //nolint:nilnil + } + + res := &probe.Result{} + + lbl := hdr.Get_volume() + if lbl[0] != 0 { + idx := bytes.IndexByte(lbl, 0) + if idx == -1 { + idx = len(lbl) + } + + res.Label = pointer.To(string(lbl[:idx])) + } + + fsUUID, err := uuid.FromBytes(hdr.Get_uuid()) + if err == nil { + res.UUID = &fsUUID + } + + // https://github.com/util-linux/util-linux/blob/c0207d354ee47fb56acfa64b03b5b559bb301280/libblkid/src/superblocks/swap.c#L47 + pageSize := m.Offset + len(m.Value) + res.BlockSize = uint32(pageSize) + res.FilesystemBlockSize = uint32(pageSize) + res.ProbedSize = uint64(pageSize) * uint64(hdr.Get_lastpage()) + + return res, nil +} diff --git a/blkid/internal/filesystems/swap/swap_header.h b/blkid/internal/filesystems/swap/swap_header.h new file mode 100644 index 0000000..3b90726 --- /dev/null +++ b/blkid/internal/filesystems/swap/swap_header.h @@ -0,0 +1,12 @@ +#include + +/* linux/include/linux/swap.h */ +struct swap_header { + uint32_t version; + uint32_t lastpage; + uint32_t nr_badpages; + unsigned char uuid[16]; + unsigned char volume[16]; + uint32_t padding[117]; + uint32_t badpages[1]; +} __attribute__((packed)); diff --git a/blkid/internal/filesystems/swap/swapheader.go b/blkid/internal/filesystems/swap/swapheader.go new file mode 100644 index 0000000..7b22935 --- /dev/null +++ b/blkid/internal/filesystems/swap/swapheader.go @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Code generated by "cstruct -pkg swap -struct SwapHeader -input swap_header.h -endianness LittleEndian"; DO NOT EDIT. + +package swap + +import "encoding/binary" + +var _ = binary.LittleEndian + +// SwapHeader is a byte slice representing the swap_header.h C header. +type SwapHeader []byte + +// Get_version returns version. +func (s SwapHeader) Get_version() uint32 { + return binary.LittleEndian.Uint32(s[0:4]) +} + +// Get_lastpage returns lastpage. +func (s SwapHeader) Get_lastpage() uint32 { + return binary.LittleEndian.Uint32(s[4:8]) +} + +// Get_nr_badpages returns nr_badpages. +func (s SwapHeader) Get_nr_badpages() uint32 { + return binary.LittleEndian.Uint32(s[8:12]) +} + +// Get_uuid returns uuid. +func (s SwapHeader) Get_uuid() []byte { + return s[12:28] +} + +// Get_volume returns volume. +func (s SwapHeader) Get_volume() []byte { + return s[28:44] +} + +// SWAPHEADER_SIZE is the size of the SwapHeader struct. +const SWAPHEADER_SIZE = 516 diff --git a/blkid/internal/filesystems/vfat/vfat.go b/blkid/internal/filesystems/vfat/vfat.go index cbdcba1..e2c699e 100644 --- a/blkid/internal/filesystems/vfat/vfat.go +++ b/blkid/internal/filesystems/vfat/vfat.go @@ -68,7 +68,7 @@ func (p *Probe) Name() string { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { vfatBuf := make([]byte, VFATSB_SIZE) msdosBuf := make([]byte, MSDOSSB_SIZE) diff --git a/blkid/internal/filesystems/xfs/xfs.go b/blkid/internal/filesystems/xfs/xfs.go index 5262f12..b244e11 100644 --- a/blkid/internal/filesystems/xfs/xfs.go +++ b/blkid/internal/filesystems/xfs/xfs.go @@ -36,7 +36,7 @@ func (p *Probe) Name() string { } // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { buf := make([]byte, SUPERBLOCK_SIZE) if _, err := r.ReadAt(buf, 0); err != nil { diff --git a/blkid/internal/partitions/gpt/gpt.go b/blkid/internal/partitions/gpt/gpt.go index 2aa0244..6b14b10 100644 --- a/blkid/internal/partitions/gpt/gpt.go +++ b/blkid/internal/partitions/gpt/gpt.go @@ -43,7 +43,7 @@ const ( ) // Probe runs the further inspection and returns the result if successful. -func (p *Probe) Probe(r probe.Reader) (*probe.Result, error) { +func (p *Probe) Probe(r probe.Reader, _ magic.Magic) (*probe.Result, error) { lastLBA, ok := lastLBA(r) if !ok { return nil, nil //nolint:nilnil diff --git a/blkid/internal/probe/probe.go b/blkid/internal/probe/probe.go index 2ce79cd..7bef6d2 100644 --- a/blkid/internal/probe/probe.go +++ b/blkid/internal/probe/probe.go @@ -28,7 +28,13 @@ type Prober interface { // Magic returns the magic value for the filesystem or volume manager. Magic() []*magic.Magic // Probe runs the further inspection and returns the result if successful. - Probe(Reader) (*Result, error) + Probe(Reader, magic.Magic) (*Result, error) +} + +// MagicMatch is a magic detection result. +type MagicMatch struct { + Prober Prober + Magic magic.Magic } // Result is a probe result. diff --git a/blkid/probe_linux.go b/blkid/probe_linux.go index 99cf2d8..7081fea 100644 --- a/blkid/probe_linux.go +++ b/blkid/probe_linux.go @@ -126,13 +126,13 @@ func (i *Info) probe(f *os.File, chain chain.Chain, offset, length uint64) (*pro } for _, matched := range chain.MagicMatches(buf) { - res, err := matched.Probe(pR) + res, err := matched.Prober.Probe(pR, matched.Magic) if err != nil || res == nil { // skip failed probes continue } - return res, matched, nil + return res, matched.Prober, nil } return nil, nil, nil