Port osbuild/images v0.33.0 with dot-notation to composer
Update the osbuild/images to the version which introduces "dot notation" for distro release versions. - Replace all uses of distroregistry by distrofactory. - Delete local version of reporegistry and use the one from the osbuild/images. - Weldr: unify `createWeldrAPI()` and `createWeldrAPI2()` into a single `createTestWeldrAPI()` function`. - store/fixture: rework fixtures to allow overriding the host distro name and host architecture name. A cleanup function to restore the host distro and arch names is always part of the fixture struct. - Delete `distro_mock` package, since it is no longer used. - Bump the required version of osbuild to 98, because the OSCAP customization is using the 'compress_results' stage option, which is not available in older versions of osbuild. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
parent
f6ff8c40dd
commit
625b1578fa
1166 changed files with 154457 additions and 5508 deletions
781
vendor/github.com/containers/storage/drivers/aufs/aufs.go
generated
vendored
Normal file
781
vendor/github.com/containers/storage/drivers/aufs/aufs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,781 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
|
||||
aufs driver directory structure
|
||||
|
||||
.
|
||||
├── layers // Metadata of layers
|
||||
│ ├── 1
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
├── diff // Content of the layer
|
||||
│ ├── 1 // Contains layers that need to be mounted for the id
|
||||
│ ├── 2
|
||||
│ └── 3
|
||||
└── mnt // Mount points for the rw layers to be mounted
|
||||
├── 1
|
||||
├── 2
|
||||
└── 3
|
||||
|
||||
*/
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/locker"
|
||||
mountpk "github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAufsNotSupported is returned if aufs is not supported by the host.
|
||||
ErrAufsNotSupported = fmt.Errorf("aufs was not found in /proc/filesystems")
|
||||
// ErrAufsNested means aufs cannot be used bc we are in a user namespace
|
||||
ErrAufsNested = fmt.Errorf("aufs cannot be used in non-init user namespace")
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("aufs", Init)
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
type Driver struct {
|
||||
sync.Mutex
|
||||
root string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
pathCacheLock sync.Mutex
|
||||
pathCache map[string]string
|
||||
naiveDiff graphdriver.DiffDriver
|
||||
locker *locker.Locker
|
||||
mountOptions string
|
||||
}
|
||||
|
||||
// Init returns a new AUFS driver.
|
||||
// An error is returned if AUFS is not supported.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
// Try to load the aufs kernel module
|
||||
if err := supportsAufs(); err != nil {
|
||||
return nil, fmt.Errorf("kernel does not support aufs: %w", graphdriver.ErrNotSupported)
|
||||
}
|
||||
|
||||
fsMagic, err := graphdriver.GetFSMagic(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
|
||||
backingFs = fsName
|
||||
}
|
||||
|
||||
switch fsMagic {
|
||||
case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicEcryptfs:
|
||||
logrus.Errorf("AUFS is not supported over %s", backingFs)
|
||||
return nil, fmt.Errorf("aufs is not supported over %q: %w", backingFs, graphdriver.ErrIncompatibleFS)
|
||||
}
|
||||
|
||||
var mountOptions string
|
||||
for _, option := range options.DriverOptions {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "aufs.mountopt":
|
||||
mountOptions = val
|
||||
default:
|
||||
return nil, fmt.Errorf("option %s not supported", option)
|
||||
}
|
||||
}
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
"layers",
|
||||
}
|
||||
|
||||
a := &Driver{
|
||||
root: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
pathCache: make(map[string]string),
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicAufs)),
|
||||
locker: locker.New(),
|
||||
mountOptions: mountOptions,
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create the root aufs driver dir and return
|
||||
// if it already exists
|
||||
// If not populate the dir structure
|
||||
if err := idtools.MkdirAllAs(home, 0o700, rootUID, rootGID); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return a, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mountpk.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate the dir structure
|
||||
for _, p := range paths {
|
||||
if err := idtools.MkdirAllAs(path.Join(home, p), 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"module": "graphdriver",
|
||||
"driver": "aufs",
|
||||
})
|
||||
|
||||
for _, path := range []string{"mnt", "diff"} {
|
||||
p := filepath.Join(home, path)
|
||||
entries, err := os.ReadDir(p)
|
||||
if err != nil {
|
||||
logger.WithError(err).WithField("dir", p).Error("error reading dir entries")
|
||||
continue
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry.Name(), "-removing") {
|
||||
logger.WithField("dir", entry.Name()).Debug("Cleaning up stale layer dir")
|
||||
if err := system.EnsureRemoveAll(filepath.Join(p, entry.Name())); err != nil {
|
||||
logger.WithField("dir", entry.Name()).WithError(err).Error("Error removing stale layer dir")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.naiveDiff = graphdriver.NewNaiveDiffDriver(a, a)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Return a nil error if the kernel supports aufs
|
||||
// We cannot modprobe because inside dind modprobe fails
|
||||
// to run
|
||||
func supportsAufs() error {
|
||||
// We can try to modprobe aufs first before looking at
|
||||
// proc/filesystems for when aufs is supported
|
||||
exec.Command("modprobe", "aufs").Run()
|
||||
|
||||
if unshare.IsRootless() {
|
||||
return ErrAufsNested
|
||||
}
|
||||
|
||||
f, err := os.Open("/proc/filesystems")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
for s.Scan() {
|
||||
if strings.Contains(s.Text(), "aufs") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrAufsNotSupported
|
||||
}
|
||||
|
||||
func (a *Driver) rootPath() string {
|
||||
return a.root
|
||||
}
|
||||
|
||||
func (*Driver) String() string {
|
||||
return "aufs"
|
||||
}
|
||||
|
||||
// Status returns current information about the filesystem such as root directory, number of directories mounted, etc.
|
||||
func (a *Driver) Status() [][2]string {
|
||||
ids, _ := loadIds(path.Join(a.rootPath(), "layers"))
|
||||
return [][2]string{
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata not implemented
|
||||
func (a *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Exists returns true if the given id is registered with
|
||||
// this driver
|
||||
func (a *Driver) Exists(id string) bool {
|
||||
if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ListLayers() returns all of the layers known to the driver.
|
||||
func (a *Driver) ListLayers() ([]string, error) {
|
||||
diffsDir := filepath.Join(a.rootPath(), "diff")
|
||||
entries, err := os.ReadDir(diffsDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
results = append(results, entry.Name())
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (a *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (a *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
if opts == nil {
|
||||
opts = &graphdriver.CreateOpts{}
|
||||
}
|
||||
return graphdriver.NaiveCreateFromTemplate(a, id, template, templateIDMappings, parent, parentIDMappings, opts, readWrite)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (a *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return a.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create three folders for each id
|
||||
// mnt, layers, and diff
|
||||
func (a *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
if opts != nil && len(opts.StorageOpt) != 0 {
|
||||
return fmt.Errorf("--storage-opt is not supported for aufs")
|
||||
}
|
||||
|
||||
if err := a.createDirsFor(id, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the layers metadata
|
||||
f, err := os.Create(path.Join(a.rootPath(), "layers", id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if parent != "" {
|
||||
ids, err := getParentIDs(a.rootPath(), parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(f, parent); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range ids {
|
||||
if _, err := fmt.Fprintln(f, i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createDirsFor creates two directories for the given id.
|
||||
// mnt and diff
|
||||
func (a *Driver) createDirsFor(id, parent string) error {
|
||||
paths := []string{
|
||||
"mnt",
|
||||
"diff",
|
||||
}
|
||||
|
||||
// Directory permission is 0555.
|
||||
// The path of directories are <aufs_root_path>/mnt/<image_id>
|
||||
// and <aufs_root_path>/diff/<image_id>
|
||||
for _, p := range paths {
|
||||
rootPair := idtools.NewIDMappingsFromMaps(a.uidMaps, a.gidMaps).RootPair()
|
||||
rootPerms := defaultPerms
|
||||
if parent != "" {
|
||||
st, err := system.Stat(path.Join(a.rootPath(), p, parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootPerms = os.FileMode(st.Mode())
|
||||
rootPair.UID = int(st.UID())
|
||||
rootPair.GID = int(st.GID())
|
||||
}
|
||||
if err := idtools.MkdirAllAndChownNew(path.Join(a.rootPath(), p, id), rootPerms, rootPair); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove will unmount and remove the given id.
|
||||
func (a *Driver) Remove(id string) error {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
mountpoint, exists := a.pathCache[id]
|
||||
a.pathCacheLock.Unlock()
|
||||
if !exists {
|
||||
mountpoint = a.getMountpoint(id)
|
||||
}
|
||||
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"module": "graphdriver",
|
||||
"driver": "aufs",
|
||||
"layer": id,
|
||||
})
|
||||
|
||||
var retries int
|
||||
for {
|
||||
mounted, err := a.mounted(mountpoint)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !mounted {
|
||||
break
|
||||
}
|
||||
|
||||
err = a.unmount(mountpoint)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if err != unix.EBUSY {
|
||||
return fmt.Errorf("aufs: unmount error: %s: %w", mountpoint, err)
|
||||
}
|
||||
if retries >= 5 {
|
||||
return fmt.Errorf("aufs: unmount error after retries: %s: %w", mountpoint, err)
|
||||
}
|
||||
// If unmount returns EBUSY, it could be a transient error. Sleep and retry.
|
||||
retries++
|
||||
logger.Warnf("unmount failed due to EBUSY: retry count: %d", retries)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Remove the layers file for the id
|
||||
if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("removing layers dir for %s: %w", id, err)
|
||||
}
|
||||
|
||||
if err := atomicRemove(a.getDiffPath(id)); err != nil {
|
||||
return fmt.Errorf("could not remove diff path for id %s: %w", id, err)
|
||||
}
|
||||
|
||||
// Atomically remove each directory in turn by first moving it out of the
|
||||
// way (so that container runtime doesn't find it anymore) before doing removal of
|
||||
// the whole tree.
|
||||
if err := atomicRemove(mountpoint); err != nil {
|
||||
if errors.Is(err, unix.EBUSY) {
|
||||
logger.WithField("dir", mountpoint).WithError(err).Warn("error performing atomic remove due to EBUSY")
|
||||
}
|
||||
return fmt.Errorf("could not remove mountpoint for id %s: %w", id, err)
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
delete(a.pathCache, id)
|
||||
a.pathCacheLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func atomicRemove(source string) error {
|
||||
target := source + "-removing"
|
||||
|
||||
err := os.Rename(source, target)
|
||||
switch {
|
||||
case err == nil, os.IsNotExist(err):
|
||||
case os.IsExist(err):
|
||||
// Got error saying the target dir already exists, maybe the source doesn't exist due to a previous (failed) remove
|
||||
if _, e := os.Stat(source); !os.IsNotExist(e) {
|
||||
return fmt.Errorf("target rename dir '%s' exists but should not, this needs to be manually cleaned up: %w", target, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("preparing atomic delete: %w", err)
|
||||
}
|
||||
|
||||
return system.EnsureRemoveAll(target)
|
||||
}
|
||||
|
||||
// Get returns the rootfs path for the id.
|
||||
// This will mount the dir at its given path
|
||||
func (a *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
parents, err := a.getParentLayerPaths(id)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
a.pathCacheLock.Unlock()
|
||||
|
||||
if !exists {
|
||||
m = a.getDiffPath(id)
|
||||
if len(parents) > 0 {
|
||||
m = a.getMountpoint(id)
|
||||
}
|
||||
}
|
||||
if count := a.ctr.Increment(m); count > 1 {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// If a dir does not have a parent ( no layers )do not try to mount
|
||||
// just return the diff path to the data
|
||||
if len(parents) > 0 {
|
||||
if err := a.mount(id, m, parents, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
a.pathCacheLock.Lock()
|
||||
a.pathCache[id] = m
|
||||
a.pathCacheLock.Unlock()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Put unmounts and updates list of active mounts.
|
||||
func (a *Driver) Put(id string) error {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
if !exists {
|
||||
m = a.getMountpoint(id)
|
||||
a.pathCache[id] = m
|
||||
}
|
||||
a.pathCacheLock.Unlock()
|
||||
if count := a.ctr.Decrement(m); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := a.unmount(m)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to unmount %s aufs: %v", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For AUFS, it queries the mountpoint for this ID.
|
||||
func (a *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
a.locker.Lock(id)
|
||||
defer a.locker.Unlock(id)
|
||||
a.pathCacheLock.Lock()
|
||||
m, exists := a.pathCache[id]
|
||||
if !exists {
|
||||
m = a.getMountpoint(id)
|
||||
a.pathCache[id] = m
|
||||
}
|
||||
a.pathCacheLock.Unlock()
|
||||
return directory.Usage(m)
|
||||
}
|
||||
|
||||
// isParent returns if the passed in parent is the direct parent of the passed in layer
|
||||
func (a *Driver) isParent(id, parent string) bool {
|
||||
parents, _ := getParentIDs(a.rootPath(), id)
|
||||
if parent == "" && len(parents) > 0 {
|
||||
return false
|
||||
}
|
||||
return !(len(parents) > 0 && parent != parents[0])
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (a *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.Diff(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
// AUFS doesn't need the parent layer to produce a diff.
|
||||
return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
ExcludePatterns: []string{archive.WhiteoutMetaPrefix + "*", "!" + archive.WhiteoutOpaqueDir},
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
}
|
||||
|
||||
type fileGetNilCloser struct {
|
||||
storage.FileGetter
|
||||
}
|
||||
|
||||
func (f fileGetNilCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiffGetter returns a FileGetCloser that can read files from the directory that
|
||||
// contains files for the layer differences. Used for direct access for tar-split.
|
||||
func (a *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p := path.Join(a.rootPath(), "diff", id)
|
||||
return fileGetNilCloser{storage.NewPathFileGetter(p)}, nil
|
||||
}
|
||||
|
||||
func (a *Driver) applyDiff(id string, idMappings *idtools.IDMappings, diff io.Reader) error {
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
return chrootarchive.UntarUncompressed(diff, path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (a *Driver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.DiffSize(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
// AUFS doesn't need the parent layer to calculate the diff size.
|
||||
return directory.Size(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (a *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.ApplyDiff(id, parent, options)
|
||||
}
|
||||
|
||||
// AUFS doesn't need the parent id to apply the diff if it is the direct parent.
|
||||
if err = a.applyDiff(id, options.Mappings, options.Diff); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return directory.Size(path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (a *Driver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error) {
|
||||
if !a.isParent(id, parent) {
|
||||
return a.naiveDiff.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// AUFS doesn't have snapshots, so we need to get changes from all parent
|
||||
// layers.
|
||||
layers, err := a.getParentLayerPaths(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.Changes(layers, path.Join(a.rootPath(), "diff", id))
|
||||
}
|
||||
|
||||
func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
|
||||
parentIds, err := getParentIDs(a.rootPath(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers := make([]string, len(parentIds))
|
||||
|
||||
// Get the diff paths for all the parent ids
|
||||
for i, p := range parentIds {
|
||||
layers[i] = path.Join(a.rootPath(), "diff", p)
|
||||
}
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func (a *Driver) mount(id string, target string, layers []string, options graphdriver.MountOpts) error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
// If the id is mounted or we get an error return
|
||||
if mounted, err := a.mounted(target); err != nil || mounted {
|
||||
return err
|
||||
}
|
||||
|
||||
rw := a.getDiffPath(id)
|
||||
|
||||
if err := a.aufsMount(layers, rw, target, options); err != nil {
|
||||
return fmt.Errorf("creating aufs mount to %s: %w", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) unmount(mountPath string) error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
if mounted, err := a.mounted(mountPath); err != nil || !mounted {
|
||||
return err
|
||||
}
|
||||
if err := Unmount(mountPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Driver) mounted(mountpoint string) (bool, error) {
|
||||
return graphdriver.Mounted(graphdriver.FsMagicAufs, mountpoint)
|
||||
}
|
||||
|
||||
// Cleanup aufs and unmount all mountpoints
|
||||
func (a *Driver) Cleanup() error {
|
||||
var dirs []string
|
||||
if err := filepath.WalkDir(a.mntPath(), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
dirs = append(dirs, path)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, m := range dirs {
|
||||
if err := a.unmount(m); err != nil {
|
||||
logrus.Debugf("aufs error unmounting %s: %s", m, err)
|
||||
}
|
||||
}
|
||||
return mountpk.Unmount(a.root)
|
||||
}
|
||||
|
||||
func (a *Driver) aufsMount(ro []string, rw, target string, options graphdriver.MountOpts) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
Unmount(target)
|
||||
}
|
||||
}()
|
||||
|
||||
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||
// layers then these are remounted individually using append.
|
||||
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len(",dirperm1")
|
||||
}
|
||||
b := make([]byte, unix.Getpagesize()-len(options.MountLabel)-offset) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
index := 0
|
||||
for ; index < len(ro); index++ {
|
||||
layer := fmt.Sprintf(":%s=ro+wh", ro[index])
|
||||
if bp+len(layer) > len(b) {
|
||||
break
|
||||
}
|
||||
bp += copy(b[bp:], layer)
|
||||
}
|
||||
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
mountOptions := a.mountOptions
|
||||
if len(options.Options) > 0 {
|
||||
mountOptions = strings.Join(options.Options, ",")
|
||||
}
|
||||
if mountOptions != "" {
|
||||
opts += fmt.Sprintf(",%s", mountOptions)
|
||||
}
|
||||
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), options.MountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for ; index < len(ro); index++ {
|
||||
layer := fmt.Sprintf(":%s=ro+wh", ro[index])
|
||||
data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), options.MountLabel)
|
||||
if err = mount("none", target, "aufs", unix.MS_REMOUNT, data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := os.MkdirTemp("", "storage-aufs-base")
|
||||
if err != nil {
|
||||
logrus.Errorf("Checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
union, err := os.MkdirTemp("", "storage-aufs-union")
|
||||
if err != nil {
|
||||
logrus.Errorf("Checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
logrus.Errorf("Checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap updates ID mappings in a layer from matching the ones
|
||||
// specified by toContainer to those specified by toHost.
|
||||
func (a *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error {
|
||||
return fmt.Errorf("aufs doesn't support changing ID mappings")
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (a *Driver) SupportsShifting() bool {
|
||||
return false
|
||||
}
|
||||
64
vendor/github.com/containers/storage/drivers/aufs/dirs.go
generated
vendored
Normal file
64
vendor/github.com/containers/storage/drivers/aufs/dirs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Return all the directories
|
||||
func loadIds(root string) ([]string, error) {
|
||||
dirs, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []string{}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
out = append(out, d.Name())
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Read the layers file for the current id and return all the
|
||||
// layers represented by new lines in the file
|
||||
//
|
||||
// If there are no lines in the file then the id has no parent
|
||||
// and an empty slice is returned.
|
||||
func getParentIDs(root, id string) ([]string, error) {
|
||||
f, err := os.Open(path.Join(root, "layers", id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
out := []string{}
|
||||
s := bufio.NewScanner(f)
|
||||
|
||||
for s.Scan() {
|
||||
if t := s.Text(); t != "" {
|
||||
out = append(out, s.Text())
|
||||
}
|
||||
}
|
||||
return out, s.Err()
|
||||
}
|
||||
|
||||
func (a *Driver) getMountpoint(id string) string {
|
||||
return path.Join(a.mntPath(), id)
|
||||
}
|
||||
|
||||
func (a *Driver) mntPath() string {
|
||||
return path.Join(a.rootPath(), "mnt")
|
||||
}
|
||||
|
||||
func (a *Driver) getDiffPath(id string) string {
|
||||
return path.Join(a.diffPath(), id)
|
||||
}
|
||||
|
||||
func (a *Driver) diffPath() string {
|
||||
return path.Join(a.rootPath(), "diff")
|
||||
}
|
||||
22
vendor/github.com/containers/storage/drivers/aufs/mount.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/aufs/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package aufs
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Unmount the target specified.
|
||||
func Unmount(target string) error {
|
||||
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
||||
logrus.Warnf("Couldn't run auplink before unmount %s: %s", target, err)
|
||||
}
|
||||
if err := unix.Unmount(target, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/containers/storage/drivers/aufs/mount_linux.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/drivers/aufs/mount_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package aufs
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func mount(source string, target string, fstype string, flags uintptr, data string) error {
|
||||
return unix.Mount(source, target, fstype, flags, data)
|
||||
}
|
||||
695
vendor/github.com/containers/storage/drivers/btrfs/btrfs.go
generated
vendored
Normal file
695
vendor/github.com/containers/storage/drivers/btrfs/btrfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,695 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
|
||||
// keep struct field name compatible with btrfs-progs < 6.1.
|
||||
#define max_referenced max_rfer
|
||||
#include <btrfs/ioctl.h>
|
||||
#include <btrfs/ctree.h>
|
||||
|
||||
static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) {
|
||||
snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("btrfs", Init)
|
||||
}
|
||||
|
||||
type btrfsOptions struct {
|
||||
minSpace uint64
|
||||
size uint64
|
||||
}
|
||||
|
||||
// Init returns a new BTRFS driver.
|
||||
// An error is returned if BTRFS is not supported.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
fsMagic, err := graphdriver.GetFSMagic(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fsMagic != graphdriver.FsMagicBtrfs {
|
||||
return nil, fmt.Errorf("%q is not on a btrfs filesystem: %w", home, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(filepath.Join(home, "subvolumes"), 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opt, userDiskQuota, err := parseOptions(options.DriverOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := &Driver{
|
||||
home: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
options: opt,
|
||||
}
|
||||
|
||||
if userDiskQuota {
|
||||
if err := driver.enableQuota(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return graphdriver.NewNaiveDiffDriver(driver, graphdriver.NewNaiveLayerIDMapUpdater(driver)), nil
|
||||
}
|
||||
|
||||
func parseOptions(opt []string) (btrfsOptions, bool, error) {
|
||||
var options btrfsOptions
|
||||
userDiskQuota := false
|
||||
for _, option := range opt {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return options, userDiskQuota, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "btrfs.min_space":
|
||||
minSpace, err := units.RAMInBytes(val)
|
||||
if err != nil {
|
||||
return options, userDiskQuota, err
|
||||
}
|
||||
userDiskQuota = true
|
||||
options.minSpace = uint64(minSpace)
|
||||
case "btrfs.mountopt":
|
||||
return options, userDiskQuota, fmt.Errorf("btrfs driver does not support mount options")
|
||||
default:
|
||||
return options, userDiskQuota, fmt.Errorf("unknown option %s (%q)", key, option)
|
||||
}
|
||||
}
|
||||
return options, userDiskQuota, nil
|
||||
}
|
||||
|
||||
// Driver contains information about the filesystem mounted.
|
||||
type Driver struct {
|
||||
// root of the file system
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
options btrfsOptions
|
||||
quotaEnabled bool
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// String prints the name of the driver (btrfs).
|
||||
func (d *Driver) String() string {
|
||||
return "btrfs"
|
||||
}
|
||||
|
||||
// Status returns current driver information in a two dimensional string array.
|
||||
// Output contains "Build Version" and "Library Version" of the btrfs libraries used.
|
||||
// Version information can be used to check compatibility with your kernel.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
status := [][2]string{}
|
||||
if bv := btrfsBuildVersion(); bv != "-" {
|
||||
status = append(status, [2]string{"Build Version", bv})
|
||||
}
|
||||
if lv := btrfsLibVersion(); lv != -1 {
|
||||
status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// Metadata returns empty metadata for this driver.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Cleanup unmounts the home directory.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return mount.Unmount(d.home)
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func openDir(path string) (*C.DIR, error) {
|
||||
Cpath := C.CString(path)
|
||||
defer free(Cpath)
|
||||
|
||||
dir := C.opendir(Cpath)
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("can't open dir %s", path)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func closeDir(dir *C.DIR) {
|
||||
if dir != nil {
|
||||
C.closedir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFd(dir *C.DIR) uintptr {
|
||||
return uintptr(C.dirfd(dir))
|
||||
}
|
||||
|
||||
func subvolCreate(path, name string) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to create btrfs subvolume: %w", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolSnapshot(src, dest, name string) error {
|
||||
srcDir, err := openDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(srcDir)
|
||||
|
||||
destDir, err := openDir(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(destDir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args_v2
|
||||
args.fd = C.__s64(getDirFd(srcDir))
|
||||
|
||||
cs := C.CString(name)
|
||||
C.set_name_btrfs_ioctl_vol_args_v2(&args, cs)
|
||||
C.free(unsafe.Pointer(cs))
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to create btrfs snapshot: %w", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSubvolume(p string) (bool, error) {
|
||||
var bufStat unix.Stat_t
|
||||
if err := unix.Lstat(p, &bufStat); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// return true if it is a btrfs subvolume
|
||||
return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
|
||||
}
|
||||
|
||||
func subvolDelete(dirpath, name string, quotaEnabled bool) error {
|
||||
dir, err := openDir(dirpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
fullPath := path.Join(dirpath, name)
|
||||
|
||||
var args C.struct_btrfs_ioctl_vol_args
|
||||
|
||||
// walk the btrfs subvolumes
|
||||
walkSubvolumes := func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) && p != fullPath {
|
||||
// missing most likely because the path was a subvolume that got removed in the previous iteration
|
||||
// since it's gone anyway, we don't care
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("walking subvolumes: %w", err)
|
||||
}
|
||||
// we want to check children only so skip itself
|
||||
// it will be removed after the filepath walk anyways
|
||||
if d.IsDir() && p != fullPath {
|
||||
sv, err := isSubvolume(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to test if %s is a btrfs subvolume: %w", p, err)
|
||||
}
|
||||
if sv {
|
||||
if err := subvolDelete(path.Dir(p), d.Name(), quotaEnabled); err != nil {
|
||||
return fmt.Errorf("failed to destroy btrfs child subvolume (%s) of parent (%s): %w", p, dirpath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := filepath.WalkDir(path.Join(dirpath, name), walkSubvolumes); err != nil {
|
||||
return fmt.Errorf("recursively walking subvolumes for %s failed: %w", dirpath, err)
|
||||
}
|
||||
|
||||
if quotaEnabled {
|
||||
if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
|
||||
var args C.struct_btrfs_ioctl_qgroup_create_args
|
||||
args.qgroupid = C.__u64(qgroupid)
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// all subvolumes have been removed
|
||||
// now remove the one originally passed in
|
||||
for i, c := range []byte(name) {
|
||||
args.name[i] = C.char(c)
|
||||
}
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to destroy btrfs snapshot %s for %s: %w", dirpath, name, errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) updateQuotaStatus() {
|
||||
d.once.Do(func() {
|
||||
if !d.quotaEnabled {
|
||||
// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
|
||||
if err := qgroupStatus(d.home); err != nil {
|
||||
// quota is still not enabled
|
||||
return
|
||||
}
|
||||
d.quotaEnabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Driver) enableQuota() error {
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if d.quotaEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir, err := openDir(d.home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_quota_ctl_args
|
||||
args.cmd = C.BTRFS_QUOTA_CTL_ENABLE
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to enable btrfs quota for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
d.quotaEnabled = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) subvolRescanQuota() error {
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if !d.quotaEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
dir, err := openDir(d.home)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_quota_rescan_args
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to rescan btrfs quota for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolLimitQgroup(path string, size uint64) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_qgroup_limit_args
|
||||
args.lim.max_rfer = C.__u64(size)
|
||||
args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to limit qgroup for %s: %w", dir, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// qgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
|
||||
// with search key of BTRFS_QGROUP_STATUS_KEY.
|
||||
// In case qgroup is enabled, the returned key type will match BTRFS_QGROUP_STATUS_KEY.
|
||||
// For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
|
||||
func qgroupStatus(path string) error {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_search_args
|
||||
args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
|
||||
args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
|
||||
args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
|
||||
args.key.max_objectid = C.__u64(math.MaxUint64)
|
||||
args.key.max_offset = C.__u64(math.MaxUint64)
|
||||
args.key.max_transid = C.__u64(math.MaxUint64)
|
||||
args.key.nr_items = 4096
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to search qgroup for %s: %w", path, errno)
|
||||
}
|
||||
sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
|
||||
if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
|
||||
return fmt.Errorf("invalid qgroup search header type for %s: %v", path, sh._type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func subvolLookupQgroup(path string) (uint64, error) {
|
||||
dir, err := openDir(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var args C.struct_btrfs_ioctl_ino_lookup_args
|
||||
args.objectid = C.BTRFS_FIRST_FREE_OBJECTID
|
||||
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP,
|
||||
uintptr(unsafe.Pointer(&args)))
|
||||
if errno != 0 {
|
||||
return 0, fmt.Errorf("failed to lookup qgroup for %s: %w", dir, errno)
|
||||
}
|
||||
if args.treeid == 0 {
|
||||
return 0, fmt.Errorf("invalid qgroup id for %s: 0", dir)
|
||||
}
|
||||
|
||||
return uint64(args.treeid), nil
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDir() string {
|
||||
return path.Join(d.home, "subvolumes")
|
||||
}
|
||||
|
||||
func (d *Driver) subvolumesDirID(id string) string {
|
||||
return path.Join(d.subvolumesDir(), id)
|
||||
}
|
||||
|
||||
func (d *Driver) quotasDir() string {
|
||||
return path.Join(d.home, "quotas")
|
||||
}
|
||||
|
||||
func (d *Driver) quotasDirID(id string) string {
|
||||
return path.Join(d.quotasDir(), id)
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create the filesystem with given id.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
quotas := d.quotasDir()
|
||||
subvolumes := d.subvolumesDir()
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(subvolumes, 0o700, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if parent == "" {
|
||||
if err := subvolCreate(subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(path.Join(subvolumes, id), defaultPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
parentDir := d.subvolumesDirID(parent)
|
||||
st, err := os.Stat(parentDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !st.IsDir() {
|
||||
return fmt.Errorf("%s: not a directory", parentDir)
|
||||
}
|
||||
if err := subvolSnapshot(parentDir, subvolumes, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
if _, ok := storageOpt["size"]; ok {
|
||||
driver := &Driver{}
|
||||
if err := d.parseStorageOpt(storageOpt, driver); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := idtools.MkdirAllAs(quotas, 0o700, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a remapped root (user namespaces enabled), change the created snapshot
|
||||
// dir ownership to match
|
||||
if rootUID != 0 || rootGID != 0 {
|
||||
if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mountLabel := ""
|
||||
if opts != nil {
|
||||
mountLabel = opts.MountLabel
|
||||
}
|
||||
|
||||
return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
|
||||
}
|
||||
|
||||
// Parse btrfs storage options
|
||||
func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
|
||||
// Read size to change the subvolume disk quota per container
|
||||
for key, val := range storageOpt {
|
||||
key := strings.ToLower(key)
|
||||
switch key {
|
||||
case "size":
|
||||
size, err := units.RAMInBytes(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
driver.options.size = uint64(size)
|
||||
default:
|
||||
return fmt.Errorf("unknown option %s (%q)", key, storageOpt)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set btrfs storage size
|
||||
func (d *Driver) setStorageSize(dir string, driver *Driver) error {
|
||||
if driver.options.size <= 0 {
|
||||
return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
|
||||
}
|
||||
if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
|
||||
return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
|
||||
}
|
||||
|
||||
if err := d.enableQuota(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the filesystem with given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
dir := d.subvolumesDirID(id)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
quotasDir := d.quotasDirID(id)
|
||||
if _, err := os.Stat(quotasDir); err == nil {
|
||||
if err := os.Remove(quotasDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call updateQuotaStatus() to invoke status update
|
||||
d.updateQuotaStatus()
|
||||
|
||||
if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
|
||||
if d.quotaEnabled {
|
||||
return err
|
||||
}
|
||||
// If quota is not enabled, fallback to rmdir syscall to delete subvolumes.
|
||||
// This would allow unprivileged user to delete their owned subvolumes
|
||||
// in kernel >= 4.18 without user_subvol_rm_alowed mount option.
|
||||
}
|
||||
if err := system.EnsureRemoveAll(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.subvolRescanQuota(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the requested filesystem id.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
dir := d.subvolumesDirID(id)
|
||||
st, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, opt := range options.Options {
|
||||
if opt == "ro" {
|
||||
// ignore "ro" option
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("btrfs driver does not support mount options")
|
||||
}
|
||||
if !st.IsDir() {
|
||||
return "", fmt.Errorf("%s: not a directory", dir)
|
||||
}
|
||||
|
||||
if quota, err := os.ReadFile(d.quotasDirID(id)); err == nil {
|
||||
if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace {
|
||||
if err := d.enableQuota(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := subvolLimitQgroup(dir, size); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Put is not implemented for BTRFS as there is no cleanup required for the id.
|
||||
func (d *Driver) Put(id string) error {
|
||||
// Get() creates no runtime resources (like e.g. mounts)
|
||||
// so this doesn't need to do anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For BTRFS, it queries the subvolumes path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.subvolumesDirID(id))
|
||||
}
|
||||
|
||||
// Exists checks if the id exists in the filesystem.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
dir := d.subvolumesDirID(id)
|
||||
_, err := os.Stat(dir)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// List all of the layers known to the driver.
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
entries, err := os.ReadDir(d.subvolumesDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
results = append(results, entry.Name())
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
4
vendor/github.com/containers/storage/drivers/btrfs/dummy_unsupported.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/drivers/btrfs/dummy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//go:build !linux || !cgo
|
||||
// +build !linux !cgo
|
||||
|
||||
package btrfs
|
||||
27
vendor/github.com/containers/storage/drivers/btrfs/version.go
generated
vendored
Normal file
27
vendor/github.com/containers/storage/drivers/btrfs/version.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//go:build linux && !btrfs_noversion && cgo
|
||||
// +build linux,!btrfs_noversion,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
#include <btrfs/version.h>
|
||||
|
||||
// around version 3.16, they did not define lib version yet
|
||||
#ifndef BTRFS_LIB_VERSION
|
||||
#define BTRFS_LIB_VERSION -1
|
||||
#endif
|
||||
|
||||
// upstream had removed it, but now it will be coming back
|
||||
#ifndef BTRFS_BUILD_VERSION
|
||||
#define BTRFS_BUILD_VERSION "-"
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return string(C.BTRFS_BUILD_VERSION)
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return int(C.BTRFS_LIB_VERSION)
|
||||
}
|
||||
15
vendor/github.com/containers/storage/drivers/btrfs/version_none.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/drivers/btrfs/version_none.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//go:build linux && btrfs_noversion && cgo
|
||||
// +build linux,btrfs_noversion,cgo
|
||||
|
||||
package btrfs
|
||||
|
||||
// TODO(vbatts) remove this work-around once supported linux distros are on
|
||||
// btrfs utilities of >= 3.16.1
|
||||
|
||||
func btrfsBuildVersion() string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
func btrfsLibVersion() int {
|
||||
return -1
|
||||
}
|
||||
135
vendor/github.com/containers/storage/drivers/chown.go
generated
vendored
Normal file
135
vendor/github.com/containers/storage/drivers/chown.go
generated
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/opencontainers/selinux/pkg/pwalk"
|
||||
)
|
||||
|
||||
const (
|
||||
chownByMapsCmd = "storage-chown-by-maps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register(chownByMapsCmd, chownByMapsMain)
|
||||
}
|
||||
|
||||
func chownByMapsMain() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "requires mapping configuration on stdin and directory path")
|
||||
os.Exit(1)
|
||||
}
|
||||
// Read and decode our configuration.
|
||||
discreteMaps := [4][]idtools.IDMap{}
|
||||
config := bytes.Buffer{}
|
||||
if _, err := config.ReadFrom(os.Stdin); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error reading configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := json.Unmarshal(config.Bytes(), &discreteMaps); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error decoding configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Try to chroot. This may not be possible, and on some systems that
|
||||
// means we just Chdir() to the directory, so from here on we should be
|
||||
// using relative paths.
|
||||
if err := chrootOrChdir(os.Args[1]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error chrooting to %q: %v", os.Args[1], err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Build the mapping objects.
|
||||
toContainer := idtools.NewIDMappingsFromMaps(discreteMaps[0], discreteMaps[1])
|
||||
if len(toContainer.UIDs()) == 0 && len(toContainer.GIDs()) == 0 {
|
||||
toContainer = nil
|
||||
}
|
||||
toHost := idtools.NewIDMappingsFromMaps(discreteMaps[2], discreteMaps[3])
|
||||
if len(toHost.UIDs()) == 0 && len(toHost.GIDs()) == 0 {
|
||||
toHost = nil
|
||||
}
|
||||
|
||||
chowner := newLChowner()
|
||||
|
||||
chown := func(path string, info os.FileInfo, _ error) error {
|
||||
if path == "." {
|
||||
return nil
|
||||
}
|
||||
return chowner.LChown(path, info, toHost, toContainer)
|
||||
}
|
||||
if err := pwalk.Walk(".", chown); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error during chown: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// ChownPathByMaps walks the filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs.
|
||||
func ChownPathByMaps(path string, toContainer, toHost *idtools.IDMappings) error {
|
||||
if toContainer == nil {
|
||||
toContainer = &idtools.IDMappings{}
|
||||
}
|
||||
if toHost == nil {
|
||||
toHost = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
config, err := json.Marshal([4][]idtools.IDMap{toContainer.UIDs(), toContainer.GIDs(), toHost.UIDs(), toHost.GIDs()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := reexec.Command(chownByMapsCmd, path)
|
||||
cmd.Stdin = bytes.NewReader(config)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 && err != nil {
|
||||
return fmt.Errorf("%s: %w", string(output), err)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(output) > 0 {
|
||||
return errors.New(string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type naiveLayerIDMapUpdater struct {
|
||||
ProtoDriver
|
||||
}
|
||||
|
||||
// NewNaiveLayerIDMapUpdater wraps the ProtoDriver in a LayerIDMapUpdater that
|
||||
// uses ChownPathByMaps to update the ownerships in a layer's filesystem tree.
|
||||
func NewNaiveLayerIDMapUpdater(driver ProtoDriver) LayerIDMapUpdater {
|
||||
return &naiveLayerIDMapUpdater{ProtoDriver: driver}
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap walks the layer's filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs.
|
||||
func (n *naiveLayerIDMapUpdater) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) (retErr error) {
|
||||
driver := n.ProtoDriver
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer driverPut(driver, id, &retErr)
|
||||
|
||||
return ChownPathByMaps(layerFs, toContainer, toHost)
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (n *naiveLayerIDMapUpdater) SupportsShifting() bool {
|
||||
return false
|
||||
}
|
||||
109
vendor/github.com/containers/storage/drivers/chown_darwin.go
generated
vendored
Normal file
109
vendor/github.com/containers/storage/drivers/chown_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
)
|
||||
|
||||
type inode struct {
|
||||
Dev uint64
|
||||
Ino uint64
|
||||
}
|
||||
|
||||
type platformChowner struct {
|
||||
mutex sync.Mutex
|
||||
inodes map[inode]bool
|
||||
}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{
|
||||
inodes: make(map[inode]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
st, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := inode{
|
||||
Dev: uint64(st.Dev),
|
||||
Ino: uint64(st.Ino),
|
||||
}
|
||||
c.mutex.Lock()
|
||||
_, found := c.inodes[i]
|
||||
if !found {
|
||||
c.inodes[i] = true
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
|
||||
if found {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map an on-disk UID/GID pair from host to container
|
||||
// using the first map, then back to the host using the
|
||||
// second map. Skip that first step if they're 0, to
|
||||
// compensate for cases where a parent layer should
|
||||
// have had a mapped value, but didn't.
|
||||
uid, gid := int(st.Uid), int(st.Gid)
|
||||
if toContainer != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedUID, mappedGID, err := toContainer.ToContainer(pair)
|
||||
if err != nil {
|
||||
if (uid != 0) || (gid != 0) {
|
||||
return fmt.Errorf("mapping host ID pair %#v for %q to container: %w", pair, path, err)
|
||||
}
|
||||
mappedUID, mappedGID = uid, gid
|
||||
}
|
||||
uid, gid = mappedUID, mappedGID
|
||||
}
|
||||
if toHost != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedPair, err := toHost.ToHostOverflow(pair)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping container ID pair %#v for %q to host: %w", pair, path, err)
|
||||
}
|
||||
uid, gid = mappedPair.UID, mappedPair.GID
|
||||
}
|
||||
if uid != int(st.Uid) || gid != int(st.Gid) {
|
||||
capability, err := system.Lgetxattr(path, "security.capability")
|
||||
if err != nil && !errors.Is(err, system.EOPNOTSUPP) && err != system.ErrNotSupportedPlatform {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
|
||||
// Make the change.
|
||||
if err := system.Lchown(path, uid, gid); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
// Restore the SUID and SGID bits if they were originally set.
|
||||
if (info.Mode()&os.ModeSymlink == 0) && info.Mode()&(os.ModeSetuid|os.ModeSetgid) != 0 {
|
||||
if err := system.Chmod(path, info.Mode()); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
if capability != nil {
|
||||
if err := system.Lsetxattr(path, "security.capability", capability, 0); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
127
vendor/github.com/containers/storage/drivers/chown_unix.go
generated
vendored
Normal file
127
vendor/github.com/containers/storage/drivers/chown_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//go:build !windows && !darwin
|
||||
// +build !windows,!darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
)
|
||||
|
||||
type inode struct {
|
||||
Dev uint64
|
||||
Ino uint64
|
||||
}
|
||||
|
||||
type platformChowner struct {
|
||||
mutex sync.Mutex
|
||||
inodes map[inode]string
|
||||
}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{
|
||||
inodes: make(map[inode]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
st, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
i := inode{
|
||||
Dev: uint64(st.Dev),
|
||||
Ino: uint64(st.Ino),
|
||||
}
|
||||
|
||||
c.mutex.Lock()
|
||||
|
||||
oldTarget, found := c.inodes[i]
|
||||
if !found {
|
||||
c.inodes[i] = path
|
||||
}
|
||||
|
||||
// If we are dealing with a file with multiple links then keep the lock until the file is
|
||||
// chowned to avoid a race where we link to the old version if the file is copied up.
|
||||
if found || st.Nlink > 1 {
|
||||
defer c.mutex.Unlock()
|
||||
} else {
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
if found {
|
||||
// If the dev/inode was already chowned then create a link to the old target instead
|
||||
// of chowning it again. This is necessary when the underlying file system breaks
|
||||
// inodes on copy-up (as it is with overlay with index=off) to maintain the original
|
||||
// link and correct file ownership.
|
||||
|
||||
// The target already exists so remove it before creating the link to the new target.
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Link(oldTarget, path)
|
||||
}
|
||||
|
||||
// Map an on-disk UID/GID pair from host to container
|
||||
// using the first map, then back to the host using the
|
||||
// second map. Skip that first step if they're 0, to
|
||||
// compensate for cases where a parent layer should
|
||||
// have had a mapped value, but didn't.
|
||||
uid, gid := int(st.Uid), int(st.Gid)
|
||||
if toContainer != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedUID, mappedGID, err := toContainer.ToContainer(pair)
|
||||
if err != nil {
|
||||
if (uid != 0) || (gid != 0) {
|
||||
return fmt.Errorf("mapping host ID pair %#v for %q to container: %w", pair, path, err)
|
||||
}
|
||||
mappedUID, mappedGID = uid, gid
|
||||
}
|
||||
uid, gid = mappedUID, mappedGID
|
||||
}
|
||||
if toHost != nil {
|
||||
pair := idtools.IDPair{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
mappedPair, err := toHost.ToHostOverflow(pair)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mapping container ID pair %#v for %q to host: %w", pair, path, err)
|
||||
}
|
||||
uid, gid = mappedPair.UID, mappedPair.GID
|
||||
}
|
||||
if uid != int(st.Uid) || gid != int(st.Gid) {
|
||||
cap, err := system.Lgetxattr(path, "security.capability")
|
||||
if err != nil && !errors.Is(err, system.EOPNOTSUPP) && !errors.Is(err, system.EOVERFLOW) && err != system.ErrNotSupportedPlatform {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
|
||||
// Make the change.
|
||||
if err := system.Lchown(path, uid, gid); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
// Restore the SUID and SGID bits if they were originally set.
|
||||
if (info.Mode()&os.ModeSymlink == 0) && info.Mode()&(os.ModeSetuid|os.ModeSetgid) != 0 {
|
||||
if err := system.Chmod(path, info.Mode()); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
if cap != nil {
|
||||
if err := system.Lsetxattr(path, "security.capability", cap, 0); err != nil {
|
||||
return fmt.Errorf("%s: %w", os.Args[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
21
vendor/github.com/containers/storage/drivers/chown_windows.go
generated
vendored
Normal file
21
vendor/github.com/containers/storage/drivers/chown_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
)
|
||||
|
||||
type platformChowner struct{}
|
||||
|
||||
func newLChowner() *platformChowner {
|
||||
return &platformChowner{}
|
||||
}
|
||||
|
||||
func (c *platformChowner) LChown(path string, info os.FileInfo, toHost, toContainer *idtools.IDMappings) error {
|
||||
return &os.PathError{"lchown", path, syscall.EWINDOWS}
|
||||
}
|
||||
22
vendor/github.com/containers/storage/drivers/chroot_unix.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/chroot_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux || darwin || freebsd || solaris
|
||||
// +build linux darwin freebsd solaris
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// chrootOrChdir() is either a chdir() to the specified path, or a chroot() to the
|
||||
// specified path followed by chdir() to the new root directory
|
||||
func chrootOrChdir(path string) error {
|
||||
if err := syscall.Chroot(path); err != nil {
|
||||
return fmt.Errorf("chrooting to %q: %w", path, err)
|
||||
}
|
||||
if err := syscall.Chdir(string(os.PathSeparator)); err != nil {
|
||||
return fmt.Errorf("changing to %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
15
vendor/github.com/containers/storage/drivers/chroot_windows.go
generated
vendored
Normal file
15
vendor/github.com/containers/storage/drivers/chroot_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// chrootOrChdir() is either a chdir() to the specified path, or a chroot() to the
|
||||
// specified path followed by chdir() to the new root directory
|
||||
func chrootOrChdir(path string) error {
|
||||
if err := syscall.Chdir(path); err != nil {
|
||||
return fmt.Errorf("changing to %q: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
307
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
Normal file
307
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
package copy
|
||||
|
||||
/*
|
||||
#include <linux/fs.h>
|
||||
|
||||
#ifndef FICLONE
|
||||
#define FICLONE _IOW(0x94, 9, int)
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/pools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Mode indicates whether to use hardlink or copy content
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Content creates a new file, and copies the content of the file
|
||||
Content Mode = iota
|
||||
// Hardlink creates a new hardlink to the existing file
|
||||
Hardlink
|
||||
)
|
||||
|
||||
// CopyRegularToFile copies the content of a file to another
|
||||
func CopyRegularToFile(srcPath string, dstFile *os.File, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { // nolint: revive,golint
|
||||
srcFile, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
if *copyWithFileClone {
|
||||
_, _, err = unix.Syscall(unix.SYS_IOCTL, dstFile.Fd(), C.FICLONE, srcFile.Fd())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
*copyWithFileClone = false
|
||||
if err == unix.EXDEV {
|
||||
*copyWithFileRange = false
|
||||
}
|
||||
}
|
||||
if *copyWithFileRange {
|
||||
err = doCopyWithFileRange(srcFile, dstFile, fileinfo)
|
||||
// Trying the file_clone may not have caught the exdev case
|
||||
// as the ioctl may not have been available (therefore EINVAL)
|
||||
if err == unix.EXDEV || err == unix.ENOSYS {
|
||||
*copyWithFileRange = false
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return legacyCopy(srcFile, dstFile)
|
||||
}
|
||||
|
||||
// CopyRegular copies the content of a file to another
|
||||
func CopyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { // nolint: revive,golint
|
||||
// If the destination file already exists, we shouldn't blow it away
|
||||
dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileinfo.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstFile.Close()
|
||||
|
||||
return CopyRegularToFile(srcPath, dstFile, fileinfo, copyWithFileRange, copyWithFileClone)
|
||||
}
|
||||
|
||||
func doCopyWithFileRange(srcFile, dstFile *os.File, fileinfo os.FileInfo) error {
|
||||
amountLeftToCopy := fileinfo.Size()
|
||||
|
||||
for amountLeftToCopy > 0 {
|
||||
n, err := unix.CopyFileRange(int(srcFile.Fd()), nil, int(dstFile.Fd()), nil, int(amountLeftToCopy), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amountLeftToCopy = amountLeftToCopy - int64(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func legacyCopy(srcFile io.Reader, dstFile io.Writer) error {
|
||||
_, err := pools.Copy(dstFile, srcFile)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXattr(srcPath, dstPath, attr string) error {
|
||||
data, err := system.Lgetxattr(srcPath, attr)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
if data != nil {
|
||||
if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fileID struct {
|
||||
dev uint64
|
||||
ino uint64
|
||||
}
|
||||
|
||||
type dirMtimeInfo struct {
|
||||
dstPath *string
|
||||
stat *syscall.Stat_t
|
||||
}
|
||||
|
||||
// DirCopy copies or hardlinks the contents of one directory to another,
|
||||
// properly handling xattrs, and soft links
|
||||
//
|
||||
// Copying xattrs can be opted out of by passing false for copyXattrs.
|
||||
func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
|
||||
copyWithFileRange := true
|
||||
copyWithFileClone := true
|
||||
|
||||
// This is a map of source file inodes to dst file paths
|
||||
copiedFiles := make(map[fileID]string)
|
||||
|
||||
dirsToSetMtimes := list.New()
|
||||
err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
relPath, err := filepath.Rel(srcDir, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dstDir, relPath)
|
||||
stat, ok := f.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get raw syscall.Stat_t data for %s", srcPath)
|
||||
}
|
||||
|
||||
isHardlink := false
|
||||
|
||||
switch mode := f.Mode(); {
|
||||
case mode.IsRegular():
|
||||
id := fileID{dev: uint64(stat.Dev), ino: stat.Ino}
|
||||
if copyMode == Hardlink {
|
||||
isHardlink = true
|
||||
if err2 := os.Link(srcPath, dstPath); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
} else if hardLinkDstPath, ok := copiedFiles[id]; ok {
|
||||
if err2 := os.Link(hardLinkDstPath, dstPath); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
} else {
|
||||
if err2 := CopyRegular(srcPath, dstPath, f, ©WithFileRange, ©WithFileClone); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
copiedFiles[id] = dstPath
|
||||
}
|
||||
|
||||
case mode.IsDir():
|
||||
if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeSymlink != 0:
|
||||
link, err := os.Readlink(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Symlink(link, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeNamedPipe != 0:
|
||||
if err := unix.Mkfifo(dstPath, stat.Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case mode&os.ModeSocket != 0:
|
||||
s, err := net.Listen("unix", dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Close()
|
||||
|
||||
case mode&os.ModeDevice != 0:
|
||||
if unshare.IsRootless() {
|
||||
// cannot create a device if running in user namespace
|
||||
return nil
|
||||
}
|
||||
if err := unix.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown file type with mode %v for %s", mode, srcPath)
|
||||
}
|
||||
|
||||
// Everything below is copying metadata from src to dst. All this metadata
|
||||
// already shares an inode for hardlinks.
|
||||
if isHardlink {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := idtools.SafeLchown(dstPath, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if copyXattrs {
|
||||
if err := doCopyXattrs(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
isSymlink := f.Mode()&os.ModeSymlink != 0
|
||||
|
||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||
// must happen after chown, as that can modify the file mode
|
||||
if !isSymlink {
|
||||
if err := os.Chmod(dstPath, f.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// system.Chtimes doesn't support a NOFOLLOW flag atm
|
||||
// nolint: unconvert
|
||||
if f.IsDir() {
|
||||
dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat})
|
||||
} else if !isSymlink {
|
||||
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
|
||||
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ts := []syscall.Timespec{stat.Atim, stat.Mtim}
|
||||
if err := system.LUtimesNano(dstPath, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() {
|
||||
mtimeInfo := e.Value.(*dirMtimeInfo)
|
||||
ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim}
|
||||
if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCopyXattrs(srcPath, dstPath string) error {
|
||||
if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xattrs, err := system.Llistxattr(srcPath)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, key := range xattrs {
|
||||
if strings.HasPrefix(key, "user.") {
|
||||
if err := copyXattr(srcPath, dstPath, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if unshare.IsRootless() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need to copy this attribute if it appears in an overlay upper layer, as
|
||||
// this function is used to copy those. It is set by overlay if a directory
|
||||
// is removed and then re-created and should not inherit anything from the
|
||||
// same dir in the lower dir.
|
||||
return copyXattr(srcPath, dstPath, "trusted.overlay.opaque")
|
||||
}
|
||||
41
vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go
generated
vendored
Normal file
41
vendor/github.com/containers/storage/drivers/copy/copy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//go:build !linux || !cgo
|
||||
// +build !linux !cgo
|
||||
|
||||
package copy //nolint: predeclared
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
)
|
||||
|
||||
// Mode indicates whether to use hardlink or copy content
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// Content creates a new file, and copies the content of the file
|
||||
Content Mode = iota
|
||||
)
|
||||
|
||||
// DirCopy copies or hardlinks the contents of one directory to another,
|
||||
// properly handling soft links
|
||||
func DirCopy(srcDir, dstDir string, _ Mode, _ bool) error {
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir)
|
||||
}
|
||||
|
||||
// CopyRegularToFile copies the content of a file to another
|
||||
func CopyRegularToFile(srcPath string, dstFile *os.File, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { //nolint: revive,golint // "func name will be used as copy.CopyRegularToFile by other packages, and that stutters"
|
||||
f, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(dstFile, f)
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyRegular copies the content of a file to another
|
||||
func CopyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { //nolint:revive,golint // "func name will be used as copy.CopyRegular by other packages, and that stutters"
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, dstPath)
|
||||
}
|
||||
68
vendor/github.com/containers/storage/drivers/counter.go
generated
vendored
Normal file
68
vendor/github.com/containers/storage/drivers/counter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package graphdriver
|
||||
|
||||
import "sync"
|
||||
|
||||
type minfo struct {
|
||||
check bool
|
||||
count int
|
||||
}
|
||||
|
||||
// RefCounter is a generic counter for use by graphdriver Get/Put calls
|
||||
type RefCounter struct {
|
||||
counts map[string]*minfo
|
||||
mu sync.Mutex
|
||||
checker Checker
|
||||
}
|
||||
|
||||
// NewRefCounter returns a new RefCounter
|
||||
func NewRefCounter(c Checker) *RefCounter {
|
||||
return &RefCounter{
|
||||
checker: c,
|
||||
counts: make(map[string]*minfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Increment increases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Increment(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count++
|
||||
})
|
||||
}
|
||||
|
||||
// Decrement decreases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Decrement(path string) int {
|
||||
return c.incdec(path, func(minfo *minfo) {
|
||||
minfo.count--
|
||||
})
|
||||
}
|
||||
|
||||
func (c *RefCounter) incdec(path string, infoOp func(minfo *minfo)) int {
|
||||
c.mu.Lock()
|
||||
m := c.counts[path]
|
||||
if m == nil {
|
||||
m = &minfo{}
|
||||
c.counts[path] = m
|
||||
}
|
||||
// if we are checking this path for the first time check to make sure
|
||||
// if it was already mounted on the system and make sure we have a correct ref
|
||||
// count if it is mounted as it is in use.
|
||||
if !m.check {
|
||||
m.check = true
|
||||
if c.checker.IsMounted(path) {
|
||||
m.count++
|
||||
}
|
||||
} else if !c.checker.IsMounted(path) {
|
||||
// if the unmount was performed outside of this process (e.g. conmon cleanup)
|
||||
// the ref counter would lose track of it. Check if it is still mounted.
|
||||
m.count = 0
|
||||
}
|
||||
infoOp(m)
|
||||
count := m.count
|
||||
if count <= 0 {
|
||||
// If the mounted path has been decremented enough have no references,
|
||||
// then its entry can be removed.
|
||||
delete(c.counts, path)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return count
|
||||
}
|
||||
254
vendor/github.com/containers/storage/drivers/devmapper/device_setup.go
generated
vendored
Normal file
254
vendor/github.com/containers/storage/drivers/devmapper/device_setup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type directLVMConfig struct {
|
||||
Device string
|
||||
ThinpPercent uint64
|
||||
ThinpMetaPercent uint64
|
||||
AutoExtendPercent uint64
|
||||
AutoExtendThreshold uint64
|
||||
MetaDataSize string
|
||||
}
|
||||
|
||||
var (
|
||||
errThinpPercentMissing = errors.New("must set both `dm.thinp_percent` and `dm.thinp_metapercent` if either is specified")
|
||||
errThinpPercentTooBig = errors.New("combined `dm.thinp_percent` and `dm.thinp_metapercent` must not be greater than 100")
|
||||
errMissingSetupDevice = errors.New("must provide device path in `dm.directlvm_device` in order to configure direct-lvm")
|
||||
)
|
||||
|
||||
func validateLVMConfig(cfg directLVMConfig) error {
|
||||
if cfg.Device == "" {
|
||||
return errMissingSetupDevice
|
||||
}
|
||||
if (cfg.ThinpPercent > 0 && cfg.ThinpMetaPercent == 0) || cfg.ThinpMetaPercent > 0 && cfg.ThinpPercent == 0 {
|
||||
return errThinpPercentMissing
|
||||
}
|
||||
|
||||
if cfg.ThinpPercent+cfg.ThinpMetaPercent > 100 {
|
||||
return errThinpPercentTooBig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevAvailable(dev string) error {
|
||||
lvmScan, err := exec.LookPath("lvmdiskscan")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find lvmdiskscan: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(lvmScan).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
if !bytes.Contains(out, []byte(dev)) {
|
||||
return fmt.Errorf("%s is not available for use with devicemapper", dev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevInVG(dev string) error {
|
||||
pvDisplay, err := exec.LookPath("pvdisplay")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find pvdisplay: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(pvDisplay, dev).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(bytes.TrimSpace(out)))
|
||||
for scanner.Scan() {
|
||||
fields := strings.SplitAfter(strings.TrimSpace(scanner.Text()), "VG Name")
|
||||
if len(fields) > 1 {
|
||||
// got "VG Name" line"
|
||||
vg := strings.TrimSpace(fields[1])
|
||||
if len(vg) > 0 {
|
||||
return fmt.Errorf("%s is already part of a volume group %q: must remove this device from any volume group or provide a different device", dev, vg)
|
||||
}
|
||||
logrus.Error(fields)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDevHasFS(dev string) error {
|
||||
blkid, err := exec.LookPath("blkid")
|
||||
if err != nil {
|
||||
logrus.Debugf("could not find blkid %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := exec.Command(blkid, dev).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := bytes.Fields(out)
|
||||
for _, f := range fields {
|
||||
kv := bytes.Split(f, []byte{'='})
|
||||
if bytes.Equal(kv[0], []byte("TYPE")) {
|
||||
v := bytes.Trim(kv[1], "\"")
|
||||
if len(v) > 0 {
|
||||
return fmt.Errorf("%s has a filesystem already, use dm.directlvm_device_force=true if you want to wipe the device", dev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyBlockDevice(dev string, force bool) error {
|
||||
absPath, err := filepath.Abs(dev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get absolute path for %s: %s", dev, err)
|
||||
}
|
||||
realPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to canonicalise path for %s: %s", dev, err)
|
||||
}
|
||||
if err := checkDevAvailable(absPath); err != nil {
|
||||
logrus.Infof("block device '%s' not available, checking '%s'", absPath, realPath)
|
||||
if err := checkDevAvailable(realPath); err != nil {
|
||||
return fmt.Errorf("neither '%s' nor '%s' are in the output of lvmdiskscan, can't use device", absPath, realPath)
|
||||
}
|
||||
}
|
||||
if err := checkDevInVG(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if force {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := checkDevHasFS(realPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readLVMConfig(root string) (directLVMConfig, error) {
|
||||
var cfg directLVMConfig
|
||||
|
||||
p := filepath.Join(root, "setup-config.json")
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return cfg, nil
|
||||
}
|
||||
return cfg, fmt.Errorf("reading existing setup config: %w", err)
|
||||
}
|
||||
|
||||
// check if this is just an empty file, no need to produce a json error later if so
|
||||
if len(b) == 0 {
|
||||
return cfg, nil
|
||||
}
|
||||
if err := json.Unmarshal(b, &cfg); err != nil {
|
||||
return cfg, fmt.Errorf("unmarshaling previous device setup config: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func writeLVMConfig(root string, cfg directLVMConfig) error {
|
||||
p := filepath.Join(root, "setup-config.json")
|
||||
b, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling direct lvm config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(p, b, 0o600); err != nil {
|
||||
return fmt.Errorf("writing direct lvm config to file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupDirectLVM(cfg directLVMConfig) error {
|
||||
lvmProfileDir := "/etc/lvm/profile"
|
||||
binaries := []string{"pvcreate", "vgcreate", "lvcreate", "lvconvert", "lvchange", "thin_check"}
|
||||
|
||||
for _, bin := range binaries {
|
||||
if _, err := exec.LookPath(bin); err != nil {
|
||||
return fmt.Errorf("looking up command `"+bin+"` while setting up direct lvm: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := os.MkdirAll(lvmProfileDir, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating lvm profile directory: %w", err)
|
||||
}
|
||||
|
||||
if cfg.AutoExtendPercent == 0 {
|
||||
cfg.AutoExtendPercent = 20
|
||||
}
|
||||
|
||||
if cfg.AutoExtendThreshold == 0 {
|
||||
cfg.AutoExtendThreshold = 80
|
||||
}
|
||||
|
||||
if cfg.ThinpPercent == 0 {
|
||||
cfg.ThinpPercent = 95
|
||||
}
|
||||
if cfg.ThinpMetaPercent == 0 {
|
||||
cfg.ThinpMetaPercent = 1
|
||||
}
|
||||
if cfg.MetaDataSize == "" {
|
||||
cfg.MetaDataSize = "128k"
|
||||
}
|
||||
|
||||
out, err := exec.Command("pvcreate", "--metadatasize", cfg.MetaDataSize, "-f", cfg.Device).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("vgcreate", "storage", cfg.Device).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpool", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpPercent)).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
out, err = exec.Command("lvcreate", "--wipesignatures", "y", "-n", "thinpoolmeta", "storage", "--extents", fmt.Sprintf("%d%%VG", cfg.ThinpMetaPercent)).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvconvert", "-y", "--zero", "n", "-c", "512K", "--thinpool", "storage/thinpool", "--poolmetadata", "storage/thinpoolmeta").CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", string(out), err)
|
||||
}
|
||||
|
||||
profile := fmt.Sprintf("activation{\nthin_pool_autoextend_threshold=%d\nthin_pool_autoextend_percent=%d\n}", cfg.AutoExtendThreshold, cfg.AutoExtendPercent)
|
||||
err = os.WriteFile(lvmProfileDir+"/storage-thinpool.profile", []byte(profile), 0o600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing storage thinp autoextend profile: %w", err)
|
||||
}
|
||||
|
||||
out, err = exec.Command("lvchange", "--metadataprofile", "storage-thinpool", "storage/thinpool").CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
2888
vendor/github.com/containers/storage/drivers/devmapper/deviceset.go
generated
vendored
Normal file
2888
vendor/github.com/containers/storage/drivers/devmapper/deviceset.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
109
vendor/github.com/containers/storage/drivers/devmapper/devmapper_doc.go
generated
vendored
Normal file
109
vendor/github.com/containers/storage/drivers/devmapper/devmapper_doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
// Definition of struct dm_task and sub structures (from lvm2)
|
||||
//
|
||||
// struct dm_ioctl {
|
||||
// /*
|
||||
// * The version number is made up of three parts:
|
||||
// * major - no backward or forward compatibility,
|
||||
// * minor - only backwards compatible,
|
||||
// * patch - both backwards and forwards compatible.
|
||||
// *
|
||||
// * All clients of the ioctl interface should fill in the
|
||||
// * version number of the interface that they were
|
||||
// * compiled with.
|
||||
// *
|
||||
// * All recognized ioctl commands (ie. those that don't
|
||||
// * return -ENOTTY) fill out this field, even if the
|
||||
// * command failed.
|
||||
// */
|
||||
// uint32_t version[3]; /* in/out */
|
||||
// uint32_t data_size; /* total size of data passed in
|
||||
// * including this struct */
|
||||
|
||||
// uint32_t data_start; /* offset to start of data
|
||||
// * relative to start of this struct */
|
||||
|
||||
// uint32_t target_count; /* in/out */
|
||||
// int32_t open_count; /* out */
|
||||
// uint32_t flags; /* in/out */
|
||||
|
||||
// /*
|
||||
// * event_nr holds either the event number (input and output) or the
|
||||
// * udev cookie value (input only).
|
||||
// * The DM_DEV_WAIT ioctl takes an event number as input.
|
||||
// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
|
||||
// * use the field as a cookie to return in the DM_COOKIE
|
||||
// * variable with the uevents they issue.
|
||||
// * For output, the ioctls return the event number, not the cookie.
|
||||
// */
|
||||
// uint32_t event_nr; /* in/out */
|
||||
// uint32_t padding;
|
||||
|
||||
// uint64_t dev; /* in/out */
|
||||
|
||||
// char name[DM_NAME_LEN]; /* device name */
|
||||
// char uuid[DM_UUID_LEN]; /* unique identifier for
|
||||
// * the block device */
|
||||
// char data[7]; /* padding or data */
|
||||
// };
|
||||
|
||||
// struct target {
|
||||
// uint64_t start;
|
||||
// uint64_t length;
|
||||
// char *type;
|
||||
// char *params;
|
||||
|
||||
// struct target *next;
|
||||
// };
|
||||
|
||||
// typedef enum {
|
||||
// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
|
||||
// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
|
||||
// } dm_add_node_t;
|
||||
|
||||
// struct dm_task {
|
||||
// int type;
|
||||
// char *dev_name;
|
||||
// char *mangled_dev_name;
|
||||
|
||||
// struct target *head, *tail;
|
||||
|
||||
// int read_only;
|
||||
// uint32_t event_nr;
|
||||
// int major;
|
||||
// int minor;
|
||||
// int allow_default_major_fallback;
|
||||
// uid_t uid;
|
||||
// gid_t gid;
|
||||
// mode_t mode;
|
||||
// uint32_t read_ahead;
|
||||
// uint32_t read_ahead_flags;
|
||||
// union {
|
||||
// struct dm_ioctl *v4;
|
||||
// } dmi;
|
||||
// char *newname;
|
||||
// char *message;
|
||||
// char *geometry;
|
||||
// uint64_t sector;
|
||||
// int no_flush;
|
||||
// int no_open_count;
|
||||
// int skip_lockfs;
|
||||
// int query_inactive_table;
|
||||
// int suppress_identical_reload;
|
||||
// dm_add_node_t add_node;
|
||||
// uint64_t existing_table_size;
|
||||
// int cookie_set;
|
||||
// int new_uuid;
|
||||
// int secure_data;
|
||||
// int retry_remove;
|
||||
// int enable_checks;
|
||||
// int expected_errno;
|
||||
|
||||
// char *uuid;
|
||||
// char *mangled_uuid;
|
||||
// };
|
||||
//
|
||||
271
vendor/github.com/containers/storage/drivers/devmapper/driver.go
generated
vendored
Normal file
271
vendor/github.com/containers/storage/drivers/devmapper/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/devicemapper"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/locker"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("devicemapper", Init)
|
||||
}
|
||||
|
||||
// Driver contains the device set mounted and the home directory
|
||||
type Driver struct {
|
||||
*DeviceSet
|
||||
home string
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
locker *locker.Locker
|
||||
}
|
||||
|
||||
// Init creates a driver with the given home and the set of options.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true, options.DriverOptions, options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(home); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
DeviceSet: deviceSet,
|
||||
home: home,
|
||||
uidMaps: options.UIDMaps,
|
||||
gidMaps: options.GIDMaps,
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
|
||||
locker: locker.New(),
|
||||
}
|
||||
return graphdriver.NewNaiveDiffDriver(d, graphdriver.NewNaiveLayerIDMapUpdater(d)), nil
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "devicemapper"
|
||||
}
|
||||
|
||||
// Status returns the status about the driver in a printable format.
|
||||
// Information returned contains Pool Name, Data File, Metadata file, disk usage by
|
||||
// the data and metadata, etc.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
s := d.DeviceSet.Status()
|
||||
|
||||
status := [][2]string{
|
||||
{"Pool Name", s.PoolName},
|
||||
{"Pool Blocksize", units.HumanSize(float64(s.SectorSize))},
|
||||
{"Base Device Size", units.HumanSize(float64(s.BaseDeviceSize))},
|
||||
{"Backing Filesystem", s.BaseDeviceFS},
|
||||
{"Data file", s.DataFile},
|
||||
{"Metadata file", s.MetadataFile},
|
||||
{"Data Space Used", units.HumanSize(float64(s.Data.Used))},
|
||||
{"Data Space Total", units.HumanSize(float64(s.Data.Total))},
|
||||
{"Data Space Available", units.HumanSize(float64(s.Data.Available))},
|
||||
{"Metadata Space Used", units.HumanSize(float64(s.Metadata.Used))},
|
||||
{"Metadata Space Total", units.HumanSize(float64(s.Metadata.Total))},
|
||||
{"Metadata Space Available", units.HumanSize(float64(s.Metadata.Available))},
|
||||
{"Thin Pool Minimum Free Space", units.HumanSize(float64(s.MinFreeSpace))},
|
||||
{"Udev Sync Supported", fmt.Sprintf("%v", s.UdevSyncSupported)},
|
||||
{"Deferred Removal Enabled", fmt.Sprintf("%v", s.DeferredRemoveEnabled)},
|
||||
{"Deferred Deletion Enabled", fmt.Sprintf("%v", s.DeferredDeleteEnabled)},
|
||||
{"Deferred Deleted Device Count", fmt.Sprintf("%v", s.DeferredDeletedDeviceCount)},
|
||||
}
|
||||
if len(s.DataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Data loop file", s.DataLoopback})
|
||||
}
|
||||
if len(s.MetadataLoopback) > 0 {
|
||||
status = append(status, [2]string{"Metadata loop file", s.MetadataLoopback})
|
||||
}
|
||||
if vStr, err := devicemapper.GetLibraryVersion(); err == nil {
|
||||
status = append(status, [2]string{"Library Version", vStr})
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// Metadata returns a map of information about the device.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
m, err := d.DeviceSet.exportDeviceMetadata(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata := make(map[string]string)
|
||||
metadata["DeviceId"] = strconv.Itoa(m.deviceID)
|
||||
metadata["DeviceSize"] = strconv.FormatUint(m.deviceSize, 10)
|
||||
metadata["DeviceName"] = m.deviceName
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
// Cleanup unmounts a device.
|
||||
func (d *Driver) Cleanup() error {
|
||||
err := d.DeviceSet.Shutdown(d.home)
|
||||
|
||||
umountErr := mount.Unmount(d.home)
|
||||
// in case we have two errors, prefer the one from Shutdown()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return umountErr
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create adds a device with a given id and the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
if err := d.DeviceSet.AddDevice(id, parent, storageOpt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a device with a given id, unmounts the filesystem, and removes the mount point.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
if !d.DeviceSet.HasDevice(id) {
|
||||
// Consider removing a non-existing device a no-op
|
||||
// This is useful to be able to progress on container removal
|
||||
// if the underlying device has gone away due to earlier errors
|
||||
return nil
|
||||
}
|
||||
|
||||
// This assumes the device has been properly Get/Put:ed and thus is unmounted
|
||||
if err := d.DeviceSet.DeleteDevice(id, false); err != nil {
|
||||
return fmt.Errorf("failed to remove device %s: %v", id, err)
|
||||
}
|
||||
|
||||
// Most probably the mount point is already removed on Put()
|
||||
// (see DeviceSet.UnmountDevice()), but just in case it was not
|
||||
// let's try to remove it here as well, ignoring errors as
|
||||
// an older kernel can return EBUSY if e.g. the mount was leaked
|
||||
// to other mount namespaces. A failure to remove the container's
|
||||
// mount point is not important and should not be treated
|
||||
// as a failure to remove the container.
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
err := unix.Rmdir(mp)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.WithField("storage-driver", "devicemapper").Warnf("unable to remove mount point %q: %s", mp, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get mounts a device with given id into the root filesystem
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
rootFs := path.Join(mp, "rootfs")
|
||||
if count := d.ctr.Increment(mp); count > 1 {
|
||||
return rootFs, nil
|
||||
}
|
||||
|
||||
uid, gid, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create the target directories if they don't exist
|
||||
if err := idtools.MkdirAllAs(path.Join(d.home, "mnt"), 0o755, uid, gid); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
if err := idtools.MkdirAs(mp, 0o755, uid, gid); err != nil && !os.IsExist(err) {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Mount the device
|
||||
if err := d.DeviceSet.MountDevice(id, mp, options); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllAs(rootFs, defaultPerms, uid, gid); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
|
||||
idFile := path.Join(mp, "id")
|
||||
if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) {
|
||||
// Create an "id" file with the container/image id in it to help reconstruct this in case
|
||||
// of later problems
|
||||
if err := os.WriteFile(idFile, []byte(id), 0o600); err != nil {
|
||||
d.ctr.Decrement(mp)
|
||||
d.DeviceSet.UnmountDevice(id, mp)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return rootFs, nil
|
||||
}
|
||||
|
||||
// Put unmounts a device and removes it.
|
||||
func (d *Driver) Put(id string) error {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
mp := path.Join(d.home, "mnt", id)
|
||||
if count := d.ctr.Decrement(mp); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := d.DeviceSet.UnmountDevice(id, mp)
|
||||
if err != nil {
|
||||
logrus.Errorf("devmapper: Error unmounting device %s: %v", id, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For devmapper, it queries the mnt path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
d.locker.Lock(id)
|
||||
defer d.locker.Unlock(id)
|
||||
return directory.Usage(path.Join(d.home, "mnt", id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the device exists.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
return d.DeviceSet.HasDevice(id)
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/devmapper/jsoniter.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/devmapper/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
89
vendor/github.com/containers/storage/drivers/devmapper/mount.go
generated
vendored
Normal file
89
vendor/github.com/containers/storage/drivers/devmapper/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// FIXME: this is copy-pasted from the aufs driver.
|
||||
// It should be moved into the core.
|
||||
|
||||
// Mounted returns true if a mount point exists.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
var mntpointSt unix.Stat_t
|
||||
if err := unix.Stat(mountpoint, &mntpointSt); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
var parentSt unix.Stat_t
|
||||
if err := unix.Stat(filepath.Join(mountpoint, ".."), &parentSt); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return mntpointSt.Dev != parentSt.Dev, nil
|
||||
}
|
||||
|
||||
type probeData struct {
|
||||
fsName string
|
||||
magic string
|
||||
offset uint64
|
||||
}
|
||||
|
||||
// ProbeFsType returns the filesystem name for the given device id.
|
||||
func ProbeFsType(device string) (string, error) {
|
||||
probes := []probeData{
|
||||
{"btrfs", "_BHRfS_M", 0x10040},
|
||||
{"ext4", "\123\357", 0x438},
|
||||
{"xfs", "XFSB", 0},
|
||||
}
|
||||
|
||||
maxLen := uint64(0)
|
||||
for _, p := range probes {
|
||||
l := p.offset + uint64(len(p.magic))
|
||||
if l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, maxLen)
|
||||
l, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if uint64(l) != maxLen {
|
||||
return "", fmt.Errorf("devmapper: unable to detect filesystem type of %s, short read", device)
|
||||
}
|
||||
|
||||
for _, p := range probes {
|
||||
if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
|
||||
return p.fsName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("devmapper: Unknown filesystem type on %s", device)
|
||||
}
|
||||
|
||||
func joinMountOptions(a, b string) string {
|
||||
if a == "" {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
return a
|
||||
}
|
||||
return a + "," + b
|
||||
}
|
||||
472
vendor/github.com/containers/storage/drivers/driver.go
generated
vendored
Normal file
472
vendor/github.com/containers/storage/drivers/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// FsMagic unsigned id of the filesystem in use.
|
||||
type FsMagic uint32
|
||||
|
||||
const (
|
||||
// FsMagicUnsupported is a predefined constant value other than a valid filesystem id.
|
||||
FsMagicUnsupported = FsMagic(0x00000000)
|
||||
)
|
||||
|
||||
var (
|
||||
// All registered drivers
|
||||
drivers map[string]InitFunc
|
||||
|
||||
// ErrNotSupported returned when driver is not supported.
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
// ErrPrerequisites returned when driver does not meet prerequisites.
|
||||
ErrPrerequisites = errors.New("prerequisites for driver not satisfied (wrong filesystem?)")
|
||||
// ErrIncompatibleFS returned when file system is not supported.
|
||||
ErrIncompatibleFS = errors.New("backing file system is unsupported for this graph driver")
|
||||
// ErrLayerUnknown returned when the specified layer is unknown by the driver.
|
||||
ErrLayerUnknown = errors.New("unknown layer")
|
||||
)
|
||||
|
||||
// CreateOpts contains optional arguments for Create() and CreateReadWrite()
|
||||
// methods.
|
||||
type CreateOpts struct {
|
||||
MountLabel string
|
||||
StorageOpt map[string]string
|
||||
*idtools.IDMappings
|
||||
ignoreChownErrors bool
|
||||
}
|
||||
|
||||
// MountOpts contains optional arguments for Driver.Get() methods.
|
||||
type MountOpts struct {
|
||||
// Mount label is the MAC Labels to assign to mount point (SELINUX)
|
||||
MountLabel string
|
||||
// UidMaps & GidMaps are the User Namespace mappings to be assigned to content in the mount point
|
||||
UidMaps []idtools.IDMap //nolint: revive,golint
|
||||
GidMaps []idtools.IDMap //nolint: revive,golint
|
||||
Options []string
|
||||
|
||||
// Volatile specifies whether the container storage can be optimized
|
||||
// at the cost of not syncing all the dirty files in memory.
|
||||
Volatile bool
|
||||
|
||||
// DisableShifting forces the driver to not do any ID shifting at runtime.
|
||||
DisableShifting bool
|
||||
}
|
||||
|
||||
// ApplyDiffOpts contains optional arguments for ApplyDiff methods.
|
||||
type ApplyDiffOpts struct {
|
||||
Diff io.Reader
|
||||
Mappings *idtools.IDMappings
|
||||
MountLabel string
|
||||
IgnoreChownErrors bool
|
||||
ForceMask *os.FileMode
|
||||
}
|
||||
|
||||
// InitFunc initializes the storage driver.
|
||||
type InitFunc func(homedir string, options Options) (Driver, error)
|
||||
|
||||
// ProtoDriver defines the basic capabilities of a driver.
|
||||
// This interface exists solely to be a minimum set of methods
|
||||
// for client code which choose not to implement the entire Driver
|
||||
// interface and use the NaiveDiffDriver wrapper constructor.
|
||||
//
|
||||
// Use of ProtoDriver directly by client code is not recommended.
|
||||
type ProtoDriver interface {
|
||||
// String returns a string representation of this driver.
|
||||
String() string
|
||||
// CreateReadWrite creates a new, empty filesystem layer that is ready
|
||||
// to be used as the storage for a container. Additional options can
|
||||
// be passed in opts. parent may be "" and opts may be nil.
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
// Create creates a new, empty, filesystem layer with the
|
||||
// specified id and parent and options passed in opts. Parent
|
||||
// may be "" and opts may be nil.
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
// CreateFromTemplate creates a new filesystem layer with the specified id
|
||||
// and parent, with contents identical to the specified template layer.
|
||||
CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error
|
||||
// Remove attempts to remove the filesystem layer with this id.
|
||||
Remove(id string) error
|
||||
// Get returns the mountpoint for the layered filesystem referred
|
||||
// to by this id. You can optionally specify a mountLabel or "".
|
||||
// Optionally it gets the mappings used to create the layer.
|
||||
// Returns the absolute path to the mounted layered filesystem.
|
||||
Get(id string, options MountOpts) (dir string, err error)
|
||||
// Put releases the system resources for the specified id,
|
||||
// e.g, unmounting layered filesystem.
|
||||
Put(id string) error
|
||||
// Exists returns whether a filesystem layer with the specified
|
||||
// ID exists on this driver.
|
||||
Exists(id string) bool
|
||||
// Returns a list of layer ids that exist on this driver (does not include
|
||||
// additional storage layers). Not supported by all backends.
|
||||
// If the driver requires that layers be removed in a particular order,
|
||||
// usually due to parent-child relationships that it cares about, The
|
||||
// list should be sorted well enough so that if all layers need to be
|
||||
// removed, they can be removed in the order in which they're returned.
|
||||
ListLayers() ([]string, error)
|
||||
// Status returns a set of key-value pairs which give low
|
||||
// level diagnostic status about this driver.
|
||||
Status() [][2]string
|
||||
// Returns a set of key-value pairs which give low level information
|
||||
// about the image/container driver is managing.
|
||||
Metadata(id string) (map[string]string, error)
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the specified ID.
|
||||
ReadWriteDiskUsage(id string) (*directory.DiskUsage, error)
|
||||
// Cleanup performs necessary tasks to release resources
|
||||
// held by the driver, e.g., unmounting all layered filesystems
|
||||
// known to this driver.
|
||||
Cleanup() error
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
AdditionalImageStores() []string
|
||||
}
|
||||
|
||||
// DiffDriver is the interface to use to implement graph diffs
|
||||
type DiffDriver interface {
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
Diff(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error)
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
Changes(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error)
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The io.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id string, parent string, options ApplyDiffOpts) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, mountLabel string) (size int64, err error)
|
||||
}
|
||||
|
||||
// LayerIDMapUpdater is the interface that implements ID map changes for layers.
|
||||
type LayerIDMapUpdater interface {
|
||||
// UpdateLayerIDMap walks the layer's filesystem tree, changing the ownership
|
||||
// information using the toContainer and toHost mappings, using them to replace
|
||||
// on-disk owner UIDs and GIDs which are "host" values in the first map with
|
||||
// UIDs and GIDs for "host" values from the second map which correspond to the
|
||||
// same "container" IDs. This method should only be called after a layer is
|
||||
// first created and populated, and before it is mounted, as other changes made
|
||||
// relative to a parent layer, but before this method is called, may be discarded
|
||||
// by Diff().
|
||||
UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in a
|
||||
// image and it is not required to Chown the files when running in an user namespace.
|
||||
SupportsShifting() bool
|
||||
}
|
||||
|
||||
// Driver is the interface for layered/snapshot file system drivers.
|
||||
type Driver interface {
|
||||
ProtoDriver
|
||||
DiffDriver
|
||||
LayerIDMapUpdater
|
||||
}
|
||||
|
||||
// DriverWithDifferOutput is the result of ApplyDiffWithDiffer
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type DriverWithDifferOutput struct {
|
||||
Differ Differ
|
||||
Target string
|
||||
Size int64
|
||||
UIDs []uint32
|
||||
GIDs []uint32
|
||||
UncompressedDigest digest.Digest
|
||||
Metadata string
|
||||
BigData map[string][]byte
|
||||
TarSplit []byte
|
||||
TOCDigest digest.Digest
|
||||
// Artifacts is a collection of additional artifacts
|
||||
// generated by the differ that the storage driver can use.
|
||||
Artifacts map[string]interface{}
|
||||
}
|
||||
|
||||
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, differOpts *DifferOptions) (DriverWithDifferOutput, error)
|
||||
}
|
||||
|
||||
// DriverWithDiffer is the interface for direct diff access.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type DriverWithDiffer interface {
|
||||
Driver
|
||||
// ApplyDiffWithDiffer applies the changes using the callback function.
|
||||
// If id is empty, then a staging directory is created. The staging directory is guaranteed to be usable with ApplyDiffFromStagingDirectory.
|
||||
ApplyDiffWithDiffer(id, parent string, options *ApplyDiffOpts, differ Differ) (output DriverWithDifferOutput, err error)
|
||||
// ApplyDiffFromStagingDirectory applies the changes using the specified staging directory.
|
||||
ApplyDiffFromStagingDirectory(id, parent, stagingDirectory string, diffOutput *DriverWithDifferOutput, options *ApplyDiffOpts) error
|
||||
// CleanupStagingDirectory cleanups the staging directory. It can be used to cleanup the staging directory on errors
|
||||
CleanupStagingDirectory(stagingDirectory string) error
|
||||
// DifferTarget gets the location where files are stored for the layer.
|
||||
DifferTarget(id string) (string, error)
|
||||
}
|
||||
|
||||
// Capabilities defines a list of capabilities a driver may implement.
|
||||
// These capabilities are not required; however, they do determine how a
|
||||
// graphdriver can be used.
|
||||
type Capabilities struct {
|
||||
// Flags that this driver is capable of reproducing exactly equivalent
|
||||
// diffs for read-only layers. If set, clients can rely on the driver
|
||||
// for consistent tar streams, and avoid extra processing to account
|
||||
// for potential differences (eg: the layer store's use of tar-split).
|
||||
ReproducesExactDiffs bool
|
||||
}
|
||||
|
||||
// CapabilityDriver is the interface for layered file system drivers that
|
||||
// can report on their Capabilities.
|
||||
type CapabilityDriver interface {
|
||||
Capabilities() Capabilities
|
||||
}
|
||||
|
||||
// AdditionalLayer represents a layer that is stored in the additional layer store
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type AdditionalLayer interface {
|
||||
// CreateAs creates a new layer from this additional layer
|
||||
CreateAs(id, parent string) error
|
||||
|
||||
// Info returns arbitrary information stored along with this layer (i.e. `info` file)
|
||||
Info() (io.ReadCloser, error)
|
||||
|
||||
// Blob returns a reader of the raw contents of this layer.
|
||||
Blob() (io.ReadCloser, error)
|
||||
|
||||
// Release tells the additional layer store that we don't use this handler.
|
||||
Release()
|
||||
}
|
||||
|
||||
// AdditionalLayerStoreDriver is the interface for driver that supports
|
||||
// additional layer store functionality.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
type AdditionalLayerStoreDriver interface {
|
||||
Driver
|
||||
|
||||
// LookupAdditionalLayer looks up additional layer store by the specified
|
||||
// digest and ref and returns an object representing that layer.
|
||||
LookupAdditionalLayer(d digest.Digest, ref string) (AdditionalLayer, error)
|
||||
|
||||
// LookupAdditionalLayer looks up additional layer store by the specified
|
||||
// ID and returns an object representing that layer.
|
||||
LookupAdditionalLayerByID(id string) (AdditionalLayer, error)
|
||||
}
|
||||
|
||||
// DiffGetterDriver is the interface for layered file system drivers that
|
||||
// provide a specialized function for getting file contents for tar-split.
|
||||
type DiffGetterDriver interface {
|
||||
Driver
|
||||
// DiffGetter returns an interface to efficiently retrieve the contents
|
||||
// of files in a layer.
|
||||
DiffGetter(id string) (FileGetCloser, error)
|
||||
}
|
||||
|
||||
// FileGetCloser extends the storage.FileGetter interface with a Close method
|
||||
// for cleaning up.
|
||||
type FileGetCloser interface {
|
||||
storage.FileGetter
|
||||
// Close cleans up any resources associated with the FileGetCloser.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Checker makes checks on specified filesystems.
|
||||
type Checker interface {
|
||||
// IsMounted returns true if the provided path is mounted for the specific checker
|
||||
IsMounted(path string) bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]InitFunc)
|
||||
}
|
||||
|
||||
// MustRegister registers an InitFunc for the driver, or panics.
|
||||
// It is suitable for package’s init() sections.
|
||||
func MustRegister(name string, initFunc InitFunc) {
|
||||
if err := Register(name, initFunc); err != nil {
|
||||
panic(fmt.Sprintf("failed to register containers/storage graph driver %q: %v", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers an InitFunc for the driver.
|
||||
func Register(name string, initFunc InitFunc) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("name already registered %s", name)
|
||||
}
|
||||
drivers[name] = initFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver initializes and returns the registered driver
|
||||
func GetDriver(name string, config Options) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(config.Root, name), config)
|
||||
}
|
||||
|
||||
logrus.Errorf("Failed to GetDriver graph %s %s", name, config.Root)
|
||||
return nil, fmt.Errorf("failed to GetDriver graph %s %s: %w", name, config.Root, ErrNotSupported)
|
||||
}
|
||||
|
||||
// getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
|
||||
func getBuiltinDriver(name, home string, options Options) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options)
|
||||
}
|
||||
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
|
||||
return nil, fmt.Errorf("failed to built-in GetDriver graph %s %s: %w", name, home, ErrNotSupported)
|
||||
}
|
||||
|
||||
// Options is used to initialize a graphdriver
|
||||
type Options struct {
|
||||
Root string
|
||||
RunRoot string
|
||||
ImageStore string
|
||||
DriverPriority []string
|
||||
DriverOptions []string
|
||||
UIDMaps []idtools.IDMap
|
||||
GIDMaps []idtools.IDMap
|
||||
ExperimentalEnabled bool
|
||||
}
|
||||
|
||||
// New creates the driver and initializes it at the specified root.
|
||||
func New(name string, config Options) (Driver, error) {
|
||||
if name != "" {
|
||||
logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver
|
||||
return GetDriver(name, config)
|
||||
}
|
||||
|
||||
// Guess for prior driver
|
||||
driversMap := ScanPriorDrivers(config.Root)
|
||||
|
||||
// use the supplied priority list unless it is empty
|
||||
prioList := config.DriverPriority
|
||||
if len(prioList) == 0 {
|
||||
prioList = Priority
|
||||
}
|
||||
|
||||
for _, name := range prioList {
|
||||
if name == "vfs" && len(config.DriverPriority) == 0 {
|
||||
// don't use vfs even if there is state present and vfs
|
||||
// has not been explicitly added to the override driver
|
||||
// priority list
|
||||
continue
|
||||
}
|
||||
if _, prior := driversMap[name]; prior {
|
||||
// of the state found from prior drivers, check in order of our priority
|
||||
// which we would prefer
|
||||
driver, err := getBuiltinDriver(name, config.Root, config)
|
||||
if err != nil {
|
||||
// unlike below, we will return error here, because there is prior
|
||||
// state, and now it is no longer supported/prereq/compatible, so
|
||||
// something changed and needs attention. Otherwise the daemon's
|
||||
// images would just "disappear".
|
||||
logrus.Errorf("[graphdriver] prior storage driver %s failed: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// abort starting when there are other prior configured drivers
|
||||
// to ensure the user explicitly selects the driver to load
|
||||
if len(driversMap)-1 > 0 {
|
||||
var driversSlice []string
|
||||
for name := range driversMap {
|
||||
driversSlice = append(driversSlice, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", config.Root, strings.Join(driversSlice, ", "))
|
||||
}
|
||||
|
||||
logrus.Infof("[graphdriver] using prior storage driver: %s", name)
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range prioList {
|
||||
driver, err := getBuiltinDriver(name, config.Root, config)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for name, initFunc := range drivers {
|
||||
driver, err := initFunc(filepath.Join(config.Root, name), config)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no supported storage backend found")
|
||||
}
|
||||
|
||||
// isDriverNotSupported returns true if the error initializing
|
||||
// the graph driver is a non-supported error.
|
||||
func isDriverNotSupported(err error) bool {
|
||||
return errors.Is(err, ErrNotSupported) || errors.Is(err, ErrPrerequisites) || errors.Is(err, ErrIncompatibleFS)
|
||||
}
|
||||
|
||||
// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers
|
||||
func ScanPriorDrivers(root string) map[string]bool {
|
||||
driversMap := make(map[string]bool)
|
||||
|
||||
for driver := range drivers {
|
||||
p := filepath.Join(root, driver)
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
driversMap[driver] = true
|
||||
}
|
||||
}
|
||||
return driversMap
|
||||
}
|
||||
|
||||
// driverPut is driver.Put, but errors are handled either by updating mainErr or just logging.
|
||||
// Typical usage:
|
||||
//
|
||||
// func …(…) (err error) {
|
||||
// …
|
||||
// defer driverPut(driver, id, &err)
|
||||
// }
|
||||
func driverPut(driver ProtoDriver, id string, mainErr *error) {
|
||||
if err := driver.Put(id); err != nil {
|
||||
err = fmt.Errorf("unmounting layer %s: %w", id, err)
|
||||
if *mainErr == nil {
|
||||
*mainErr = err
|
||||
} else {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/containers/storage/drivers/driver_darwin.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/drivers/driver_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in order
|
||||
var Priority = []string{
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
48
vendor/github.com/containers/storage/drivers/driver_freebsd.go
generated
vendored
Normal file
48
vendor/github.com/containers/storage/drivers/driver_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"zfs",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
// No-op on FreeBSD.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
||||
202
vendor/github.com/containers/storage/drivers/driver_linux.go
generated
vendored
Normal file
202
vendor/github.com/containers/storage/drivers/driver_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicAufs filesystem id for Aufs
|
||||
FsMagicAufs = FsMagic(0x61756673)
|
||||
// FsMagicBtrfs filesystem id for Btrfs
|
||||
FsMagicBtrfs = FsMagic(0x9123683E)
|
||||
// FsMagicCramfs filesystem id for Cramfs
|
||||
FsMagicCramfs = FsMagic(0x28cd3d45)
|
||||
// FsMagicEcryptfs filesystem id for eCryptfs
|
||||
FsMagicEcryptfs = FsMagic(0xf15f)
|
||||
// FsMagicExtfs filesystem id for Extfs
|
||||
FsMagicExtfs = FsMagic(0x0000EF53)
|
||||
// FsMagicF2fs filesystem id for F2fs
|
||||
FsMagicF2fs = FsMagic(0xF2F52010)
|
||||
// FsMagicGPFS filesystem id for GPFS
|
||||
FsMagicGPFS = FsMagic(0x47504653)
|
||||
// FsMagicJffs2Fs filesystem if for Jffs2Fs
|
||||
FsMagicJffs2Fs = FsMagic(0x000072b6)
|
||||
// FsMagicJfs filesystem id for Jfs
|
||||
FsMagicJfs = FsMagic(0x3153464a)
|
||||
// FsMagicNfsFs filesystem id for NfsFs
|
||||
FsMagicNfsFs = FsMagic(0x00006969)
|
||||
// FsMagicRAMFs filesystem id for RamFs
|
||||
FsMagicRAMFs = FsMagic(0x858458f6)
|
||||
// FsMagicReiserFs filesystem id for ReiserFs
|
||||
FsMagicReiserFs = FsMagic(0x52654973)
|
||||
// FsMagicSmbFs filesystem id for SmbFs
|
||||
FsMagicSmbFs = FsMagic(0x0000517B)
|
||||
// FsMagicSquashFs filesystem id for SquashFs
|
||||
FsMagicSquashFs = FsMagic(0x73717368)
|
||||
// FsMagicTmpFs filesystem id for TmpFs
|
||||
FsMagicTmpFs = FsMagic(0x01021994)
|
||||
// FsMagicVxFS filesystem id for VxFs
|
||||
FsMagicVxFS = FsMagic(0xa501fcf5)
|
||||
// FsMagicXfs filesystem id for Xfs
|
||||
FsMagicXfs = FsMagic(0x58465342)
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
// FsMagicOverlay filesystem id for overlay
|
||||
FsMagicOverlay = FsMagic(0x794C7630)
|
||||
// FsMagicFUSE filesystem id for FUSE
|
||||
FsMagicFUSE = FsMagic(0x65735546)
|
||||
// FsMagicAcfs filesystem id for Acfs
|
||||
FsMagicAcfs = FsMagic(0x61636673)
|
||||
// FsMagicAfs filesystem id for Afs
|
||||
FsMagicAfs = FsMagic(0x5346414f)
|
||||
// FsMagicCephFs filesystem id for Ceph
|
||||
FsMagicCephFs = FsMagic(0x00C36400)
|
||||
// FsMagicCIFS filesystem id for CIFS
|
||||
FsMagicCIFS = FsMagic(0xFF534D42)
|
||||
// FsMagicEROFS filesystem id for EROFS
|
||||
FsMagicEROFS = FsMagic(0xE0F5E1E2)
|
||||
// FsMagicFHGFS filesystem id for FHGFS
|
||||
FsMagicFHGFSFs = FsMagic(0x19830326)
|
||||
// FsMagicIBRIX filesystem id for IBRIX
|
||||
FsMagicIBRIX = FsMagic(0x013111A8)
|
||||
// FsMagicKAFS filesystem id for KAFS
|
||||
FsMagicKAFS = FsMagic(0x6B414653)
|
||||
// FsMagicLUSTRE filesystem id for LUSTRE
|
||||
FsMagicLUSTRE = FsMagic(0x0BD00BD0)
|
||||
// FsMagicNCP filesystem id for NCP
|
||||
FsMagicNCP = FsMagic(0x564C)
|
||||
// FsMagicNFSD filesystem id for NFSD
|
||||
FsMagicNFSD = FsMagic(0x6E667364)
|
||||
// FsMagicOCFS2 filesystem id for OCFS2
|
||||
FsMagicOCFS2 = FsMagic(0x7461636F)
|
||||
// FsMagicPANFS filesystem id for PANFS
|
||||
FsMagicPANFS = FsMagic(0xAAD7AAEA)
|
||||
// FsMagicPRLFS filesystem id for PRLFS
|
||||
FsMagicPRLFS = FsMagic(0x7C7C6673)
|
||||
// FsMagicSMB2 filesystem id for SMB2
|
||||
FsMagicSMB2 = FsMagic(0xFE534D42)
|
||||
// FsMagicSNFS filesystem id for SNFS
|
||||
FsMagicSNFS = FsMagic(0xBEEFDEAD)
|
||||
// FsMagicVBOXSF filesystem id for VBOXSF
|
||||
FsMagicVBOXSF = FsMagic(0x786F4256)
|
||||
// FsMagicVXFS filesystem id for VXFS
|
||||
FsMagicVXFS = FsMagic(0xA501FCF5)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"overlay",
|
||||
// We don't support devicemapper without configuration
|
||||
// "devicemapper",
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"zfs",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicAufs: "aufs",
|
||||
FsMagicBtrfs: "btrfs",
|
||||
FsMagicCramfs: "cramfs",
|
||||
FsMagicEcryptfs: "ecryptfs",
|
||||
FsMagicEROFS: "erofs",
|
||||
FsMagicExtfs: "extfs",
|
||||
FsMagicF2fs: "f2fs",
|
||||
FsMagicGPFS: "gpfs",
|
||||
FsMagicJffs2Fs: "jffs2",
|
||||
FsMagicJfs: "jfs",
|
||||
FsMagicNfsFs: "nfs",
|
||||
FsMagicOverlay: "overlayfs",
|
||||
FsMagicRAMFs: "ramfs",
|
||||
FsMagicReiserFs: "reiserfs",
|
||||
FsMagicSmbFs: "smb",
|
||||
FsMagicSquashFs: "squashfs",
|
||||
FsMagicTmpFs: "tmpfs",
|
||||
FsMagicUnsupported: "unsupported",
|
||||
FsMagicVxFS: "vxfs",
|
||||
FsMagicXfs: "xfs",
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
var buf unix.Statfs_t
|
||||
path := filepath.Dir(rootpath)
|
||||
if err := unix.Statfs(path, &buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, ok := FsNames[FsMagic(buf.Type)]; !ok {
|
||||
logrus.Debugf("Unknown filesystem type %#x reported for %s", buf.Type, path)
|
||||
}
|
||||
return FsMagic(buf.Type), nil
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provided FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// isMountPoint checks that the given path is a mount point
|
||||
func isMountPoint(mountPath string) (bool, error) {
|
||||
// it is already the root
|
||||
if mountPath == "/" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var s1, s2 unix.Stat_t
|
||||
if err := unix.Stat(mountPath, &s1); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if err := unix.Stat(filepath.Dir(mountPath), &s2); err != nil {
|
||||
return true, err
|
||||
}
|
||||
return s1.Dev != s2.Dev, nil
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf unix.Statfs_t
|
||||
|
||||
if err := unix.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if FsMagic(buf.Type) != fsType {
|
||||
return false, nil
|
||||
}
|
||||
return isMountPoint(mountPath)
|
||||
}
|
||||
96
vendor/github.com/containers/storage/drivers/driver_solaris.go
generated
vendored
Normal file
96
vendor/github.com/containers/storage/drivers/driver_solaris.go
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//go:build solaris && cgo
|
||||
// +build solaris,cgo
|
||||
|
||||
package graphdriver
|
||||
|
||||
/*
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline struct statvfs *getstatfs(char *s) {
|
||||
struct statvfs *buf;
|
||||
int err;
|
||||
buf = (struct statvfs *)malloc(sizeof(struct statvfs));
|
||||
err = statvfs(s, buf);
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
Priority = []string{
|
||||
"zfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provided FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
// No-op on Solaris.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct{}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
// Solaris supports only ZFS for now
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
cs := C.CString(filepath.Dir(mountPath))
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
buf := C.getstatfs(cs)
|
||||
defer C.free(unsafe.Pointer(buf))
|
||||
|
||||
// on Solaris buf.f_basetype contains ['z', 'f', 's', 0 ... ]
|
||||
if (buf.f_basetype[0] != 122) || (buf.f_basetype[1] != 102) || (buf.f_basetype[2] != 115) ||
|
||||
(buf.f_basetype[3] != 0) {
|
||||
logrus.Debugf("[zfs] no zfs dataset found for rootdir '%s'", mountPath)
|
||||
return false, ErrPrerequisites
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
14
vendor/github.com/containers/storage/drivers/driver_unsupported.go
generated
vendored
Normal file
14
vendor/github.com/containers/storage/drivers/driver_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//go:build !linux && !windows && !freebsd && !solaris && !darwin
|
||||
// +build !linux,!windows,!freebsd,!solaris,!darwin
|
||||
|
||||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in an order
|
||||
var Priority = []string{
|
||||
"unsupported",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
12
vendor/github.com/containers/storage/drivers/driver_windows.go
generated
vendored
Normal file
12
vendor/github.com/containers/storage/drivers/driver_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package graphdriver
|
||||
|
||||
// Slice of drivers that should be used in order
|
||||
var Priority = []string{
|
||||
"windowsfilter",
|
||||
}
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
||||
229
vendor/github.com/containers/storage/drivers/fsdiff.go
generated
vendored
Normal file
229
vendor/github.com/containers/storage/drivers/fsdiff.go
generated
vendored
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/chrootarchive"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ApplyUncompressedLayer defines the unpack method used by the graph
|
||||
// driver.
|
||||
var ApplyUncompressedLayer = chrootarchive.ApplyUncompressedLayer
|
||||
|
||||
// NaiveDiffDriver takes a ProtoDriver and adds the
|
||||
// capability of the Diffing methods which it may or may not
|
||||
// support on its own. See the comment on the exported
|
||||
// NewNaiveDiffDriver function below.
|
||||
// Notably, the AUFS driver doesn't need to be wrapped like this.
|
||||
type NaiveDiffDriver struct {
|
||||
ProtoDriver
|
||||
LayerIDMapUpdater
|
||||
}
|
||||
|
||||
// NewNaiveDiffDriver returns a fully functional driver that wraps the
|
||||
// given ProtoDriver and adds the capability of the following methods which
|
||||
// it may or may not support on its own:
|
||||
//
|
||||
// Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error)
|
||||
// Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error)
|
||||
// ApplyDiff(id, parent string, options ApplyDiffOpts) (size int64, err error)
|
||||
// DiffSize(id string, idMappings *idtools.IDMappings, parent, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error)
|
||||
func NewNaiveDiffDriver(driver ProtoDriver, updater LayerIDMapUpdater) Driver {
|
||||
return &NaiveDiffDriver{ProtoDriver: driver, LayerIDMapUpdater: updater}
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (gdw *NaiveDiffDriver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (arch io.ReadCloser, err error) {
|
||||
startTime := time.Now()
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
Options: []string{"ro"},
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
driverPut(driver, id, &err)
|
||||
}
|
||||
}()
|
||||
|
||||
if parent == "" {
|
||||
archive, err := archive.TarWithOptions(layerFs, &archive.TarOptions{
|
||||
Compression: archive.Uncompressed,
|
||||
UIDMaps: idMappings.UIDs(),
|
||||
GIDMaps: idMappings.GIDs(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driverPut(driver, id, &err)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
options.Options = append(options.Options, "ro")
|
||||
parentFs, err := driver.Get(parent, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, parent, &err)
|
||||
|
||||
changes, err := archive.ChangesDirs(layerFs, idMappings, parentFs, parentMappings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(layerFs, changes, idMappings.UIDs(), idMappings.GIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driverPut(driver, id, &err)
|
||||
|
||||
// NaiveDiffDriver compares file metadata with parent layers. Parent layers
|
||||
// are extracted from tar's with full second precision on modified time.
|
||||
// We need this hack here to make sure calls within same second receive
|
||||
// correct result.
|
||||
time.Sleep(time.Until(startTime.Truncate(time.Second).Add(time.Second)))
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (_ []archive.Change, retErr error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, id, &retErr)
|
||||
|
||||
parentFs := ""
|
||||
|
||||
if parent != "" {
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
Options: []string{"ro"},
|
||||
}
|
||||
parentFs, err = driver.Get(parent, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driverPut(driver, parent, &retErr)
|
||||
}
|
||||
|
||||
return archive.ChangesDirs(layerFs, idMappings, parentFs, parentMappings)
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, options ApplyDiffOpts) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if options.Mappings == nil {
|
||||
options.Mappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
// Mount the root filesystem so we can apply the diff/layer.
|
||||
mountOpts := MountOpts{
|
||||
MountLabel: options.MountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, mountOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driverPut(driver, id, &err)
|
||||
|
||||
defaultForceMask := os.FileMode(0o700)
|
||||
var forceMask *os.FileMode // = nil
|
||||
if runtime.GOOS == "darwin" {
|
||||
forceMask = &defaultForceMask
|
||||
}
|
||||
|
||||
tarOptions := &archive.TarOptions{
|
||||
InUserNS: unshare.IsRootless(),
|
||||
IgnoreChownErrors: options.IgnoreChownErrors,
|
||||
ForceMask: forceMask,
|
||||
}
|
||||
if options.Mappings != nil {
|
||||
tarOptions.UIDMaps = options.Mappings.UIDs()
|
||||
tarOptions.GIDMaps = options.Mappings.GIDs()
|
||||
}
|
||||
start := time.Now().UTC()
|
||||
logrus.Debug("Start untar layer")
|
||||
if size, err = ApplyUncompressedLayer(layerFs, options.Diff, tarOptions); err != nil {
|
||||
logrus.Errorf("While applying layer: %s", err)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified layer
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (gdw *NaiveDiffDriver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
if idMappings == nil {
|
||||
idMappings = &idtools.IDMappings{}
|
||||
}
|
||||
if parentMappings == nil {
|
||||
parentMappings = &idtools.IDMappings{}
|
||||
}
|
||||
|
||||
changes, err := gdw.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
options := MountOpts{
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
layerFs, err := driver.Get(id, options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driverPut(driver, id, &err)
|
||||
|
||||
return archive.ChangesSize(layerFs, changes), nil
|
||||
}
|
||||
5
vendor/github.com/containers/storage/drivers/jsoniter.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/drivers/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package graphdriver
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
310
vendor/github.com/containers/storage/drivers/overlay/check.go
generated
vendored
Normal file
310
vendor/github.com/containers/storage/drivers/overlay/check.go
generated
vendored
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/idmap"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// doesSupportNativeDiff checks whether the filesystem has a bug
|
||||
// which copies up the opaque flag when copying up an opaque
|
||||
// directory or the kernel enable CONFIG_OVERLAY_FS_REDIRECT_DIR.
|
||||
// When these exist naive diff should be used.
|
||||
func doesSupportNativeDiff(d, mountOpts string) error {
|
||||
td, err := os.MkdirTemp(d, "opaque-bug-check")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make directories l1/d, l1/d1, l2/d, l3, work, merged
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1", "d"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1", "d1"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l2", "d"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "l3"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mark l2/d as opaque
|
||||
if err := system.Lsetxattr(filepath.Join(td, "l2", "d"), archive.GetOverlayXattrName("opaque"), []byte("y"), 0); err != nil {
|
||||
return fmt.Errorf("failed to set opaque flag on middle layer: %w", err)
|
||||
}
|
||||
|
||||
mountFlags := "lowerdir=%s:%s,upperdir=%s,workdir=%s"
|
||||
if unshare.IsRootless() {
|
||||
mountFlags = mountFlags + ",userxattr"
|
||||
}
|
||||
|
||||
opts := fmt.Sprintf(mountFlags, path.Join(td, "l2"), path.Join(td, "l1"), path.Join(td, "l3"), path.Join(td, "work"))
|
||||
flags, data := mount.ParseOptions(mountOpts)
|
||||
if data != "" {
|
||||
opts = fmt.Sprintf("%s,%s", opts, data)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil {
|
||||
return fmt.Errorf("failed to mount overlay: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Touch file in d to force copy up of opaque directory "d" from "l2" to "l3"
|
||||
if err := os.WriteFile(filepath.Join(td, "merged", "d", "f"), []byte{}, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write to merged directory: %w", err)
|
||||
}
|
||||
|
||||
// Check l3/d does not have opaque flag
|
||||
xattrOpaque, err := system.Lgetxattr(filepath.Join(td, "l3", "d"), archive.GetOverlayXattrName("opaque"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read opaque flag on upper layer: %w", err)
|
||||
}
|
||||
if string(xattrOpaque) == "y" {
|
||||
return errors.New("opaque flag erroneously copied up, consider update to kernel 4.8 or later to fix")
|
||||
}
|
||||
|
||||
// rename "d1" to "d2"
|
||||
if err := os.Rename(filepath.Join(td, "merged", "d1"), filepath.Join(td, "merged", "d2")); err != nil {
|
||||
// if rename failed with syscall.EXDEV, the kernel doesn't have CONFIG_OVERLAY_FS_REDIRECT_DIR enabled
|
||||
if err.(*os.LinkError).Err == syscall.EXDEV {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to rename dir in merged directory: %w", err)
|
||||
}
|
||||
// get the xattr of "d2"
|
||||
xattrRedirect, err := system.Lgetxattr(filepath.Join(td, "l3", "d2"), archive.GetOverlayXattrName("redirect"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read redirect flag on upper layer: %w", err)
|
||||
}
|
||||
|
||||
if string(xattrRedirect) == "d1" {
|
||||
return errors.New("kernel has CONFIG_OVERLAY_FS_REDIRECT_DIR enabled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doesMetacopy checks if the filesystem is going to optimize changes to
|
||||
// metadata by using nodes marked with an "overlay.metacopy" attribute to avoid
|
||||
// copying up a file from a lower layer unless/until its contents are being
|
||||
// modified
|
||||
func doesMetacopy(d, mountOpts string) (bool, error) {
|
||||
td, err := os.MkdirTemp(d, "metacopy-check")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Make directories l1, l2, work, merged
|
||||
if err := os.MkdirAll(filepath.Join(td, "l1"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(filepath.Join(td, "l1", "f"), []byte{0xff}, 0o700); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "l2"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Mount using the mandatory options and configured options
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", path.Join(td, "l1"), path.Join(td, "l2"), path.Join(td, "work"))
|
||||
if unshare.IsRootless() {
|
||||
opts = fmt.Sprintf("%s,userxattr", opts)
|
||||
}
|
||||
flags, data := mount.ParseOptions(mountOpts)
|
||||
if data != "" {
|
||||
opts = fmt.Sprintf("%s,%s", opts, data)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", uintptr(flags), opts); err != nil {
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
logrus.Infof("overlay: metacopy option not supported on this kernel, checked using options %q", mountOpts)
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to mount overlay for metacopy check with %q options: %w", mountOpts, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
// Make a change that only impacts the inode, and check if the pulled-up copy is marked
|
||||
// as a metadata-only copy
|
||||
if err := os.Chmod(filepath.Join(td, "merged", "f"), 0o600); err != nil {
|
||||
return false, fmt.Errorf("changing permissions on file for metacopy check: %w", err)
|
||||
}
|
||||
metacopy, err := system.Lgetxattr(filepath.Join(td, "l2", "f"), archive.GetOverlayXattrName("metacopy"))
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.ENOTSUP) {
|
||||
logrus.Info("metacopy option not supported")
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("metacopy flag was not set on file in upper layer: %w", err)
|
||||
}
|
||||
return metacopy != nil, nil
|
||||
}
|
||||
|
||||
// doesVolatile checks if the filesystem supports the "volatile" mount option
|
||||
func doesVolatile(d string) (bool, error) {
|
||||
td, err := os.MkdirTemp(d, "volatile-check")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(td); err != nil {
|
||||
logrus.Warnf("Failed to remove check directory %v: %v", td, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.MkdirAll(filepath.Join(td, "lower"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(td, "upper"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "work"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(td, "merged"), 0o755); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Mount using the mandatory options and configured options
|
||||
opts := fmt.Sprintf("volatile,lowerdir=%s,upperdir=%s,workdir=%s", path.Join(td, "lower"), path.Join(td, "upper"), path.Join(td, "work"))
|
||||
if unshare.IsRootless() {
|
||||
opts = fmt.Sprintf("%s,userxattr", opts)
|
||||
}
|
||||
if err := unix.Mount("overlay", filepath.Join(td, "merged"), "overlay", 0, opts); err != nil {
|
||||
return false, fmt.Errorf("failed to mount overlay for volatile check: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Unmount(filepath.Join(td, "merged"), 0); err != nil {
|
||||
logrus.Warnf("Failed to unmount check directory %v: %v", filepath.Join(td, "merged"), err)
|
||||
}
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// supportsIdmappedLowerLayers checks if the kernel supports mounting overlay on top of
|
||||
// a idmapped lower layer.
|
||||
func supportsIdmappedLowerLayers(home string) (bool, error) {
|
||||
layerDir, err := os.MkdirTemp(home, "compat")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(layerDir)
|
||||
}()
|
||||
|
||||
mergedDir := filepath.Join(layerDir, "merged")
|
||||
lowerDir := filepath.Join(layerDir, "lower")
|
||||
lowerMappedDir := filepath.Join(layerDir, "lower-mapped")
|
||||
upperDir := filepath.Join(layerDir, "upper")
|
||||
workDir := filepath.Join(layerDir, "work")
|
||||
|
||||
_ = idtools.MkdirAs(mergedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerMappedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(upperDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(workDir, 0o700, 0, 0)
|
||||
|
||||
mapping := []idtools.IDMap{
|
||||
{
|
||||
ContainerID: 0,
|
||||
HostID: 0,
|
||||
Size: 1,
|
||||
},
|
||||
}
|
||||
pid, cleanupFunc, err := idmap.CreateUsernsProcess(mapping, mapping)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer cleanupFunc()
|
||||
|
||||
if err := idmap.CreateIDMappedMount(lowerDir, lowerMappedDir, int(pid)); err != nil {
|
||||
return false, fmt.Errorf("create mapped mount: %w", err)
|
||||
}
|
||||
defer unix.Unmount(lowerMappedDir, unix.MNT_DETACH)
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerMappedDir, upperDir, workDir)
|
||||
flags := uintptr(0)
|
||||
if err := unix.Mount("overlay", mergedDir, "overlay", flags, opts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = unix.Unmount(mergedDir, unix.MNT_DETACH)
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// supportsDataOnlyLayers checks if the kernel supports mounting a overlay file system
|
||||
// that uses data-only layers.
|
||||
func supportsDataOnlyLayers(home string) (bool, error) {
|
||||
layerDir, err := os.MkdirTemp(home, "compat")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.RemoveAll(layerDir)
|
||||
}()
|
||||
|
||||
mergedDir := filepath.Join(layerDir, "merged")
|
||||
lowerDir := filepath.Join(layerDir, "lower")
|
||||
lowerDirDataOnly := filepath.Join(layerDir, "lower-data")
|
||||
upperDir := filepath.Join(layerDir, "upper")
|
||||
workDir := filepath.Join(layerDir, "work")
|
||||
|
||||
_ = idtools.MkdirAs(mergedDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(lowerDirDataOnly, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(upperDir, 0o700, 0, 0)
|
||||
_ = idtools.MkdirAs(workDir, 0o700, 0, 0)
|
||||
|
||||
opts := fmt.Sprintf("lowerdir=%s::%s,upperdir=%s,workdir=%s,metacopy=on", lowerDir, lowerDirDataOnly, upperDir, workDir)
|
||||
flags := uintptr(0)
|
||||
if err := unix.Mount("overlay", mergedDir, "overlay", flags, opts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
_ = unix.Unmount(mergedDir, unix.MNT_DETACH)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
45
vendor/github.com/containers/storage/drivers/overlay/check_116.go
generated
vendored
Normal file
45
vendor/github.com/containers/storage/drivers/overlay/check_116.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func scanForMountProgramIndicators(home string) (detected bool, err error) {
|
||||
err = filepath.WalkDir(home, func(path string, d fs.DirEntry, err error) error {
|
||||
if detected {
|
||||
return fs.SkipDir
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basename := filepath.Base(path)
|
||||
if strings.HasPrefix(basename, archive.WhiteoutPrefix) {
|
||||
detected = true
|
||||
return fs.SkipDir
|
||||
}
|
||||
if d.IsDir() {
|
||||
xattrs, err := system.Llistxattr(path)
|
||||
if err != nil && !errors.Is(err, unix.EOPNOTSUPP) {
|
||||
return err
|
||||
}
|
||||
for _, xattr := range xattrs {
|
||||
if strings.HasPrefix(xattr, "user.fuseoverlayfs.") || strings.HasPrefix(xattr, "user.containers.") {
|
||||
detected = true
|
||||
return fs.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return detected, err
|
||||
}
|
||||
24
vendor/github.com/containers/storage/drivers/overlay/composefs_notsupported.go
generated
vendored
Normal file
24
vendor/github.com/containers/storage/drivers/overlay/composefs_notsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//go:build !linux || !composefs || !cgo
|
||||
// +build !linux !composefs !cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func composeFsSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func generateComposeFsBlob(verityDigests map[string]string, toc interface{}, composefsDir string) error {
|
||||
return fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
|
||||
func mountComposefsBlob(dataDir, mountPoint string) error {
|
||||
return fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
|
||||
func enableVerityRecursive(path string) (map[string]string, error) {
|
||||
return nil, fmt.Errorf("composefs is not supported")
|
||||
}
|
||||
219
vendor/github.com/containers/storage/drivers/overlay/composefs_supported.go
generated
vendored
Normal file
219
vendor/github.com/containers/storage/drivers/overlay/composefs_supported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
//go:build linux && composefs && cgo
|
||||
// +build linux,composefs,cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/chunked/dump"
|
||||
"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("mkcomposefs")
|
||||
})
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type verityDigest struct {
|
||||
Fsv unix.FsverityDigest
|
||||
Buf [64]byte
|
||||
}
|
||||
|
||||
func measureVerity(description string, fd int) (string, error) {
|
||||
var digest verityDigest
|
||||
digest.Fsv.Size = 64
|
||||
_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_MEASURE_VERITY), uintptr(unsafe.Pointer(&digest)))
|
||||
if e1 != 0 {
|
||||
return "", fmt.Errorf("failed to measure verity for %q: %w", description, e1)
|
||||
}
|
||||
return fmt.Sprintf("%x", digest.Buf[:digest.Fsv.Size]), nil
|
||||
}
|
||||
|
||||
func enableVerityRecursive(root string) (map[string]string, error) {
|
||||
digests := make(map[string]string)
|
||||
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
|
||||
}
|
||||
|
||||
verity, err := measureVerity(path, int(f.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digests[relPath] = verity
|
||||
return nil
|
||||
}
|
||||
err := filepath.WalkDir(root, walkFn)
|
||||
return digests, err
|
||||
}
|
||||
|
||||
func getComposefsBlob(dataDir string) string {
|
||||
return filepath.Join(dataDir, "composefs.blob")
|
||||
}
|
||||
|
||||
func generateComposeFsBlob(verityDigests map[string]string, toc interface{}, composefsDir string) error {
|
||||
if err := os.MkdirAll(composefsDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dumpReader, err := dump.GenerateDump(toc, verityDigests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destFile := getComposefsBlob(composefsDir)
|
||||
writerJson, err := getComposeFsHelper()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find mkcomposefs: %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 %q: %w", destFile, 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)
|
||||
}
|
||||
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, "--from-file", "-", "/proc/self/fd/3")
|
||||
cmd.ExtraFiles = []*os.File{outFd}
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = dumpReader
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
typedef enum {
|
||||
LCFS_EROFS_FLAGS_HAS_ACL = (1 << 0),
|
||||
} lcfs_erofs_flag_t;
|
||||
|
||||
struct lcfs_erofs_header_s {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t flags;
|
||||
uint32_t unused[5];
|
||||
} __attribute__((__packed__));
|
||||
*/
|
||||
|
||||
// hasACL returns true if the erofs blob has ACLs enabled
|
||||
func hasACL(path string) (bool, error) {
|
||||
const LCFS_EROFS_FLAGS_HAS_ACL = (1 << 0)
|
||||
|
||||
fd, err := unix.Openat(unix.AT_FDCWD, path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
// do not worry about checking the magic number, if the file is invalid
|
||||
// we will fail to mount it anyway
|
||||
flags := make([]byte, 4)
|
||||
nread, err := unix.Pread(fd, flags, 8)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if nread != 4 {
|
||||
return false, fmt.Errorf("failed to read flags from %q", path)
|
||||
}
|
||||
return binary.LittleEndian.Uint32(flags)&LCFS_EROFS_FLAGS_HAS_ACL != 0, nil
|
||||
}
|
||||
|
||||
func mountComposefsBlob(dataDir, mountPoint string) error {
|
||||
blobFile := getComposefsBlob(dataDir)
|
||||
loop, err := loopback.AttachLoopDeviceRO(blobFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer loop.Close()
|
||||
|
||||
hasACL, err := hasACL(blobFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mountOpts := "ro"
|
||||
if !hasACL {
|
||||
mountOpts += ",noacl"
|
||||
}
|
||||
|
||||
return unix.Mount(loop.Name(), mountPoint, "erofs", unix.MS_RDONLY, mountOpts)
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/overlay/jsoniter.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/overlay/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
189
vendor/github.com/containers/storage/drivers/overlay/mount.go
generated
vendored
Normal file
189
vendor/github.com/containers/storage/drivers/overlay/mount.go
generated
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register("storage-mountfrom", mountOverlayFromMain)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
type mountOptions struct {
|
||||
Device string
|
||||
Target string
|
||||
Type string
|
||||
Label string
|
||||
Flag uint32
|
||||
}
|
||||
|
||||
func mountOverlayFrom(dir, device, target, mType string, flags uintptr, label string) error {
|
||||
options := &mountOptions{
|
||||
Device: device,
|
||||
Target: target,
|
||||
Type: mType,
|
||||
Flag: uint32(flags),
|
||||
Label: label,
|
||||
}
|
||||
|
||||
cmd := reexec.Command("storage-mountfrom", dir)
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mountfrom error on pipe creation: %w", err)
|
||||
}
|
||||
|
||||
output := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
if err := cmd.Start(); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("mountfrom error on re-exec cmd: %w", err)
|
||||
}
|
||||
// write the options to the pipe for the untar exec to read
|
||||
if err := json.NewEncoder(w).Encode(options); err != nil {
|
||||
w.Close()
|
||||
return fmt.Errorf("mountfrom json encode to pipe failed: %w", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("mountfrom re-exec output: %s: error: %w", output, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mountfromMain is the entry-point for storage-mountfrom on re-exec.
|
||||
func mountOverlayFromMain() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
var options *mountOptions
|
||||
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// Mount the arguments passed from the specified directory. Some of the
|
||||
// paths mentioned in the values we pass to the kernel are relative to
|
||||
// the specified directory.
|
||||
homedir := flag.Arg(0)
|
||||
if err := os.Chdir(homedir); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
pageSize := unix.Getpagesize()
|
||||
if len(options.Label) < pageSize {
|
||||
if err := unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Those arguments still took up too much space. Open the diff
|
||||
// directories and use their descriptor numbers as lowers, using
|
||||
// /proc/self/fd as the current directory.
|
||||
|
||||
// Split out the various options, since we need to manipulate the
|
||||
// paths, but we don't want to mess with other options.
|
||||
var upperk, upperv, workk, workv, lowerk, lowerv, labelk, labelv, others string
|
||||
for _, arg := range strings.Split(options.Label, ",") {
|
||||
kv := strings.SplitN(arg, "=", 2)
|
||||
switch kv[0] {
|
||||
case "upperdir":
|
||||
upperk = "upperdir="
|
||||
upperv = kv[1]
|
||||
case "workdir":
|
||||
workk = "workdir="
|
||||
workv = kv[1]
|
||||
case "lowerdir":
|
||||
lowerk = "lowerdir="
|
||||
lowerv = kv[1]
|
||||
case "label":
|
||||
labelk = "label="
|
||||
labelv = kv[1]
|
||||
default:
|
||||
if others == "" {
|
||||
others = arg
|
||||
} else {
|
||||
others = others + "," + arg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure upperdir, workdir, and the target are absolute paths.
|
||||
if upperv != "" && !filepath.IsAbs(upperv) {
|
||||
upperv = filepath.Join(homedir, upperv)
|
||||
}
|
||||
if workv != "" && !filepath.IsAbs(workv) {
|
||||
workv = filepath.Join(homedir, workv)
|
||||
}
|
||||
if !filepath.IsAbs(options.Target) {
|
||||
options.Target = filepath.Join(homedir, options.Target)
|
||||
}
|
||||
|
||||
// Get a descriptor for each lower, and use that descriptor's name as
|
||||
// the new value for the list of lowers, because it's shorter.
|
||||
if lowerv != "" {
|
||||
lowers := strings.Split(lowerv, ":")
|
||||
var newLowers []string
|
||||
dataOnly := false
|
||||
for _, lowerPath := range lowers {
|
||||
if lowerPath == "" {
|
||||
dataOnly = true
|
||||
continue
|
||||
}
|
||||
lowerFd, err := unix.Open(lowerPath, unix.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
var lower string
|
||||
if dataOnly {
|
||||
lower = fmt.Sprintf(":%d", lowerFd)
|
||||
dataOnly = false
|
||||
} else {
|
||||
lower = fmt.Sprintf("%d", lowerFd)
|
||||
}
|
||||
newLowers = append(newLowers, lower)
|
||||
}
|
||||
lowerv = strings.Join(newLowers, ":")
|
||||
}
|
||||
|
||||
// Reconstruct the Label field.
|
||||
options.Label = upperk + upperv + "," + workk + workv + "," + lowerk + lowerv + "," + labelk + labelv + "," + others
|
||||
options.Label = strings.ReplaceAll(options.Label, ",,", ",")
|
||||
|
||||
// Okay, try this, if we managed to make the arguments fit.
|
||||
var err error
|
||||
if len(options.Label) < pageSize {
|
||||
if err := os.Chdir("/proc/self/fd"); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
err = unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label)
|
||||
} else {
|
||||
err = fmt.Errorf("cannot mount layer, mount data %q too large %d >= page size %d", options.Label, len(options.Label), pageSize)
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "creating overlay mount to %s, mount_data=%q\n", options.Target, options.Label)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
2556
vendor/github.com/containers/storage/drivers/overlay/overlay.go
generated
vendored
Normal file
2556
vendor/github.com/containers/storage/drivers/overlay/overlay.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
22
vendor/github.com/containers/storage/drivers/overlay/overlay_cgo.go
generated
vendored
Normal file
22
vendor/github.com/containers/storage/drivers/overlay/overlay_cgo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//go:build linux && cgo
|
||||
// +build linux,cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
)
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For Overlay, it attempts to check the XFS quota for size, and falls back to
|
||||
// finding the size of the "diff" directory.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
usage := &directory.DiskUsage{}
|
||||
if d.quotaCtl != nil {
|
||||
err := d.quotaCtl.GetDiskUsage(d.dir(id), usage)
|
||||
return usage, err
|
||||
}
|
||||
return directory.Usage(path.Join(d.dir(id), "diff"))
|
||||
}
|
||||
17
vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go
generated
vendored
Normal file
17
vendor/github.com/containers/storage/drivers/overlay/overlay_nocgo.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//go:build linux && !cgo
|
||||
// +build linux,!cgo
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
)
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For Overlay, it attempts to check the XFS quota for size, and falls back to
|
||||
// finding the size of the "diff" directory.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(path.Join(d.dir(id), "diff"))
|
||||
}
|
||||
8
vendor/github.com/containers/storage/drivers/overlay/overlay_unsupported.go
generated
vendored
Normal file
8
vendor/github.com/containers/storage/drivers/overlay/overlay_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package overlay
|
||||
|
||||
func SupportsNativeOverlay(graphroot, rundir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
82
vendor/github.com/containers/storage/drivers/overlay/randomid.go
generated
vendored
Normal file
82
vendor/github.com/containers/storage/drivers/overlay/randomid.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlay
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// generateID creates a new random string identifier with the given length
|
||||
func generateID(l int) string {
|
||||
const (
|
||||
// ensures we backoff for less than 450ms total. Use the following to
|
||||
// select new value, in units of 10ms:
|
||||
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
|
||||
maxretries = 9
|
||||
backoff = time.Millisecond * 10
|
||||
)
|
||||
|
||||
var (
|
||||
totalBackoff time.Duration
|
||||
count int
|
||||
retries int
|
||||
size = (l*5 + 7) / 8
|
||||
u = make([]byte, size)
|
||||
)
|
||||
// TODO: Include time component, counter component, random component
|
||||
|
||||
for {
|
||||
// This should never block but the read may fail. Because of this,
|
||||
// we just try to read the random number generator until we get
|
||||
// something. This is a very rare condition but may happen.
|
||||
b := time.Duration(retries) * backoff
|
||||
time.Sleep(b)
|
||||
totalBackoff += b
|
||||
|
||||
n, err := io.ReadFull(rand.Reader, u[count:])
|
||||
if err != nil {
|
||||
if retryOnError(err) && retries < maxretries {
|
||||
count += n
|
||||
retries++
|
||||
logrus.Errorf("Generating version 4 uuid, retrying: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other errors represent a system problem. What did someone
|
||||
// do to /dev/urandom?
|
||||
panic(fmt.Errorf("reading random number generator, retried for %v: %w", totalBackoff.String(), err))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
s := base32.StdEncoding.EncodeToString(u)
|
||||
|
||||
return s[:l]
|
||||
}
|
||||
|
||||
// retryOnError tries to detect whether or not retrying would be fruitful.
|
||||
func retryOnError(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return retryOnError(err.Err) // unpack the target error
|
||||
case syscall.Errno:
|
||||
if err == unix.EPERM {
|
||||
// EPERM represents an entropy pool exhaustion, a condition under
|
||||
// which we backoff and retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
20
vendor/github.com/containers/storage/drivers/overlayutils/overlayutils.go
generated
vendored
Normal file
20
vendor/github.com/containers/storage/drivers/overlayutils/overlayutils.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package overlayutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
)
|
||||
|
||||
// ErrDTypeNotSupported denotes that the backing filesystem doesn't support d_type.
|
||||
func ErrDTypeNotSupported(driver, backingFs string) error {
|
||||
msg := fmt.Sprintf("%s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.", driver, backingFs)
|
||||
if backingFs == "xfs" {
|
||||
msg += " Reformat the filesystem with ftype=1 to enable d_type support."
|
||||
}
|
||||
msg += " Running without d_type is not supported."
|
||||
return fmt.Errorf("%s: %w", msg, graphdriver.ErrNotSupported)
|
||||
}
|
||||
453
vendor/github.com/containers/storage/drivers/quota/projectquota.go
generated
vendored
Normal file
453
vendor/github.com/containers/storage/drivers/quota/projectquota.go
generated
vendored
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
//go:build linux && !exclude_disk_quota && cgo
|
||||
// +build linux,!exclude_disk_quota,cgo
|
||||
|
||||
//
|
||||
// projectquota.go - implements XFS project quota controls
|
||||
// for setting quota limits on a newly created directory.
|
||||
// It currently supports the legacy XFS specific ioctls.
|
||||
//
|
||||
// TODO: use generic quota control ioctl FS_IOC_FS{GET,SET}XATTR
|
||||
// for both xfs/ext4 for kernel version >= v4.5
|
||||
//
|
||||
|
||||
package quota
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/quota.h>
|
||||
#include <linux/dqblk_xfs.h>
|
||||
|
||||
#ifndef FS_XFLAG_PROJINHERIT
|
||||
struct fsxattr {
|
||||
__u32 fsx_xflags;
|
||||
__u32 fsx_extsize;
|
||||
__u32 fsx_nextents;
|
||||
__u32 fsx_projid;
|
||||
unsigned char fsx_pad[12];
|
||||
};
|
||||
#define FS_XFLAG_PROJINHERIT 0x00000200
|
||||
#endif
|
||||
#ifndef FS_IOC_FSGETXATTR
|
||||
#define FS_IOC_FSGETXATTR _IOR ('X', 31, struct fsxattr)
|
||||
#endif
|
||||
#ifndef FS_IOC_FSSETXATTR
|
||||
#define FS_IOC_FSSETXATTR _IOW ('X', 32, struct fsxattr)
|
||||
#endif
|
||||
|
||||
#ifndef PRJQUOTA
|
||||
#define PRJQUOTA 2
|
||||
#endif
|
||||
#ifndef FS_PROJ_QUOTA
|
||||
#define FS_PROJ_QUOTA 2
|
||||
#endif
|
||||
#ifndef Q_XSETPQLIM
|
||||
#define Q_XSETPQLIM QCMD(Q_XSETQLIM, PRJQUOTA)
|
||||
#endif
|
||||
#ifndef Q_XGETPQUOTA
|
||||
#define Q_XGETPQUOTA QCMD(Q_XGETQUOTA, PRJQUOTA)
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const projectIDsAllocatedPerQuotaHome = 10000
|
||||
|
||||
// BackingFsBlockDeviceLink is the name of a file that we place in
|
||||
// the home directory of a driver that uses this package.
|
||||
const BackingFsBlockDeviceLink = "backingFsBlockDev"
|
||||
|
||||
// Quota limit params - currently we only control blocks hard limit and inodes
|
||||
type Quota struct {
|
||||
Size uint64
|
||||
Inodes uint64
|
||||
}
|
||||
|
||||
// Control - Context to be used by storage driver (e.g. overlay)
|
||||
// who wants to apply project quotas to container dirs
|
||||
type Control struct {
|
||||
backingFsBlockDev string
|
||||
nextProjectID uint32
|
||||
quotas *sync.Map
|
||||
basePath string
|
||||
}
|
||||
|
||||
// Attempt to generate a unigue projectid. Multiple directories
|
||||
// per file system can have quota and they need a group of unique
|
||||
// ids. This function attempts to allocate at least projectIDsAllocatedPerQuotaHome(10000)
|
||||
// unique projectids, based on the inode of the basepath.
|
||||
func generateUniqueProjectID(path string) (uint32, error) {
|
||||
fileinfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stat, ok := fileinfo.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("not a syscall.Stat_t %s", path)
|
||||
}
|
||||
projectID := projectIDsAllocatedPerQuotaHome + (stat.Ino*projectIDsAllocatedPerQuotaHome)%(math.MaxUint32-projectIDsAllocatedPerQuotaHome)
|
||||
return uint32(projectID), nil
|
||||
}
|
||||
|
||||
// NewControl - initialize project quota support.
|
||||
// Test to make sure that quota can be set on a test dir and find
|
||||
// the first project id to be used for the next container create.
|
||||
//
|
||||
// Returns nil (and error) if project quota is not supported.
|
||||
//
|
||||
// First get the project id of the basePath directory.
|
||||
// This test will fail if the backing fs is not xfs.
|
||||
//
|
||||
// xfs_quota tool can be used to assign a project id to the driver home directory, e.g.:
|
||||
// echo 100000:/var/lib/containers/storage/overlay >> /etc/projects
|
||||
// echo 200000:/var/lib/containers/storage/volumes >> /etc/projects
|
||||
// echo storage:100000 >> /etc/projid
|
||||
// echo volumes:200000 >> /etc/projid
|
||||
// xfs_quota -x -c 'project -s storage volumes' /<xfs mount point>
|
||||
//
|
||||
// In the example above, the storage directory project id will be used as a
|
||||
// "start offset" and all containers will be assigned larger project ids
|
||||
// (e.g. >= 100000). Then the volumes directory project id will be used as a
|
||||
// "start offset" and all volumes will be assigned larger project ids
|
||||
// (e.g. >= 200000).
|
||||
// This is a way to prevent xfs_quota management from conflicting with
|
||||
// containers/storage.
|
||||
|
||||
// Then try to create a test directory with the next project id and set a quota
|
||||
// on it. If that works, continue to scan existing containers to map allocated
|
||||
// project ids.
|
||||
func NewControl(basePath string) (*Control, error) {
|
||||
//
|
||||
// Get project id of parent dir as minimal id to be used by driver
|
||||
//
|
||||
minProjectID, err := getProjectID(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if minProjectID == 0 {
|
||||
// Indicates the storage was never initialized
|
||||
// Generate a unique range of Projectids for this basepath
|
||||
minProjectID, err = generateUniqueProjectID(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
// create backing filesystem device node
|
||||
//
|
||||
backingFsBlockDev, err := makeBackingFsDev(basePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//
|
||||
// Test if filesystem supports project quotas by trying to set
|
||||
// a quota on the first available project id
|
||||
//
|
||||
quota := Quota{
|
||||
Size: 0,
|
||||
Inodes: 0,
|
||||
}
|
||||
|
||||
q := Control{
|
||||
backingFsBlockDev: backingFsBlockDev,
|
||||
nextProjectID: minProjectID + 1,
|
||||
quotas: &sync.Map{},
|
||||
basePath: basePath,
|
||||
}
|
||||
|
||||
if err := q.setProjectQuota(minProjectID, quota); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//
|
||||
// get first project id to be used for next container
|
||||
//
|
||||
err = q.findNextProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("NewControl(%s): nextProjectID = %d", basePath, q.nextProjectID)
|
||||
return &q, nil
|
||||
}
|
||||
|
||||
// SetQuota - assign a unique project id to directory and set the quota limits
|
||||
// for that project id
|
||||
func (q *Control) SetQuota(targetPath string, quota Quota) error {
|
||||
var projectID uint32
|
||||
value, ok := q.quotas.Load(targetPath)
|
||||
if ok {
|
||||
projectID, ok = value.(uint32)
|
||||
}
|
||||
if !ok {
|
||||
projectID = q.nextProjectID
|
||||
|
||||
//
|
||||
// assign project id to new container directory
|
||||
//
|
||||
err := setProjectID(targetPath, projectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.quotas.Store(targetPath, projectID)
|
||||
q.nextProjectID++
|
||||
}
|
||||
|
||||
//
|
||||
// set the quota limit for the container's project id
|
||||
//
|
||||
logrus.Debugf("SetQuota path=%s, size=%d, inodes=%d, projectID=%d", targetPath, quota.Size, quota.Inodes, projectID)
|
||||
return q.setProjectQuota(projectID, quota)
|
||||
}
|
||||
|
||||
// ClearQuota removes the map entry in the quotas map for targetPath.
|
||||
// It does so to prevent the map leaking entries as directories are deleted.
|
||||
func (q *Control) ClearQuota(targetPath string) {
|
||||
q.quotas.Delete(targetPath)
|
||||
}
|
||||
|
||||
// setProjectQuota - set the quota for project id on xfs block device
|
||||
func (q *Control) setProjectQuota(projectID uint32, quota Quota) error {
|
||||
var d C.fs_disk_quota_t
|
||||
d.d_version = C.FS_DQUOT_VERSION
|
||||
d.d_id = C.__u32(projectID)
|
||||
d.d_flags = C.FS_PROJ_QUOTA
|
||||
|
||||
if quota.Size > 0 {
|
||||
d.d_fieldmask = d.d_fieldmask | C.FS_DQ_BHARD | C.FS_DQ_BSOFT
|
||||
d.d_blk_hardlimit = C.__u64(quota.Size / 512)
|
||||
d.d_blk_softlimit = d.d_blk_hardlimit
|
||||
}
|
||||
if quota.Inodes > 0 {
|
||||
d.d_fieldmask = d.d_fieldmask | C.FS_DQ_IHARD | C.FS_DQ_ISOFT
|
||||
d.d_ino_hardlimit = C.__u64(quota.Inodes)
|
||||
d.d_ino_softlimit = d.d_ino_hardlimit
|
||||
}
|
||||
|
||||
cs := C.CString(q.backingFsBlockDev)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
||||
runQuotactl := func() syscall.Errno {
|
||||
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XSETPQLIM,
|
||||
uintptr(unsafe.Pointer(cs)), uintptr(d.d_id),
|
||||
uintptr(unsafe.Pointer(&d)), 0, 0)
|
||||
return errno
|
||||
}
|
||||
|
||||
errno := runQuotactl()
|
||||
|
||||
// If the backingFsBlockDev does not exist any more then try to recreate it.
|
||||
if errors.Is(errno, unix.ENOENT) {
|
||||
if _, err := makeBackingFsDev(q.basePath); err != nil {
|
||||
return fmt.Errorf(
|
||||
"failed to recreate missing backingFsBlockDev %s for projid %d: %w",
|
||||
q.backingFsBlockDev, projectID, err,
|
||||
)
|
||||
}
|
||||
|
||||
if errno := runQuotactl(); errno != 0 {
|
||||
return fmt.Errorf("failed to set quota limit for projid %d on %s after backingFsBlockDev recreation: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
} else if errno != 0 {
|
||||
return fmt.Errorf("failed to set quota limit for projid %d on %s: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetQuota - get the quota limits of a directory that was configured with SetQuota
|
||||
func (q *Control) GetQuota(targetPath string, quota *Quota) error {
|
||||
d, err := q.fsDiskQuotaFromPath(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quota.Size = uint64(d.d_blk_hardlimit) * 512
|
||||
quota.Inodes = uint64(d.d_ino_hardlimit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDiskUsage - get the current disk usage of a directory that was configured with SetQuota
|
||||
func (q *Control) GetDiskUsage(targetPath string, usage *directory.DiskUsage) error {
|
||||
d, err := q.fsDiskQuotaFromPath(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usage.Size = int64(d.d_bcount) * 512
|
||||
usage.InodeCount = int64(d.d_icount)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Control) fsDiskQuotaFromPath(targetPath string) (C.fs_disk_quota_t, error) {
|
||||
var d C.fs_disk_quota_t
|
||||
var projectID uint32
|
||||
value, ok := q.quotas.Load(targetPath)
|
||||
if ok {
|
||||
projectID, ok = value.(uint32)
|
||||
}
|
||||
if !ok {
|
||||
return d, fmt.Errorf("quota not found for path : %s", targetPath)
|
||||
}
|
||||
|
||||
//
|
||||
// get the quota limit for the container's project id
|
||||
//
|
||||
cs := C.CString(q.backingFsBlockDev)
|
||||
defer C.free(unsafe.Pointer(cs))
|
||||
|
||||
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XGETPQUOTA,
|
||||
uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(projectID)),
|
||||
uintptr(unsafe.Pointer(&d)), 0, 0)
|
||||
if errno != 0 {
|
||||
return d, fmt.Errorf("failed to get quota limit for projid %d on %s: %w",
|
||||
projectID, q.backingFsBlockDev, errno)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// getProjectID - get the project id of path on xfs
|
||||
func getProjectID(targetPath string) (uint32, error) {
|
||||
dir, err := openDir(targetPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var fsx C.struct_fsxattr
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return 0, fmt.Errorf("failed to get projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
|
||||
return uint32(fsx.fsx_projid), nil
|
||||
}
|
||||
|
||||
// setProjectID - set the project id of path on xfs
|
||||
func setProjectID(targetPath string, projectID uint32) error {
|
||||
dir, err := openDir(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closeDir(dir)
|
||||
|
||||
var fsx C.struct_fsxattr
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to get projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
fsx.fsx_projid = C.__u32(projectID)
|
||||
fsx.fsx_xflags |= C.FS_XFLAG_PROJINHERIT
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSSETXATTR,
|
||||
uintptr(unsafe.Pointer(&fsx)))
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set projid for %s: %w", targetPath, errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findNextProjectID - find the next project id to be used for containers
|
||||
// by scanning driver home directory to find used project ids
|
||||
func (q *Control) findNextProjectID() error {
|
||||
files, err := os.ReadDir(q.basePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read directory failed : %s", q.basePath)
|
||||
}
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(q.basePath, file.Name())
|
||||
projid, err := getProjectID(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if projid > 0 {
|
||||
q.quotas.Store(path, projid)
|
||||
}
|
||||
if q.nextProjectID <= projid {
|
||||
q.nextProjectID = projid + 1
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func free(p *C.char) {
|
||||
C.free(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func openDir(path string) (*C.DIR, error) {
|
||||
Cpath := C.CString(path)
|
||||
defer free(Cpath)
|
||||
|
||||
dir, errno := C.opendir(Cpath)
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("can't open dir %v: %w", Cpath, errno)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func closeDir(dir *C.DIR) {
|
||||
if dir != nil {
|
||||
C.closedir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func getDirFd(dir *C.DIR) uintptr {
|
||||
return uintptr(C.dirfd(dir))
|
||||
}
|
||||
|
||||
// Get the backing block device of the driver home directory
|
||||
// and create a block device node under the home directory
|
||||
// to be used by quotactl commands
|
||||
func makeBackingFsDev(home string) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(home, &stat); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
backingFsBlockDev := path.Join(home, BackingFsBlockDeviceLink)
|
||||
backingFsBlockDevTmp := backingFsBlockDev + ".tmp"
|
||||
// Re-create just in case someone copied the home directory over to a new device
|
||||
if err := unix.Mknod(backingFsBlockDevTmp, unix.S_IFBLK|0o600, int(stat.Dev)); err != nil {
|
||||
if !errors.Is(err, unix.EEXIST) {
|
||||
return "", fmt.Errorf("failed to mknod %s: %w", backingFsBlockDevTmp, err)
|
||||
}
|
||||
// On EEXIST, try again after unlinking any potential leftover.
|
||||
_ = unix.Unlink(backingFsBlockDevTmp)
|
||||
if err := unix.Mknod(backingFsBlockDevTmp, unix.S_IFBLK|0o600, int(stat.Dev)); err != nil {
|
||||
return "", fmt.Errorf("failed to mknod %s: %w", backingFsBlockDevTmp, err)
|
||||
}
|
||||
}
|
||||
if err := unix.Rename(backingFsBlockDevTmp, backingFsBlockDev); err != nil {
|
||||
return "", fmt.Errorf("failed to rename %s to %s: %w", backingFsBlockDevTmp, backingFsBlockDev, err)
|
||||
}
|
||||
|
||||
return backingFsBlockDev, nil
|
||||
}
|
||||
37
vendor/github.com/containers/storage/drivers/quota/projectquota_unsupported.go
generated
vendored
Normal file
37
vendor/github.com/containers/storage/drivers/quota/projectquota_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
//go:build !linux || exclude_disk_quota || !cgo
|
||||
// +build !linux exclude_disk_quota !cgo
|
||||
|
||||
package quota
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Quota limit params - currently we only control blocks hard limit
|
||||
type Quota struct {
|
||||
Size uint64
|
||||
Inodes uint64
|
||||
}
|
||||
|
||||
// Control - Context to be used by storage driver (e.g. overlay)
|
||||
// who wants to apply project quotas to container dirs
|
||||
type Control struct{}
|
||||
|
||||
func NewControl(basePath string) (*Control, error) {
|
||||
return nil, errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// SetQuota - assign a unique project id to directory and set the quota limits
|
||||
// for that project id
|
||||
func (q *Control) SetQuota(targetPath string, quota Quota) error {
|
||||
return errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// GetQuota - get the quota limits of a directory that was configured with SetQuota
|
||||
func (q *Control) GetQuota(targetPath string, quota *Quota) error {
|
||||
return errors.New("filesystem does not support, or has not enabled quotas")
|
||||
}
|
||||
|
||||
// ClearQuota removes the map entry in the quotas map for targetPath.
|
||||
// It does so to prevent the map leaking entries as directories are deleted.
|
||||
func (q *Control) ClearQuota(targetPath string) {}
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_aufs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_aufs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_aufs && linux
|
||||
// +build !exclude_graphdriver_aufs,linux
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the aufs graphdriver
|
||||
_ "github.com/containers/storage/drivers/aufs"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_btrfs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_btrfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_btrfs && linux
|
||||
// +build !exclude_graphdriver_btrfs,linux
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the btrfs graphdriver
|
||||
_ "github.com/containers/storage/drivers/btrfs"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_devicemapper.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_devicemapper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_devicemapper && linux && cgo
|
||||
// +build !exclude_graphdriver_devicemapper,linux,cgo
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the devmapper graphdriver
|
||||
_ "github.com/containers/storage/drivers/devmapper"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_overlay.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_overlay.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build !exclude_graphdriver_overlay && linux && cgo
|
||||
// +build !exclude_graphdriver_overlay,linux,cgo
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the overlay graphdriver
|
||||
_ "github.com/containers/storage/drivers/overlay"
|
||||
)
|
||||
6
vendor/github.com/containers/storage/drivers/register/register_vfs.go
generated
vendored
Normal file
6
vendor/github.com/containers/storage/drivers/register/register_vfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package register
|
||||
|
||||
import (
|
||||
// register vfs
|
||||
_ "github.com/containers/storage/drivers/vfs"
|
||||
)
|
||||
6
vendor/github.com/containers/storage/drivers/register/register_windows.go
generated
vendored
Normal file
6
vendor/github.com/containers/storage/drivers/register/register_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package register
|
||||
|
||||
import (
|
||||
// register the windows graph driver
|
||||
_ "github.com/containers/storage/drivers/windows"
|
||||
)
|
||||
9
vendor/github.com/containers/storage/drivers/register/register_zfs.go
generated
vendored
Normal file
9
vendor/github.com/containers/storage/drivers/register/register_zfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
//go:build (!exclude_graphdriver_zfs && linux) || (!exclude_graphdriver_zfs && freebsd) || solaris
|
||||
// +build !exclude_graphdriver_zfs,linux !exclude_graphdriver_zfs,freebsd solaris
|
||||
|
||||
package register
|
||||
|
||||
import (
|
||||
// register the zfs driver
|
||||
_ "github.com/containers/storage/drivers/zfs"
|
||||
)
|
||||
52
vendor/github.com/containers/storage/drivers/template.go
generated
vendored
Normal file
52
vendor/github.com/containers/storage/drivers/template.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TemplateDriver is just barely enough of a driver that we can implement a
|
||||
// naive version of CreateFromTemplate on top of it.
|
||||
type TemplateDriver interface {
|
||||
DiffDriver
|
||||
CreateReadWrite(id, parent string, opts *CreateOpts) error
|
||||
Create(id, parent string, opts *CreateOpts) error
|
||||
Remove(id string) error
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as
|
||||
// another layer. Internally, it may even depend on that other layer
|
||||
// continuing to exist, as if it were actually a child of the child layer.
|
||||
func NaiveCreateFromTemplate(d TemplateDriver, id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *CreateOpts, readWrite bool) error {
|
||||
var err error
|
||||
if readWrite {
|
||||
err = d.CreateReadWrite(id, parent, opts)
|
||||
} else {
|
||||
err = d.Create(id, parent, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diff, err := d.Diff(template, templateIDMappings, parent, parentIDMappings, opts.MountLabel)
|
||||
if err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer diff.Close()
|
||||
|
||||
applyOptions := ApplyDiffOpts{
|
||||
Diff: diff,
|
||||
Mappings: templateIDMappings,
|
||||
MountLabel: opts.MountLabel,
|
||||
IgnoreChownErrors: opts.ignoreChownErrors,
|
||||
}
|
||||
if _, err = d.ApplyDiff(id, parent, applyOptions); err != nil {
|
||||
if err2 := d.Remove(id); err2 != nil {
|
||||
logrus.Errorf("Removing layer %q: %v", id, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
Normal file
7
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package vfs
|
||||
|
||||
import "github.com/containers/storage/drivers/copy"
|
||||
|
||||
func dirCopy(srcDir, dstDir string) error {
|
||||
return copy.DirCopy(srcDir, dstDir, copy.Content, true)
|
||||
}
|
||||
10
vendor/github.com/containers/storage/drivers/vfs/copy_unsupported.go
generated
vendored
Normal file
10
vendor/github.com/containers/storage/drivers/vfs/copy_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package vfs // import "github.com/containers/storage/drivers/vfs"
|
||||
|
||||
import "github.com/containers/storage/pkg/chrootarchive"
|
||||
|
||||
func dirCopy(srcDir, dstDir string) error {
|
||||
return chrootarchive.NewArchiver(nil).CopyWithTar(srcDir, dstDir)
|
||||
}
|
||||
330
vendor/github.com/containers/storage/drivers/vfs/driver.go
generated
vendored
Normal file
330
vendor/github.com/containers/storage/drivers/vfs/driver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("vfs", Init)
|
||||
}
|
||||
|
||||
// Init returns a new VFS driver.
|
||||
// This sets the home directory for the driver and returns NaiveDiffDriver.
|
||||
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
|
||||
d := &Driver{
|
||||
name: "vfs",
|
||||
homes: []string{home},
|
||||
idMappings: idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps),
|
||||
}
|
||||
|
||||
rootIDs := d.idMappings.RootPair()
|
||||
if err := idtools.MkdirAllAndChown(filepath.Join(home, "dir"), 0o700, rootIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, option := range options.DriverOptions {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "vfs.imagestore", ".imagestore":
|
||||
d.homes = append(d.homes, strings.Split(val, ",")...)
|
||||
continue
|
||||
case "vfs.mountopt":
|
||||
return nil, fmt.Errorf("vfs driver does not support mount options")
|
||||
case ".ignore_chown_errors", "vfs.ignore_chown_errors":
|
||||
logrus.Debugf("vfs: ignore_chown_errors=%s", val)
|
||||
var err error
|
||||
d.ignoreChownErrors, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("vfs driver does not support %s options", key)
|
||||
}
|
||||
}
|
||||
// If --imagestore is provided, lets add writable graphRoot
|
||||
// to vfs's additional image store, as it is done for
|
||||
// `overlay` driver.
|
||||
if options.ImageStore != "" {
|
||||
d.homes = append(d.homes, options.ImageStore)
|
||||
}
|
||||
d.updater = graphdriver.NewNaiveLayerIDMapUpdater(d)
|
||||
d.naiveDiff = graphdriver.NewNaiveDiffDriver(d, d.updater)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Driver holds information about the driver, home directory of the driver.
|
||||
// Driver implements graphdriver.ProtoDriver. It uses only basic vfs operations.
|
||||
// In order to support layering, files are copied from the parent layer into the new layer. There is no copy-on-write support.
|
||||
// Driver must be wrapped in NaiveDiffDriver to be used as a graphdriver.Driver
|
||||
type Driver struct {
|
||||
name string
|
||||
homes []string
|
||||
idMappings *idtools.IDMappings
|
||||
ignoreChownErrors bool
|
||||
naiveDiff graphdriver.DiffDriver
|
||||
updater graphdriver.LayerIDMapUpdater
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "vfs"
|
||||
}
|
||||
|
||||
// Status is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any status information.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metadata is used for implementing the graphdriver.ProtoDriver interface. VFS does not currently have any meta data.
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return nil, nil //nolint: nilnil
|
||||
}
|
||||
|
||||
// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type fileGetNilCloser struct {
|
||||
storage.FileGetter
|
||||
}
|
||||
|
||||
func (f fileGetNilCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiffGetter returns a FileGetCloser that can read files from the directory that
|
||||
// contains files for the layer differences. Used for direct access for tar-split.
|
||||
func (d *Driver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p := d.dir(id)
|
||||
return fileGetNilCloser{storage.NewPathFileGetter(p)}, nil
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
if readWrite {
|
||||
return d.CreateReadWrite(id, template, opts)
|
||||
}
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// ApplyDiff applies the new layer into a root
|
||||
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
|
||||
if d.ignoreChownErrors {
|
||||
options.IgnoreChownErrors = d.ignoreChownErrors
|
||||
}
|
||||
return d.naiveDiff.ApplyDiff(id, parent, options)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.create(id, parent, opts, false)
|
||||
}
|
||||
|
||||
// Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.create(id, parent, opts, true)
|
||||
}
|
||||
|
||||
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, ro bool) (retErr error) {
|
||||
if opts != nil && len(opts.StorageOpt) != 0 {
|
||||
return fmt.Errorf("--storage-opt is not supported for vfs")
|
||||
}
|
||||
|
||||
idMappings := d.idMappings
|
||||
if opts != nil && opts.IDMappings != nil {
|
||||
idMappings = opts.IDMappings
|
||||
}
|
||||
|
||||
dir := d.dir(id)
|
||||
rootIDs := idMappings.RootPair()
|
||||
if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0o700, rootIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}()
|
||||
|
||||
rootPerms := defaultPerms
|
||||
if runtime.GOOS == "darwin" {
|
||||
rootPerms = os.FileMode(0o700)
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
st, err := system.Stat(d.dir(parent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootPerms = os.FileMode(st.Mode())
|
||||
rootIDs.UID = int(st.UID())
|
||||
rootIDs.GID = int(st.GID())
|
||||
}
|
||||
if err := idtools.MkdirAndChown(dir, rootPerms, rootIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
labelOpts := []string{"level:s0"}
|
||||
if _, mountLabel, err := label.InitLabels(labelOpts); err == nil {
|
||||
label.SetFileLabel(dir, mountLabel)
|
||||
}
|
||||
if parent != "" {
|
||||
parentDir, err := d.Get(parent, graphdriver.MountOpts{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", parent, err)
|
||||
}
|
||||
if err := dirCopy(parentDir, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) dir(id string) string {
|
||||
for i, home := range d.homes {
|
||||
if i > 0 {
|
||||
home = filepath.Join(home, d.String())
|
||||
}
|
||||
candidate := filepath.Join(home, "dir", filepath.Base(id))
|
||||
fi, err := os.Stat(candidate)
|
||||
if err == nil && fi.IsDir() {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return filepath.Join(d.homes[0], "dir", filepath.Base(id))
|
||||
}
|
||||
|
||||
// Remove deletes the content from the directory for a given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
return system.EnsureRemoveAll(d.dir(id))
|
||||
}
|
||||
|
||||
// Get returns the directory for the given id.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) {
|
||||
dir := d.dir(id)
|
||||
|
||||
for _, opt := range options.Options {
|
||||
if opt == "ro" {
|
||||
// ignore "ro" option
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("vfs driver does not support mount options")
|
||||
}
|
||||
if st, err := os.Stat(dir); err != nil {
|
||||
return "", err
|
||||
} else if !st.IsDir() {
|
||||
return "", fmt.Errorf("%s: not a directory", dir)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// Put is a noop for vfs that return nil for the error, since this driver has no runtime resources to clean up.
|
||||
func (d *Driver) Put(id string) error {
|
||||
// The vfs driver has no runtime resources (e.g. mounts)
|
||||
// to clean up, so we don't need anything here
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For VFS, it queries the directory for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.dir(id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the directory exists for the given id.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
_, err := os.Stat(d.dir(id))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// List layers (not including additional image stores)
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
entries, err := os.ReadDir(filepath.Join(d.homes[0], "dir"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layers := make([]string, 0)
|
||||
|
||||
for _, entry := range entries {
|
||||
id := entry.Name()
|
||||
// Does it look like a datadir directory?
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
layers = append(layers, id)
|
||||
}
|
||||
|
||||
return layers, err
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
if len(d.homes) > 1 {
|
||||
return d.homes[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsShifting tells whether the driver support shifting of the UIDs/GIDs in an userNS
|
||||
func (d *Driver) SupportsShifting() bool {
|
||||
return d.updater.SupportsShifting()
|
||||
}
|
||||
|
||||
// UpdateLayerIDMap updates ID mappings in a from matching the ones specified
|
||||
// by toContainer to those specified by toHost.
|
||||
func (d *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error {
|
||||
if err := d.updater.UpdateLayerIDMap(id, toContainer, toHost, mountLabel); err != nil {
|
||||
return err
|
||||
}
|
||||
dir := d.dir(id)
|
||||
rootIDs, err := toHost.ToHost(idtools.IDPair{UID: 0, GID: 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chown(dir, rootIDs.UID, rootIDs.GID)
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (d *Driver) Changes(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) ([]archive.Change, error) {
|
||||
return d.naiveDiff.Changes(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (d *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (io.ReadCloser, error) {
|
||||
return d.naiveDiff.Diff(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (d *Driver) DiffSize(id string, idMappings *idtools.IDMappings, parent string, parentMappings *idtools.IDMappings, mountLabel string) (size int64, err error) {
|
||||
return d.naiveDiff.DiffSize(id, idMappings, parent, parentMappings, mountLabel)
|
||||
}
|
||||
5
vendor/github.com/containers/storage/drivers/windows/jsoniter_windows.go
generated
vendored
Normal file
5
vendor/github.com/containers/storage/drivers/windows/jsoniter_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package windows
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
1010
vendor/github.com/containers/storage/drivers/windows/windows.go
generated
vendored
Normal file
1010
vendor/github.com/containers/storage/drivers/windows/windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
2
vendor/github.com/containers/storage/drivers/zfs/MAINTAINERS
generated
vendored
Normal file
2
vendor/github.com/containers/storage/drivers/zfs/MAINTAINERS
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Jörg Thalheim <joerg@higgsboson.tk> (@Mic92)
|
||||
Arthur Gautier <baloo@gandi.net> (@baloose)
|
||||
518
vendor/github.com/containers/storage/drivers/zfs/zfs.go
generated
vendored
Normal file
518
vendor/github.com/containers/storage/drivers/zfs/zfs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/containers/storage/pkg/directory"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/mount"
|
||||
"github.com/containers/storage/pkg/parsers"
|
||||
zfs "github.com/mistifyio/go-zfs/v3"
|
||||
"github.com/opencontainers/selinux/go-selinux/label"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type zfsOptions struct {
|
||||
fsName string
|
||||
mountPath string
|
||||
mountOptions string
|
||||
}
|
||||
|
||||
const defaultPerms = os.FileMode(0o555)
|
||||
|
||||
func init() {
|
||||
graphdriver.MustRegister("zfs", Init)
|
||||
}
|
||||
|
||||
// Logger returns a zfs logger implementation.
|
||||
type Logger struct{}
|
||||
|
||||
// Log wraps log message from ZFS driver with a prefix '[zfs]'.
|
||||
func (*Logger) Log(cmd []string) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("%s", strings.Join(cmd, " "))
|
||||
}
|
||||
|
||||
// Init returns a new ZFS driver.
|
||||
// It takes base mount path and an array of options which are represented as key value pairs.
|
||||
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
|
||||
func Init(base string, opt graphdriver.Options) (graphdriver.Driver, error) {
|
||||
var err error
|
||||
|
||||
logger := logrus.WithField("storage-driver", "zfs")
|
||||
|
||||
if _, err := exec.LookPath("zfs"); err != nil {
|
||||
logger.Debugf("zfs command is not available: %v", err)
|
||||
return nil, fmt.Errorf("the 'zfs' command is not available: %w", graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
file, err := unix.Open("/dev/zfs", unix.O_RDWR, 0o600)
|
||||
if err != nil {
|
||||
logger.Debugf("cannot open /dev/zfs: %v", err)
|
||||
return nil, fmt.Errorf("could not open /dev/zfs: %v: %w", err, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
defer unix.Close(file)
|
||||
|
||||
options, err := parseOptions(opt.DriverOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options.mountPath = base
|
||||
|
||||
rootdir := path.Dir(base)
|
||||
|
||||
if options.fsName == "" {
|
||||
err = checkRootdirFs(rootdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if options.fsName == "" {
|
||||
options.fsName, err = lookupZfsDataset(rootdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
zfs.SetLogger(new(Logger))
|
||||
|
||||
filesystems, err := zfs.Filesystems(options.fsName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find root filesystem %s: %w", options.fsName, err)
|
||||
}
|
||||
|
||||
filesystemsCache := make(map[string]bool, len(filesystems))
|
||||
var rootDataset *zfs.Dataset
|
||||
for _, fs := range filesystems {
|
||||
if fs.Name == options.fsName {
|
||||
rootDataset = fs
|
||||
}
|
||||
filesystemsCache[fs.Name] = true
|
||||
}
|
||||
|
||||
if rootDataset == nil {
|
||||
return nil, fmt.Errorf("zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(opt.UIDMaps, opt.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get root uid/gid: %w", err)
|
||||
}
|
||||
if err := idtools.MkdirAllAs(base, 0o700, rootUID, rootGID); err != nil {
|
||||
return nil, fmt.Errorf("failed to create '%s': %w", base, err)
|
||||
}
|
||||
|
||||
d := &Driver{
|
||||
dataset: rootDataset,
|
||||
options: options,
|
||||
filesystemsCache: filesystemsCache,
|
||||
uidMaps: opt.UIDMaps,
|
||||
gidMaps: opt.GIDMaps,
|
||||
ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
|
||||
}
|
||||
return graphdriver.NewNaiveDiffDriver(d, graphdriver.NewNaiveLayerIDMapUpdater(d)), nil
|
||||
}
|
||||
|
||||
func parseOptions(opt []string) (zfsOptions, error) {
|
||||
var options zfsOptions
|
||||
options.fsName = ""
|
||||
for _, option := range opt {
|
||||
key, val, err := parsers.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return options, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "zfs.fsname":
|
||||
options.fsName = val
|
||||
case "zfs.mountopt":
|
||||
options.mountOptions = val
|
||||
default:
|
||||
return options, fmt.Errorf("unknown option %s", key)
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func lookupZfsDataset(rootdir string) (string, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Stat(rootdir, &stat); err != nil {
|
||||
return "", fmt.Errorf("failed to access '%s': %w", rootdir, err)
|
||||
}
|
||||
wantedDev := stat.Dev
|
||||
|
||||
mounts, err := mount.GetMounts()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, m := range mounts {
|
||||
if err := unix.Stat(m.Mountpoint, &stat); err != nil {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
|
||||
continue // may fail on fuse file systems
|
||||
}
|
||||
|
||||
if stat.Dev == wantedDev && m.FSType == "zfs" {
|
||||
return m.Source, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
|
||||
}
|
||||
|
||||
// Driver holds information about the driver, such as zfs dataset, options and cache.
|
||||
type Driver struct {
|
||||
dataset *zfs.Dataset
|
||||
options zfsOptions
|
||||
sync.Mutex // protects filesystem cache against concurrent access
|
||||
filesystemsCache map[string]bool
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
ctr *graphdriver.RefCounter
|
||||
}
|
||||
|
||||
func (d *Driver) String() string {
|
||||
return "zfs"
|
||||
}
|
||||
|
||||
// Cleanup is called on when program exits, it is a no-op for ZFS.
|
||||
func (d *Driver) Cleanup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
|
||||
// such as pool name, dataset name, disk usage, parent quota and compression used.
|
||||
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
|
||||
// 'Space Available', 'Parent Quota' and 'Compression'.
|
||||
func (d *Driver) Status() [][2]string {
|
||||
parts := strings.Split(d.dataset.Name, "/")
|
||||
pool, err := zfs.GetZpool(parts[0])
|
||||
|
||||
var poolName, poolHealth string
|
||||
if err == nil {
|
||||
poolName = pool.Name
|
||||
poolHealth = pool.Health
|
||||
} else {
|
||||
poolName = fmt.Sprintf("error while getting pool information %v", err)
|
||||
poolHealth = "not available"
|
||||
}
|
||||
|
||||
quota := "no"
|
||||
if d.dataset.Quota != 0 {
|
||||
quota = strconv.FormatUint(d.dataset.Quota, 10)
|
||||
}
|
||||
|
||||
return [][2]string{
|
||||
{"Zpool", poolName},
|
||||
{"Zpool Health", poolHealth},
|
||||
{"Parent Dataset", d.dataset.Name},
|
||||
{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
|
||||
{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
|
||||
{"Parent Quota", quota},
|
||||
{"Compression", d.dataset.Compression},
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata returns image/container metadata related to graph driver
|
||||
func (d *Driver) Metadata(id string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"Mountpoint": d.mountPath(id),
|
||||
"Dataset": d.zfsPath(id),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Driver) cloneFilesystem(name, parentName string) error {
|
||||
snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
|
||||
parentDataset := zfs.Dataset{Name: parentName}
|
||||
snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
d.filesystemsCache[name] = true
|
||||
d.Unlock()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
snapshot.Destroy(zfs.DestroyDeferDeletion)
|
||||
return err
|
||||
}
|
||||
return snapshot.Destroy(zfs.DestroyDeferDeletion)
|
||||
}
|
||||
|
||||
func (d *Driver) zfsPath(id string) string {
|
||||
return d.options.fsName + "/" + id
|
||||
}
|
||||
|
||||
func (d *Driver) mountPath(id string) string {
|
||||
return path.Join(d.options.mountPath, "graph", getMountpoint(id))
|
||||
}
|
||||
|
||||
// CreateFromTemplate creates a layer with the same contents and parent as another layer.
|
||||
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
|
||||
return d.Create(id, template, opts)
|
||||
}
|
||||
|
||||
// CreateReadWrite creates a layer that is writable for use as a container
|
||||
// file system.
|
||||
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
return d.Create(id, parent, opts)
|
||||
}
|
||||
|
||||
// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
|
||||
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
err := d.create(id, parent, opts)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if zfsError, ok := err.(*zfs.Error); ok {
|
||||
if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
|
||||
return err
|
||||
}
|
||||
// aborted build -> cleanup
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
dataset := zfs.Dataset{Name: d.zfsPath(id)}
|
||||
if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// retry
|
||||
return d.create(id, parent, opts)
|
||||
}
|
||||
|
||||
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) error {
|
||||
var storageOpt map[string]string
|
||||
if opts != nil {
|
||||
storageOpt = opts.StorageOpt
|
||||
}
|
||||
|
||||
name := d.zfsPath(id)
|
||||
mountpoint := d.mountPath(id)
|
||||
quota, err := parseStorageOpt(storageOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parent == "" {
|
||||
var rootUID, rootGID int
|
||||
var mountLabel string
|
||||
if opts != nil {
|
||||
rootUID, rootGID, err = idtools.GetRootUIDGID(opts.UIDs(), opts.GIDs())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get root uid/gid: %w", err)
|
||||
}
|
||||
mountLabel = opts.MountLabel
|
||||
}
|
||||
mountoptions := map[string]string{"mountpoint": "legacy"}
|
||||
fs, err := zfs.CreateFilesystem(name, mountoptions)
|
||||
if err == nil {
|
||||
err = setQuota(name, quota)
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
d.filesystemsCache[fs.Name] = true
|
||||
d.Unlock()
|
||||
}
|
||||
|
||||
if err := idtools.MkdirAllAs(mountpoint, defaultPerms, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
|
||||
logrus.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
mountOpts := label.FormatMountLabel(d.options.mountOptions, mountLabel)
|
||||
|
||||
if err := mount.Mount(name, mountpoint, "zfs", mountOpts); err != nil {
|
||||
return fmt.Errorf("creating zfs mount: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := detachUnmount(mountpoint); err != nil {
|
||||
logrus.Warnf("failed to unmount %s mount %s: %v", id, mountpoint, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.Chmod(mountpoint, defaultPerms); err != nil {
|
||||
return fmt.Errorf("setting permissions on zfs mount: %w", err)
|
||||
}
|
||||
|
||||
// this is our first mount after creation of the filesystem, and the root dir may still have root
|
||||
// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
|
||||
if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
|
||||
return fmt.Errorf("modifying zfs mountpoint (%s) ownership: %w", mountpoint, err)
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = d.cloneFilesystem(name, d.zfsPath(parent))
|
||||
if err == nil {
|
||||
err = setQuota(name, quota)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func parseStorageOpt(storageOpt map[string]string) (string, error) {
|
||||
// Read size to change the disk quota per container
|
||||
for k, v := range storageOpt {
|
||||
key := strings.ToLower(k)
|
||||
switch key {
|
||||
case "size":
|
||||
return v, nil
|
||||
default:
|
||||
return "0", fmt.Errorf("unknown option %s", key)
|
||||
}
|
||||
}
|
||||
return "0", nil
|
||||
}
|
||||
|
||||
func setQuota(name string, quota string) error {
|
||||
if quota == "0" {
|
||||
return nil
|
||||
}
|
||||
fs, err := zfs.GetDataset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.SetProperty("quota", quota)
|
||||
}
|
||||
|
||||
// Remove deletes the dataset, filesystem and the cache for the given id.
|
||||
func (d *Driver) Remove(id string) error {
|
||||
name := d.zfsPath(id)
|
||||
dataset := zfs.Dataset{Name: name}
|
||||
err := dataset.Destroy(zfs.DestroyRecursive)
|
||||
if err == nil {
|
||||
d.Lock()
|
||||
delete(d.filesystemsCache, name)
|
||||
d.Unlock()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get returns the mountpoint for the given id after creating the target directories if necessary.
|
||||
func (d *Driver) Get(id string, options graphdriver.MountOpts) (_ string, retErr error) {
|
||||
mountpoint := d.mountPath(id)
|
||||
if count := d.ctr.Increment(mountpoint); count > 1 {
|
||||
return mountpoint, nil
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
if c := d.ctr.Decrement(mountpoint); c <= 0 {
|
||||
if mntErr := unix.Unmount(mountpoint, 0); mntErr != nil {
|
||||
logrus.WithField("storage-driver", "zfs").Errorf("Error unmounting %v: %v", mountpoint, mntErr)
|
||||
}
|
||||
if rmErr := unix.Rmdir(mountpoint); rmErr != nil && !os.IsNotExist(rmErr) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("Failed to remove %s: %v", id, rmErr)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// In the case of a read-only mount we first mount read-write so we can set the
|
||||
// correct permissions on the mount point and remount read-only afterwards.
|
||||
remountReadOnly := false
|
||||
mountOptions := d.options.mountOptions
|
||||
if len(options.Options) > 0 {
|
||||
var newOptions []string
|
||||
for _, option := range options.Options {
|
||||
if option == "ro" {
|
||||
// Filter out read-only mount option but remember for later remounting.
|
||||
remountReadOnly = true
|
||||
} else {
|
||||
newOptions = append(newOptions, option)
|
||||
}
|
||||
}
|
||||
mountOptions = strings.Join(newOptions, ",")
|
||||
}
|
||||
|
||||
filesystem := d.zfsPath(id)
|
||||
opts := label.FormatMountLabel(mountOptions, options.MountLabel)
|
||||
logrus.WithField("storage-driver", "zfs").Debugf(`mount("%s", "%s", "%s")`, filesystem, mountpoint, opts)
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create the target directories if they don't exist
|
||||
if err := idtools.MkdirAllAs(mountpoint, 0o755, rootUID, rootGID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil {
|
||||
return "", fmt.Errorf("creating zfs mount: %w", err)
|
||||
}
|
||||
|
||||
if remountReadOnly {
|
||||
opts = label.FormatMountLabel("remount,ro", options.MountLabel)
|
||||
if err := mount.Mount(filesystem, mountpoint, "zfs", opts); err != nil {
|
||||
return "", fmt.Errorf("remounting zfs mount read-only: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return mountpoint, nil
|
||||
}
|
||||
|
||||
// Put removes the existing mountpoint for the given id if it exists.
|
||||
func (d *Driver) Put(id string) error {
|
||||
mountpoint := d.mountPath(id)
|
||||
if count := d.ctr.Decrement(mountpoint); count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := logrus.WithField("storage-driver", "zfs")
|
||||
|
||||
logger.Debugf(`unmount("%s")`, mountpoint)
|
||||
|
||||
if err := detachUnmount(mountpoint); err != nil {
|
||||
logger.Warnf("Failed to unmount %s mount %s: %v", id, mountpoint, err)
|
||||
}
|
||||
if err := unix.Rmdir(mountpoint); err != nil && !os.IsNotExist(err) {
|
||||
logger.Debugf("Failed to remove %s mount point %s: %v", id, mountpoint, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadWriteDiskUsage returns the disk usage of the writable directory for the ID.
|
||||
// For ZFS, it queries the full mount path for this ID.
|
||||
func (d *Driver) ReadWriteDiskUsage(id string) (*directory.DiskUsage, error) {
|
||||
return directory.Usage(d.mountPath(id))
|
||||
}
|
||||
|
||||
// Exists checks to see if the cache entry exists for the given id.
|
||||
func (d *Driver) Exists(id string) bool {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
return d.filesystemsCache[d.zfsPath(id)]
|
||||
}
|
||||
|
||||
// List layers (not including additional image stores). Our layers aren't all
|
||||
// dependent on a single well-known dataset, so we can't reliably tell which
|
||||
// datasets are ours and which ones just look like they could be ours.
|
||||
func (d *Driver) ListLayers() ([]string, error) {
|
||||
return nil, graphdriver.ErrNotSupported
|
||||
}
|
||||
|
||||
// AdditionalImageStores returns additional image stores supported by the driver
|
||||
func (d *Driver) AdditionalImageStores() []string {
|
||||
return nil
|
||||
}
|
||||
33
vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go
generated
vendored
Normal file
33
vendor/github.com/containers/storage/drivers/zfs/zfs_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checkRootdirFs(rootdir string) error {
|
||||
var buf unix.Statfs_t
|
||||
if err := unix.Statfs(rootdir, &buf); err != nil {
|
||||
return fmt.Errorf("failed to access '%s': %s", rootdir, err)
|
||||
}
|
||||
|
||||
// on FreeBSD buf.Fstypename contains ['z', 'f', 's', 0 ... ]
|
||||
if (buf.Fstypename[0] != 122) || (buf.Fstypename[1] != 102) || (buf.Fstypename[2] != 115) || (buf.Fstypename[3] != 0) {
|
||||
logrus.WithField("storage-driver", "zfs").Debugf("no zfs dataset found for rootdir '%s'", rootdir)
|
||||
return fmt.Errorf("no zfs dataset found for rootdir '%s': %w", rootdir, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMountpoint(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func detachUnmount(mountpoint string) error {
|
||||
// FreeBSD's MNT_FORCE is roughly equivalent to MNT_DETACH
|
||||
return unix.Unmount(mountpoint, unix.MNT_FORCE)
|
||||
}
|
||||
35
vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go
generated
vendored
Normal file
35
vendor/github.com/containers/storage/drivers/zfs/zfs_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package zfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checkRootdirFs(rootDir string) error {
|
||||
fsMagic, err := graphdriver.GetFSMagic(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backingFS := "unknown"
|
||||
if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
|
||||
backingFS = fsName
|
||||
}
|
||||
|
||||
if fsMagic != graphdriver.FsMagicZfs {
|
||||
logrus.WithField("root", rootDir).WithField("backingFS", backingFS).WithField("storage-driver", "zfs").Error("No zfs dataset found for root")
|
||||
return fmt.Errorf("no zfs dataset found for rootdir '%s': %w", rootDir, graphdriver.ErrPrerequisites)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMountpoint(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func detachUnmount(mountpoint string) error {
|
||||
return unix.Unmount(mountpoint, unix.MNT_DETACH)
|
||||
}
|
||||
4
vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go
generated
vendored
Normal file
4
vendor/github.com/containers/storage/drivers/zfs/zfs_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//go:build !linux && !freebsd
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package zfs
|
||||
Loading…
Add table
Add a link
Reference in a new issue