Skip to content

Commit d0480ec

Browse files
author
Ramkumar Chinchani
committed
feat: initial commit for erofs support
Signed-off-by: Ramkumar Chinchani <[email protected]>
1 parent eaa7b43 commit d0480ec

30 files changed

+1507
-358
lines changed

atomfs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package atomfs

cmd/atomfs/mount.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import (
99

1010
"github.com/pkg/errors"
1111
"github.com/urfave/cli"
12-
13-
"machinerun.io/atomfs"
14-
"machinerun.io/atomfs/squashfs"
12+
"machinerun.io/atomfs/pkg/common"
13+
"machinerun.io/atomfs/pkg/molecule"
1514
)
1615

1716
var mountCmd = cli.Command{
@@ -51,7 +50,7 @@ func findImage(ctx *cli.Context) (string, string, error) {
5150
}
5251
ocidir := r[0]
5352
tag := r[1]
54-
if !atomfs.PathExists(ocidir) {
53+
if !common.PathExists(ocidir) {
5554
return "", "", fmt.Errorf("oci directory %s does not exist: %w", ocidir, mountUsage(ctx.App.Name))
5655
}
5756
return ocidir, tag, nil
@@ -94,7 +93,7 @@ func doMount(ctx *cli.Context) error {
9493
return fmt.Errorf("--persist requires an argument")
9594
}
9695
}
97-
opts := atomfs.MountOCIOpts{
96+
opts := molecule.MountOCIOpts{
9897
OCIDir: absOCIDir,
9998
Tag: tag,
10099
Target: absTarget,
@@ -104,7 +103,7 @@ func doMount(ctx *cli.Context) error {
104103
MetadataDir: ctx.String("metadir"), // nil here means /run/atomfs
105104
}
106105

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

133132
func squashUmount(p string) error {
134133
if amPrivileged() {
135-
return squashfs.Umount(p)
134+
return common.Umount(p)
136135
}
137136
return RunCommand("fusermount", "-u", p)
138137
}

cmd/atomfs/umount.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"syscall"
88

99
"github.com/urfave/cli"
10-
"machinerun.io/atomfs"
11-
"machinerun.io/atomfs/mount"
10+
"machinerun.io/atomfs/pkg/common"
11+
"machinerun.io/atomfs/pkg/mount"
1212
)
1313

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

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

7171
mountsdir := filepath.Join(metadir, "mounts")
7272
mounts, err := os.ReadDir(mountsdir)

cmd/atomfs/verify.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"strings"
77

88
"github.com/urfave/cli"
9-
"machinerun.io/atomfs"
10-
"machinerun.io/atomfs/log"
11-
"machinerun.io/atomfs/mount"
12-
"machinerun.io/atomfs/squashfs"
9+
"machinerun.io/atomfs/pkg/common"
10+
"machinerun.io/atomfs/pkg/log"
11+
"machinerun.io/atomfs/pkg/mount"
12+
"machinerun.io/atomfs/pkg/verity"
1313
)
1414

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

52-
mountNSName, err := atomfs.GetMountNSName()
52+
mountNSName, err := common.GetMountNSName()
5353
if err != nil {
5454
return err
5555
}
5656

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

6060
mounts, err := mount.ParseMounts("/proc/self/mountinfo")
@@ -83,7 +83,7 @@ func doVerify(ctx *cli.Context) error {
8383
continue
8484
}
8585
checkedCount = checkedCount + 1
86-
err = squashfs.ConfirmExistingVerityDeviceCurrentValidity(m.Source)
86+
err = verity.ConfirmExistingVerityDeviceCurrentValidity(m.Source)
8787
if err != nil {
8888
fmt.Printf("%s: CORRUPTION FOUND\n", m.Source)
8989
allOK = false

pkg/common/common_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
type uidmapTestcase struct {
10+
uidmap string
11+
expected bool
12+
}
13+
14+
var uidmapTests = []uidmapTestcase{
15+
{
16+
uidmap: ` 0 0 4294967295`,
17+
expected: true,
18+
},
19+
{
20+
uidmap: ` 0 0 1000
21+
2000 2000 1`,
22+
expected: false,
23+
},
24+
{
25+
uidmap: ` 0 0 1000`,
26+
expected: false,
27+
},
28+
{
29+
uidmap: ` 10 0 4294967295`,
30+
expected: false,
31+
},
32+
{
33+
uidmap: ` 0 10 4294967295`,
34+
expected: false,
35+
},
36+
{
37+
uidmap: ` 0 0 1`,
38+
expected: false,
39+
},
40+
}
41+
42+
func TestAmHostRoot(t *testing.T) {
43+
t.Parallel()
44+
assert := assert.New(t)
45+
for _, testcase := range uidmapTests {
46+
v := uidmapIsHost(testcase.uidmap)
47+
assert.Equal(v, testcase.expected)
48+
}
49+
}

pkg/common/exclude.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package common
2+
3+
import (
4+
"bytes"
5+
"path"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// ExcludePaths represents a list of paths to exclude in a squashfs listing.
11+
// Users should do something like filepath.Walk() over the whole filesystem,
12+
// calling AddExclude() or AddInclude() based on whether they want to include
13+
// or exclude a particular file. Note that if e.g. /usr is excluded, then
14+
// everyting underneath is also implicitly excluded. The
15+
// AddExclude()/AddInclude() methods do the math to figure out what is the
16+
// correct set of things to exclude or include based on what paths have been
17+
// previously included or excluded.
18+
type ExcludePaths struct {
19+
exclude map[string]bool
20+
include []string
21+
}
22+
23+
func NewExcludePaths() *ExcludePaths {
24+
return &ExcludePaths{
25+
exclude: map[string]bool{},
26+
include: []string{},
27+
}
28+
}
29+
30+
func (eps *ExcludePaths) AddExclude(p string) {
31+
for _, inc := range eps.include {
32+
// If /usr/bin/ls has changed but /usr hasn't, we don't want to list
33+
// /usr in the include paths any more, so let's be sure to only
34+
// add things which aren't prefixes.
35+
if strings.HasPrefix(inc, p) {
36+
return
37+
}
38+
}
39+
eps.exclude[p] = true
40+
}
41+
42+
func (eps *ExcludePaths) AddInclude(orig string, isDir bool) {
43+
// First, remove this thing and all its parents from exclude.
44+
p := orig
45+
46+
// normalize to the first dir
47+
if !isDir {
48+
p = path.Dir(p)
49+
}
50+
for {
51+
// our paths are all absolute, so this is a base case
52+
if p == "/" {
53+
break
54+
}
55+
56+
delete(eps.exclude, p)
57+
p = filepath.Dir(p)
58+
}
59+
60+
// now add it to the list of includes, so we don't accidentally re-add
61+
// anything above.
62+
eps.include = append(eps.include, orig)
63+
}
64+
65+
func (eps *ExcludePaths) String() (string, error) {
66+
var buf bytes.Buffer
67+
for p := range eps.exclude {
68+
_, err := buf.WriteString(p)
69+
if err != nil {
70+
return "", err
71+
}
72+
_, err = buf.WriteString("\n")
73+
if err != nil {
74+
return "", err
75+
}
76+
}
77+
78+
_, err := buf.WriteString("\n")
79+
if err != nil {
80+
return "", err
81+
}
82+
83+
return buf.String(), nil
84+
}

pkg/common/fs.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package common
2+
3+
import (
4+
"os"
5+
"strings"
6+
)
7+
8+
func FileChanged(a os.FileInfo, path string) bool {
9+
b, err := os.Lstat(path)
10+
if err != nil {
11+
return true
12+
}
13+
return !os.SameFile(a, b)
14+
}
15+
16+
func AmHostRoot() bool {
17+
// if not uid 0, not host root
18+
if os.Geteuid() != 0 {
19+
return false
20+
}
21+
// if uid_map doesn't map 0 to 0, not host root
22+
bytes, err := os.ReadFile("/proc/self/uid_map")
23+
if err != nil {
24+
return false
25+
}
26+
return uidmapIsHost(string(bytes))
27+
}
28+
29+
// Takes /proc/self/uid_map contents as one string
30+
// Returns true if this is a uidmap representing the whole host
31+
// uid range.
32+
func uidmapIsHost(oneline string) bool {
33+
oneline = strings.TrimSuffix(oneline, "\n")
34+
if len(oneline) == 0 {
35+
return false
36+
}
37+
lines := strings.Split(oneline, "\n")
38+
if len(lines) != 1 {
39+
return false
40+
}
41+
words := strings.Fields(lines[0])
42+
if len(words) != 3 || words[0] != "0" || words[1] != "0" || words[2] != "4294967295" {
43+
return false
44+
}
45+
46+
return true
47+
}

pkg/common/mount.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package common
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
8+
"github.com/pkg/errors"
9+
"golang.org/x/sys/unix"
10+
"machinerun.io/atomfs/pkg/mount"
11+
"machinerun.io/atomfs/pkg/verity"
12+
)
13+
14+
func HostMount(squashfs string, mountpoint string, rootHash string, veritySize int64, verityOffset uint64) error {
15+
return verity.VerityHostMount(squashfs, mountpoint, rootHash, veritySize, verityOffset)
16+
}
17+
18+
// Mount a filesystem as container root, without host root
19+
// privileges. We do this using squashfuse.
20+
func GuestMount(squashFile string, mountpoint string, cmd *exec.Cmd) error {
21+
if isMountpoint(mountpoint) {
22+
return errors.Errorf("%s is already mounted", mountpoint)
23+
}
24+
25+
abs, err := filepath.Abs(squashFile)
26+
if err != nil {
27+
return errors.Errorf("Failed to get absolute path for %s: %v", squashFile, err)
28+
}
29+
squashFile = abs
30+
31+
abs, err = filepath.Abs(mountpoint)
32+
if err != nil {
33+
return errors.Errorf("Failed to get absolute path for %s: %v", mountpoint, err)
34+
}
35+
mountpoint = abs
36+
37+
if err := cmd.Process.Release(); err != nil {
38+
return errors.Errorf("Failed to release process after guestmount %s: %v", squashFile, err)
39+
}
40+
return nil
41+
}
42+
43+
func Umount(mountpoint string) error {
44+
mounts, err := mount.ParseMounts("/proc/self/mountinfo")
45+
if err != nil {
46+
return err
47+
}
48+
49+
// first, find the verity device that backs the mount
50+
theMount, found := mounts.FindMount(mountpoint)
51+
if !found {
52+
return errors.Errorf("%s is not a mountpoint", mountpoint)
53+
}
54+
55+
err = unix.Unmount(mountpoint, 0)
56+
if err != nil {
57+
return errors.Wrapf(err, "failed unmounting %v", mountpoint)
58+
}
59+
60+
if _, err := os.Stat(theMount.Source); err != nil {
61+
if os.IsNotExist(err) {
62+
return nil
63+
}
64+
return errors.WithStack(err)
65+
}
66+
67+
return verity.VerityUnmount(theMount.Source)
68+
}
69+
70+
func isMountpoint(dest string) bool {
71+
mounted, err := mount.IsMountpoint(dest)
72+
return err == nil && mounted
73+
}
74+
75+
func IsMountedAtDir(_, dest string) (bool, error) {
76+
dstat, err := os.Stat(dest)
77+
if os.IsNotExist(err) {
78+
return false, nil
79+
}
80+
if !dstat.IsDir() {
81+
return false, nil
82+
}
83+
mounts, err := mount.ParseMounts("/proc/self/mountinfo")
84+
if err != nil {
85+
return false, err
86+
}
87+
88+
fdest, err := filepath.Abs(dest)
89+
if err != nil {
90+
return false, err
91+
}
92+
for _, m := range mounts {
93+
if m.Target == fdest {
94+
return true, nil
95+
}
96+
}
97+
98+
return false, nil
99+
}

0 commit comments

Comments
 (0)