Skip to content

Commit

Permalink
Move metadir to /run, mount logic into pkg
Browse files Browse the repository at this point in the history
1 - To make it easy to read the config.json for a given mount,
move the metadata path to
/run/atomfs/meta/$current-mountns/$munged-mountpt-name/

using the current mount NS name in the path means we can track mounting
different images on to the same mountpoint in different mount
namespaces.

2 - move mount logic including the metadata dir logic from
cmd/atomfs/mount.go to atomfs/molecule.go so that users of the package
will also get the same metadir / config.json etc behavior that the
`atomfs` tool does.

As part of this move, we no longer mount an RO overlay in one place and
then either remount another overlay or bind mount to the final target
mountpoint. Instead we build one overlay mount for the mountpoint and
either it has an upperdir/workdir or not.

Signed-off-by: Michael McCracken <[email protected]>
  • Loading branch information
mikemccracken committed Oct 16, 2024
1 parent aca7b80 commit 40887fd
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 170 deletions.
117 changes: 23 additions & 94 deletions cmd/atomfs/mount.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package main

import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"

"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/sys/unix"

"machinerun.io/atomfs"
"machinerun.io/atomfs/squashfs"
)
Expand Down Expand Up @@ -44,7 +43,7 @@ func findImage(ctx *cli.Context) (string, string, error) {
}
ocidir := r[0]
tag := r[1]
if !PathExists(ocidir) {
if !atomfs.PathExists(ocidir) {
return "", "", fmt.Errorf("oci directory %s does not exist: %w", ocidir, mountUsage(ctx.App.Name))
}
return ocidir, tag, nil
Expand All @@ -70,92 +69,42 @@ func doMount(ctx *cli.Context) error {
os.Exit(1)
}
target := ctx.Args()[1]
metadir := filepath.Join(target, "meta")

complete := false

defer func() {
if !complete {
cleanupDest(metadir)
}
}()

if PathExists(metadir) {
return fmt.Errorf("%q exists: cowardly refusing to mess with it", metadir)
}

if err := EnsureDir(metadir); err != nil {
return err
}

rodest := filepath.Join(metadir, "ro")
if err = EnsureDir(rodest); err != nil {
return err
}

opts := atomfs.MountOCIOpts{
OCIDir: ocidir,
MetadataPath: metadir,
Tag: tag,
Target: rodest,
}

mol, err := atomfs.BuildMoleculeFromOCI(opts)
absTarget, err := filepath.Abs(target)
if err != nil {
return err
}

err = mol.Mount(rodest)
absOCIDir, err := filepath.Abs(ocidir)
if err != nil {
return err
}

if ctx.Bool("writeable") || ctx.IsSet("persist") {
err = overlay(target, rodest, metadir, ctx)
} else {
err = bind(target, rodest)
}

complete = err == nil
return err
}

