Skip to content

Commit

Permalink
gadget,tests: add support for volume-assignment syntax in gadget.yaml…
Browse files Browse the repository at this point in the history
…, this will allow the gadget yaml to specify specific disk mappings when either installing or updating the system
  • Loading branch information
Meulengracht committed Oct 3, 2024
1 parent 0d7c868 commit a5c5e3f
Show file tree
Hide file tree
Showing 16 changed files with 1,912 additions and 478 deletions.
4 changes: 4 additions & 0 deletions gadget/device_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ var errNotImplemented = errors.New("not implemented")
func FindDeviceForStructure(vs *VolumeStructure) (string, error) {
return "", errNotImplemented
}

func ResolveDeviceForStructure(device string) (string, error) {
return "", errNotImplemented
}
77 changes: 47 additions & 30 deletions gadget/device_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,10 @@ import (

var evalSymlinks = filepath.EvalSymlinks

// FindDeviceForStructure attempts to find an existing block device matching
// given volume structure, by inspecting its name and, optionally, the
// filesystem label. Assumes that the host's udev has set up device symlinks
// correctly.
func FindDeviceForStructure(vs *VolumeStructure) (string, error) {
var candidates []string

if vs.Name != "" {
byPartlabel := filepath.Join(dirs.GlobalRootDir, "/dev/disk/by-partlabel/", disks.BlkIDEncodeLabel(vs.Name))
candidates = append(candidates, byPartlabel)
}
if vs.HasFilesystem() {
fsLabel := vs.Label
if fsLabel == "" && vs.Name != "" {
// when image is built and the structure has no
// filesystem label, the structure name will be used by
// default as the label
fsLabel = vs.Name
}
if fsLabel != "" {
candLabel, err := disks.CandidateByLabelPath(fsLabel)
if err == nil {
candidates = append(candidates, candLabel)
} else {
logger.Debugf("no by-label candidate for %q: %v", fsLabel, err)
}
}
}

func resolveMaybeDiskPaths(diskPaths []string) (string, error) {
var found string
var match string
for _, candidate := range candidates {
for _, candidate := range diskPaths {
if !osutil.FileExists(candidate) {
continue
}
Expand Down Expand Up @@ -91,3 +63,48 @@ func FindDeviceForStructure(vs *VolumeStructure) (string, error) {

return found, nil
}

func discoverDeviceDiskCandidatesForStructure(vs *VolumeStructure) (candidates []string) {
if vs.Name != "" {
byPartlabel := filepath.Join(dirs.GlobalRootDir, "/dev/disk/by-partlabel/", disks.BlkIDEncodeLabel(vs.Name))
candidates = append(candidates, byPartlabel)
}
if vs.HasFilesystem() {
fsLabel := vs.Label
if fsLabel == "" && vs.Name != "" {
// when image is built and the structure has no
// filesystem label, the structure name will be used by
// default as the label
fsLabel = vs.Name
}
if fsLabel != "" {
candLabel, err := disks.CandidateByLabelPath(fsLabel)
if err == nil {
candidates = append(candidates, candLabel)
} else {
logger.Debugf("no by-label candidate for %q: %v", fsLabel, err)
}
}
}
return candidates
}

// FindDeviceForStructure attempts to find an existing block device matching
// given volume structure, by inspecting its name and, optionally, the
// filesystem label. Assumes that the host's udev has set up device symlinks
// correctly.
func FindDeviceForStructure(vs *VolumeStructure) (string, error) {
candidates := discoverDeviceDiskCandidatesForStructure(vs)
return resolveMaybeDiskPaths(candidates)
}

// ResolveDeviceForStructure is an opposite to FindDeviceForStructure that allows
// supplying a device path to resolve a specific disk. Calling this without a filter
// (i.e device == ""), will return an error
// The device path must be a path into /dev/disk/**
func ResolveDeviceForStructure(device string) (string, error) {
if device == "" {
return "", fmt.Errorf("internal error: device must be supplied")
}
return resolveMaybeDiskPaths([]string{device})
}
45 changes: 45 additions & 0 deletions gadget/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package gadget_test

import (
"errors"
"fmt"
"os"
"path/filepath"

Expand Down Expand Up @@ -244,3 +245,47 @@ func (d *deviceSuite) TestDeviceFindBadEvalSymlinks(c *C) {
c.Check(err, ErrorMatches, `cannot read device link: failed`)
c.Check(found, Equals, "")
}

func (d *deviceSuite) TestResolveDeviceEmptyBase(c *C) {
found, err := gadget.ResolveDeviceForStructure("")
c.Check(err, ErrorMatches, `internal error: device must be supplied`)
c.Check(found, Equals, "")
}

func (d *deviceSuite) TestResolveDeviceBaseDeviceNotSymlink(c *C) {
// only the by-filesystem-label symlink
fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
c.Assert(os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo")), IsNil)
c.Assert(os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/relative")), IsNil)

found, err := gadget.ResolveDeviceForStructure(fakedevice)
c.Check(err, ErrorMatches, fmt.Sprintf(`candidate %s/dev/fakedevice is not a symlink`, d.dir))
c.Check(found, Equals, "")
}

func (d *deviceSuite) TestResolveDeviceMatchesBaseDeviceAsSymlink(c *C) {
// only the by-filesystem-label symlink
fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
c.Assert(os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo")), IsNil)
c.Assert(os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/relative")), IsNil)

found, err := gadget.ResolveDeviceForStructure(filepath.Join(d.dir, "/dev/disk/by-label/foo"))
c.Check(err, IsNil)
c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
}

func (d *deviceSuite) TestResolveDeviceNoMatchesBaseDevice(c *C) {
// fake two devices
// only the by-filesystem-label symlink
fakedevice0 := filepath.Join(d.dir, "/dev/fakedevice")
fakedevice1 := filepath.Join(d.dir, "/dev/fakedevice1")

c.Assert(os.Symlink(fakedevice0, filepath.Join(d.dir, "/dev/disk/by-label/foo")), IsNil)
c.Assert(os.Symlink(fakedevice1, filepath.Join(d.dir, "/dev/disk/by-label/bar")), IsNil)

c.Assert(os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/relative")), IsNil)

found, err := gadget.ResolveDeviceForStructure(fakedevice1)
c.Check(err, ErrorMatches, `device not found`)
c.Check(found, Equals, "")
}
11 changes: 10 additions & 1 deletion gadget/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

package gadget

import "github.com/snapcore/snapd/gadget/quantity"
import (
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/testutil"
)

type (
MountedFilesystemUpdater = mountedFilesystemUpdater
Expand Down Expand Up @@ -97,3 +100,9 @@ func NewInvalidOffsetError(offset, lowerBound, upperBound quantity.Offset) *Inva
func (v *Volume) YamlIdxToStructureIdx(yamlIdx int) (int, error) {
return v.yamlIdxToStructureIdx(yamlIdx)
}

func MockFindVolumesMatchingDeviceAssignment(f func(gi *Info) (map[string]DeviceVolume, error)) (restore func()) {
r := testutil.Backup(&FindVolumesMatchingDeviceAssignment)
FindVolumesMatchingDeviceAssignment = f
return r
}
Loading

0 comments on commit a5c5e3f

Please sign in to comment.