Skip to content

Commit

Permalink
feat: detect Linux swap and LVM2
Browse files Browse the repository at this point in the history
Also modify file system probing methods to propagate the offset magic was found at

Signed-off-by: Dmitry Sharshakov <[email protected]>
  • Loading branch information
dsseng committed Apr 5, 2024
1 parent 21c66f8 commit 3265299
Show file tree
Hide file tree
Showing 19 changed files with 460 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 99 additions & 7 deletions blkid/blkid_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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")
Expand Down Expand Up @@ -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)
}

Expand Down
10 changes: 7 additions & 3 deletions blkid/internal/chain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand All @@ -57,6 +59,8 @@ func Default() Chain {
&xfs.Probe{},
&ext.Probe{},
&vfat.Probe{},
&swap.Probe{},
&lvm2.Probe{},
&gpt.Probe{},
&luks.Probe{},
&iso9660.Probe{},
Expand Down
2 changes: 1 addition & 1 deletion blkid/internal/chain/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ import (
)

func TestMaxMagicSize(t *testing.T) {
assert.Equal(t, 32774, chain.Default().MaxMagicSize())
assert.Equal(t, 65536, chain.Default().MaxMagicSize())
}
2 changes: 1 addition & 1 deletion blkid/internal/filesystems/bluestore/bluestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion blkid/internal/filesystems/ext/ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion blkid/internal/filesystems/iso9660/iso9660.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion blkid/internal/filesystems/luks/luks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
87 changes: 87 additions & 0 deletions blkid/internal/filesystems/lvm2/lvm2.go
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions blkid/internal/filesystems/lvm2/lvm2_header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <stdint.h>

/* 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));
47 changes: 47 additions & 0 deletions blkid/internal/filesystems/lvm2/lvm2header.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3265299

Please sign in to comment.