Skip to content

Commit

Permalink
Merge pull request canonical#9777 from mvo5/kernel-dtb-refs-2.2
Browse files Browse the repository at this point in the history
gadget: add gadget.ResolveContentPaths()
  • Loading branch information
mvo5 authored Jan 18, 2021
2 parents 64b24d2 + 8e12871 commit 8ac3dd4
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 1 deletion.
11 changes: 10 additions & 1 deletion gadget/gadget.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,19 @@ type VolumeContent struct {
Size quantity.Size `yaml:"size"`

Unpack bool `yaml:"unpack"`

// resolvedSource is the absolute path of the Source after resolving
// any references (e.g. to a "$kernel:" snap)
resolvedSource string
// TODO: provide resolvedImage too
}

func (vc VolumeContent) ResolvedSource() string {
// TODO: implement resolved sources
// TODO: ensure that sources are always resolved and only return
// vc.resolvedSource(). This will come in the next PR.
if vc.resolvedSource != "" {
return vc.resolvedSource
}
return vc.UnresolvedSource
}

Expand Down
64 changes: 64 additions & 0 deletions gadget/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ import (
"os"
"path/filepath"
"sort"
"strings"

"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
"github.com/snapcore/snapd/strutil"
)

// LayoutConstraints defines the constraints for arranging structures within a
Expand Down Expand Up @@ -240,6 +243,67 @@ func LayoutVolume(gadgetRootDir string, volume *Volume, constraints LayoutConstr
return vol, nil
}

// ResolveContentPaths resolves any "$kernel:" refs in the gadget
// content and populates VolumeContent.resolvedSource with absolute
// paths.
//
// XXX: maybe move into LayoutVolume(), operator on *Volume and make private?
func ResolveContentPaths(lv *LaidOutVolume, gadgetRootDir, kernelRootDir string) error {
// Note that the kernelRootDir may reference the running
// kernel if there is a gadget update or the new kernel if
// there is a kernel update.
kernelInfo, err := kernel.ReadInfo(kernelRootDir)
if err != nil {
return err
}
for i := range lv.Volume.Structure {
if err := resolveContentPathsForStructure(gadgetRootDir, kernelRootDir, kernelInfo, &lv.Volume.Structure[i]); err != nil {
return err
}
}

return nil
}

func resolveContentPathsForStructure(gadgetRootDir, kernelRootDir string, kernelInfo *kernel.Info, ps *VolumeStructure) error {
for i := range ps.Content {
source := ps.Content[i].UnresolvedSource
if source != "" {
newSource, err := resolveContentOne(gadgetRootDir, kernelRootDir, kernelInfo, source)
if err != nil {
return err
}
if strings.HasSuffix(source, "/") {
// restore trailing / if one was there
newSource += "/"
}
ps.Content[i].resolvedSource = newSource
}
}

return nil
}

func resolveContentOne(gadgetRootDir, kernelRootDir string, kernelInfo *kernel.Info, pathOrRef string) (string, error) {
// content may refer to "$kernel:<name>/<content>"
if strings.HasPrefix(pathOrRef, "$kernel:") {
wantedAsset, wantedContent, err := splitKernelRef(pathOrRef)
if err != nil {
return "", fmt.Errorf("cannot parse kernel ref: %v", err)
}
kernelAsset, ok := kernelInfo.Assets[wantedAsset]
if !ok {
return "", fmt.Errorf("cannot find %q in kernel info from %q", wantedAsset, kernelRootDir)
}
if !strutil.ListContains(kernelAsset.Content, wantedContent) {
return "", fmt.Errorf("cannot find wanted kernel content %q in %q", wantedContent, kernelRootDir)
}
return filepath.Join(kernelRootDir, wantedContent), nil
}

return filepath.Join(gadgetRootDir, pathOrRef), nil
}

type byContentStartOffset []LaidOutContent

func (b byContentStartOffset) Len() int { return len(b) }
Expand Down
154 changes: 154 additions & 0 deletions gadget/layout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/kernel"
)

type layoutTestSuite struct {
Expand Down Expand Up @@ -1137,3 +1140,154 @@ volumes:
},
})
}

func mockKernel(c *C, kernelYaml string, filesWithContent map[string]string) string {
// sanity
_, err := kernel.InfoFromKernelYaml([]byte(kernelYaml))
c.Assert(err, IsNil)

kernelRootDir := c.MkDir()
err = os.MkdirAll(filepath.Join(kernelRootDir, "meta"), 0755)
c.Assert(err, IsNil)
err = ioutil.WriteFile(filepath.Join(kernelRootDir, "meta/kernel.yaml"), []byte(kernelYaml), 0644)
c.Assert(err, IsNil)

for fname, content := range filesWithContent {
p := filepath.Join(kernelRootDir, fname)
err = os.MkdirAll(filepath.Dir(p), 0755)
c.Assert(err, IsNil)
err = ioutil.WriteFile(p, []byte(content), 0644)
c.Assert(err, IsNil)
}

return kernelRootDir
}