func cleanupDest(metadir string) {
fmt.Printf("Failure detected: cleaning up %q", metadir)
rodest := filepath.Join(metadir, "ro")
if PathExists(rodest) {
if err := unix.Unmount(rodest, 0); err != nil {
fmt.Printf("Failed unmounting %q: %v", rodest, err)
persistPath := ""
if ctx.IsSet("persist") {
persistPath = ctx.String("persist")
if persistPath == "" {
return fmt.Errorf("--persist requires an argument")
}
}
opts := atomfs.MountOCIOpts{
OCIDir: absOCIDir,
Tag: tag,
Target: absTarget,
AddWriteableOverlay: ctx.Bool("writeable") || ctx.IsSet("persist"),
WriteableOverlayPath: persistPath,
}

mountsdir := filepath.Join(metadir, "mounts")
entries, err := os.ReadDir(mountsdir)
mol, err := atomfs.BuildMoleculeFromOCI(opts)
if err != nil {
fmt.Printf("Failed reading contents of %q: %v", mountsdir, err)
os.RemoveAll(metadir)
return
return errors.Wrapf(err, "couldn't build molecule with opts %+v", opts)
}

wd, err := os.Getwd()
err = mol.Mount(target)
if err != nil {
fmt.Printf("Failed getting working directory")
os.RemoveAll(metadir)
}
for _, e := range entries {
n := filepath.Base(e.Name())
if n == "workaround" {
continue
}
if strings.HasSuffix(n, ".log") {
continue
}
p := filepath.Join(wd, mountsdir, e.Name())
if err := squashUmount(p); err != nil {
fmt.Printf("Failed unmounting %q: %v\n", p, err)
}
return errors.Wrapf(err, "couldn't mount molecule at mntpt %q ", target)
}
os.RemoveAll(metadir)

return nil
}

func RunCommand(args ...string) error {
Expand All @@ -177,23 +126,3 @@ func squashUmount(p string) error {
}
return RunCommand("fusermount", "-u", p)
}

func overlay(target, rodest, metadir string, ctx *cli.Context) error {
workdir := filepath.Join(metadir, "work")
if err := EnsureDir(workdir); err != nil {
return err
}
upperdir := filepath.Join(metadir, "persist")
if ctx.IsSet("persist") {
upperdir = ctx.String("persist")
}
if err := EnsureDir(upperdir); err != nil {
return err
}
overlayArgs := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,index=off,userxattr", rodest, upperdir, workdir)
return unix.Mount("overlayfs", target, "overlay", 0, overlayArgs)
}

func bind(target, source string) error {
return syscall.Mount(source, target, "", syscall.MS_BIND, "")
}
27 changes: 15 additions & 12 deletions cmd/atomfs/umount.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"syscall"

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

Expand Down Expand Up @@ -43,36 +45,37 @@ func doUmount(ctx *cli.Context) error {
}
}

// We expect the argument to be the mountpoint - either a readonly
// bind mount, or a writeable overlay.
// We expect the argument to be the mountpoint of the overlay
err = syscall.Unmount(mountpoint, 0)
if err != nil {
errs = append(errs, fmt.Errorf("Failed unmounting %s: %v", mountpoint, err))
}

// Now that we've unmounted the mountpoint, we expect the following
// under there:
// $mountpoint/meta/ro - the original readonly overlay mountpoint
// $mountpoint/meta/mounts/* - the original squashfs mounts
metadir := filepath.Join(mountpoint, "meta")
p := filepath.Join(metadir, "ro")
err = syscall.Unmount(p, 0)
// We expect the following in the metadir
//
// $metadir/mounts/* - the original squashfs mounts
// $metadir/meta/config.json

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

mountsdir := filepath.Join(metadir, "mounts")
mounts, err := os.ReadDir(mountsdir)
if err != nil {
errs = append(errs, fmt.Errorf("Failed reading list of mounts: %v", err))
return fmt.Errorf("Encountered errors: %#v", errs)
return fmt.Errorf("Encountered errors: %v", errs)
}

for _, m := range mounts {
p = filepath.Join(mountsdir, m.Name())
p := filepath.Join(mountsdir, m.Name())
if !m.IsDir() || !isMountpoint(p) {
continue
}

err = syscall.Unmount(p, 0)
if err != nil {
errs = append(errs, fmt.Errorf("Failed unmounting squashfs dir %s: %v", p, err))
Expand Down
22 changes: 0 additions & 22 deletions cmd/atomfs/utils.go

This file was deleted.

10 changes: 7 additions & 3 deletions cmd/atomfs/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/urfave/cli"
"machinerun.io/atomfs"
"machinerun.io/atomfs/mount"
"machinerun.io/atomfs/squashfs"
)
Expand Down Expand Up @@ -41,9 +42,12 @@ func doVerify(ctx *cli.Context) error {
return fmt.Errorf("%s is not a mountpoint", mountpoint)
}

// hidden by the final overlay mount, but visible in the mountinfo:
// $mountpoint/meta/mounts/* - the original squashfs mounts
mountsdir := filepath.Join(mountpoint, "meta", "mounts")
mountNSName, err := atomfs.GetMountNSName()
if err != nil {
return err
}

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

mounts, err := mount.ParseMounts("/proc/self/mountinfo")
if err != nil {
Expand Down
Loading

0 comments on commit 40887fd

Please sign in to comment.