Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

composefs integration in the overlay driver #1646

Merged
merged 7 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,27 @@ type DriverWithDifferOutput struct {
TOCDigest digest.Digest
}

type DifferOutputFormat int

const (
// DifferOutputFormatDir means the output is a directory and it will
// keep the original layout.
DifferOutputFormatDir = iota
// DifferOutputFormatFlat will store the files by their checksum, in the form
// checksum[0:2]/checksum[2:]
DifferOutputFormatFlat
)

// DifferOptions overrides how the differ work
type DifferOptions struct {
// Format defines the destination directory layout format
Format DifferOutputFormat
}

// Differ defines the interface for using a custom differ.
// This API is experimental and can be changed without bumping the major version number.
type Differ interface {
ApplyDiff(dest string, options *archive.TarOptions) (DriverWithDifferOutput, error)
ApplyDiff(dest string, options *archive.TarOptions, differOpts *DifferOptions) (DriverWithDifferOutput, error)
}

// DriverWithDiffer is the interface for direct diff access.
Expand Down
24 changes: 24 additions & 0 deletions drivers/overlay/composefs_notsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//go:build !linux || !composefs || !cgo
// +build !linux !composefs !cgo

package overlay

import (
"fmt"
)

func composeFsSupported() bool {
return false
}

func generateComposeFsBlob(toc []byte, destFile string) error {
return fmt.Errorf("composefs is not supported")
}

func mountErofsBlob(blobFile, mountPoint string) error {
return fmt.Errorf("composefs is not supported")
}

func enableVerityRecursive(path string) error {
return fmt.Errorf("composefs is not supported")
}
130 changes: 130 additions & 0 deletions drivers/overlay/composefs_supported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//go:build linux && composefs && cgo
// +build linux,composefs,cgo

package overlay

import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"unsafe"

"github.com/containers/storage/pkg/loopback"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)

var (
composeFsHelperOnce sync.Once
composeFsHelperPath string
composeFsHelperErr error
)

func getComposeFsHelper() (string, error) {
composeFsHelperOnce.Do(func() {
composeFsHelperPath, composeFsHelperErr = exec.LookPath("composefs-from-json")
})
return composeFsHelperPath, composeFsHelperErr
}

func composeFsSupported() bool {
_, err := getComposeFsHelper()
return err == nil
}

func enableVerity(description string, fd int) error {
enableArg := unix.FsverityEnableArg{
Version: 1,
Hash_algorithm: unix.FS_VERITY_HASH_ALG_SHA256,
Block_size: 4096,
}

_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_ENABLE_VERITY), uintptr(unsafe.Pointer(&enableArg)))
if e1 != 0 && !errors.Is(e1, unix.EEXIST) {
return fmt.Errorf("failed to enable verity for %q: %w", description, e1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should silently accept EEXIST (fs-verity already enabled for the file).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, couldn't this happen if a layer file is hard-linked from another layer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this could happen with hard link deduplication

}
return nil
}

func enableVerityRecursive(path string) error {
walkFn := func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.Type().IsRegular() {
return nil
}

f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

if err := enableVerity(path, int(f.Fd())); err != nil {
return err
}
return nil
}
return filepath.WalkDir(path, walkFn)
}

func generateComposeFsBlob(toc []byte, destFile string) error {
writerJson, err := getComposeFsHelper()
if err != nil {
return fmt.Errorf("failed to find composefs-from-json: %w", err)
}

fd, err := unix.Openat(unix.AT_FDCWD, destFile, unix.O_WRONLY|unix.O_CREAT|unix.O_TRUNC|unix.O_EXCL|unix.O_CLOEXEC, 0o644)
if err != nil {
return fmt.Errorf("failed to open output file: %w", err)
}
outFd := os.NewFile(uintptr(fd), "outFd")

fd, err = unix.Open(fmt.Sprintf("/proc/self/fd/%d", outFd.Fd()), unix.O_RDONLY|unix.O_CLOEXEC, 0)
if err != nil {
outFd.Close()
return fmt.Errorf("failed to dup output file: %w", err)
giuseppe marked this conversation as resolved.
Show resolved Hide resolved
}
newFd := os.NewFile(uintptr(fd), "newFd")
defer newFd.Close()

err = func() error {
// a scope to close outFd before setting fsverity on the read-only fd.
defer outFd.Close()

cmd := exec.Command(writerJson, "--format=erofs", "--out=/proc/self/fd/3", "/proc/self/fd/0")
cmd.ExtraFiles = []*os.File{outFd}
cmd.Stderr = os.Stderr
cmd.Stdin = bytes.NewReader(toc)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to convert json to erofs: %w", err)
}
return nil
}()
if err != nil {
return err
}

if err := enableVerity("manifest file", int(newFd.Fd())); err != nil && !errors.Is(err, unix.ENOTSUP) && !errors.Is(err, unix.ENOTTY) {
logrus.Warningf("%s", err)
giuseppe marked this conversation as resolved.
Show resolved Hide resolved
}

return nil
}

func mountErofsBlob(blobFile, mountPoint string) error {
giuseppe marked this conversation as resolved.
Show resolved Hide resolved
loop, err := loopback.AttachLoopDevice(blobFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not setting the loopback device read-only. I think we should do that. (composefs does)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it should enable direct-io to avoid caching metadata twice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, ideally it should pass "noacl" if the composefs header flag that says there are no acls in the file is set (this will improve performance quite a lot).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, noacl is in the mount options below, not the loop properties.

if err != nil {
return err
}
defer loop.Close()

return unix.Mount(loop.Name(), mountPoint, "erofs", unix.MS_RDONLY, "ro")
}
Loading