Skip to content

Commit

Permalink
feat: initial commit for erofs support
Browse files Browse the repository at this point in the history
Signed-off-by: Ramkumar Chinchani <[email protected]>
  • Loading branch information
rchincha committed Nov 13, 2024
1 parent eaa7b43 commit f808b69
Show file tree
Hide file tree
Showing 30 changed files with 1,507 additions and 358 deletions.
1 change: 1 addition & 0 deletions atomfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package atomfs
13 changes: 6 additions & 7 deletions cmd/atomfs/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import (

"github.com/pkg/errors"
"github.com/urfave/cli"

"machinerun.io/atomfs"
"machinerun.io/atomfs/squashfs"
"machinerun.io/atomfs/pkg/common"
"machinerun.io/atomfs/pkg/molecule"
)

var mountCmd = cli.Command{
Expand Down Expand Up @@ -51,7 +50,7 @@ func findImage(ctx *cli.Context) (string, string, error) {
}
ocidir := r[0]
tag := r[1]
if !atomfs.PathExists(ocidir) {
if !common.PathExists(ocidir) {
return "", "", fmt.Errorf("oci directory %s does not exist: %w", ocidir, mountUsage(ctx.App.Name))
}
return ocidir, tag, nil
Expand Down Expand Up @@ -94,7 +93,7 @@ func doMount(ctx *cli.Context) error {
return fmt.Errorf("--persist requires an argument")
}
}
opts := atomfs.MountOCIOpts{
opts := molecule.MountOCIOpts{
OCIDir: absOCIDir,
Tag: tag,
Target: absTarget,
Expand All @@ -104,7 +103,7 @@ func doMount(ctx *cli.Context) error {
MetadataDir: ctx.String("metadir"), // nil here means /run/atomfs
}

mol, err := atomfs.BuildMoleculeFromOCI(opts)
mol, err := molecule.BuildMoleculeFromOCI(opts)
if err != nil {
return errors.Wrapf(err, "couldn't build molecule with opts %+v", opts)
}
Expand Down Expand Up @@ -132,7 +131,7 @@ func amPrivileged() bool {

func squashUmount(p string) error {
if amPrivileged() {
return squashfs.Umount(p)
return common.Umount(p)
}
return RunCommand("fusermount", "-u", p)
}
8 changes: 4 additions & 4 deletions cmd/atomfs/umount.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"syscall"

"github.com/urfave/cli"
"machinerun.io/atomfs"
"machinerun.io/atomfs/mount"
"machinerun.io/atomfs/pkg/common"
"machinerun.io/atomfs/pkg/mount"
)

var umountCmd = cli.Command{
Expand Down Expand Up @@ -62,11 +62,11 @@ func doUmount(ctx *cli.Context) error {
// $metadir/meta/config.json

// TODO: want to know mountnsname for a target mountpoint... not for our current proc???
mountNSName, err := atomfs.GetMountNSName()
mountNSName, err := common.GetMountNSName()
if err != nil {
errs = append(errs, fmt.Errorf("Failed to get mount namespace name"))
}
metadir := filepath.Join(atomfs.RuntimeDir(ctx.String("metadir")), "meta", mountNSName, atomfs.ReplacePathSeparators(mountpoint))
metadir := filepath.Join(common.RuntimeDir(ctx.String("metadir")), "meta", mountNSName, common.ReplacePathSeparators(mountpoint))

mountsdir := filepath.Join(metadir, "mounts")
mounts, err := os.ReadDir(mountsdir)
Expand Down
14 changes: 7 additions & 7 deletions cmd/atomfs/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
"strings"

"github.com/urfave/cli"
"machinerun.io/atomfs"
"machinerun.io/atomfs/log"
"machinerun.io/atomfs/mount"
"machinerun.io/atomfs/squashfs"
"machinerun.io/atomfs/pkg/common"
"machinerun.io/atomfs/pkg/log"
"machinerun.io/atomfs/pkg/mount"
"machinerun.io/atomfs/pkg/verity"
)

var verifyCmd = cli.Command{
Expand Down Expand Up @@ -49,12 +49,12 @@ func doVerify(ctx *cli.Context) error {
return fmt.Errorf("%s is not a mountpoint", mountpoint)
}

mountNSName, err := atomfs.GetMountNSName()
mountNSName, err := common.GetMountNSName()
if err != nil {
return err
}

metadir := filepath.Join(atomfs.RuntimeDir(ctx.String("metadir")), "meta", mountNSName, atomfs.ReplacePathSeparators(mountpoint))
metadir := filepath.Join(common.RuntimeDir(ctx.String("metadir")), "meta", mountNSName, common.ReplacePathSeparators(mountpoint))
mountsdir := filepath.Join(metadir, "mounts")

mounts, err := mount.ParseMounts("/proc/self/mountinfo")
Expand Down Expand Up @@ -83,7 +83,7 @@ func doVerify(ctx *cli.Context) error {
continue
}
checkedCount = checkedCount + 1
err = squashfs.ConfirmExistingVerityDeviceCurrentValidity(m.Source)
err = verity.ConfirmExistingVerityDeviceCurrentValidity(m.Source)
if err != nil {
fmt.Printf("%s: CORRUPTION FOUND\n", m.Source)
allOK = false
Expand Down
49 changes: 49 additions & 0 deletions pkg/common/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package common

import (
"testing"

"github.com/stretchr/testify/assert"
)

type uidmapTestcase struct {
uidmap string
expected bool
}

var uidmapTests = []uidmapTestcase{
{
uidmap: ` 0 0 4294967295`,
expected: true,
},
{
uidmap: ` 0 0 1000
2000 2000 1`,
expected: false,
},
{
uidmap: ` 0 0 1000`,
expected: false,
},
{
uidmap: ` 10 0 4294967295`,
expected: false,
},
{
uidmap: ` 0 10 4294967295`,
expected: false,
},
{
uidmap: ` 0 0 1`,
expected: false,
},
}

func TestAmHostRoot(t *testing.T) {
t.Parallel()
assert := assert.New(t)
for _, testcase := range uidmapTests {
v := uidmapIsHost(testcase.uidmap)
assert.Equal(v, testcase.expected)
}
}
84 changes: 84 additions & 0 deletions pkg/common/exclude.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package common

import (
"bytes"
"path"
"path/filepath"
"strings"
)

// ExcludePaths represents a list of paths to exclude in a squashfs listing.
// Users should do something like filepath.Walk() over the whole filesystem,
// calling AddExclude() or AddInclude() based on whether they want to include
// or exclude a particular file. Note that if e.g. /usr is excluded, then
// everyting underneath is also implicitly excluded. The
// AddExclude()/AddInclude() methods do the math to figure out what is the
// correct set of things to exclude or include based on what paths have been
// previously included or excluded.
type ExcludePaths struct {
exclude map[string]bool
include []string
}

func NewExcludePaths() *ExcludePaths {
return &ExcludePaths{
exclude: map[string]bool{},
include: []string{},
}
}

func (eps *ExcludePaths) AddExclude(p string) {
for _, inc := range eps.include {
// If /usr/bin/ls has changed but /usr hasn't, we don't want to list
// /usr in the include paths any more, so let's be sure to only
// add things which aren't prefixes.
if strings.HasPrefix(inc, p) {
return
}
}
eps.exclude[p] = true
}

func (eps *ExcludePaths) AddInclude(orig string, isDir bool) {
// First, remove this thing and all its parents from exclude.
p := orig

// normalize to the first dir
if !isDir {
p = path.Dir(p)
}
for {
// our paths are all absolute, so this is a base case
if p == "/" {
break
}

delete(eps.exclude, p)
p = filepath.Dir(p)
}

// now add it to the list of includes, so we don't accidentally re-add
// anything above.
eps.include = append(eps.include, orig)
}

func (eps *ExcludePaths) String() (string, error) {
var buf bytes.Buffer
for p := range eps.exclude {
_, err := buf.WriteString(p)
if err != nil {
return "", err
}
_, err = buf.WriteString("\n")
if err != nil {
return "", err
}
}

_, err := buf.WriteString("\n")
if err != nil {
return "", err
}

return buf.String(), nil
}
47 changes: 47 additions & 0 deletions pkg/common/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package common

import (
"os"
"strings"
)

func FileChanged(a os.FileInfo, path string) bool {
b, err := os.Lstat(path)
if err != nil {
return true
}
return !os.SameFile(a, b)
}

// Takes /proc/self/uid_map contents as one string
// Returns true if this is a uidmap representing the whole host
// uid range.
func uidmapIsHost(oneline string) bool {
oneline = strings.TrimSuffix(oneline, "\n")
if len(oneline) == 0 {
return false
}
lines := strings.Split(oneline, "\n")
if len(lines) != 1 {
return false
}
words := strings.Fields(lines[0])
if len(words) != 3 || words[0] != "0" || words[1] != "0" || words[2] != "4294967295" {
return false
}

return true
}

func AmHostRoot() bool {
// if not uid 0, not host root
if os.Geteuid() != 0 {
return false
}
// if uid_map doesn't map 0 to 0, not host root
bytes, err := os.ReadFile("/proc/self/uid_map")
if err != nil {
return false
}
return uidmapIsHost(string(bytes))
}
99 changes: 99 additions & 0 deletions pkg/common/mount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package common

import (
"os"
"os/exec"
"path/filepath"

"github.com/pkg/errors"
"golang.org/x/sys/unix"
"machinerun.io/atomfs/pkg/mount"
"machinerun.io/atomfs/pkg/verity"
)

func HostMount(squashfs string, mountpoint string, rootHash string, veritySize int64, verityOffset uint64) error {
return verity.VerityHostMount(squashfs, mountpoint, rootHash, veritySize, verityOffset)
}

// Mount a filesystem as container root, without host root
// privileges. We do this using squashfuse.
func GuestMount(squashFile string, mountpoint string, cmd *exec.Cmd) error {
if isMountpoint(mountpoint) {
return errors.Errorf("%s is already mounted", mountpoint)
}

abs, err := filepath.Abs(squashFile)
if err != nil {
return errors.Errorf("Failed to get absolute path for %s: %v", squashFile, err)
}
squashFile = abs

abs, err = filepath.Abs(mountpoint)
if err != nil {
return errors.Errorf("Failed to get absolute path for %s: %v", mountpoint, err)
}
mountpoint = abs

if err := cmd.Process.Release(); err != nil {
return errors.Errorf("Failed to release process after guestmount %s: %v", squashFile, err)
}
return nil
}

func Umount(mountpoint string) error {
mounts, err := mount.ParseMounts("/proc/self/mountinfo")
if err != nil {
return err
}

// first, find the verity device that backs the mount
theMount, found := mounts.FindMount(mountpoint)
if !found {
return errors.Errorf("%s is not a mountpoint", mountpoint)
}

err = unix.Unmount(mountpoint, 0)
if err != nil {
return errors.Wrapf(err, "failed unmounting %v", mountpoint)
}

if _, err := os.Stat(theMount.Source); err != nil {
if os.IsNotExist(err) {
return nil
}
return errors.WithStack(err)
}

return verity.VerityUnmount(theMount.Source)
}

func isMountpoint(dest string) bool {
mounted, err := mount.IsMountpoint(dest)
return err == nil && mounted
}

func IsMountedAtDir(_, dest string) (bool, error) {
dstat, err := os.Stat(dest)
if os.IsNotExist(err) {
return false, nil
}
if !dstat.IsDir() {
return false, nil
}
mounts, err := mount.ParseMounts("/proc/self/mountinfo")
if err != nil {
return false, err
}

fdest, err := filepath.Abs(dest)
if err != nil {
return false, err
}
for _, m := range mounts {
if m.Target == fdest {
return true, nil
}
}

return false, nil
}
Loading

0 comments on commit f808b69

Please sign in to comment.