var gadgetYamlWithKernelRef = `
volumes:
pi:
bootloader: u-boot
structure:
- type: 00000000-0000-0000-0000-dd00deadbeef
filesystem: vfat
filesystem-label: system-boot
size: 128M
content:
- source: $kernel:dtbs/boot-assets/
target: /
- source: $kernel:dtbs/some-file
target: /
- source: file-from-gadget
target: /
- source: dir-from-gadget/
target: /
`

func (p *layoutTestSuite) TestResolveContentPathsNotInWantedAssets(c *C) {
kernelYaml := ""

vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
c.Assert(vol.Structure, HasLen, 1)

kernelSnapFiles := map[string]string{}
kernelSnapDir := mockKernel(c, kernelYaml, kernelSnapFiles)
lv, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
c.Assert(err, IsNil)

err = gadget.ResolveContentPaths(lv, p.dir, kernelSnapDir)
c.Assert(err, ErrorMatches, `cannot find "dtbs" in kernel info from "/.*"`)
}

func (p *layoutTestSuite) TestResolveContentPathsErrorInKernelRef(c *C) {
kernelYaml := ""

// create invalid kernel ref
s := strings.Replace(gadgetYamlWithKernelRef, "$kernel:dtbs", "$kernel:-invalid-kernel-ref", -1)
// Note that mustParseVolume does not call ValidateContent() which
// would be needed to validate "$kernel:" refs.
vol := mustParseVolume(c, s, "pi")
c.Assert(vol.Structure, HasLen, 1)

kernelSnapFiles := map[string]string{}
kernelSnapDir := mockKernel(c, kernelYaml, kernelSnapFiles)
lv, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
c.Assert(err, IsNil)

err = gadget.ResolveContentPaths(lv, p.dir, kernelSnapDir)
c.Assert(err, ErrorMatches, `cannot parse kernel ref: invalid asset name in kernel ref "\$kernel:-invalid-kernel-ref/boot-assets/"`)
}

func (p *layoutTestSuite) TestResolveContentPathsNotInWantedeContent(c *C) {
kernelYaml := `
assets:
dtbs:
update: true
content:
- dtbs
`

vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
c.Assert(vol.Structure, HasLen, 1)

kernelSnapFiles := map[string]string{}
kernelSnapDir := mockKernel(c, kernelYaml, kernelSnapFiles)
lv, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
c.Assert(err, IsNil)

err = gadget.ResolveContentPaths(lv, p.dir, kernelSnapDir)
c.Assert(err, ErrorMatches, `cannot find wanted kernel content "boot-assets/" in "/.*"`)
}

func (p *layoutTestSuite) TestResolveContentPaths(c *C) {
kernelYaml := `
assets:
dtbs:
update: true
content:
- boot-assets/
- some-file
`
vol := mustParseVolume(c, gadgetYamlWithKernelRef, "pi")
c.Assert(vol.Structure, HasLen, 1)

kernelSnapFiles := map[string]string{
"boot-assets/foo": "foo-content",
}
kernelSnapDir := mockKernel(c, kernelYaml, kernelSnapFiles)
lv, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
c.Assert(err, IsNil)
content := lv.Volume.Structure[0].Content
c.Assert(content, DeepEquals, []gadget.VolumeContent{
{
UnresolvedSource: "$kernel:dtbs/boot-assets/",
Target: "/",
},
{
UnresolvedSource: "$kernel:dtbs/some-file",
Target: "/",
},
{
UnresolvedSource: "file-from-gadget",
Target: "/",
},
{
UnresolvedSource: "dir-from-gadget/",
Target: "/",
},
})

// now resolve the kernel references
err = gadget.ResolveContentPaths(lv, p.dir, kernelSnapDir)
c.Assert(err, IsNil)

c.Assert(lv.Volume.Structure, HasLen, 1)
c.Check(content, HasLen, 4)
// note the trailing "/" here
c.Check(content[0].ResolvedSource(), Equals, filepath.Join(kernelSnapDir, "boot-assets/")+"/")
// no trailing "/" here
c.Check(content[1].ResolvedSource(), Equals, filepath.Join(kernelSnapDir, "some-file"))
// from gadget, no trailing "/" here
c.Check(content[2].ResolvedSource(), Equals, filepath.Join(p.dir, "file-from-gadget"))
// note the trailing "/" here
c.Check(content[3].ResolvedSource(), Equals, filepath.Join(p.dir, "dir-from-gadget")+"/")
}

0 comments on commit 8ac3dd4

Please sign in to comment.